feat: register OdooDocAgent as PeerBus specialist agent

Wraps odootrain RAG API (http://192.168.2.9:8000) as a BaseAgent so any
specialist agent can query Odoo 18 docs mid-execution via PeerBus
request_type=query_docs. Participates in sweep health checks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Carlos Garcia
2026-05-14 22:53:22 -04:00
parent 384b42ab03
commit e215a26c58
2 changed files with 135 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
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 = []
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)}],
)

View File

@@ -155,6 +155,13 @@ async def lifespan(app: FastAPI):
def _register_specialist_agents(agent_registry, peer_bus, odoo, llm_router) -> None:
try:
from .agents.odoo_doc_agent import OdooDocAgent
agent_registry.register('odoo_doc_agent', OdooDocAgent(odoo=odoo, llm=llm_router, peer_bus=peer_bus))
logger.info('odoo_doc_agent registered (RAG @ http://192.168.2.9:8000)')
except Exception as exc:
logger.warning('Could not register odoo_doc_agent: %s', exc)
try:
from .agents.finance_agent import FinanceAgent
agent_registry.register('finance_agent', FinanceAgent(odoo=odoo, llm=llm_router, peer_bus=peer_bus))