From 6ab9624ec6547bda8ef237814f016bc6bf29b568 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Sat, 16 May 2026 01:37:36 -0400 Subject: [PATCH] fix: harden master agent synthesize/memory, fix expense create fields - _synthesize: short-circuit on any single-agent report (avoids extra Ollama call that can timeout); wrap multi-agent LLM call in try/except - _update_memory: catch exceptions so DB/memory failures don't kill reply - _log_directive_start: use 0 instead of NULL for channel_id (NOT NULL col) - create_expense: drop 'description' field (not valid on hr.expense in Odoo 18) Co-Authored-By: Claude Sonnet 4.6 --- agent_service/agents/master_agent.py | 38 ++++++++++++++++----------- agent_service/tools/expenses_tools.py | 2 -- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/agent_service/agents/master_agent.py b/agent_service/agents/master_agent.py index 936ad19..d875388 100644 --- a/agent_service/agents/master_agent.py +++ b/agent_service/agents/master_agent.py @@ -273,29 +273,37 @@ class MasterAgent: async def _synthesize(self, reports, context: MasterContext) -> str: if not reports: return 'No agent responses received.' - if len(reports) == 1 and reports[0].status == 'complete': - return reports[0].summary + if len(reports) == 1: + return reports[0].summary or '(no summary)' summaries = chr(10).join(f'{r.agent}: {r.summary}' for r in reports) msg = ('Synthesize these agent reports into one coherent response. ' 'Business language only. No internal IDs. ' 'Separate: actions completed, items pending approval, recommendations.' + chr(10) + summaries) - resp = await self._llm.submit( - [{'role': 'system', 'content': 'You are a business intelligence assistant.'}, - {'role': 'user', 'content': msg}], - caller='master_synthesis') - return resp.content + try: + resp = await self._llm.submit( + [{'role': 'system', 'content': 'You are a business intelligence assistant.'}, + {'role': 'user', 'content': msg}], + caller='master_synthesis') + return resp.content or summaries + except Exception as exc: + logger.warning('_synthesize LLM call failed, falling back to raw summaries: %s', exc) + return summaries async def _update_memory(self, user_id, message, response, reports, directive_id): - # User message is persisted at the top of handle_message — only save - # the assistant reply here. - await self._memory.append_message(user_id, 'assistant', response, directive_id) + try: + await self._memory.append_message(user_id, 'assistant', response or '', directive_id) + except Exception as exc: + logger.warning('_update_memory: append_message failed: %s', exc) for report in reports: if report.data: - await self._memory.store_findings( - scope=report.agent.replace('_agent', ''), - summary=report.summary, raw_data=report.data, - source_directive_id=directive_id) + try: + await self._memory.store_findings( + scope=report.agent.replace('_agent', ''), + summary=report.summary, raw_data=report.data, + source_directive_id=directive_id) + except Exception as exc: + logger.warning('_update_memory: store_findings failed agent=%s: %s', report.agent, exc) async def handle_approval(self, directive_id, item_id, approved, approver_uid) -> str: if approved: @@ -314,7 +322,7 @@ class MasterAgent: '(directive_id, user_id, channel_id, raw_message, status) ' 'VALUES ($1, $2, $3, $4, $5) ON CONFLICT (directive_id) DO NOTHING') async with pool.acquire(timeout=10) as conn: - await conn.execute(sql, directive_id, user_id, channel_id, message, 'processing') + await conn.execute(sql, directive_id, user_id, channel_id or 0, message, 'processing') except Exception as exc: logger.warning('_log_directive_start failed: %s', exc) diff --git a/agent_service/tools/expenses_tools.py b/agent_service/tools/expenses_tools.py index e66faa1..0310a61 100644 --- a/agent_service/tools/expenses_tools.py +++ b/agent_service/tools/expenses_tools.py @@ -127,8 +127,6 @@ class ExpensesTools: vals['date'] = date if product_id: vals['product_id'] = product_id - if description: - vals['description'] = description return await self._o.create('hr.expense', vals) async def attach_receipt(self, model: str, record_id: int, filename: str,