Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .copier-answers.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Do NOT update manually; changes here will be overwritten by Copier
_commit: v1.42
_commit: v1.43
_src_path: git+https://github.com/OCA/oca-addons-repo-template
additional_ruff_rules: []
convert_readme_fragments_to_markdown: true
Expand All @@ -16,6 +16,7 @@ odoo_test_flavor: Both
odoo_version: 18.0
org_name: Odoo Community Association (OCA)
org_slug: OCA
postgres_image: pgvector/pgvector:pg16
rebel_module_groups: []
repo_description: ai
repo_name: ai
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
makepot: "true"
services:
postgres:
image: postgres:12
image: pgvector/pgvector:pg16
env:
POSTGRES_USER: odoo
POSTGRES_PASSWORD: odoo
Expand Down
145 changes: 145 additions & 0 deletions field_vector_config/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
=================
Field Vector Fill
=================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:722ec05a7f6f24bb142697333fdd71c40ae7a2539242bd672c0affe2056d77c6
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |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-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fai-lightgray.png?logo=github
:target: https://github.com/OCA/ai/tree/18.0/field_vector_config
:alt: OCA/ai
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/ai-18-0/ai-18-0-field_vector_config
: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/ai&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module allows to configure vector fields dynamically. Quite
interesting if you want to handle them by using AI.

**Table of contents**

.. contents::
:local:

Use Cases / Context
===================

The original field_vector module implements vector configuration,
however, it is hard to handle how to create and search them.

With this module, we added the functionality to do it.

Configuration
=============

With this module, we can easily add a configuration on a vector to
configure it dynamically on our database.

.. code:: python

from odoo.addons.field_vector_config.fields import ComputedVector


class ResPartner(models.Model):
_inherit = "res.partner"

embedding = ComputedVector(string="Embedding")

With that, we can go to **Settings / Technical / Database Structure** to
add the field manually and configure it. There you can configure the
size of the vector (depends on the method and model), computation
information and so on.

Important notes:

- If you make a field that is computed, we recommend to create it in a
pre_init_hook to avoid the creation and allow the user to configure it
properly
- If you change the size of the vector, update the column. It will
update the size and do nothing if it has the proper size.

Usage
=====

After the configuration, we can easily compute and search using this
vector. For example:

.. code:: python

from odoo.addons.ai_tool.tools import aitool


class ProductProduct(models.Model):

_inherit = "product.product"

product_vector = ComputedVector(
compute="_compute_product_vector",
store=True,
)

@api.depends("name", "description")
def _compute_product_vector(self):
for record in self:
record.product_vector = record._encode_vector("product_vector", f"{record.name}\n{record.description}\n{record.description_purchase}")[0]

def _find_vector_product(self, value, limit=5):
records = self.search_vector("product_vector", value, limit=limit)
return [{"id": r.id, "name": r.name, "description": r.description} for r in records]

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/ai/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 <https://github.com/OCA/ai/issues/new?body=module:%20field_vector_config%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

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

Credits
=======

Authors
-------

* Dixmit

Contributors
------------

- `Dixmit <https://www.dixmit.com>`__

- Enric Tobella

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.

This module is part of the `OCA/ai <https://github.com/OCA/ai/tree/18.0/field_vector_config>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
2 changes: 2 additions & 0 deletions field_vector_config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import fields
19 changes: 19 additions & 0 deletions field_vector_config/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2026 Dixmit
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "Field Vector Fill",
"summary": """Autogenerate vectors and add search functions""",
"version": "18.0.1.0.0",
"license": "AGPL-3",
"author": "Dixmit,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/ai",
"depends": [
"field_vector",
],
"data": [
"security/ir.model.access.csv",
"views/ir_model_field_vector.xml",
],
"demo": [],
}
43 changes: 43 additions & 0 deletions field_vector_config/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2026 Dixmit
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo.addons.field_vector.fields import Vector


def split_text_chunks(text, max_words=350, overlap=50):
words = text.split()
chunks = []
start = 0
while start < len(words):
chunk = " ".join(words[start : start + max_words])
chunks.append(chunk)
start += max_words - overlap
return chunks


class ComputedVector(Vector):
type = "computed_vector"

def __init__(
self,
dimensions=10,
**kwargs,
):
# We set a default dimensions value
super().__init__(dimensions=dimensions, **kwargs)

@property
def column_type(self):
return ("vector", "vector")

def vector_dimensions(self, model):
dimensions = (
model.env["ir.model.fields"]
.sudo()
.search(
[("model", "=", model._name), ("name", "=", self.name)],
limit=1,
)
.vector_configuration_ids.dimensions
)
return dimensions or super().vector_dimensions(model)
3 changes: 3 additions & 0 deletions field_vector_config/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import ir_model_field_vector
from . import ir_model_fields
from . import base
82 changes: 82 additions & 0 deletions field_vector_config/models/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright 2026 Dixmit
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import api, fields, models
from odoo.exceptions import UserError
from odoo.tools import sql

from ..fields import ComputedVector


class Base(models.AbstractModel):
_inherit = "base"

@api.model
@api.readonly
@api.returns("self")
def search_vector(self, field_name, data, domain=None, limit=5, minim=0.5):
if domain is None:
domain = []
if not isinstance(self._fields[field_name], ComputedVector):
raise UserError(f"Field {field_name} is not a computed_vector field")
to_search = self._encode_vector(field_name, data, is_search=True)[0]
query = self._search(domain, limit=limit)
distance = sql.SQL(
"%s <=> %s::vector",
self._field_to_sql(self._table, field_name, query),
to_search,
)
sql_terms = [
self._field_to_sql(self._table, "id", "query"),
sql.SQL("%s as distance", distance),
]
query.order = "distance ASC"
if minim:
query.add_where(sql.SQL("%s < %s", distance, minim))
self.env.cr.execute(query.select(*sql_terms))
return self.browse([row[0] for row in self.env.cr.fetchall()])

@api.model
@api.readonly
@api.returns("self")
def search_vector_grouped(
self, field_name, data, final_field, domain=None, limit=5, minim=0.5
):
if domain is None:
domain = []
if not isinstance(self._fields[field_name], ComputedVector):
raise UserError(f"Field {field_name} is not a computed_vector field")
if not isinstance(self._fields[final_field], fields.Many2one):
raise UserError(f"Field {final_field} is not a Many2one field")
to_search = self._encode_vector(field_name, data, is_search=True)[0]
query = self._search(domain, limit=limit)
distance = sql.SQL(
"MIN(%s <=> %s::vector)",
self._field_to_sql(self._table, field_name, query),
to_search,
)
sql_terms = [
self._field_to_sql(self._table, final_field, "query"),
sql.SQL("%s as distance", distance),
]
query.order = "distance ASC"
query.groupby = self._field_to_sql(self._table, final_field, query)
if minim:
query.having = sql.SQL("%s < %s", distance, minim)
self.env.cr.execute(query.select(*sql_terms))
rows = self.env.cr.fetchall()
return self[final_field].browse([row[0] for row in rows])

def _encode_vector(self, field_name, data, is_search=False):
config = (
self.env["ir.model.fields"]
.sudo()
.search(
[("model", "=", self._name), ("name", "=", field_name)],
limit=1,
)
.vector_configuration_ids
)
if not config:
raise UserError(f"Field {field_name} is not configured")
return config._encode_vector(data, is_search=is_search)
Loading
Loading