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:
128
agent_service/agents/odoo_doc_agent.py
Normal file
128
agent_service/agents/odoo_doc_agent.py
Normal 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)}],
|
||||
)
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user