fix: prevent master agent asking for clarification when receipts are uploaded

When a zip/image arrives via /upload, the LLM was classifying the
message as needs_clarification=True (because the chat body was just a
filename like "download (8).zip", not an instruction), and the early
return on line 91 fired before the receipts safety guard on line 106,
so the guard never executed.

master_agent: move the receipts safety guard to BEFORE the
needs_clarification early-return.  If extra_context contains receipts,
unconditionally set needs_clarification=False and ensure expenses_agent
is in the agents list — the LLM cannot veto an upload with a question.

upload router: normalize empty or filename-only messages (e.g. when the
user drops a file in Discuss chat with no text) to
"Create an expense report from these uploaded receipts." so the LLM
intent classification also has a sensible string to work with.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Carlos Garcia
2026-05-20 22:13:46 -04:00
parent 68b7b3f0f3
commit cc025695ac
2 changed files with 24 additions and 5 deletions

View File

@@ -1,9 +1,14 @@
from __future__ import annotations
import asyncio
import logging
import re
import uuid
from typing import List, Optional
# Matches messages that are just a filename (e.g. "download (8).zip", "receipt.jpg").
# When the chat body is only the filename, the LLM has nothing useful to classify.
_FILENAME_ONLY_RE = re.compile(r'^[\w\s\-.()\[\]]+\.\w{2,6}$')
from fastapi import APIRouter, File, Form, HTTPException, Request, UploadFile, status
from ..config import get_settings
@@ -35,6 +40,14 @@ async def upload(
from concurrent.futures import ThreadPoolExecutor
_ocr_executor = ThreadPoolExecutor(max_workers=2)
# Normalise message: if the user sent no text (or the chat body is just the
# filename Odoo auto-inserts), give the master agent a clear instruction so
# it routes to expenses_agent rather than asking for clarification.
stripped = (message or '').strip()
if not stripped or _FILENAME_ONLY_RE.match(stripped):
message = 'Create an expense report from these uploaded receipts.'
logger.debug('upload: normalised empty/filename message for user_id=%s', user_id)
receipts: list[dict] = []
loop = asyncio.get_event_loop()
for f in files: