fix: catch the more common tool-skip case — short reply, no tool call (v0.57.2)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
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
|
## 0.57.1 — 2026-07-20 20:00
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -37,6 +37,24 @@ function looksLikeUnstructuredRecipe(text: string): boolean {
|
|||||||
return listLines.length >= 3;
|
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<string, { noun: RegExp; verb: RegExp }> = {
|
||||||
|
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) {
|
export async function POST(req: NextRequest) {
|
||||||
const { session, response } = await requireSessionOrApiKey(req);
|
const { session, response } = await requireSessionOrApiKey(req);
|
||||||
if (response) return response;
|
if (response) return response;
|
||||||
@@ -77,10 +95,14 @@ export async function POST(req: NextRequest) {
|
|||||||
let final = result.data;
|
let final = result.data;
|
||||||
|
|
||||||
// Weaker/free-tier models sometimes ignore the MUST-call-the-tool rule
|
// Weaker/free-tier models sometimes ignore the MUST-call-the-tool rule
|
||||||
// above and just write the recipe out in prose instead. Rather than
|
// above — either by spelling the recipe out in prose, or (more common,
|
||||||
// surface that (or fail the request), force the tool call on one extra
|
// and easy to miss) giving a short reply that *claims* a draft exists
|
||||||
// pass — same system+prompt, just no longer letting the model opt out.
|
// without ever calling the tool, so nothing actually renders below it.
|
||||||
if (final.toolCalls.length === 0 && looksLikeUnstructuredRecipe(final.text)) {
|
// 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 {
|
try {
|
||||||
const forced = await generateText({
|
const forced = await generateText({
|
||||||
model,
|
model,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Mirrors CHANGELOG.md at the repo root — update both together.
|
// 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 = {
|
export type ChangelogEntry = {
|
||||||
version: string;
|
version: string;
|
||||||
@@ -11,6 +11,13 @@ export type ChangelogEntry = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CHANGELOG: 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",
|
version: "0.57.1",
|
||||||
date: "2026-07-20 20:00",
|
date: "2026-07-20 20:00",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@epicure/web",
|
"name": "@epicure/web",
|
||||||
"version": "0.57.1",
|
"version": "0.57.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "epicure",
|
"name": "epicure",
|
||||||
"version": "0.57.1",
|
"version": "0.57.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm --filter web dev",
|
"dev": "pnpm --filter web dev",
|
||||||
|
|||||||
Reference in New Issue
Block a user