From f6214e60dfc68feccce924c1c54472ac23fb543c Mon Sep 17 00:00:00 2001 From: Arnaud Date: Mon, 20 Jul 2026 19:46:52 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20catch=20the=20more=20common=20tool-skip?= =?UTF-8?q?=20case=20=E2=80=94=20short=20reply,=20no=20tool=20call=20(v0.5?= =?UTF-8?q?7.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix's looksLikeUnstructuredRecipe only fires when the model spells the whole recipe out as a numbered/bulleted list — it does nothing for a short reply that just *claims* a draft exists ("Voici une recette de Bœuf Bourguignon... brouillon à vérifier ci-dessous") without ever calling the tool, which is what the user actually hit. Detecting the model's claimed compliance is fragile (varies by phrasing/ model), so instead detect intent from the user's own message — the same noun+verb pattern ("recipe"+create/make/... or "recette"+créer/fais/...) the system prompt's own createRecipe examples already describe. Forces the retry whenever there's no tool call AND either signal fires. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 5 ++++ apps/web/app/api/v1/ai/cooking-chat/route.ts | 30 +++++++++++++++++--- apps/web/lib/changelog.ts | 9 +++++- apps/web/package.json | 2 +- package.json | 2 +- 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d41c8f4..b4cab81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to Epicure are documented here. This file is mirrored in-app at `/changelog` (and in the admin dashboard) via `apps/web/lib/changelog.ts` — update both together. +## 0.57.2 — 2026-07-20 20:15 + +### Fixed +- The previous chatbot fix only caught the model spelling a recipe out as prose. The more common case: a short reply that claims a draft exists ("here's a draft, check below") without ever calling the tool, so nothing renders. Now also detects recipe-creation intent from the user's own message and forces the tool call in that case too. + ## 0.57.1 — 2026-07-20 20:00 ### Fixed diff --git a/apps/web/app/api/v1/ai/cooking-chat/route.ts b/apps/web/app/api/v1/ai/cooking-chat/route.ts index fddfff1..1598deb 100644 --- a/apps/web/app/api/v1/ai/cooking-chat/route.ts +++ b/apps/web/app/api/v1/ai/cooking-chat/route.ts @@ -37,6 +37,24 @@ function looksLikeUnstructuredRecipe(text: string): boolean { return listLines.length >= 3; } +// The above only catches the model spelling the whole recipe out. A second, +// more common failure with weaker models: it gives a short reply that even +// *claims* to have drafted something ("here's a draft, check below") without +// ever calling the tool — so nothing renders below it. Rather than guess from +// that claim (fragile across models/phrasing), detect intent from the user's +// own message instead: same noun+verb pattern the system prompt itself uses +// as its createRecipe examples, checked in whichever of the two supported +// locales the request is in. +const RECIPE_INTENT: Record = { + en: { noun: /\brecipe\b/i, verb: /\b(create|make|generate|give|write|save|invent)\b/i }, + fr: { noun: /\brecettes?\b/i, verb: /\b(cr[ée]e?r?|fais|donne|[ée]cri[st]|note[rz]?|sauvegard\w*|enregistr\w*|invente\w*|g[ée]n[èe]re\w*)\b/i }, +}; + +function looksLikeRecipeCreationRequest(question: string, locale: string): boolean { + const pattern = RECIPE_INTENT[locale] ?? RECIPE_INTENT["en"]!; + return pattern.noun.test(question) && pattern.verb.test(question); +} + export async function POST(req: NextRequest) { const { session, response } = await requireSessionOrApiKey(req); if (response) return response; @@ -77,10 +95,14 @@ export async function POST(req: NextRequest) { let final = result.data; // Weaker/free-tier models sometimes ignore the MUST-call-the-tool rule - // above and just write the recipe out in prose instead. Rather than - // surface that (or fail the request), force the tool call on one extra - // pass — same system+prompt, just no longer letting the model opt out. - if (final.toolCalls.length === 0 && looksLikeUnstructuredRecipe(final.text)) { + // above — either by spelling the recipe out in prose, or (more common, + // and easy to miss) giving a short reply that *claims* a draft exists + // without ever calling the tool, so nothing actually renders below it. + // Catch both: the prose-list shape, or the user's own message clearly + // asking for a recipe in the first place. Rather than surface either + // failure, force the tool call on one extra pass — same system+prompt, + // just no longer letting the model opt out. + if (final.toolCalls.length === 0 && (looksLikeUnstructuredRecipe(final.text) || looksLikeRecipeCreationRequest(parsed.data.question, locale))) { try { const forced = await generateText({ model, diff --git a/apps/web/lib/changelog.ts b/apps/web/lib/changelog.ts index 12ceb4a..a0c8be7 100644 --- a/apps/web/lib/changelog.ts +++ b/apps/web/lib/changelog.ts @@ -1,5 +1,5 @@ // Mirrors CHANGELOG.md at the repo root — update both together. -export const APP_VERSION = "0.57.1"; +export const APP_VERSION = "0.57.2"; export type ChangelogEntry = { version: string; @@ -11,6 +11,13 @@ export type ChangelogEntry = { }; export const CHANGELOG: ChangelogEntry[] = [ + { + version: "0.57.2", + date: "2026-07-20 20:15", + fixed: [ + "The previous chatbot fix only caught the model spelling a recipe out as prose. The more common case: a short reply that claims a draft exists (\"here's a draft, check below\") without ever calling the tool, so nothing renders. Now also detects recipe-creation intent from the user's own message and forces the tool call in that case too.", + ], + }, { version: "0.57.1", date: "2026-07-20 20:00", diff --git a/apps/web/package.json b/apps/web/package.json index bd42f74..bb27401 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@epicure/web", - "version": "0.57.1", + "version": "0.57.2", "private": true, "scripts": { "dev": "next dev", diff --git a/package.json b/package.json index e8a5f20..e03ea19 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "epicure", - "version": "0.57.1", + "version": "0.57.2", "private": true, "scripts": { "dev": "pnpm --filter web dev",