mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 11:28:47 +02:00
Assistant: suppress repeated render-tool calls
After the client returns a render tool's result, the model sometimes re-issues the identical call with a fresh id on the follow-up run, painting a second copy of the spending-summary card (observed live on MAF 1.10 / CK 1.62). DeduplicateToolCallMiddleware now tracks which render tools already ran in the current user turn and drops repeats — their streamed TOOL_CALL_* events and their entries in the run's MESSAGES_SNAPSHOT. The system prompt also tells the model the card is already displayed once its "ok" result is present. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -120,6 +120,27 @@ class DeduplicateToolCallMiddleware extends (Middleware as any) {
|
||||
const open = new Set<string>(); // tool call IDs currently in progress
|
||||
const closed = new Set<string>(); // tool call IDs that already received END
|
||||
|
||||
// Render tools are display-once: after the client returns their result,
|
||||
// the model sometimes re-issues the same call (fresh id) on the
|
||||
// follow-up run, painting a second identical card. Track which render
|
||||
// tools already ran in the current user turn (from the request's own
|
||||
// message history) and suppress repeats — both their streamed events
|
||||
// and their entries in this run's MESSAGES_SNAPSHOT.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const inputMessages: any[] = Array.isArray(input?.messages) ? input.messages : [];
|
||||
let lastUserIdx = -1;
|
||||
inputMessages.forEach((m, i) => { if (m?.role === "user") lastUserIdx = i; });
|
||||
const renderedThisTurn = new Set<string>();
|
||||
for (const m of inputMessages.slice(lastUserIdx + 1)) {
|
||||
const calls = m?.toolCalls ?? m?.tool_calls;
|
||||
if (m?.role !== "assistant" || !Array.isArray(calls)) continue;
|
||||
for (const tc of calls) {
|
||||
const name = tc?.function?.name;
|
||||
if (name && RENDER_TOOLS.has(name)) renderedThisTurn.add(name);
|
||||
}
|
||||
}
|
||||
const suppressed = new Set<string>(); // toolCallIds of dropped repeats
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return new Observable<any>((observer) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -132,13 +153,51 @@ class DeduplicateToolCallMiddleware extends (Middleware as any) {
|
||||
|
||||
if (type === EventType.TOOL_CALL_START) {
|
||||
if (!id || closed.has(id) || open.has(id)) return; // duplicate
|
||||
const name: string | undefined = event?.toolCallName;
|
||||
if (name && RENDER_TOOLS.has(name)) {
|
||||
if (renderedThisTurn.has(name)) {
|
||||
suppressed.add(id);
|
||||
return; // repeat render call — drop the whole call
|
||||
}
|
||||
renderedThisTurn.add(name);
|
||||
}
|
||||
open.add(id);
|
||||
} else if (type === EventType.TOOL_CALL_ARGS || type === EventType.TOOL_CALL_END) {
|
||||
if (id && suppressed.has(id)) return;
|
||||
if (id && closed.has(id)) return; // already completed, drop
|
||||
if (type === EventType.TOOL_CALL_END && id) {
|
||||
open.delete(id);
|
||||
closed.add(id);
|
||||
}
|
||||
} else if (type === EventType.MESSAGES_SNAPSHOT && suppressed.size > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const msgs: any[] = Array.isArray(event?.messages) ? event.messages : [];
|
||||
const cleaned = msgs
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.map((m: any) => {
|
||||
const calls = m?.toolCalls ?? m?.tool_calls;
|
||||
if (m?.role !== "assistant" || !Array.isArray(calls)) return m;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const kept = calls.filter((tc: any) => !suppressed.has(String(tc?.id ?? "")));
|
||||
if (kept.length === calls.length) return m;
|
||||
const { toolCalls: _a, tool_calls: _b, ...rest } = m;
|
||||
return kept.length > 0 ? { ...rest, toolCalls: kept } : rest;
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.filter((m: any) => {
|
||||
if (m?.role === "tool" && suppressed.has(String(m?.toolCallId ?? m?.tool_call_id ?? ""))) {
|
||||
return false;
|
||||
}
|
||||
if (m?.role === "assistant") {
|
||||
const calls = m?.toolCalls ?? m?.tool_calls;
|
||||
const hasCalls = Array.isArray(calls) && calls.length > 0;
|
||||
const hasText = typeof m?.content === "string" && m.content.length > 0;
|
||||
if (!hasCalls && !hasText) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
observer.next({ ...event, messages: cleaned });
|
||||
return;
|
||||
}
|
||||
|
||||
observer.next(event); // emit raw BaseEvent
|
||||
|
||||
Reference in New Issue
Block a user