From 628691d2dfc1d7f1b847bfa277644f29df9c2120 Mon Sep 17 00:00:00 2001 From: jules Date: Sun, 3 May 2026 22:18:18 +1000 Subject: [PATCH] ai: shake-out fixes from first end-to-end search_kb run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three problems surfaced when driving search_kb / read_chunk through a real DeepSeek chat for the first time: 1. vite.config.ts: minisearch wasn't in `aliasedDeps`, so when @crema/lexical-rag-ui imports it, Vite couldn't resolve the bare specifier from the sibling lib's location. Added it next to the other shared deps. tsconfig paths alone is not enough — the Vite alias is what propagates resolution to sibling-lib code. 2. ai.tsx reindexKB: was using `toast.show?.()` which doesn't exist on useToast()'s API. Optional chaining silently no-op'd, so the button click ran the fetch but produced zero UI feedback (success or failure). Switched to the actual API: toast.info / toast.success / toast.error. Added a console.error in the catch arm so the underlying exception is visible in DevTools when something does go wrong. 3. ai.tsx MAX_TOOL_ITERATIONS: cap was 3, which is too tight for agentic search→read→search loops on real questions. Bumped to 6. More importantly, when the cap IS reached, the runner now synthesises tool-error messages for each pending tool_call and continues the chat — instead of silently dropping them, which left the conversation with an assistant.tool_calls turn but no matching tool messages. DeepSeek (and the OpenAI spec) reject that conversation with 400 ("insufficient tool messages following tool_calls"), poisoning the thread. Now the model gets a clean "max iterations reached" signal and produces a final answer. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/routes/ai.tsx | 43 +++++++++++++++++++++++++++++++------------ vite.config.ts | 1 + 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/app/routes/ai.tsx b/app/routes/ai.tsx index 707ceeb..10e5b28 100644 --- a/app/routes/ai.tsx +++ b/app/routes/ai.tsx @@ -127,9 +127,10 @@ async function reindexKB( window.sessionStorage.getItem("arcadia_access_token")) || "dev" const url = `${baseUrl}/index/${encodeURIComponent(corpus)}/build` - toast.show?.({ - title: "Reindexing…", - description: `Rebuilding corpus '${corpus}'.`, + // Use useToast's actual API (see lib-notification-ui): tone helpers + // success/error/info/warning, plus generic toast({ title, description, tone }). + toast.info(`Reindexing '${corpus}'…`, { + description: `POST ${url}`, }) try { const res = await fetch(url, { @@ -140,16 +141,14 @@ async function reindexKB( throw new Error(`HTTP ${res.status}: ${await res.text()}`) } const out = (await res.json()) as { chunk_count: number; built_at: string } - toast.show?.({ - title: "Reindex complete", + toast.success("Reindex complete", { description: `${out.chunk_count} chunks indexed for '${corpus}'.`, }) } catch (err) { - toast.show?.({ - title: "Reindex failed", - description: err instanceof Error ? err.message : String(err), - tone: "error", - }) + const msg = err instanceof Error ? err.message : String(err) + // eslint-disable-next-line no-console + console.error("[reindexKB]", err) + toast.error("Reindex failed", { description: msg }) } } @@ -818,7 +817,7 @@ function ChatSurface({ } } }, [activeAgent, messages, isStreaming, agentHistory]) - const MAX_TOOL_ITERATIONS = 3 + const MAX_TOOL_ITERATIONS = 6 const [pendingConfirm, setPendingConfirm] = useState<{ /** Message index that emitted the write calls. */ afterIndex: number @@ -856,7 +855,27 @@ function ChatSurface({ toolIterationsRef.current = 0 return } - if (toolIterationsRef.current >= MAX_TOOL_ITERATIONS) return + if (toolIterationsRef.current >= MAX_TOOL_ITERATIONS) { + // Cap reached. Synthesize a tool-error response for each pending + // call so the conversation stays valid (every assistant tool_calls + // turn must be followed by tool messages — otherwise the next + // request to DeepSeek 400s on "insufficient tool messages"). Then + // continue the chat so the model writes a final answer with what + // it has, or apologizes for not finishing. + const cappedMessages = calls.map((c) => ({ + role: "tool" as const, + content: JSON.stringify({ + error: `MAX_TOOL_ITERATIONS (${MAX_TOOL_ITERATIONS}) reached — please answer with what you have so far.`, + }), + toolCallId: c.id, + name: c.name, + })) + void continueChat(cappedMessages, { + system: systemPrompt, + tools: getOpenAITools(), + }) + return + } toolIterationsRef.current += 1 void (async () => { const { reads, writes } = classifyCalls(calls) diff --git a/vite.config.ts b/vite.config.ts index b1fa382..6cd8ec8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -104,6 +104,7 @@ const aliasedDeps = [ "@tiptap/extension-link", "@tiptap/extension-placeholder", "@tiptap/extension-image", + "minisearch", ] const sharedDepAliases = Object.fromEntries( aliasedDeps.map((name) => [name, resolvePath(nodeModules, name)]),