feat: auto-inject Odoo workflow context into every agent execution

BaseAgent._lookup_odoo_context() calls odoo_doc_agent via PeerBus before
_plan() runs on every directive. The RAG answer is stored in
self._gathered['odoo_context'] and injected into every _loop() LLM call
so agents reason with correct Odoo 18 workflow steps automatically.

No changes required to individual agents. odoo_doc_agent opts out via
auto_rag=False to prevent self-referential calls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Carlos Garcia
2026-05-14 23:35:03 -04:00
parent 147a99cab2
commit 65920d6128
2 changed files with 47 additions and 0 deletions

View File

@@ -81,6 +81,7 @@ class BaseAgent(ABC):
required_odoo_module: str = 'base'
system_prompt_file: str = ''
tools: list = []
auto_rag: bool = True # set False on knowledge-only agents to prevent self-calls
def __init__(self, odoo, llm, peer_bus=None):
self._odoo = odoo
@@ -92,6 +93,34 @@ class BaseAgent(ABC):
validate_agent_tools(self.tools, self.name)
self._validator = ToolCallValidator(self.tools)
async def _lookup_odoo_context(self) -> str:
"""
Auto-fetch Odoo 18 workflow guidance from odoo_doc_agent via PeerBus.
Result is stored in self._gathered['odoo_context'] and injected into
every _loop() LLM call so agents reason with correct Odoo steps.
Returns empty string if unavailable (non-fatal).
"""
if not self.auto_rag or not self._peer_bus or not self._directive:
return ''
module = self.domain if self.domain not in ('base', 'documentation') else None
try:
response = await self._peer_bus.request(
from_agent=self.name,
to_agent='odoo_doc_agent',
request_type='query_docs',
params={
'question': self._directive.task,
'module': module,
'top_k': 4,
},
reason='auto workflow guidance lookup',
)
if response.available and response.success:
return response.data.get('answer', '')
except Exception as exc:
logger.debug('agent=%s auto-RAG skipped: %s', self.name, exc)
return ''
async def execute(self, directive: AgentDirective) -> AgentReport:
self._directive = directive
self._gathered = {}
@@ -99,6 +128,10 @@ class BaseAgent(ABC):
t0 = time.monotonic()
try:
await self._receive(directive)
odoo_ctx = await self._lookup_odoo_context()
if odoo_ctx:
self._gathered['odoo_context'] = odoo_ctx
logger.info('agent=%s odoo_context=%d chars injected', self.name, len(odoo_ctx))
plan = await self._plan()
await self._gather(plan)
reasoning = await self._reason()
@@ -164,6 +197,19 @@ class BaseAgent(ABC):
async def _loop(self, messages, tools=None, max_iter=10) -> str:
current = list(messages)
# Inject Odoo workflow context retrieved during execute()
odoo_ctx = self._gathered.get('odoo_context', '')
if odoo_ctx:
inject = [
{'role': 'user', 'content': f'## Odoo 18 Workflow Reference\n\n{odoo_ctx}'},
{'role': 'assistant', 'content': 'Understood. I will follow the documented Odoo 18 workflow steps.'},
]
if current and current[0].get('role') == 'system':
current = [current[0]] + inject + current[1:]
else:
current = inject + current
active_tools = tools or self.tools
for iteration in range(max_iter):
resp = await self._llm.submit(current, tools=active_tools, caller=self.name)

View File

@@ -30,6 +30,7 @@ class OdooDocAgent(BaseAgent):
required_odoo_module = 'base'
system_prompt_file = ''
tools = []
auto_rag = False # prevent self-referential PeerBus calls
async def _plan(self) -> dict:
return {