Files
odoo-ai/agent_service/agents/odoo_doc_agent.py
Carlos Garcia 65920d6128 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>
2026-05-14 23:35:03 -04:00

130 lines
4.6 KiB
Python

from __future__ import annotations
import logging
import httpx
from .base_agent import BaseAgent, AgentReport, SweepReport
logger = logging.getLogger(__name__)
RAG_URL = "http://192.168.2.9:8000"
RAG_TIMEOUT = 60
class OdooDocAgent(BaseAgent):
"""
Read-only knowledge agent backed by the odootrain RAG stack.
Answers questions about Odoo 18 workflows using the indexed documentation.
Other agents query it via PeerBus:
response = await self._peer_bus.request(
from_agent=self.name,
to_agent='odoo_doc_agent',
request_type='query_docs',
params={'question': '...', 'module': 'accounting'}, # module optional
reason='Need Odoo workflow guidance',
)
guidance = response.data.get('answer', '')
"""
name = 'odoo_doc_agent'
domain = 'documentation'
required_odoo_module = 'base'
system_prompt_file = ''
tools = []
auto_rag = False # prevent self-referential PeerBus calls
async def _plan(self) -> dict:
return {
'question': self._directive.task,
'module': self._directive.params.get('module'),
'top_k': self._directive.params.get('top_k', 6),
}
async def _gather(self, plan: dict) -> None:
payload = {
'question': plan['question'],
'top_k': plan['top_k'],
}
if plan.get('module'):
payload['module'] = plan['module']
try:
async with httpx.AsyncClient(timeout=RAG_TIMEOUT) as client:
resp = await client.post(f"{RAG_URL}/ask", json=payload)
resp.raise_for_status()
self._gathered = resp.json()
except Exception as exc:
logger.error('odoo_doc_agent RAG call failed: %s', exc)
self._gathered = {'answer': '', 'sources': [], 'error': str(exc)}
async def _reason(self) -> dict:
return {}
async def _act(self, reasoning: dict) -> None:
pass
async def _report(self) -> AgentReport:
answer = self._gathered.get('answer', '')
sources = self._gathered.get('sources', [])
error = self._gathered.get('error')
status = 'failed' if error and not answer else 'complete'
summary = answer or f'RAG lookup failed: {error}'
return AgentReport(
directive_id=self._directive.directive_id,
agent=self.name,
status=status,
summary=summary,
data={
'answer': answer,
'sources': sources,
'model': self._gathered.get('model', ''),
},
error=error,
)
async def handle_peer_request(self, request_type: str, params: dict, directive_id: str) -> dict:
if request_type != 'query_docs':
return {'success': False, 'error': f'Unknown request type: {request_type}'}
question = params.get('question', '')
if not question:
return {'success': False, 'error': 'question is required'}
payload = {'question': question, 'top_k': params.get('top_k', 6)}
if params.get('module'):
payload['module'] = params['module']
try:
async with httpx.AsyncClient(timeout=RAG_TIMEOUT) as client:
resp = await client.post(f"{RAG_URL}/ask", json=payload)
resp.raise_for_status()
data = resp.json()
return {
'success': True,
'answer': data.get('answer', ''),
'sources': data.get('sources', []),
}
except Exception as exc:
logger.error('odoo_doc_agent peer request failed: %s', exc)
return {'success': False, 'error': str(exc)}
async def sweep(self) -> SweepReport:
try:
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get(f"{RAG_URL}/health")
health = resp.json()
qdrant_ok = 'ok' in str(health.get('qdrant', ''))
ollama_ok = 'ok' in str(health.get('ollama', ''))
findings = []
if not qdrant_ok:
findings.append({'issue': 'Qdrant unhealthy', 'severity': 'high', 'detail': health})
if not ollama_ok:
findings.append({'issue': 'Ollama unhealthy', 'severity': 'high', 'detail': health})
return SweepReport(agent=self.name, findings=findings)
except Exception as exc:
return SweepReport(
agent=self.name,
findings=[{'issue': 'RAG API unreachable', 'severity': 'high', 'detail': str(exc)}],
)