Skip to content
Merged
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
108 changes: 108 additions & 0 deletions ai_automation/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

=============
Ai Automation
=============

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

.. |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-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/16.0/ai_automation
:alt: OCA/ai
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/ai-16-0/ai-16-0-ai_automation
: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=16.0
:alt: Try me on Runboat

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

This module integrates AI connections with Odoo server actions, allowing
you to define AI-powered automations. By default it provides Ollama
support, but it can be extended with additional providers by adding new
``kind`` options to ``ai.connection``.

**Table of contents**

.. contents::
:local:

Usage
=====

This module adds a new server action type: **AI OCA Action**.

To use it:

1. Go to ``Settings > Technical > Actions > Server Actions``
2. Create a new action and select **AI OCA Action** as the state
3. Select an AI Connection (Ollama)
4. Define the prompt — supports dynamic placeholders using Qweb syntax
(e.g. ``{{ object.name }}``)
5. Optionally select tools the AI can call during execution
6. Define what to do with the result:

- **Post Message**: posts the AI response as a chatter message on the
record
- **Update Record**: writes the AI response to a specific field

To extend with a new AI provider, inherit ``ai.connection`` and add a
new selection value to ``kind``, then implement the corresponding
``_run_{kind}`` method.

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:%20ai_automation%0Aversion:%2016.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/16.0/ai_automation>`_ project on GitHub.

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

{
"name": "Ai Automation",
"summary": """Integrate `ai_tools` with server actions to automate tasks using AI.""",
"version": "16.0.1.0.0",
"license": "AGPL-3",
"author": "Dixmit,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/ai",
"depends": ["ai_tool"],
"external_dependencies": {
"python": ["ollama"],
},
"data": [
"views/ir_actions_server.xml",
"security/ir.model.access.csv",
"views/ai_connection.xml",
],
"demo": [],
}
2 changes: 2 additions & 0 deletions ai_automation/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import ai_connection
from . import ir_actions_server
73 changes: 73 additions & 0 deletions ai_automation/models/ai_connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Copyright 2026 Dixmit
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

import json

import ollama

from odoo import fields, models


class AiConnection(models.Model):
_name = "ai.connection"
_description = "AI Connection"

name = fields.Char(required=True)
kind = fields.Selection([("ollama", "Ollama")], required=True, default="ollama")
active = fields.Boolean(default=True)
url = fields.Char(groups="base.group_system")
model = fields.Char(groups="base.group_system")

def _run(self, prompt, tools=None, record=None):
return getattr(self, f"_run_{self.kind}")(prompt, tools=tools, record=record)

def _run_ollama(self, prompt, tools=None, messages=None, record=None):
tool_definition = []
for tool in tools or []:
definition = tool._get_tool_definition()
input_schema = definition["inputSchema"]
input_schema["additionalProperties"] = False
tool_definition.append(
{
"type": "function",
"function": {
"name": definition["name"],
"description": definition["description"],
"parameters": input_schema,
},
}
)
ollama_client = ollama.Client(**self._get_ollama_client_parameters())
if messages is None:
messages = []
messages.append({"role": "user", "content": prompt})
while True:
response = ollama_client.chat(
model=self.model,
messages=messages,
tools=tool_definition,
)
if not response.message.tool_calls:
return response.message.content
messages.append(response.message)
for call in response.message.tool_calls:
tool = tools.filtered(lambda t: t.name == call.function.name)
tool_output = tool._execute_tool(
record=record, **call.function.arguments
)
if isinstance(tool_output, dict):
tool_output = json.dumps(tool_output)
messages.append(
{
"role": "tool",
"tool_name": call.function.name,
"content": tool_output,
}
)

def _get_ollama_client_parameters(self):
"""
We provide this hook so people can modify the client and other configurations
like headers and so on.
"""
return {"host": self.url, "headers": {}}
72 changes: 72 additions & 0 deletions ai_automation/models/ir_actions_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright 2026 Dixmit
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from lxml import etree

from odoo import api, fields, models

from odoo.addons.web_editor.models.ir_qweb_fields import html_to_text


class IrActionsServer(models.Model):

_inherit = "ir.actions.server"

state = fields.Selection(
selection_add=[("ai_oca", "AI OCA Action")], ondelete={"ai_oca": "cascade"}
)
ai_connection_id = fields.Many2one(
"ai.connection", string="AI Connection", groups="base.group_system"
)
ai_tool_ids = fields.Many2many(
"ai.tool",
string="AI Tools",
groups="base.group_system",
)
ai_prompt = fields.Html(string="AI Prompt", sanitize=False)
mailing_model_real = fields.Char(compute="_compute_mailing_model_real")
ai_result_action = fields.Selection(
[
("post_message", "Post Message"),
("update_record", "Update Record"),
],
string="AI Result Action",
)
ai_update_record_field_id = fields.Many2one(
"ir.model.fields",
string="AI Update Record Field",
domain="[('model_id', '=', model_id), ('ttype', 'in', ['char', 'text'])]",
)

@api.depends("model_id")
def _compute_mailing_model_real(self):
for record in self:
record.mailing_model_real = (
record.model_id.model if record.model_id else False
)

def _run_action_ai_oca(self, eval_context=None):
record = eval_context.get("record")
result = self.ai_connection_id._run(
self._get_ai_oca_prompt(record), tools=self.ai_tool_ids, record=record
)
self._post_run_action_ai_oca(result, record)

def _post_run_action_ai_oca(self, result, record):
if self.ai_result_action == "post_message":
self.env["ai.tool"]._ai_post_message(result, record=record)
elif (
self.ai_result_action == "update_record"
and record
and self.ai_update_record_field_id
):
record.write({self.ai_update_record_field_id.name: result})

def _get_ai_oca_prompt(self, record):
ai_prompt = self.ai_prompt
if record:
ai_prompt = str(
self.env["mail.render.mixin"]._render_template_qweb(
self.ai_prompt, record and record._name, record and record.ids
)[record.id]
)
return html_to_text(etree.fromstring("<t>" + ai_prompt + "</t>"))
2 changes: 2 additions & 0 deletions ai_automation/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- [Dixmit](https://www.dixmit.com)
- Enric Tobella
3 changes: 3 additions & 0 deletions ai_automation/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This module integrates AI connections with Odoo server actions, allowing you to
define AI-powered automations. By default it provides Ollama support, but it can
be extended with additional providers by adding new `kind` options to `ai.connection`.
14 changes: 14 additions & 0 deletions ai_automation/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
This module adds a new server action type: **AI OCA Action**.

To use it:
1. Go to `Settings > Technical > Actions > Server Actions`
2. Create a new action and select **AI OCA Action** as the state
3. Select an AI Connection (Ollama)
4. Define the prompt — supports dynamic placeholders using Qweb syntax (e.g. `{{ object.name }}`)
5. Optionally select tools the AI can call during execution
6. Define what to do with the result:
- **Post Message**: posts the AI response as a chatter message on the record
- **Update Record**: writes the AI response to a specific field

To extend with a new AI provider, inherit `ai.connection` and add a new selection
value to `kind`, then implement the corresponding `_run_{kind}` method.
3 changes: 3 additions & 0 deletions ai_automation/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ai_connection,access_ai_connection,model_ai_connection,base.group_user,1,0,0,0
manage_ai_connection,manage_ai_connection,model_ai_connection,base.group_system,1,1,1,0
Binary file added ai_automation/static/description/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading