fix(agent): v4.15.1 - protocole tool_calls conforme + plus de repli silencieux sur le mock (echos)
FlowDeck CI / test (push) Failing after 11s
FlowDeck CI / docker (push) Skipped

- L'engine envoie le message assistant avec ses tool_calls (id) et chaque resultat d'outil avec son tool_call_id, y compris en cas de refus; la passe suivante n'est plus refusee par l'API
- Un fournisseur reel configure qui echoue remonte maintenant une erreur (SSE error) au lieu de repeter la question via le mock hors-ligne (mock reserve a offline/sans cle)
- llm_client conserve id + arguments_raw des tool_calls
- test de regression test_engine_tool_protocol_messages
This commit is contained in:
2026-09-06 13:06:41 -04:00
parent 571d78115b
commit 113f880863
6 changed files with 130 additions and 33 deletions
+14 -5
View File
@@ -116,9 +116,12 @@ class LLMClient:
self._http_complete(messages, model, tools),
timeout=settings.agent_run_timeout_seconds,
)
except Exception as exc: # noqa: BLE001 — degrade gracefully to mock
logger.warning("LLM provider '%s' failed (%s); falling back to offline", self.provider, exc)
return await self._mock_complete(messages, model, tools)
except Exception as exc: # noqa: BLE001 — never mask a real-provider failure
# On NE retombe PAS silencieusement sur le mock quand un fournisseur
# réel est configuré : l'erreur doit remonter (SSE "error") pour que
# l'utilisateur voie pourquoi rien n'a été généré.
logger.warning("LLM provider '%s' failed (%s)", self.provider, exc)
raise
async def is_available(self) -> bool:
"""True when a real provider is configured."""
@@ -172,11 +175,17 @@ class LLMClient:
text = choice.get("content") or ""
tool_calls = []
for tc in choice.get("tool_calls") or []:
fn = tc.get("function") or {}
try:
args = json.loads(tc["function"].get("arguments") or "{}")
args = json.loads(fn.get("arguments") or "{}")
except json.JSONDecodeError:
args = {}
tool_calls.append({"name": tc["function"]["name"], "arguments": args})
tool_calls.append({
"id": tc.get("id") or "",
"name": fn.get("name"),
"arguments": args,
"arguments_raw": fn.get("arguments") or "",
})
return LLMResponse(
text=text,