From f07b736040751810d2f2cc6419080968f8c1a304 Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Fri, 4 Apr 2025 10:16:53 +0200 Subject: [PATCH 01/10] [ADD] field_vector: New field type for vector column --- field_vector/README.rst | 284 ++++++++++ field_vector/__init__.py | 2 + field_vector/__manifest__.py | 18 + field_vector/fields.py | 224 ++++++++ field_vector/hooks.py | 40 ++ field_vector/models/__init__.py | 1 + field_vector/models/ir_model_fields.py | 30 + field_vector/readme/CONFIGURE.md | 10 + field_vector/readme/CONTEXT.md | 4 + field_vector/readme/CONTRIBUTORS.md | 1 + field_vector/readme/CREDITS.md | 4 + field_vector/readme/DESCRIPTION.md | 1 + field_vector/readme/INSTALL.md | 1 + field_vector/readme/ROADMAP.md | 2 + field_vector/readme/USAGE.md | 93 +++ field_vector/register.py | 37 ++ field_vector/static/description/icon.png | Bin 0 -> 9455 bytes field_vector/static/description/index.html | 621 +++++++++++++++++++++ field_vector/tests/__init__.py | 1 + field_vector/tests/models.py | 16 + field_vector/tests/test_field_vector.py | 147 +++++ 21 files changed, 1537 insertions(+) create mode 100644 field_vector/README.rst create mode 100644 field_vector/__init__.py create mode 100644 field_vector/__manifest__.py create mode 100644 field_vector/fields.py create mode 100644 field_vector/hooks.py create mode 100644 field_vector/models/__init__.py create mode 100644 field_vector/models/ir_model_fields.py create mode 100644 field_vector/readme/CONFIGURE.md create mode 100644 field_vector/readme/CONTEXT.md create mode 100644 field_vector/readme/CONTRIBUTORS.md create mode 100644 field_vector/readme/CREDITS.md create mode 100644 field_vector/readme/DESCRIPTION.md create mode 100644 field_vector/readme/INSTALL.md create mode 100644 field_vector/readme/ROADMAP.md create mode 100644 field_vector/readme/USAGE.md create mode 100644 field_vector/register.py create mode 100644 field_vector/static/description/icon.png create mode 100644 field_vector/static/description/index.html create mode 100644 field_vector/tests/__init__.py create mode 100644 field_vector/tests/models.py create mode 100644 field_vector/tests/test_field_vector.py diff --git a/field_vector/README.rst b/field_vector/README.rst new file mode 100644 index 00000000000..26f3e82c4ec --- /dev/null +++ b/field_vector/README.rst @@ -0,0 +1,284 @@ +============ +Field Vector +============ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:4ebe6e838b46ae08fd3c564ea4cf777d8b4d95d61102147c0953d05a1dacc9a4 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github + :target: https://github.com/OCA/server-tools/tree/16.0/field_vector + :alt: OCA/server-tools +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-tools-16-0/server-tools-16-0-field_vector + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This addon provides a new field type called "Vector" that allows you to +store and manage vector into your Odoo database. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +The advent of large language models (LLMs) has highlighted the +importance of vector representation as a powerful representation of data +to easily determine the similarity between different pieces of +information. Vector representation is a way of encoding information in a +numerical format that captures the semantic meaning of the data. This +allows for efficient similarity comparisons. + +Installation +============ + +To install this module, you need to ensure that the +`pgvector `__ extension is +installed and available in your PostgreSQL instance. + +Configuration +============= + +[ This file is not always required; it should explain **how to configure +the module before using it**; it is aimed at users with administration +privileges. + +Please be detailed on the path to configuration (eg: do you need to +activate developer mode?), describe step by step configurations and the +use of screenshots is strongly recommended.] + +To configure this module, you need to: + +- Go to *App* > Menu > Menu item +- Activate boolean… > save +- … + +Usage +===== + + | **⚠️ Warning** + | This addon is **not compatible** with the Python ``pgvector`` + library. Please ensure that you do not use this library alongside + the addon to avoid potential issues. This is mainly due to the fact + that numpy arrays can't be stored into the odoo cache since they + are not comparable with the default '==' or '!=' operators. + +The module is a technical module providing a new field type called +"Vector". It's intended to be used by developers who want to store and +manage vector data in their Odoo database when they develop their own +modules. + +Field declaration +----------------- + +To declare a field of type vector, you can use the following syntax: + +.. code:: python + + + from odoo.addons.field_vector.fields import Vector + + + class YourModel(models.Model): + _name = 'your.model' + + vector_field = Vector(dimensions=3) + +The ``dimensions`` parameter is required and specifies the number of +dimensions of the vector. The field will be stored as a ``vector`` type +in PostgreSQL, which is a native type for storing vectors. + +By default the field is declared as no ``prefetch=False`` and with +``autopad=True``. You can override these parameters by passing them as +arguments to the field: + +.. code:: python + + from odoo.addons.field_vector.fields import Vector + class YourModel(models.Model): + _name = 'your.model' + + vector_field = Vector(dimensions=3, prefetch=True, autopad=False) + +The ``prefetch`` parameter allows you to enable or disable prefetching +of the field when loading records. If set to ``True``, the field will be +prefetched when loading records, which can improve performance when +accessing the field frequently. If set to ``False``, the field will not +be prefetched, which can save memory and improve performance when +accessing the field infrequently (which would be the common case). + +The ``autopad`` parameter allows you to enable or disable automatic +padding of the vector when storing it in the database. If set to +``True``, the vector will be automatically padded with zeros to match +the specified dimensions. If set to ``False``, the vector will not be +padded but if the vector is shorter than the specified dimensions an +error will be raised. + +Field usage +----------- + +The vector field can be used like any other field in Odoo. When +accessing the field, it will always return an +``odoo.addons.field_vector.fields.VectorValue`` object, which is a +wrapper around value stored into the database. This object provides a +convenient way to get the value of the vector as a numpy array. + +.. code:: python + + import numpy as np + from odoo.addons.field_vector.fields import VectorValue + + record = self.env['your.model'].create({ + 'vector_field': [1.0, 2.0, 3.0] + }) + + assert isinstance(record.vector_field, VectorValue) + assert isinstance(record.vector_field.value, np.ndarray) + +When setting the field, you can pass a list of values or a numpy array +or a ``VectorValue`` object or a list/tuple of values. The field will +automatically convert the value to a VectorValue and store it in the +database into the vector format. + +.. code:: python + + + record.vector_field = [1.0, 2.0, 3.0] + assert isinstance(record.vector_field, VectorValue) + + record.vector_field = np.array([1.0, 2.0, 3.0]) + assert isinstance(record.vector_field, VectorValue) + + record.vector_field = VectorValue([1.0, 2.0, 3.0]) + assert isinstance(record.vector_field, VectorValue) + +Plain SQL queries +----------------- + +When reading the field in plain SQL queries, the field will be returned +as a ``VectorValue`` object. You can use the ``value`` property to get +the value of the vector as a numpy array. + +.. code:: python + + + env.cr.execute('SELECT vector_field FROM your_model WHERE id = 1') + record = env.cr.fetchone() + vector_value = record[0] + assert isinstance(vector_value, VectorValue) + +When writing the field in plain SQL queries, you can pass a numpy array +or a list of values or a VectorValue object as the value of the field +(in this specific case tuples are not supported). + +.. code:: python + + + env.cr.execute('UPDATE your_model SET vector_field = %s WHERE id = 1', (np.array([1.0, 2.0, 3.0]),)) + env.cr.execute('UPDATE your_model SET vector_field = %s WHERE id = 1', ([1.0, 2.0, 3.0],)) + env.cr.execute('UPDATE your_model SET vector_field = %s WHERE id = 1', (VectorValue([1.0, 2.0, 3.0]),)) + +Known issues / Roadmap +====================== + +- allows the use of specific operators into domain filters to search for + similar vectors. +- dedicated widget to display the vector in a more user-friendly way. + +Changelog +========= + +[ The change log. The goal of this file is to help readers understand +changes between version. The primary audience is end users and +integrators. Purely technical changes such as code refactoring must not +be mentioned here. + +This file may contain ONE level of section titles, underlined with the ~ +(tilde) character. Other section markers are forbidden and will likely +break the structure of the README.rst or other documents where this +fragment is included. ] + +11.0.x.y.z (YYYY-MM-DD) +----------------------- + +- [BREAKING] Breaking changes come first. + (`#70 `__) +- [ADD] New feature. (`#74 `__) +- [FIX] Correct this. (`#71 `__) + +11.0.x.y.z (YYYY-MM-DD) +----------------------- + +- ... + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* ACSONE SA/NV + +Contributors +------------ + +- Laurent Mignon laurent.mignon@acsone.eu (https://www.acsone.eu) + +Other credits +------------- + +The development of this module has been financially supported by: + +- `Alcyon Belux `__ + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-lmignon| image:: https://github.com/lmignon.png?size=40px + :target: https://github.com/lmignon + :alt: lmignon + +Current `maintainer `__: + +|maintainer-lmignon| + +This module is part of the `OCA/server-tools `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/field_vector/__init__.py b/field_vector/__init__.py new file mode 100644 index 00000000000..6d58305f5dd --- /dev/null +++ b/field_vector/__init__.py @@ -0,0 +1,2 @@ +from . import models +from .hooks import pre_init_hook diff --git a/field_vector/__manifest__.py b/field_vector/__manifest__.py new file mode 100644 index 00000000000..e8e5919ee1b --- /dev/null +++ b/field_vector/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2025 ACSONE SA/NV +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +{ + "name": "Field Vector", + "summary": """New specialized field to store vector data""", + "version": "16.0.1.0.0", + "license": "LGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/server-tools", + "depends": ["base"], + "maintainers": ["lmignon"], + "installable": True, + "pre_init_hook": "pre_init_hook", + "external_dependencies": { + "python": ["numpy"], + }, +} diff --git a/field_vector/fields.py b/field_vector/fields.py new file mode 100644 index 00000000000..865bfb27531 --- /dev/null +++ b/field_vector/fields.py @@ -0,0 +1,224 @@ +# Copyright 2025 ACSONE SA/NV +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +from __future__ import annotations + +import re +from operator import attrgetter + +import numpy as np +from psycopg2.extensions import AsIs + +from odoo import fields +from odoo.tools import sql + + +class VectorValue: + """ + Class to represent a vector value. + This class as a wrapper around the text representation of the vector + to allow for easy manipulation and conversion to/from other formats. + + It's designed to be put in the record's cache and returned as record's value. + It's also used when the database is queried to convert the value to/from + the database format in a transparent way. + """ + + def __init__(self, value: list | tuple | np.ndarray, dimensions=None, autopad=True): + if not isinstance(value, (list, tuple, np.ndarray)): + raise ValueError( + f"Invalid type '{type(value)}' for VectorValue: " + "Only list, tuple or np.ndarray are allowed." + ) + if isinstance(value, np.ndarray): + if value.dtype != ">f4": + value = value.astype(">f4") + value = value.tolist() + self._value = value + if dimensions is not None and len(value) != dimensions and autopad: + self.pad(dimensions) + + def __repr__(self): + return f"VectorValue({self._value})" + + def __eq__(self, value: object, /) -> bool: + if isinstance(value, self.__class__): + return np.array_equal(self._value, value._value) + return False + + def __len__(self): + return len(self._value) + + def to_list(self): + """ + Convert the vector value to a list. + """ + return list(self._value) + + def pad(self, dimensions: int): + """ + Pad the vector value to the given size. + """ + if len(self._value) < dimensions: + self._value = [*self._value, *([0] * (dimensions - self.dimensions))] + return self + + @property + def value(self): + """ + Return the value as a numpy array. + """ + return np.asarray(self._value, dtype=">f4") + + @property + def dimensions(self): + """ + Return the dimensions of the vector. + """ + return len(self._value) + + @classmethod + def _from_db(cls, value: str) -> VectorValue: + """ + Convert a binary value from the database to a VectorValue. + """ + if value is None: + return None + return cls([float(v) for v in value[1:-1].split(",")]) + + @classmethod + def _to_db(cls, value: list | tuple | np.ndarray | VectorValue) -> str: + """ + Convert a VectorValue to a binary value for the database. + """ + if value is None: + return None + if isinstance(value, list | tuple | np.ndarray): + value = cls(value) + if not isinstance(value, cls): + raise ValueError( + f"Invalid type '{type(value)}' for VectorValue: " + "Only list, tuple or np.ndarray or VectoreValue are allowed." + ) + return "[" + ",".join([str(float(v)) for v in value.value]) + "]" + + +class Vector(fields.Field): + """ + Specialized field to store vector data. + This field is based on the pgvector extension for PostgreSQL. + It allows to store and manipulate vector data efficiently. + + This field can be used to store vectors of any size. + The dimension of the vector is defined at the field level. + + By default, the field is not pre-fetched. + To ease the use of the field, it is automatically padded to the size of the vector. + + + """ + + type = "vector" + dimensions = None + prefetch = False + autopad = True + + def __init__( + self, + dimensions=fields.Default, + autopad=fields.Default, + string=fields.Default, + **kwargs, + ): + super().__init__( + dimensions=dimensions, string=string, autopad=autopad, **kwargs + ) + + def _setup_attrs(self, model_class, name): + res = super()._setup_attrs(model_class, name) + if ( + self.dimensions == fields.Default + or self.dimensions is None + or not isinstance(self.dimensions, int) + ): + raise ValueError( + "The size of the vector field must be an integer and cannot be None." + ) + return res + + @property + def column_type(self): + return ("vector", f"vector({self.dimensions})") + + def get_current_vector_size(self, cr, table, column): + """Fetch the current vector size from pg_typeof()""" + cr.execute( + "SELECT pg_typeof(%s)::text FROM %s LIMIT 1;", (AsIs(column), AsIs(table)) + ) + result = cr.fetchone() + if result and result[0]: + match = re.search(r"vector\((\d+)\)", result[0]) + if match: + return int(match.group(1)) + return None + + def update_db_column(self, model, column): + if column: + db_size = self.get_current_vector_size(model._cr, model._table, self.name) + if db_size is not None and db_size != self.dimensions: + sql.convert_column( + model._cr, model._table, self.name, self.column_type[1] + ) + return super().update_db_column(model, column) + + _related_dimensions = property(attrgetter("dimensions")) + _description_dimensions = property(attrgetter("dimensions")) + + def convert_to_export(self, value: VectorValue, record): + return value.to_list() if value else None + + def convert_to_cache(self, value, record, validate=True): + if value is None or value is False: + return None + if not isinstance(value, (list, tuple, np.ndarray, VectorValue)): + raise ValueError( + f"Invalid type '{type(value)}' for {self.name}: " + "Only np.ndarray or list of floats/int are allowed." + ) + if not isinstance(value, VectorValue): + value = VectorValue(value, dimensions=self.dimensions, autopad=self.autopad) + if self.autopad and value.dimensions < self.dimensions: + value = value.pad(self.dimensions) + if validate and value.dimensions != self.dimensions: + raise ValueError( + f"Invalid vector size for {self.name}: {value.dimensions} != {self.dimensions}" + ) + return value + + def convert_to_record(self, value, record): + if value is None or value is False: + return None + if not isinstance(value, (list, tuple, np.ndarray, VectorValue)): + raise ValueError( + f"Invalid type '{type(value)}' for {self.name}: " + "Only np.ndarray, list of floats/int or VectorValue are allowed." + ) + if not isinstance(value, VectorValue): + value = VectorValue(value, dimensions=self.dimensions, autopad=self.autopad) + if self.autopad and value.dimensions < self.dimensions: + value = value.pad(self.dimensions) + + if value.dimensions != self.dimensions: + raise ValueError( + f"Invalid vector dimensions for {self.name}: " + "{value.dimensions} != {self.dimensions}" + ) + return value + + def convert_to_read(self, value, record, use_name_get=True): + return self.convert_to_export(value, record) + + def convert_to_column(self, value, record, values=None, validate=True): + return self.convert_to_record(value, record) + + def convert_to_write(self, value, record, values=None): + return self.convert_to_column(value, record, values) diff --git a/field_vector/hooks.py b/field_vector/hooks.py new file mode 100644 index 00000000000..0894b6e5b47 --- /dev/null +++ b/field_vector/hooks.py @@ -0,0 +1,40 @@ +# Copyright 2025 ACSONE SA/NV +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from odoo import _ +from odoo.exceptions import MissingError + + +def pre_init_hook(cr): + """setup vector""" + cr.execute( + """ + SELECT + tablename + FROM + pg_tables + WHERE + tablename='spatial_ref_sys'; + """ + ) + check = cr.fetchone() + if check: + return {} + try: + cr.execute( + """ + CREATE EXTENSION IF NOT EXISTS vector; + """ + ) + except Exception as exc: + raise MissingError( + _( + "Error, can not automatically initialize vector" + " support. Database user may have to be superuser and" + " pgvector extensions to be installed. If you do not" + " want Odoo to connect with a super user you can manually" + " prepare your database. To dothis, open a client to your" + " database using a super user and run:\n" + "CREATE EXTENSION vector;\n" + ) + ) from exc diff --git a/field_vector/models/__init__.py b/field_vector/models/__init__.py new file mode 100644 index 00000000000..4236f0a44c0 --- /dev/null +++ b/field_vector/models/__init__.py @@ -0,0 +1 @@ +from . import ir_model_fields diff --git a/field_vector/models/ir_model_fields.py b/field_vector/models/ir_model_fields.py new file mode 100644 index 00000000000..8166a0d950a --- /dev/null +++ b/field_vector/models/ir_model_fields.py @@ -0,0 +1,30 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + +from ..register import register_vector + + +class IrModelFields(models.Model): + _inherit = "ir.model.fields" + + ttype = fields.Selection( + selection_add=[("vector", "Vector")], + ondelete={"vector": "cascade"}, + ) + + def init(self): + # This method is called when the module is installed + # at intallation time to register the field type in the database. + # This is needed to ensure that the type is registered + # where runnig tests at installation time. + res = super().init() + register_vector(self.env.cr) + return res + + def _register_hook(self): + # This method is called when the module is loaded to + # register the field type in the database. + res = super()._register_hook() + register_vector(self.env.cr) + return res diff --git a/field_vector/readme/CONFIGURE.md b/field_vector/readme/CONFIGURE.md new file mode 100644 index 00000000000..2fdb0e64a6f --- /dev/null +++ b/field_vector/readme/CONFIGURE.md @@ -0,0 +1,10 @@ +[ This file is not always required; it should explain **how to configure the module before using it**; it is aimed at users with administration privileges. + +Please be detailed on the path to configuration (eg: do you need to activate developer mode?), describe step by step configurations and the use of screenshots is strongly recommended.] + + +To configure this module, you need to: + +- Go to *App* > Menu > Menu item +- Activate boolean… > save +- … diff --git a/field_vector/readme/CONTEXT.md b/field_vector/readme/CONTEXT.md new file mode 100644 index 00000000000..692befa8dc3 --- /dev/null +++ b/field_vector/readme/CONTEXT.md @@ -0,0 +1,4 @@ +The advent of large language models (LLMs) has highlighted the importance of vector +representation as a powerful representation of data to easily determine the +similarity between different pieces of information. +Vector representation is a way of encoding information in a numerical format that captures the semantic meaning of the data. This allows for efficient similarity comparisons. \ No newline at end of file diff --git a/field_vector/readme/CONTRIBUTORS.md b/field_vector/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..8af73de7ea8 --- /dev/null +++ b/field_vector/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Laurent Mignon (https://www.acsone.eu) \ No newline at end of file diff --git a/field_vector/readme/CREDITS.md b/field_vector/readme/CREDITS.md new file mode 100644 index 00000000000..dc3c6118c1d --- /dev/null +++ b/field_vector/readme/CREDITS.md @@ -0,0 +1,4 @@ +The development of this module has been financially supported by: + +- [Alcyon Belux](https://www.alcyonbelux.be/) + diff --git a/field_vector/readme/DESCRIPTION.md b/field_vector/readme/DESCRIPTION.md new file mode 100644 index 00000000000..b5e83335ce4 --- /dev/null +++ b/field_vector/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This addon provides a new field type called "Vector" that allows you to store and manage vector into your Odoo database. \ No newline at end of file diff --git a/field_vector/readme/INSTALL.md b/field_vector/readme/INSTALL.md new file mode 100644 index 00000000000..ab8ddeaed1f --- /dev/null +++ b/field_vector/readme/INSTALL.md @@ -0,0 +1 @@ +To install this module, you need to ensure that the [**pgvector**](https://github.com/pgvector/pgvector) extension is installed and available in your PostgreSQL instance. diff --git a/field_vector/readme/ROADMAP.md b/field_vector/readme/ROADMAP.md new file mode 100644 index 00000000000..37d9e8ea843 --- /dev/null +++ b/field_vector/readme/ROADMAP.md @@ -0,0 +1,2 @@ +- allows the use of specific operators into domain filters to search for similar vectors. +- dedicated widget to display the vector in a more user-friendly way. \ No newline at end of file diff --git a/field_vector/readme/USAGE.md b/field_vector/readme/USAGE.md new file mode 100644 index 00000000000..0ebfd68be1a --- /dev/null +++ b/field_vector/readme/USAGE.md @@ -0,0 +1,93 @@ + +> **⚠️ Warning** +> This addon is **not compatible** with the Python `pgvector` library. Please ensure that you do not use this library alongside the addon to avoid potential issues. This is mainly due to the fact that numpy arrays can't be stored into the odoo cache since they are not comparable with the default '==' or '!=' operators. + +The module is a technical module providing a new field type called "Vector". It's intended to be used by developers who want to store and manage vector data in their Odoo database when they develop their own modules. + +## Field declaration + +To declare a field of type vector, you can use the following syntax: + +```python + +from odoo.addons.field_vector.fields import Vector + + +class YourModel(models.Model): + _name = 'your.model' + + vector_field = Vector(dimensions=3) +``` + +The `dimensions` parameter is required and specifies the number of dimensions of the vector. The field will be stored as a `vector` type in PostgreSQL, which is a native type for storing vectors. + +By default the field is declared as no `prefetch=False` and with `autopad=True`. +You can override these parameters by passing them as arguments to the field: + +```python +from odoo.addons.field_vector.fields import Vector +class YourModel(models.Model): + _name = 'your.model' + + vector_field = Vector(dimensions=3, prefetch=True, autopad=False) +``` + +The `prefetch` parameter allows you to enable or disable prefetching of the field when loading records. If set to `True`, the field will be prefetched when loading records, which can improve performance when accessing the field frequently. If set to `False`, the field will not be prefetched, which can save memory and improve performance when accessing the field infrequently (which would be the common case). + +The `autopad` parameter allows you to enable or disable automatic padding of the vector when storing it in the database. If set to `True`, the vector will be automatically padded with zeros to match the specified dimensions. If set to `False`, the vector will not be padded but if the vector is shorter than the specified dimensions an error will be raised. + +## Field usage + +The vector field can be used like any other field in Odoo. When accessing the field, it will always return an `odoo.addons.field_vector.fields.VectorValue` object, which is a wrapper around value stored into the database. This object +provides a convenient way to get the value of the vector as a numpy array. + +```python +import numpy as np +from odoo.addons.field_vector.fields import VectorValue + +record = self.env['your.model'].create({ + 'vector_field': [1.0, 2.0, 3.0] +}) + +assert isinstance(record.vector_field, VectorValue) +assert isinstance(record.vector_field.value, np.ndarray) + +``` + +When setting the field, you can pass a list of values or a numpy array or a `VectorValue` object or a list/tuple of values. The field will automatically convert the value to a VectorValue and store it in the database into the vector format. + +```python + +record.vector_field = [1.0, 2.0, 3.0] +assert isinstance(record.vector_field, VectorValue) + +record.vector_field = np.array([1.0, 2.0, 3.0]) +assert isinstance(record.vector_field, VectorValue) + +record.vector_field = VectorValue([1.0, 2.0, 3.0]) +assert isinstance(record.vector_field, VectorValue) + +``` + +## Plain SQL queries + +When reading the field in plain SQL queries, the field will be returned as a +`VectorValue` object. You can use the `value` property to get the value of the vector as a numpy array. + +```python + +env.cr.execute('SELECT vector_field FROM your_model WHERE id = 1') +record = env.cr.fetchone() +vector_value = record[0] +assert isinstance(vector_value, VectorValue) +``` + +When writing the field in plain SQL queries, you can pass a numpy array or a list of values or a VectorValue object as the value of the field (in this specific case tuples are not supported). + +```python + +env.cr.execute('UPDATE your_model SET vector_field = %s WHERE id = 1', (np.array([1.0, 2.0, 3.0]),)) +env.cr.execute('UPDATE your_model SET vector_field = %s WHERE id = 1', ([1.0, 2.0, 3.0],)) +env.cr.execute('UPDATE your_model SET vector_field = %s WHERE id = 1', (VectorValue([1.0, 2.0, 3.0]),)) + +``` diff --git a/field_vector/register.py b/field_vector/register.py new file mode 100644 index 00000000000..a730e732834 --- /dev/null +++ b/field_vector/register.py @@ -0,0 +1,37 @@ +# Copyright 2025 ACSONE SA/NV +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +import numpy as np +from psycopg2.extensions import adapt, new_type, register_adapter, register_type + +from .fields import VectorValue + +_is_vector_type_registered = False + + +class VectorAdapter: + def __init__(self, value): + self._value = value + + def getquoted(self): + return adapt(VectorValue._to_db(self._value)).getquoted() + + +def cast_vector(value, cur): + return VectorValue._from_db(value) + + +def register_vector(cr): + global _is_vector_type_registered + if _is_vector_type_registered: + return + cr.execute("SELECT typname, oid FROM pg_type WHERE oid = to_regtype('vector')") + type_info = dict(cr.fetchall()) + if "vector" not in type_info: + raise ValueError("vector type not found in the database") + + vector = new_type((type_info["vector"],), "VECTOR", cast_vector) + register_type(vector) + register_adapter(np.ndarray, VectorAdapter) + register_adapter(VectorValue, VectorAdapter) + _is_vector_type_registered = True diff --git a/field_vector/static/description/icon.png b/field_vector/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/field_vector/static/description/index.html b/field_vector/static/description/index.html new file mode 100644 index 00000000000..4f12bcd1875 --- /dev/null +++ b/field_vector/static/description/index.html @@ -0,0 +1,621 @@ + + + + + +Field Vector + + + +
+

Field Vector

+ + +

Beta License: LGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

+

This addon provides a new field type called “Vector” that allows you to +store and manage vector into your Odoo database.

+

Table of contents

+ +
+

Use Cases / Context

+

The advent of large language models (LLMs) has highlighted the +importance of vector representation as a powerful representation of data +to easily determine the similarity between different pieces of +information. Vector representation is a way of encoding information in a +numerical format that captures the semantic meaning of the data. This +allows for efficient similarity comparisons.

+
+
+

Installation

+

To install this module, you need to ensure that the +pgvector extension is +installed and available in your PostgreSQL instance.

+
+
+

Configuration

+

[ This file is not always required; it should explain how to configure +the module before using it; it is aimed at users with administration +privileges.

+

Please be detailed on the path to configuration (eg: do you need to +activate developer mode?), describe step by step configurations and the +use of screenshots is strongly recommended.]

+

To configure this module, you need to:

+
    +
  • Go to App > Menu > Menu item
  • +
  • Activate boolean… > save
  • +
  • +
+
+
+

Usage

+
+
+
⚠️ Warning
+
This addon is not compatible with the Python pgvector +library. Please ensure that you do not use this library alongside +the addon to avoid potential issues. This is mainly due to the fact +that numpy arrays can’t be stored into the odoo cache since they +are not comparable with the default ‘==’ or ‘!=’ operators.
+
+
+

The module is a technical module providing a new field type called +“Vector”. It’s intended to be used by developers who want to store and +manage vector data in their Odoo database when they develop their own +modules.

+
+

Field declaration

+

To declare a field of type vector, you can use the following syntax:

+
+from odoo.addons.field_vector.fields import Vector
+
+
+class YourModel(models.Model):
+    _name = 'your.model'
+
+    vector_field = Vector(dimensions=3)
+
+

The dimensions parameter is required and specifies the number of +dimensions of the vector. The field will be stored as a vector type +in PostgreSQL, which is a native type for storing vectors.

+

By default the field is declared as no prefetch=False and with +autopad=True. You can override these parameters by passing them as +arguments to the field:

+
+from odoo.addons.field_vector.fields import Vector
+class YourModel(models.Model):
+    _name = 'your.model'
+
+    vector_field = Vector(dimensions=3, prefetch=True, autopad=False)
+
+

The prefetch parameter allows you to enable or disable prefetching +of the field when loading records. If set to True, the field will be +prefetched when loading records, which can improve performance when +accessing the field frequently. If set to False, the field will not +be prefetched, which can save memory and improve performance when +accessing the field infrequently (which would be the common case).

+

The autopad parameter allows you to enable or disable automatic +padding of the vector when storing it in the database. If set to +True, the vector will be automatically padded with zeros to match +the specified dimensions. If set to False, the vector will not be +padded but if the vector is shorter than the specified dimensions an +error will be raised.

+
+
+

Field usage

+

The vector field can be used like any other field in Odoo. When +accessing the field, it will always return an +odoo.addons.field_vector.fields.VectorValue object, which is a +wrapper around value stored into the database. This object provides a +convenient way to get the value of the vector as a numpy array.

+
+import numpy as np
+from odoo.addons.field_vector.fields import  VectorValue
+
+record = self.env['your.model'].create({
+    'vector_field': [1.0, 2.0, 3.0]
+})
+
+assert isinstance(record.vector_field, VectorValue)
+assert isinstance(record.vector_field.value, np.ndarray)
+
+

When setting the field, you can pass a list of values or a numpy array +or a VectorValue object or a list/tuple of values. The field will +automatically convert the value to a VectorValue and store it in the +database into the vector format.

+
+record.vector_field = [1.0, 2.0, 3.0]
+assert isinstance(record.vector_field, VectorValue)
+
+record.vector_field = np.array([1.0, 2.0, 3.0])
+assert isinstance(record.vector_field, VectorValue)
+
+record.vector_field = VectorValue([1.0, 2.0, 3.0])
+assert isinstance(record.vector_field, VectorValue)
+
+
+
+

Plain SQL queries

+

When reading the field in plain SQL queries, the field will be returned +as a VectorValue object. You can use the value property to get +the value of the vector as a numpy array.

+
+env.cr.execute('SELECT vector_field FROM your_model WHERE id = 1')
+record = env.cr.fetchone()
+vector_value = record[0]
+assert isinstance(vector_value, VectorValue)
+
+

When writing the field in plain SQL queries, you can pass a numpy array +or a list of values or a VectorValue object as the value of the field +(in this specific case tuples are not supported).

+
+env.cr.execute('UPDATE your_model SET vector_field = %s WHERE id = 1', (np.array([1.0, 2.0, 3.0]),))
+env.cr.execute('UPDATE your_model SET vector_field = %s WHERE id = 1', ([1.0, 2.0, 3.0],))
+env.cr.execute('UPDATE your_model SET vector_field = %s WHERE id = 1', (VectorValue([1.0, 2.0, 3.0]),))
+
+
+
+
+

Known issues / Roadmap

+
    +
  • allows the use of specific operators into domain filters to search for +similar vectors.
  • +
  • dedicated widget to display the vector in a more user-friendly way.
  • +
+
+
+

Changelog

+

[ The change log. The goal of this file is to help readers understand +changes between version. The primary audience is end users and +integrators. Purely technical changes such as code refactoring must not +be mentioned here.

+

This file may contain ONE level of section titles, underlined with the ~ +(tilde) character. Other section markers are forbidden and will likely +break the structure of the README.rst or other documents where this +fragment is included. ]

+
+

11.0.x.y.z (YYYY-MM-DD)

+
    +
  • [BREAKING] Breaking changes come first. +(#70)
  • +
  • [ADD] New feature. (#74)
  • +
  • [FIX] Correct this. (#71)
  • +
+
+ +
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • ACSONE SA/NV
  • +
+
+ +
+

Other credits

+

The development of this module has been financially supported by:

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

lmignon

+

This module is part of the OCA/server-tools project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/field_vector/tests/__init__.py b/field_vector/tests/__init__.py new file mode 100644 index 00000000000..129c0b574c6 --- /dev/null +++ b/field_vector/tests/__init__.py @@ -0,0 +1 @@ +from . import test_field_vector diff --git a/field_vector/tests/models.py b/field_vector/tests/models.py new file mode 100644 index 00000000000..72fc65f574f --- /dev/null +++ b/field_vector/tests/models.py @@ -0,0 +1,16 @@ +# Copyright 2025 ACSONE SA/NV +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +# DON'T IMPORT THIS MODULE IN INIT TO AVOID THE CREATION OF THE MODELS +# DEFINED FOR TESTS INTO YOUR ODOO INSTANCE +from odoo import models + +from ..fields import Vector + + +class TestModel(models.Model): + _name = "vector.model" + _description = "vector.model Fake Model" + + vector = Vector(dimensions=3, string="Default Vector") + no_autopad = Vector(dimensions=3, string="Vector not autopadded", autopad=False) diff --git a/field_vector/tests/test_field_vector.py b/field_vector/tests/test_field_vector.py new file mode 100644 index 00000000000..6ad6e5198eb --- /dev/null +++ b/field_vector/tests/test_field_vector.py @@ -0,0 +1,147 @@ +# Copyright 2025 ACSONE SA/NV +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +import numpy as np +from odoo_test_helper import FakeModelLoader +from psycopg2.extensions import AsIs + +from odoo.addons.base.tests.common import BaseCommon + +from ..fields import VectorValue + + +class TestFieldVector(BaseCommon): + @classmethod + def setUpClass(cls): + res = super().setUpClass() + cls.loader = FakeModelLoader(cls.env, cls.__module__) + cls.loader.backup_registry() + cls.addClassCleanup(cls.loader.restore_registry) + + # pylint: disable=import-outside-toplevel + from .models import TestModel + + cls.loader.update_registry([TestModel]) + + cls.TestModel = cls.env[TestModel._name] + + return res + + def test_create_from_tuple(self): + record = self.TestModel.create({"vector": (1, 2, 3)}) + self.assertListEqual([1, 2, 3], record.vector.to_list()) + + def test_create_from_list(self): + record = self.TestModel.create({"vector": [1, 2, 3]}) + self.assertListEqual([1, 2, 3], record.vector.to_list()) + + def test_create_autopad(self): + record = self.TestModel.create({"vector": [1, 2]}) + self.assertListEqual([1, 2, 0], record.vector.to_list()) + + def test_create_no_autopad(self): + with self.assertRaisesRegex( + ValueError, + "Invalid vector dimensions", + ): + self.TestModel.create({"no_autopad": [1, 2]}) + + record = self.TestModel.create({"no_autopad": [1, 2, 3]}) + self.assertListEqual([1, 2, 3], record.no_autopad.to_list()) + + def test_from_db(self): + record = self.TestModel.create({"vector": [1, 2, 3]}) + record.flush_recordset() + record.invalidate_model() + new_record = self.TestModel.browse(record.id) + val = new_record.vector + self.assertIsInstance(val, VectorValue) + self.assertEqual(val.to_list(), [1, 2, 3]) + + def test_plain_sql_select(self): + record = self.TestModel.create({"vector": [1, 2, 3]}) + record.flush_recordset() + self.env.cr.execute( + "SELECT vector FROM %s WHERE id = %s", + ( + AsIs(record._table), + record.id, + ), + ) + val = self.env.cr.fetchone()[0] + # Even if we use plain SQL, the value is still a VectorValue + # because of the adapter registered for the vector type + # in the database. + self.assertIsInstance(val, VectorValue) + self.assertEqual(val.to_list(), [1, 2, 3]) + + def test_plain_sql_write(self): + record = self.TestModel.create({"vector": [1, 2, 3]}) + record.flush_recordset() + # as VectorValue + self.env.cr.execute( + "UPDATE %s SET vector = %s WHERE id = %s", + ( + AsIs(record._table), + VectorValue([4, 5, 6]), + record.id, + ), + ) + record.invalidate_model() + new_record = self.TestModel.browse(record.id) + val = new_record.vector + self.assertIsInstance(val, VectorValue) + self.assertEqual(val.to_list(), [4, 5, 6]) + + # as list + self.env.cr.execute( + "UPDATE %s SET vector = %s WHERE id = %s", + ( + AsIs(record._table), + [7, 8, 9], + record.id, + ), + ) + record.invalidate_model() + new_record = self.TestModel.browse(record.id) + val = new_record.vector + self.assertIsInstance(val, VectorValue) + self.assertEqual(val.to_list(), [7, 8, 9]) + + # as numpy array + self.env.cr.execute( + "UPDATE %s SET vector = %s WHERE id = %s", + ( + AsIs(record._table), + np.array([10, 11, 12]), + record.id, + ), + ) + record.invalidate_model() + new_record = self.TestModel.browse(record.id) + val = new_record.vector + self.assertIsInstance(val, VectorValue) + self.assertEqual(val.to_list(), [10, 11, 12]) + + def test_write(self): + record = self.TestModel.create({"vector": [1, 2, 3]}) + record.flush_recordset() + record.vector = [4, 5, 6] + value = record.vector + self.assertIsInstance(value, VectorValue) + self.assertEqual(value.to_list(), [4, 5, 6]) + record.flush_recordset() + record.invalidate_model() + new_record = self.TestModel.browse(record.id) + self.assertEqual(new_record.vector.to_list(), [4, 5, 6]) + record.vector = np.array([7, 8, 9]) + value = record.vector + self.assertIsInstance(value, VectorValue) + self.assertEqual(value.to_list(), [7, 8, 9]) + + def test_read(self): + record = self.TestModel.create({"vector": [1, 2, 3]}) + record.flush_recordset() + record.invalidate_model() + new_record = self.TestModel.browse(record.id) + val = new_record.read(["vector"])[0]["vector"] + self.assertEqual(val, [1, 2, 3]) From e543f151112625586efbaa6e2291e61aa9a04bc8 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Fri, 24 Oct 2025 08:30:30 +0000 Subject: [PATCH 02/10] [UPD] Update field_vector.pot --- field_vector/i18n/field_vector.pot | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 field_vector/i18n/field_vector.pot diff --git a/field_vector/i18n/field_vector.pot b/field_vector/i18n/field_vector.pot new file mode 100644 index 00000000000..8a90f8e781a --- /dev/null +++ b/field_vector/i18n/field_vector.pot @@ -0,0 +1,43 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * field_vector +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: field_vector +#. odoo-python +#: code:addons/field_vector/hooks.py:0 +#, python-format +msgid "" +"Error, can not automatically initialize vector support. Database user may have to be superuser and pgvector extensions to be installed. If you do not want Odoo to connect with a super user you can manually prepare your database. To dothis, open a client to your database using a super user and run:\n" +"CREATE EXTENSION vector;\n" +msgstr "" + +#. module: field_vector +#: model:ir.model.fields,field_description:field_vector.field_ir_model_fields__ttype +msgid "Field Type" +msgstr "" + +#. module: field_vector +#: model:ir.model,name:field_vector.model_ir_model_fields +msgid "Fields" +msgstr "" + +#. module: field_vector +#: model:ir.model.fields,field_description:field_vector.field_ir_model_fields__smart_search +msgid "Smart Search" +msgstr "" + +#. module: field_vector +#: model:ir.model.fields.selection,name:field_vector.selection__ir_model_fields__ttype__vector +msgid "Vector" +msgstr "" From 0cb86a1bcfc7cd1ba04092c756de9c0345d7c123 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 24 Oct 2025 08:39:02 +0000 Subject: [PATCH 03/10] [BOT] post-merge updates --- field_vector/README.rst | 34 ++------- field_vector/static/description/index.html | 87 ++++++++-------------- 2 files changed, 37 insertions(+), 84 deletions(-) diff --git a/field_vector/README.rst b/field_vector/README.rst index 26f3e82c4ec..351c74fefb9 100644 --- a/field_vector/README.rst +++ b/field_vector/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ============ Field Vector ============ @@ -7,13 +11,13 @@ Field Vector !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:4ebe6e838b46ae08fd3c564ea4cf777d8b4d95d61102147c0953d05a1dacc9a4 + !! source digest: sha256:9545962191ec37e88fe11fd9d6e8c01b540134dad5a6482b6a0c19110c7fd3f4 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github @@ -202,32 +206,6 @@ Known issues / Roadmap similar vectors. - dedicated widget to display the vector in a more user-friendly way. -Changelog -========= - -[ The change log. The goal of this file is to help readers understand -changes between version. The primary audience is end users and -integrators. Purely technical changes such as code refactoring must not -be mentioned here. - -This file may contain ONE level of section titles, underlined with the ~ -(tilde) character. Other section markers are forbidden and will likely -break the structure of the README.rst or other documents where this -fragment is included. ] - -11.0.x.y.z (YYYY-MM-DD) ------------------------ - -- [BREAKING] Breaking changes come first. - (`#70 `__) -- [ADD] New feature. (`#74 `__) -- [FIX] Correct this. (`#71 `__) - -11.0.x.y.z (YYYY-MM-DD) ------------------------ - -- ... - Bug Tracker =========== diff --git a/field_vector/static/description/index.html b/field_vector/static/description/index.html index 4f12bcd1875..ae4895f5dd1 100644 --- a/field_vector/static/description/index.html +++ b/field_vector/static/description/index.html @@ -3,7 +3,7 @@ -Field Vector +README.rst -
-

Field Vector

+
+ + +Odoo Community Association + +
+

Field Vector

-

Beta License: LGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

+

Beta License: LGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

This addon provides a new field type called “Vector” that allows you to store and manage vector into your Odoo database.

Table of contents

@@ -385,23 +390,18 @@

Field Vector

  • Known issues / Roadmap
  • -
  • Changelog -
  • -
  • Bug Tracker
  • -
  • Credits
  • -

    Use Cases / Context

    +

    Use Cases / Context

    The advent of large language models (LLMs) has highlighted the importance of vector representation as a powerful representation of data to easily determine the similarity between different pieces of @@ -410,13 +410,13 @@

    Use Cases / Context

    allows for efficient similarity comparisons.

    -

    Installation

    +

    Installation

    To install this module, you need to ensure that the pgvector extension is installed and available in your PostgreSQL instance.

    -

    Configuration

    +

    Configuration

    [ This file is not always required; it should explain how to configure the module before using it; it is aimed at users with administration privileges.

    @@ -431,7 +431,7 @@

    Configuration

    -

    Usage

    +

    Usage

    ⚠️ Warning
    @@ -447,7 +447,7 @@

    Usage

    manage vector data in their Odoo database when they develop their own modules.

    -

    Field declaration

    +

    Field declaration

    To declare a field of type vector, you can use the following syntax:

     from odoo.addons.field_vector.fields import Vector
    @@ -485,7 +485,7 @@ 

    Field declaration

    error will be raised.

    -

    Field usage

    +

    Field usage

    The vector field can be used like any other field in Odoo. When accessing the field, it will always return an odoo.addons.field_vector.fields.VectorValue object, which is a @@ -518,7 +518,7 @@

    Field usage

    -

    Plain SQL queries

    +

    Plain SQL queries

    When reading the field in plain SQL queries, the field will be returned as a VectorValue object. You can use the value property to get the value of the vector as a numpy array.

    @@ -539,41 +539,15 @@

    Plain SQL queries

    -

    Known issues / Roadmap

    +

    Known issues / Roadmap

    • allows the use of specific operators into domain filters to search for similar vectors.
    • dedicated widget to display the vector in a more user-friendly way.
    -
    -

    Changelog

    -

    [ The change log. The goal of this file is to help readers understand -changes between version. The primary audience is end users and -integrators. Purely technical changes such as code refactoring must not -be mentioned here.

    -

    This file may contain ONE level of section titles, underlined with the ~ -(tilde) character. Other section markers are forbidden and will likely -break the structure of the README.rst or other documents where this -fragment is included. ]

    -
    -

    11.0.x.y.z (YYYY-MM-DD)

    -
      -
    • [BREAKING] Breaking changes come first. -(#70)
    • -
    • [ADD] New feature. (#74)
    • -
    • [FIX] Correct this. (#71)
    • -
    -
    - -
    -

    Bug Tracker

    +

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -581,28 +555,28 @@

    Bug Tracker

    Do not contact contributors directly about support or help with technical issues.

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • ACSONE SA/NV
    -

    Other credits

    +

    Other credits

    The development of this module has been financially supported by:

    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association @@ -617,5 +591,6 @@

    Maintainers

    +
    From 0c0fe6eb2cec87dfd3a722b27fd4bdc514730d4f Mon Sep 17 00:00:00 2001 From: mymage Date: Mon, 27 Oct 2025 07:36:22 +0000 Subject: [PATCH 04/10] Added translation using Weblate (Italian) --- field_vector/i18n/it.po | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 field_vector/i18n/it.po diff --git a/field_vector/i18n/it.po b/field_vector/i18n/it.po new file mode 100644 index 00000000000..b1bcb945f7a --- /dev/null +++ b/field_vector/i18n/it.po @@ -0,0 +1,44 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * field_vector +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: field_vector +#. odoo-python +#: code:addons/field_vector/hooks.py:0 +#, python-format +msgid "" +"Error, can not automatically initialize vector support. Database user may have to be superuser and pgvector extensions to be installed. If you do not want Odoo to connect with a super user you can manually prepare your database. To dothis, open a client to your database using a super user and run:\n" +"CREATE EXTENSION vector;\n" +msgstr "" + +#. module: field_vector +#: model:ir.model.fields,field_description:field_vector.field_ir_model_fields__ttype +msgid "Field Type" +msgstr "" + +#. module: field_vector +#: model:ir.model,name:field_vector.model_ir_model_fields +msgid "Fields" +msgstr "" + +#. module: field_vector +#: model:ir.model.fields,field_description:field_vector.field_ir_model_fields__smart_search +msgid "Smart Search" +msgstr "" + +#. module: field_vector +#: model:ir.model.fields.selection,name:field_vector.selection__ir_model_fields__ttype__vector +msgid "Vector" +msgstr "" From 2c7fdce527ab7784162483064c85076b0224d71a Mon Sep 17 00:00:00 2001 From: mymage Date: Mon, 10 Nov 2025 08:43:39 +0000 Subject: [PATCH 05/10] Translated using Weblate (Italian) Currently translated at 100.0% (5 of 5 strings) Translation: server-tools-16.0/server-tools-16.0-field_vector Translate-URL: https://translation.odoo-community.org/projects/server-tools-16-0/server-tools-16-0-field_vector/it/ --- field_vector/i18n/it.po | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/field_vector/i18n/it.po b/field_vector/i18n/it.po index b1bcb945f7a..d2f5683d65f 100644 --- a/field_vector/i18n/it.po +++ b/field_vector/i18n/it.po @@ -6,13 +6,15 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2025-11-10 09:29+0000\n" +"Last-Translator: mymage \n" "Language-Team: none\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" #. module: field_vector #. odoo-python @@ -22,23 +24,30 @@ msgid "" "Error, can not automatically initialize vector support. Database user may have to be superuser and pgvector extensions to be installed. If you do not want Odoo to connect with a super user you can manually prepare your database. To dothis, open a client to your database using a super user and run:\n" "CREATE EXTENSION vector;\n" msgstr "" +"Errore, impossibile inizializzare automaticamente il supporto vettoriale. " +"L'utente del database potrebbe dover essere un superutente e le estensioni " +"pgvector devono essere installate. Se non si desidera che Odoo si connetta " +"con un superutente, è possibile preparare manualmente il database. Per " +"farlo, aprire un client per il database utilizzando un superutente ed " +"eseguire:\n" +"CREATE EXTENSION vector;\n" #. module: field_vector #: model:ir.model.fields,field_description:field_vector.field_ir_model_fields__ttype msgid "Field Type" -msgstr "" +msgstr "Tipo campo" #. module: field_vector #: model:ir.model,name:field_vector.model_ir_model_fields msgid "Fields" -msgstr "" +msgstr "Campi" #. module: field_vector #: model:ir.model.fields,field_description:field_vector.field_ir_model_fields__smart_search msgid "Smart Search" -msgstr "" +msgstr "Ricerca intelligente" #. module: field_vector #: model:ir.model.fields.selection,name:field_vector.selection__ir_model_fields__ttype__vector msgid "Vector" -msgstr "" +msgstr "Vettore" From 8cd3eb1857c596f2e51dafdd4acf9536af77624e Mon Sep 17 00:00:00 2001 From: "Ignacio J. Ortega" Date: Wed, 1 Apr 2026 19:14:02 +0200 Subject: [PATCH 06/10] [MIG] field_vector: Migration to 18.0 --- field_vector/README.rst | 16 +++--- field_vector/__manifest__.py | 2 +- field_vector/fields.py | 17 ++++--- field_vector/hooks.py | 34 ++++++------- field_vector/pyproject.toml | 3 ++ field_vector/static/description/index.html | 57 +++++++++------------- requirements.txt | 1 + 7 files changed, 59 insertions(+), 71 deletions(-) create mode 100644 field_vector/pyproject.toml diff --git a/field_vector/README.rst b/field_vector/README.rst index 351c74fefb9..5566abb3460 100644 --- a/field_vector/README.rst +++ b/field_vector/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - ============ Field Vector ============ @@ -17,17 +13,17 @@ Field Vector .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github - :target: https://github.com/OCA/server-tools/tree/16.0/field_vector + :target: https://github.com/OCA/server-tools/tree/18.0/field_vector :alt: OCA/server-tools .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/server-tools-16-0/server-tools-16-0-field_vector + :target: https://translation.odoo-community.org/projects/server-tools-18-0/server-tools-18-0-field_vector :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=16.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=18.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -212,7 +208,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -257,6 +253,6 @@ Current `maintainer `__: |maintainer-lmignon| -This module is part of the `OCA/server-tools `_ project on GitHub. +This module is part of the `OCA/server-tools `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/field_vector/__manifest__.py b/field_vector/__manifest__.py index e8e5919ee1b..243e53c2272 100644 --- a/field_vector/__manifest__.py +++ b/field_vector/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Field Vector", "summary": """New specialized field to store vector data""", - "version": "16.0.1.0.0", + "version": "18.0.1.0.0", "license": "LGPL-3", "author": "ACSONE SA/NV,Odoo Community Association (OCA)", "website": "https://github.com/OCA/server-tools", diff --git a/field_vector/fields.py b/field_vector/fields.py index 865bfb27531..a488244a65e 100644 --- a/field_vector/fields.py +++ b/field_vector/fields.py @@ -24,7 +24,7 @@ class VectorValue: """ def __init__(self, value: list | tuple | np.ndarray, dimensions=None, autopad=True): - if not isinstance(value, (list, tuple, np.ndarray)): + if not isinstance(value, list | tuple | np.ndarray): raise ValueError( f"Invalid type '{type(value)}' for VectorValue: " "Only list, tuple or np.ndarray are allowed." @@ -124,9 +124,9 @@ class Vector(fields.Field): def __init__( self, - dimensions=fields.Default, - autopad=fields.Default, - string=fields.Default, + dimensions=fields.SENTINEL, + autopad=fields.SENTINEL, + string=fields.SENTINEL, **kwargs, ): super().__init__( @@ -136,7 +136,7 @@ def __init__( def _setup_attrs(self, model_class, name): res = super()._setup_attrs(model_class, name) if ( - self.dimensions == fields.Default + self.dimensions == fields.SENTINEL or self.dimensions is None or not isinstance(self.dimensions, int) ): @@ -179,7 +179,7 @@ def convert_to_export(self, value: VectorValue, record): def convert_to_cache(self, value, record, validate=True): if value is None or value is False: return None - if not isinstance(value, (list, tuple, np.ndarray, VectorValue)): + if not isinstance(value, list | tuple | np.ndarray | VectorValue): raise ValueError( f"Invalid type '{type(value)}' for {self.name}: " "Only np.ndarray or list of floats/int are allowed." @@ -190,14 +190,15 @@ def convert_to_cache(self, value, record, validate=True): value = value.pad(self.dimensions) if validate and value.dimensions != self.dimensions: raise ValueError( - f"Invalid vector size for {self.name}: {value.dimensions} != {self.dimensions}" + f"Invalid vector size for {self.name}: " + f"{value.dimensions} != {self.dimensions}" ) return value def convert_to_record(self, value, record): if value is None or value is False: return None - if not isinstance(value, (list, tuple, np.ndarray, VectorValue)): + if not isinstance(value, list | tuple | np.ndarray | VectorValue): raise ValueError( f"Invalid type '{type(value)}' for {self.name}: " "Only np.ndarray, list of floats/int or VectorValue are allowed." diff --git a/field_vector/hooks.py b/field_vector/hooks.py index 0894b6e5b47..c1a1e07d45b 100644 --- a/field_vector/hooks.py +++ b/field_vector/hooks.py @@ -1,13 +1,10 @@ # Copyright 2025 ACSONE SA/NV # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). -from odoo import _ -from odoo.exceptions import MissingError - -def pre_init_hook(cr): +def pre_init_hook(env): """setup vector""" - cr.execute( + env.cr.execute( """ SELECT tablename @@ -17,24 +14,23 @@ def pre_init_hook(cr): tablename='spatial_ref_sys'; """ ) - check = cr.fetchone() + check = env.cr.fetchone() if check: return {} try: - cr.execute( + env.cr.execute( """ CREATE EXTENSION IF NOT EXISTS vector; """ ) - except Exception as exc: - raise MissingError( - _( - "Error, can not automatically initialize vector" - " support. Database user may have to be superuser and" - " pgvector extensions to be installed. If you do not" - " want Odoo to connect with a super user you can manually" - " prepare your database. To dothis, open a client to your" - " database using a super user and run:\n" - "CREATE EXTENSION vector;\n" - ) - ) from exc + except Exception: + import logging + + _logger = logging.getLogger(__name__) + _logger.warning( + "Could not automatically initialize pgvector support. " + "Database user may need superuser privileges and pgvector " + "extension must be installed. To manually prepare your " + "database, run as superuser:\n" + "CREATE EXTENSION vector;" + ) diff --git a/field_vector/pyproject.toml b/field_vector/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/field_vector/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/field_vector/static/description/index.html b/field_vector/static/description/index.html index ae4895f5dd1..bbebcbcd367 100644 --- a/field_vector/static/description/index.html +++ b/field_vector/static/description/index.html @@ -3,16 +3,15 @@ -README.rst +Field Vector -
    +
    +

    Field Vector

    - - -Odoo Community Association - -
    -

    Field Vector

    -

    Beta License: LGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

    +

    Beta License: LGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

    This addon provides a new field type called “Vector” that allows you to store and manage vector into your Odoo database.

    Table of contents

    @@ -401,7 +395,7 @@

    Field Vector

    -

    Use Cases / Context

    +

    Use Cases / Context

    The advent of large language models (LLMs) has highlighted the importance of vector representation as a powerful representation of data to easily determine the similarity between different pieces of @@ -410,13 +404,13 @@

    Use Cases / Context

    allows for efficient similarity comparisons.

    -

    Installation

    +

    Installation

    To install this module, you need to ensure that the pgvector extension is installed and available in your PostgreSQL instance.

    -

    Configuration

    +

    Configuration

    [ This file is not always required; it should explain how to configure the module before using it; it is aimed at users with administration privileges.

    @@ -431,7 +425,7 @@

    Configuration

    -

    Usage

    +

    Usage

    ⚠️ Warning
    @@ -447,7 +441,7 @@

    Usage

    manage vector data in their Odoo database when they develop their own modules.

    -

    Field declaration

    +

    Field declaration

    To declare a field of type vector, you can use the following syntax:

     from odoo.addons.field_vector.fields import Vector
    @@ -485,7 +479,7 @@ 

    Field declaration

    error will be raised.

    -

    Field usage

    +

    Field usage

    The vector field can be used like any other field in Odoo. When accessing the field, it will always return an odoo.addons.field_vector.fields.VectorValue object, which is a @@ -518,7 +512,7 @@

    Field usage

    -

    Plain SQL queries

    +

    Plain SQL queries

    When reading the field in plain SQL queries, the field will be returned as a VectorValue object. You can use the value property to get the value of the vector as a numpy array.

    @@ -539,7 +533,7 @@

    Plain SQL queries

    -

    Known issues / Roadmap

    +

    Known issues / Roadmap

    • allows the use of specific operators into domain filters to search for similar vectors.
    • @@ -547,50 +541,47 @@

      Known issues / Roadmap

    -

    Bug Tracker

    +

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

    +feedback.

    Do not contact contributors directly about support or help with technical issues.

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • ACSONE SA/NV
    -

    Other credits

    +

    Other credits

    The development of this module has been financially supported by:

    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    - -Odoo Community Association - +Odoo Community Association

    OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

    Current maintainer:

    lmignon

    -

    This module is part of the OCA/server-tools project on GitHub.

    +

    This module is part of the OCA/server-tools project on GitHub.

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

    -
    diff --git a/requirements.txt b/requirements.txt index 5d1fefa6f23..0f89476f74c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ # generated from manifests external_dependencies cryptography dataclasses +numpy odoo_test_helper odoorpc openpyxl From 90e3e77897649d9187ab72e45d3d62b7f5ff1027 Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Fri, 22 May 2026 10:58:02 +0200 Subject: [PATCH 07/10] [IMP] field_vector: Fix some bugs, finish migration, add extra tests --- .github/workflows/test.yml | 2 +- field_vector/README.rst | 6 +++ field_vector/fields.py | 27 +++++++------ field_vector/hooks.py | 17 ++------ field_vector/models/ir_model_fields.py | 2 +- field_vector/readme/ROADMAP.md | 7 +++- field_vector/static/description/index.html | 17 ++++++-- field_vector/tests/__init__.py | 1 + field_vector/tests/models.py | 6 +++ .../tests/test_field_vector_update.py | 39 +++++++++++++++++++ 10 files changed, 92 insertions(+), 32 deletions(-) create mode 100644 field_vector/tests/test_field_vector_update.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a06488079fe..0139a2bdc6a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,7 @@ jobs: makepot: "true" services: postgres: - image: postgres:12.0 + image: pgvector/pgvector:pg12 env: POSTGRES_USER: odoo POSTGRES_PASSWORD: odoo diff --git a/field_vector/README.rst b/field_vector/README.rst index 5566abb3460..f388f236980 100644 --- a/field_vector/README.rst +++ b/field_vector/README.rst @@ -201,6 +201,12 @@ Known issues / Roadmap - allows the use of specific operators into domain filters to search for similar vectors. - dedicated widget to display the vector in a more user-friendly way. +- evaluate removing the psycopg2 adapter (register.py) in favor of + explicit casting in convert_to_column/convert_to_cache. Currently the + adapter must be registered before any SQL query reads vector columns, + which creates a implicit dependency on ir.model.fields._register_hook + execution order. Without the adapter, plain SQL queries would return + raw strings instead of VectorValue objects. Bug Tracker =========== diff --git a/field_vector/fields.py b/field_vector/fields.py index a488244a65e..52c52aa6a71 100644 --- a/field_vector/fields.py +++ b/field_vector/fields.py @@ -2,11 +2,9 @@ # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). from __future__ import annotations -import re from operator import attrgetter import numpy as np -from psycopg2.extensions import AsIs from odoo import fields from odoo.tools import sql @@ -59,7 +57,7 @@ def pad(self, dimensions: int): Pad the vector value to the given size. """ if len(self._value) < dimensions: - self._value = [*self._value, *([0] * (dimensions - self.dimensions))] + self._value = [*self._value, *([0] * (dimensions - len(self._value)))] return self @property @@ -125,8 +123,8 @@ class Vector(fields.Field): def __init__( self, dimensions=fields.SENTINEL, - autopad=fields.SENTINEL, string=fields.SENTINEL, + autopad=fields.SENTINEL, **kwargs, ): super().__init__( @@ -135,7 +133,7 @@ def __init__( def _setup_attrs(self, model_class, name): res = super()._setup_attrs(model_class, name) - if ( + if self.store and ( self.dimensions == fields.SENTINEL or self.dimensions is None or not isinstance(self.dimensions, int) @@ -152,13 +150,18 @@ def column_type(self): def get_current_vector_size(self, cr, table, column): """Fetch the current vector size from pg_typeof()""" cr.execute( - "SELECT pg_typeof(%s)::text FROM %s LIMIT 1;", (AsIs(column), AsIs(table)) + """ + SELECT atttypmod + FROM pg_attribute + JOIN pg_class ON pg_class.oid = pg_attribute.attrelid + WHERE pg_class.relname = %s + AND pg_attribute.attname = %s + """, + (table, column), ) result = cr.fetchone() if result and result[0]: - match = re.search(r"vector\((\d+)\)", result[0]) - if match: - return int(match.group(1)) + return result[0] return None def update_db_column(self, model, column): @@ -211,7 +214,7 @@ def convert_to_record(self, value, record): if value.dimensions != self.dimensions: raise ValueError( f"Invalid vector dimensions for {self.name}: " - "{value.dimensions} != {self.dimensions}" + f"{value.dimensions} != {self.dimensions}" ) return value @@ -221,5 +224,5 @@ def convert_to_read(self, value, record, use_name_get=True): def convert_to_column(self, value, record, values=None, validate=True): return self.convert_to_record(value, record) - def convert_to_write(self, value, record, values=None): - return self.convert_to_column(value, record, values) + def convert_to_write(self, value, record): + return self.convert_to_column(value, record) diff --git a/field_vector/hooks.py b/field_vector/hooks.py index c1a1e07d45b..0421a32fead 100644 --- a/field_vector/hooks.py +++ b/field_vector/hooks.py @@ -3,19 +3,10 @@ def pre_init_hook(env): - """setup vector""" - env.cr.execute( - """ - SELECT - tablename - FROM - pg_tables - WHERE - tablename='spatial_ref_sys'; - """ - ) - check = env.cr.fetchone() - if check: + """setup vector extension if not already setup""" + env.cr.execute("SELECT typname, oid FROM pg_type WHERE oid = to_regtype('vector')") + type_info = dict(env.cr.fetchall()) + if "vector" in type_info: return {} try: env.cr.execute( diff --git a/field_vector/models/ir_model_fields.py b/field_vector/models/ir_model_fields.py index 8166a0d950a..99ffd847dbb 100644 --- a/field_vector/models/ir_model_fields.py +++ b/field_vector/models/ir_model_fields.py @@ -1,5 +1,5 @@ # Copyright 2025 ACSONE SA/NV -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). from odoo import fields, models from ..register import register_vector diff --git a/field_vector/readme/ROADMAP.md b/field_vector/readme/ROADMAP.md index 37d9e8ea843..215bfbb5275 100644 --- a/field_vector/readme/ROADMAP.md +++ b/field_vector/readme/ROADMAP.md @@ -1,2 +1,7 @@ - allows the use of specific operators into domain filters to search for similar vectors. -- dedicated widget to display the vector in a more user-friendly way. \ No newline at end of file +- dedicated widget to display the vector in a more user-friendly way. +- evaluate removing the psycopg2 adapter (register.py) in favor of explicit + casting in convert_to_column/convert_to_cache. Currently the adapter must be + registered before any SQL query reads vector columns, which creates a implicit + dependency on ir.model.fields._register_hook execution order. Without the adapter, + plain SQL queries would return raw strings instead of VectorValue objects. diff --git a/field_vector/static/description/index.html b/field_vector/static/description/index.html index bbebcbcd367..3b2bab1b309 100644 --- a/field_vector/static/description/index.html +++ b/field_vector/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -538,6 +539,12 @@

    Known issues / Roadmap

  • allows the use of specific operators into domain filters to search for similar vectors.
  • dedicated widget to display the vector in a more user-friendly way.
  • +
  • evaluate removing the psycopg2 adapter (register.py) in favor of +explicit casting in convert_to_column/convert_to_cache. Currently the +adapter must be registered before any SQL query reads vector columns, +which creates a implicit dependency on ir.model.fields._register_hook +execution order. Without the adapter, plain SQL queries would return +raw strings instead of VectorValue objects.
  • @@ -572,7 +579,9 @@

    Other credits

    Maintainers

    This module is maintained by the OCA.

    -Odoo Community Association + +Odoo Community Association +

    OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

    diff --git a/field_vector/tests/__init__.py b/field_vector/tests/__init__.py index 129c0b574c6..4bea0213768 100644 --- a/field_vector/tests/__init__.py +++ b/field_vector/tests/__init__.py @@ -1 +1,2 @@ from . import test_field_vector +from . import test_field_vector_update diff --git a/field_vector/tests/models.py b/field_vector/tests/models.py index 72fc65f574f..e9930069d28 100644 --- a/field_vector/tests/models.py +++ b/field_vector/tests/models.py @@ -14,3 +14,9 @@ class TestModel(models.Model): vector = Vector(dimensions=3, string="Default Vector") no_autopad = Vector(dimensions=3, string="Vector not autopadded", autopad=False) + + +class TestModelUpgrade(models.Model): + _inherit = "vector.model" + + vector = Vector(dimensions=5) diff --git a/field_vector/tests/test_field_vector_update.py b/field_vector/tests/test_field_vector_update.py new file mode 100644 index 00000000000..b8e4125c220 --- /dev/null +++ b/field_vector/tests/test_field_vector_update.py @@ -0,0 +1,39 @@ +# Copyright 2025 ACSONE SA/NV +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +from odoo_test_helper import FakeModelLoader + +from odoo.addons.base.tests.common import BaseCommon + + +class TestFieldVectorUpdate(BaseCommon): + def setUp(self): + res = super().setUp() + self.loader = FakeModelLoader(self.env, self.__module__) + self.loader.backup_registry() + self.addCleanup(self.loader.restore_registry) + + # pylint: disable=import-outside-toplevel + from .models import TestModel + + self.loader.update_registry([TestModel]) + + self.TestModel = self.env[TestModel._name] + + return res + + def test_update_db_column(self): + self.assertEqual( + self.TestModel._fields["vector"].get_current_vector_size( + self.env.cr, self.TestModel._table, "vector" + ), + 3, + ) + from .models import TestModelUpgrade + + self.loader.update_registry([TestModelUpgrade]) + self.assertEqual( + self.TestModel._fields["vector"].get_current_vector_size( + self.env.cr, self.TestModel._table, "vector" + ), + 5, + ) From a42229711388552bb6ea5c70ca4e5e503baef864 Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Fri, 22 May 2026 17:44:59 +0200 Subject: [PATCH 08/10] [IMP] field_vector: Added some hoooks to allow configuration from other modules --- field_vector/fields.py | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/field_vector/fields.py b/field_vector/fields.py index 52c52aa6a71..ca8ca4615ee 100644 --- a/field_vector/fields.py +++ b/field_vector/fields.py @@ -131,6 +131,9 @@ def __init__( dimensions=dimensions, string=string, autopad=autopad, **kwargs ) + def vector_dimensions(self, record): + return self.dimensions + def _setup_attrs(self, model_class, name): res = super()._setup_attrs(model_class, name) if self.store and ( @@ -145,7 +148,10 @@ def _setup_attrs(self, model_class, name): @property def column_type(self): - return ("vector", f"vector({self.dimensions})") + return ("vector", self._get_pg_type(self.dimensions)) + + def _get_pg_type(self, dimensions): + return f"vector({dimensions})" def get_current_vector_size(self, cr, table, column): """Fetch the current vector size from pg_typeof()""" @@ -167,9 +173,12 @@ def get_current_vector_size(self, cr, table, column): def update_db_column(self, model, column): if column: db_size = self.get_current_vector_size(model._cr, model._table, self.name) - if db_size is not None and db_size != self.dimensions: + if db_size is not None and db_size != self.vector_dimensions(model): sql.convert_column( - model._cr, model._table, self.name, self.column_type[1] + model._cr, + model._table, + self.name, + self._get_pg_type(self.vector_dimensions(model)), ) return super().update_db_column(model, column) @@ -188,13 +197,15 @@ def convert_to_cache(self, value, record, validate=True): "Only np.ndarray or list of floats/int are allowed." ) if not isinstance(value, VectorValue): - value = VectorValue(value, dimensions=self.dimensions, autopad=self.autopad) - if self.autopad and value.dimensions < self.dimensions: - value = value.pad(self.dimensions) - if validate and value.dimensions != self.dimensions: + value = VectorValue( + value, dimensions=self.vector_dimensions(record), autopad=self.autopad + ) + if self.autopad and value.dimensions < self.vector_dimensions(record): + value = value.pad(self.vector_dimensions(record)) + if validate and value.dimensions != self.vector_dimensions(record): raise ValueError( f"Invalid vector size for {self.name}: " - f"{value.dimensions} != {self.dimensions}" + f"{value.dimensions} != {self.vector_dimensions(record)}" ) return value @@ -207,11 +218,13 @@ def convert_to_record(self, value, record): "Only np.ndarray, list of floats/int or VectorValue are allowed." ) if not isinstance(value, VectorValue): - value = VectorValue(value, dimensions=self.dimensions, autopad=self.autopad) - if self.autopad and value.dimensions < self.dimensions: - value = value.pad(self.dimensions) + value = VectorValue( + value, dimensions=self.vector_dimensions(record), autopad=self.autopad + ) + if self.autopad and value.dimensions < self.vector_dimensions(record): + value = value.pad(self.vector_dimensions(record)) - if value.dimensions != self.dimensions: + if value.dimensions != self.vector_dimensions(record): raise ValueError( f"Invalid vector dimensions for {self.name}: " f"{value.dimensions} != {self.dimensions}" From 0844486cb1a914b00fac1122c583dc8a7a0c8e2e Mon Sep 17 00:00:00 2001 From: oca-ci Date: Mon, 1 Jun 2026 09:53:40 +0000 Subject: [PATCH 09/10] [UPD] Update field_vector.pot --- field_vector/i18n/field_vector.pot | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/field_vector/i18n/field_vector.pot b/field_vector/i18n/field_vector.pot index 8a90f8e781a..59f5418dbad 100644 --- a/field_vector/i18n/field_vector.pot +++ b/field_vector/i18n/field_vector.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" +"Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -13,15 +13,6 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" -#. module: field_vector -#. odoo-python -#: code:addons/field_vector/hooks.py:0 -#, python-format -msgid "" -"Error, can not automatically initialize vector support. Database user may have to be superuser and pgvector extensions to be installed. If you do not want Odoo to connect with a super user you can manually prepare your database. To dothis, open a client to your database using a super user and run:\n" -"CREATE EXTENSION vector;\n" -msgstr "" - #. module: field_vector #: model:ir.model.fields,field_description:field_vector.field_ir_model_fields__ttype msgid "Field Type" From d47ce02b8d783d733b49e6c09c88fb35937bfbc3 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 1 Jun 2026 10:01:50 +0000 Subject: [PATCH 10/10] [BOT] post-merge updates --- README.md | 1 + field_vector/README.rst | 8 +++- field_vector/static/description/index.html | 44 ++++++++++++---------- setup/_metapackage/pyproject.toml | 3 +- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 4b62afca2e4..080d1f03aa0 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ addon | version | maintainers | summary [excel_import_export](excel_import_export/) | 18.0.1.0.0 | kittiu | Base module for developing Excel import/export/report [fetchmail_attach_from_folder](fetchmail_attach_from_folder/) | 18.0.2.0.0 | NL66278 | Attach mails in an IMAP folder to existing objects [fetchmail_notify_error_to_sender](fetchmail_notify_error_to_sender/) | 18.0.1.0.0 | | If fetching mails gives error, send an email to sender +[field_vector](field_vector/) | 18.0.1.0.0 | lmignon | New specialized field to store vector data [html_text](html_text/) | 18.0.1.0.0 | | Generate excerpts from any HTML field [iap_alternative_provider](iap_alternative_provider/) | 18.0.1.0.0 | sebastienbeau | Base module for providing alternative provider for iap apps [jsonifier](jsonifier/) | 18.0.1.1.1 | | JSON-ify data for all models diff --git a/field_vector/README.rst b/field_vector/README.rst index f388f236980..72ee2a9e6eb 100644 --- a/field_vector/README.rst +++ b/field_vector/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ============ Field Vector ============ @@ -7,13 +11,13 @@ Field Vector !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:9545962191ec37e88fe11fd9d6e8c01b540134dad5a6482b6a0c19110c7fd3f4 + !! source digest: sha256:cc9c0caa318b8abd50983092bbe0bed65b7903cba44089fe272c31e4c0b6ab32 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github diff --git a/field_vector/static/description/index.html b/field_vector/static/description/index.html index 3b2bab1b309..501cef37310 100644 --- a/field_vector/static/description/index.html +++ b/field_vector/static/description/index.html @@ -3,7 +3,7 @@ -Field Vector +README.rst -
    -

    Field Vector

    +
    + + +Odoo Community Association + +
    +

    Field Vector

    -

    Beta License: LGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

    +

    Beta License: LGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

    This addon provides a new field type called “Vector” that allows you to store and manage vector into your Odoo database.

    Table of contents

    @@ -396,7 +401,7 @@

    Field Vector

    -

    Use Cases / Context

    +

    Use Cases / Context

    The advent of large language models (LLMs) has highlighted the importance of vector representation as a powerful representation of data to easily determine the similarity between different pieces of @@ -405,13 +410,13 @@

    Use Cases / Context

    allows for efficient similarity comparisons.

    -

    Installation

    +

    Installation

    To install this module, you need to ensure that the pgvector extension is installed and available in your PostgreSQL instance.

    -

    Configuration

    +

    Configuration

    [ This file is not always required; it should explain how to configure the module before using it; it is aimed at users with administration privileges.

    @@ -426,7 +431,7 @@

    Configuration

    -

    Usage

    +

    Usage

    ⚠️ Warning
    @@ -442,7 +447,7 @@

    Usage

    manage vector data in their Odoo database when they develop their own modules.

    -

    Field declaration

    +

    Field declaration

    To declare a field of type vector, you can use the following syntax:

     from odoo.addons.field_vector.fields import Vector
    @@ -480,7 +485,7 @@ 

    Field declaration

    error will be raised.

    -

    Field usage

    +

    Field usage

    The vector field can be used like any other field in Odoo. When accessing the field, it will always return an odoo.addons.field_vector.fields.VectorValue object, which is a @@ -513,7 +518,7 @@

    Field usage

    -

    Plain SQL queries

    +

    Plain SQL queries

    When reading the field in plain SQL queries, the field will be returned as a VectorValue object. You can use the value property to get the value of the vector as a numpy array.

    @@ -534,7 +539,7 @@

    Plain SQL queries

    -

    Known issues / Roadmap

    +

    Known issues / Roadmap

    • allows the use of specific operators into domain filters to search for similar vectors.
    • @@ -548,7 +553,7 @@

      Known issues / Roadmap

    -

    Bug Tracker

    +

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -556,28 +561,28 @@

    Bug Tracker

    Do not contact contributors directly about support or help with technical issues.

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • ACSONE SA/NV
    -

    Other credits

    +

    Other credits

    The development of this module has been financially supported by:

    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association @@ -592,5 +597,6 @@

    Maintainers

    +
    diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml index 783bf6d717f..cb30beca583 100644 --- a/setup/_metapackage/pyproject.toml +++ b/setup/_metapackage/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "odoo-addons-oca-server-tools" -version = "18.0.20260520.0" +version = "18.0.20260601.0" dependencies = [ "odoo-addon-attachment_delete_restrict==18.0.*", "odoo-addon-attachment_queue==18.0.*", @@ -36,6 +36,7 @@ dependencies = [ "odoo-addon-excel_import_export==18.0.*", "odoo-addon-fetchmail_attach_from_folder==18.0.*", "odoo-addon-fetchmail_notify_error_to_sender==18.0.*", + "odoo-addon-field_vector==18.0.*", "odoo-addon-html_text==18.0.*", "odoo-addon-iap_alternative_provider==18.0.*", "odoo-addon-jsonifier==18.0.*",