Skip to content

Commit 21b340e

Browse files
committed
reformat
1 parent 1e85224 commit 21b340e

9 files changed

+426
-33
lines changed

llm_knowledge/models/llm_document_collection.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def sync_documents(self):
148148
existing_docs = collection.document_ids
149149

150150
# Track which existing documents should be kept
151-
docs_to_keep = self.env['llm.document']
151+
docs_to_keep = self.env["llm.document"]
152152

153153
# Process all matching records to create/link documents
154154
for model_name, record_id in matching_records:
@@ -201,7 +201,9 @@ def sync_documents(self):
201201
# Remove documents that no longer match any domains
202202
if docs_to_remove:
203203
# Only remove from this collection, not delete the documents
204-
collection.write({"document_ids": [(3, doc.id) for doc in docs_to_remove]})
204+
collection.write(
205+
{"document_ids": [(3, doc.id) for doc in docs_to_remove]}
206+
)
205207
removed_count = len(docs_to_remove)
206208

207209
# Post summary message
@@ -216,7 +218,9 @@ def sync_documents(self):
216218
)
217219
else:
218220
collection.message_post(
219-
body=_("No changes made - collection is already in sync with domains."),
221+
body=_(
222+
"No changes made - collection is already in sync with domains."
223+
),
220224
message_type="notification",
221225
)
222226

llm_knowledge/views/llm_document_collection_views.xml

+30-30
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,40 @@
88
<form>
99
<header>
1010
<button
11-
name="sync_documents"
12-
string="Sync Documents"
13-
type="object"
14-
class="btn-primary"
15-
attrs="{'invisible': [('domain_ids', '=', [])]}"
16-
confirm="This will synchronize the collection with domain filters, adding new documents and removing those no longer matching. Continue?"
17-
/>
11+
name="sync_documents"
12+
string="Sync Documents"
13+
type="object"
14+
class="btn-primary"
15+
attrs="{'invisible': [('domain_ids', '=', [])]}"
16+
confirm="This will synchronize the collection with domain filters, adding new documents and removing those no longer matching. Continue?"
17+
/>
1818
<button
19-
name="action_open_upload_wizard"
20-
string="Upload Documents"
21-
type="object"
22-
class="btn-primary"
23-
/>
19+
name="action_open_upload_wizard"
20+
string="Upload Documents"
21+
type="object"
22+
class="btn-primary"
23+
/>
2424
<button
25-
name="process_documents"
26-
string="Process Documents"
27-
type="object"
28-
class="btn-primary"
29-
confirm="This will process documents through the RAG pipeline until chunked state. Continue?"
30-
/>
25+
name="process_documents"
26+
string="Process Documents"
27+
type="object"
28+
class="btn-primary"
29+
confirm="This will process documents through the RAG pipeline until chunked state. Continue?"
30+
/>
3131
<button
32-
name="embed_documents"
33-
string="Embed Documents"
34-
type="object"
35-
class="btn-primary"
36-
confirm="This will embed all document chunks with the selected embedding model. Continue?"
37-
/>
32+
name="embed_documents"
33+
string="Embed Documents"
34+
type="object"
35+
class="btn-primary"
36+
confirm="This will embed all document chunks with the selected embedding model. Continue?"
37+
/>
3838
<button
39-
name="reindex_collection"
40-
string="Reindex Collection"
41-
type="object"
42-
class="btn-secondary"
43-
confirm="This will recreate vector indexes for documents. Continue?"
44-
/>
39+
name="reindex_collection"
40+
string="Reindex Collection"
41+
type="object"
42+
class="btn-secondary"
43+
confirm="This will recreate vector indexes for documents. Continue?"
44+
/>
4545
</header>
4646
<sheet>
4747
<div class="oe_button_box" name="button_box">

llm_knowledge_automation/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import models
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "LLM Knowledge Automation",
3+
"summary": "Automates RAG document creation and synchronization with collections",
4+
"description": """
5+
Extends the LLM Knowledge module to automatically keep collections synchronized
6+
with updated records through automated actions.
7+
8+
Features:
9+
- Automatically create/update RAG documents when records change
10+
- Synchronize collections with their domain filters via automated actions
11+
- Remove documents from collections when they no longer match filters
12+
- Trigger document processing pipeline automatically
13+
""",
14+
"category": "Technical",
15+
"version": "16.0.1.0.0",
16+
"depends": ["llm_knowledge", "base_automation"],
17+
"external_dependencies": {
18+
"python": [],
19+
},
20+
"author": "Apexive Solutions LLC",
21+
"website": "https://github.com/apexive/odoo-llm",
22+
"data": [
23+
"views/llm_document_collection_views.xml",
24+
],
25+
"license": "LGPL-3",
26+
"installable": True,
27+
"application": False,
28+
"auto_install": False,
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import llm_document_collection
2+
from . import base_automation
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import logging
2+
from odoo import _, api, fields, models
3+
from odoo.tools import safe_eval
4+
5+
_logger = logging.getLogger(__name__)
6+
7+
class BaseAutomation(models.Model):
8+
_inherit = "base.automation"
9+
10+
# We need to extend the selection field properly
11+
# Cannot directly use selection_add because it needs the original selection
12+
# Instead, we'll override the field completely
13+
state = fields.Selection(selection=[
14+
('code', 'Execute Python Code'),
15+
('object_create', 'Create a new Record'),
16+
('object_write', 'Update the Record'),
17+
('mail_post', 'Post a Message'),
18+
('followers', 'Add Followers'),
19+
('next_activity', 'Create Next Activity'),
20+
('llm_update', 'Update LLM Document'),
21+
], string='Action To Do',
22+
default='code', required=True, copy=True,
23+
help="Type of server action")
24+
25+
llm_collection_id = fields.Many2one(
26+
"llm.document.collection",
27+
string="LLM Collection",
28+
ondelete="cascade",
29+
help="LLM Collection that this automated action is linked to",
30+
)
31+
32+
llm_auto_process = fields.Boolean(
33+
string="Auto Process Documents",
34+
default=True,
35+
help="Automatically process documents through the RAG pipeline",
36+
)
37+
38+
@api.model
39+
def _get_states(self):
40+
"""Add llm_update to the states dictionary."""
41+
states = super()._get_states()
42+
states["llm_update"] = _("Update related LLM document")
43+
return states
44+
45+
def _process_llm_update(self, records):
46+
"""Process the update LLM document action."""
47+
self.ensure_one()
48+
49+
if not self.llm_collection_id:
50+
_logger.error("Cannot execute LLM Update action without a collection")
51+
return False
52+
53+
collection = self.llm_collection_id
54+
model_id = self.model_id.id
55+
56+
# Apply filter_domain to get matched records
57+
domain = self.filter_domain or "[]"
58+
matched_records = records
59+
60+
# If this isn't on_create, we need to filter the records
61+
if self.trigger != "on_create" and domain != "[]":
62+
eval_context = self._get_eval_context()
63+
domain_result = safe_eval.safe_eval(domain, eval_context)
64+
matched_records = records.filtered_domain(domain_result)
65+
66+
# Process matched records: either create new or add to collection
67+
for record in matched_records:
68+
# Try to find existing document
69+
existing_doc = self.env["llm.document"].search([
70+
("model_id", "=", model_id),
71+
("res_id", "=", record.id)
72+
], limit=1)
73+
74+
if existing_doc:
75+
# If it exists but not in this collection, add it
76+
if collection.id not in existing_doc.collection_ids.ids:
77+
existing_doc.write({"collection_ids": [(4, collection.id)]})
78+
else:
79+
# Create a new document
80+
# Get a meaningful name
81+
if hasattr(record, "display_name") and record.display_name:
82+
name = record.display_name
83+
elif hasattr(record, "name") and record.name:
84+
name = record.name
85+
else:
86+
name = f"{self.model_id.name} #{record.id}"
87+
88+
# Create the document and add to collection
89+
doc = self.env["llm.document"].create({
90+
"name": name,
91+
"model_id": model_id,
92+
"res_id": record.id,
93+
"collection_ids": [(4, collection.id)],
94+
})
95+
96+
# Process the document if auto_process is enabled
97+
if self.llm_auto_process:
98+
doc.process_document()
99+
100+
# For on_write and on_unlink, handle records that no longer match the domain
101+
if self.trigger in ["on_write", "on_unlink"]:
102+
unmatched_records = records - matched_records
103+
104+
for record in unmatched_records:
105+
# Find document
106+
doc = self.env["llm.document"].search([
107+
("model_id", "=", model_id),
108+
("res_id", "=", record.id)
109+
], limit=1)
110+
111+
if doc and collection.id in doc.collection_ids.ids:
112+
# Remove from this collection
113+
doc.write({"collection_ids": [(3, collection.id)]})
114+
115+
# If document doesn't belong to any collection, remove it
116+
if not doc.collection_ids:
117+
doc.unlink()
118+
119+
return True
120+
121+
def _process(self, records, domain_post=None):
122+
"""Override _process to handle the llm_update state."""
123+
# Handle standard behavior for other states
124+
if self.state != "llm_update":
125+
return super()._process(records, domain_post=domain_post)
126+
127+
# Filter out the records on which self has already been done
128+
action_done = self._context.get("__action_done") or {}
129+
records_done = action_done.get(self, records.browse())
130+
records -= records_done
131+
if not records:
132+
return
133+
134+
# Mark the remaining records as done (to avoid recursive processing)
135+
action_done = dict(action_done)
136+
action_done[self] = records_done + records
137+
self = self.with_context(__action_done=action_done)
138+
records = records.with_context(__action_done=action_done)
139+
140+
# Update document modification date if field exists
141+
values = {}
142+
if "date_action_last" in records._fields:
143+
values["date_action_last"] = fields.Datetime.now()
144+
if values:
145+
records.write(values)
146+
147+
# Process the LLM document update
148+
self._process_llm_update(records)

0 commit comments

Comments
 (0)