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.
This commit is contained in:
Carlos Garcia
2026-04-24 23:27:06 -04:00
parent 27325bc140
commit f774cca7ab
2 changed files with 29 additions and 1 deletions

View File

@@ -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.'

View File

@@ -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