ai: shake-out fixes from first end-to-end search_kb run

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) <noreply@anthropic.com>
This commit is contained in:
jules
2026-05-03 22:18:18 +10:00
parent f5189305c7
commit 628691d2df
2 changed files with 32 additions and 12 deletions

View File

@@ -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)

View File

@@ -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)]),