From f774cca7abbabc4202cc661bad9e36a5f1022442 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Fri, 24 Apr 2026 23:27:06 -0400 Subject: [PATCH] feat(agent): direct-answer fallback for non-Odoo questions Previously when the LLM classified a message as needing no specialist agent, the dispatcher built zero directives and _synthesize returned 'No agent responses received.' Greetings, follow-up clarifications, and general questions all fell into this dead end. Now when intent.agents is empty and no clarification is needed, the master makes a second LLM call with the recent conversation as context and answers directly. Updated master_system.txt to steer the classifier toward agents=[] for chitchat instead of forcing a clarification loop. --- agent_service/agents/master_agent.py | 25 +++++++++++++++++++++++++ agent_service/prompts/master_system.txt | 5 ++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/agent_service/agents/master_agent.py b/agent_service/agents/master_agent.py index ebf208f..8af8eec 100644 --- a/agent_service/agents/master_agent.py +++ b/agent_service/agents/master_agent.py @@ -93,6 +93,13 @@ class MasterAgent: await self._log_directive_complete(directive_id, 'awaiting_clarification', q) return MasterResponse(directive_id=directive_id, response=q, status='awaiting_clarification') + if not intent.agents: + # No specialist agent applies — answer directly with the LLM. + response_text = await self._direct_answer(context, message) + await self._memory.append_message(user_id, 'assistant', response_text, directive_id) + await self._log_directive_complete(directive_id, 'complete', response_text) + return MasterResponse(directive_id=directive_id, response=response_text, + status='complete') access = await self._check_access(user_id, intent.agents) if not access.allowed: denied = ', '.join(access.denied_agents) @@ -215,6 +222,24 @@ class MasterAgent: reports.append(r) return reports + async def _direct_answer(self, context: MasterContext, message) -> str: + """Answer general / off-topic messages directly without dispatching agents.""" + history = [ + {'role': m['role'], 'content': m['content']} + for m in context.conversation[-10:] + if m.get('role') in ('user', 'assistant') + ] + system = ( + "You are ActiveBlue AI, the assistant for the Active Blue Odoo " + "instance. Answer the user directly and concisely. Use any prior " + "conversation for context. Plain text only — no JSON, no markdown " + "code fences." + ) + msgs = [{'role': 'system', 'content': system}, *history, + {'role': 'user', 'content': message}] + resp = await self._llm.submit(msgs, caller='master_direct') + return (resp.content or '').strip() or 'Sorry, I have no answer for that.' + async def _synthesize(self, reports, context: MasterContext) -> str: if not reports: return 'No agent responses received.' diff --git a/agent_service/prompts/master_system.txt b/agent_service/prompts/master_system.txt index 85f3938..8db04fd 100644 --- a/agent_service/prompts/master_system.txt +++ b/agent_service/prompts/master_system.txt @@ -17,7 +17,10 @@ Active specialist agents: If a user requests something for an agent not listed, tell them the Odoo module is not installed. Rules: -- Ask ONE clarifying question if intent is ambiguous - then dispatch +- Ask ONE clarifying question ONLY if a request is genuinely ambiguous about + which Odoo data is needed. For general questions, chitchat, greetings, or + anything unrelated to the listed specialist agents, set "agents": [] and + "needs_clarification": false — a direct answer will be generated separately. - Confirm multi-step plans before executing - Surface escalations with approve/reject options - Never expose agent names, tool names, or system internals to users