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
82 changes: 82 additions & 0 deletions ai_tool_mail_message_search/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
=============================
AI Tool - Mail Message Search
=============================

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

.. |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-qrtl%2Fqrtl--oca-lightgray.png?logo=github
:target: https://github.com/qrtl/qrtl-oca/tree/16.0/ai_tool_mail_message_search
:alt: qrtl/qrtl-oca

|badge1| |badge2| |badge3|

This module exposes chatter (``mail.message``) search as ``ai_tool`` MCP
tools, allowing AI agents to look up conversation history across any
record.

Two tools are provided:

- **search_messages**: searches chatter messages by one or more keywords
(matching subject, body and record name), returning the matching
messages' content plus the model/id of the record they are attached
to, grouped per keyword.
- **get_messages_for_records**: given a list of ``{model, id}`` record
references, returns the full chatter message history logged against
each of those records.

**Table of contents**

.. contents::
:local:

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

Bugs are tracked on `GitHub Issues <https://github.com/qrtl/qrtl-oca/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/qrtl/qrtl-oca/issues/new?body=module:%20ai_tool_mail_message_search%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
-------

* Quartile

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

- `Quartile <https://www.quartile.co>`__:

- Toshikimi Shigenobu

Maintainers
-----------

.. |maintainer-nobuQuartile| image:: https://github.com/nobuQuartile.png?size=40px
:target: https://github.com/nobuQuartile
:alt: nobuQuartile

Current maintainer:

|maintainer-nobuQuartile|

This module is part of the `qrtl/qrtl-oca <https://github.com/qrtl/qrtl-oca/tree/16.0/ai_tool_mail_message_search>`_ project on GitHub.

You are welcome to contribute.
1 change: 1 addition & 0 deletions ai_tool_mail_message_search/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
15 changes: 15 additions & 0 deletions ai_tool_mail_message_search/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "AI Tool - Mail Message Search",
"summary": "Expose chatter message search/lookup as ai_tool MCP tools",
"version": "16.0.1.0.0",
"website": "https://github.com/OCA/ai",
"license": "AGPL-3",
"author": "Quartile",
"maintainers": ["nobuQuartile"],
"depends": [
"ai_tool",
],
"data": [
"data/ai_tools.xml",
],
}
27 changes: 27 additions & 0 deletions ai_tool_mail_message_search/data/ai_tools.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>

<record id="search_messages_tool" model="ai.tool">
<field name="name">search_messages</field>
<field name="description">
Search across all chatter messages by one or more keywords (matches
subject, body and record name). Returns, per keyword, the matching
messages' content plus the model/id of the record they are attached to.
</field>
<field name="model_id" ref="mail.model_mail_message" />
<field name="function_name">_ai_search_messages</field>
<field name="kind">generic</field>
</record>

<record id="get_messages_for_records_tool" model="ai.tool">
<field name="name">get_messages_for_records</field>
<field name="description">
Given a list of {model, id} record references, return the full chatter
message history logged against each of those records.
</field>
<field name="model_id" ref="mail.model_mail_message" />
<field name="function_name">_ai_get_messages_for_records</field>
<field name="kind">generic</field>
</record>

</odoo>
1 change: 1 addition & 0 deletions ai_tool_mail_message_search/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import mail_message
137 changes: 137 additions & 0 deletions ai_tool_mail_message_search/models/mail_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
from odoo import models
from odoo.osv import expression
from odoo.tools import html2plaintext

from odoo.addons.ai_tool.tools import aitool

SNIPPET_LEN = 500
DEFAULT_LIMIT = 20
# Same field list/word-tokenization approach as qrtlrepos/axls-oca/mail_message_search's
# mail.thread._search_message_search, adapted here to search mail.message directly
# across all models instead of being scoped to one already-known model.
SEARCH_FIELDS = ["record_name", "subject", "body", "email_from", "reply_to"]


class MailMessage(models.Model):
_inherit = "mail.message"

@aitool(
input_schema={
"terms": {"type": "array", "items": {"type": "string"}},
},
required_inputs=["terms"],
output_schema={
"results": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "object",
"properties": {
"content": {"type": "string"},
"model": {"type": "string"},
"id": {"type": "integer"},
"date": {"type": "string", "format": "date-time"},
"record_name": {"type": "string"},
"author": {"type": "string"},
},
},
},
},
},
)
def _ai_search_messages(self, terms):
results = {}
for term in terms:
words = term.split()
if not words:
results[term] = []
continue
word_domains = [
expression.OR([[(field, "ilike", word)] for field in SEARCH_FIELDS])
for word in words
]
domain = expression.AND(word_domains)
messages = self.search(domain, limit=DEFAULT_LIMIT, order="date desc")
results[term] = [
{
"content": html2plaintext(message.body or "")[:SNIPPET_LEN],
"model": message.model,
"id": message.res_id,
"date": message.date.isoformat() if message.date else "",
"record_name": message.record_name or "",
"author": message.author_id.name or message.email_from or "",
}
for message in messages
if message.model and message.res_id
]
return {"results": results}

@aitool(
input_schema={
"records": {
"type": "array",
"items": {
"type": "object",
"properties": {
"model": {"type": "string"},
"id": {"type": "integer"},
},
"required": ["model", "id"],
},
},
},
required_inputs=["records"],
output_schema={
"records": {
"type": "array",
"items": {
"type": "object",
"properties": {
"model": {"type": "string"},
"id": {"type": "integer"},
"record_name": {"type": "string"},
"messages": {
"type": "array",
"items": {
"type": "object",
"properties": {
"content": {"type": "string"},
"date": {"type": "string", "format": "date-time"},
"author": {"type": "string"},
},
},
},
},
},
},
},
)
def _ai_get_messages_for_records(self, records):
result = []
for ref in records:
# Cap and keep only the most recent messages, then restore
# chronological order (oldest first) for display.
messages = self.search(
[("model", "=", ref["model"]), ("res_id", "=", ref["id"])],
order="date desc, id desc",
limit=DEFAULT_LIMIT,
)[::-1]
result.append(
{
"model": ref["model"],
"id": ref["id"],
"record_name": messages[-1].record_name if messages else "",
"messages": [
{
"content": html2plaintext(message.body or ""),
"date": message.date.isoformat() if message.date else "",
"author": message.author_id.name
or message.email_from
or "",
}
for message in messages
],
}
)
return {"records": result}
2 changes: 2 additions & 0 deletions ai_tool_mail_message_search/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- [Quartile](https://www.quartile.co):
- Toshikimi Shigenobu
12 changes: 12 additions & 0 deletions ai_tool_mail_message_search/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
This module exposes chatter (`mail.message`) search as `ai_tool` MCP tools,
allowing AI agents to look up conversation history across any record.

Two tools are provided:

- **search_messages**: searches chatter messages by one or more keywords
(matching subject, body and record name), returning the matching messages'
content plus the model/id of the record they are attached to, grouped per
keyword.
- **get_messages_for_records**: given a list of `{model, id}` record
references, returns the full chatter message history logged against each
of those records.
Loading
Loading