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
106 changes: 106 additions & 0 deletions ai_server_action/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
=============
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/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/ai_server_action
: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-ai_server_action
: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 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_server_action%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

- [SDi] (https://sdi.es)

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/ai_server_action>`_ 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_server_action/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
16 changes: 16 additions & 0 deletions ai_server_action/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# 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": "18.0.1.0.0",
"license": "AGPL-3",
"author": "Dixmit,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/ai",
"depends": ["ai_connection", "ai_tool"],
"data": [
"views/ir_actions_server.xml",
],
"demo": [],
}
1 change: 1 addition & 0 deletions ai_server_action/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import ir_actions_server
116 changes: 116 additions & 0 deletions ai_server_action/models/ir_actions_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Copyright 2026 Dixmit
# Copyright 2026 SDi - Ángel Moya <amoya@sdi.es>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import api, fields, models
from odoo.tools.mail import html2plaintext, html_sanitize, plaintext2html

try:
import markdown
except ImportError:
markdown = None


class IrActionsServer(models.Model):
_inherit = "ir.actions.server"

state = fields.Selection(
selection_add=[("ai_oca", "Run AI Prompt")],
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_output_mode = fields.Selection(
[
("post_message", "Post Message"),
("update_record", "Update Record"),
("store_variable", "Store in Context Variable"),
("none", "None"),
],
string="Output Mode",
default="none",
groups="base.group_system",
)
ai_update_record_field_id = fields.Many2one(
"ir.model.fields",
string="Update Record Field",
domain=(
"[('model_id', '=', model_id), "
"('ttype', 'in', ['char', 'text', 'html'])]"
),
groups="base.group_system",
)
ai_context_variable = fields.Char(
string="Context Variable",
groups="base.group_system",
help=(
"Name of the variable where the response will be stored "
"in the evaluation context."
),
)

@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):
self.ensure_one()
eval_context = eval_context or {}
record = eval_context.get("record")
prompt = self._get_ai_prompt(record)
result = self.ai_connection_id._run(
prompt,
tools=self.ai_tool_ids,
record=record,
)[0]
self._post_run_action_ai_run(result, eval_context, record)

def _get_ai_prompt(self, record):
self.ensure_one()
prompt = self.ai_prompt or ""
if record:
prompt = str(
self.env["mail.render.mixin"]._render_template_qweb(
prompt, record._name, record.ids
)[record.id]
)
return html2plaintext(prompt)

def _post_run_action_ai_run(self, result, eval_context, record):
self.ensure_one()
if self.ai_output_mode == "post_message" and record and record.exists():
body = self._prepare_message_body(result)
record.message_post(body=body)
elif self.ai_output_mode == "update_record":
if record and self.ai_update_record_field_id:
if markdown and self.ai_update_record_field_id.ttype == "html":
value = self._prepare_message_body(result)
else:
value = result
record.write({self.ai_update_record_field_id.name: value})
elif self.ai_output_mode == "store_variable":
if self.ai_context_variable:
eval_context[self.ai_context_variable] = result

def _prepare_message_body(self, result):
if markdown:
return html_sanitize(markdown.markdown(result))
return plaintext2html(result)
3 changes: 3 additions & 0 deletions ai_server_action/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
3 changes: 3 additions & 0 deletions ai_server_action/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- [Dixmit](https://www.dixmit.com)
- Enric Tobella
- [SDi] (https://sdi.es)
3 changes: 3 additions & 0 deletions ai_server_action/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_server_action/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.
Binary file added ai_server_action/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