From a5ed4076b781e1af65e1962feffc49ed828e20e3 Mon Sep 17 00:00:00 2001 From: ztimson Date: Tue, 16 Dec 2025 12:22:14 -0500 Subject: [PATCH] Handle anthropic multiple responses better. --- package.json | 2 +- src/antrhopic.ts | 38 +++++++++++++++++++++++--------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 4339b64..2c91622 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ztimson/ai-utils", - "version": "0.1.15", + "version": "0.1.16", "description": "AI Utility library", "author": "Zak Timson", "license": "MIT", diff --git a/src/antrhopic.ts b/src/antrhopic.ts index 5e2ba22..71907f1 100644 --- a/src/antrhopic.ts +++ b/src/antrhopic.ts @@ -13,24 +13,29 @@ export class Anthropic extends LLMProvider { } private toStandard(history: any[]): LLMMessage[] { + const merged: any[] = []; for(let i = 0; i < history.length; i++) { - const orgI = i; - if(typeof history[orgI].content != 'string') { - if(history[orgI].role == 'assistant') { - history[orgI].content.filter((c: any) => c.type =='tool_use').forEach((c: any) => { - i++; - history.splice(i, 0, {role: 'tool', id: c.id, name: c.name, args: c.input}); + const msg = history[i]; + if(typeof msg.content != 'string') { + if(msg.role == 'assistant') { + msg.content.filter((c: any) => c.type == 'tool_use').forEach((c: any) => { + merged.push({role: 'tool', id: c.id, name: c.name, args: c.input}); }); - } else if(history[orgI].role == 'user') { - history[orgI].content.filter((c: any) => c.type =='tool_result').forEach((c: any) => { - const h = history.find((h: any) => h.id == c.tool_use_id); - h[c.is_error ? 'error' : 'content'] = c.content; + } else if(msg.role == 'user') { + msg.content.filter((c: any) => c.type == 'tool_result').forEach((c: any) => { + const h = merged.find((h: any) => h.id == c.tool_use_id); + if(h) h[c.is_error ? 'error' : 'content'] = c.content; }); } - history[orgI].content = history[orgI].content.filter((c: any) => c.type == 'text').map((c: any) => c.text).join('\n\n'); + msg.content = msg.content.filter((c: any) => c.type == 'text').map((c: any) => c.text).join('\n\n'); + } + if(msg.content) { + const last = merged.at(-1); + if(last && last.role == 'assistant' && msg.role == 'assistant') last.content += '\n\n' + msg.content; + else merged.push({role: msg.role, content: msg.content}); } } - return history.filter(h => !!h.content); + return merged; } private fromStandard(history: LLMMessage[]): any[] { @@ -71,13 +76,15 @@ export class Anthropic extends LLMProvider { stream: !!options.stream, }; - // Run tool changes let resp: any; + let isFirstMessage = true; do { resp = await this.client.messages.create(requestParams); - // Streaming mode if(options.stream) { + if(!isFirstMessage) options.stream({text: '\n\n'}); + isFirstMessage = false; + resp.content = []; for await (const chunk of resp) { if(controller.signal.aborted) break; @@ -104,7 +111,6 @@ export class Anthropic extends LLMProvider { } } - // Run tools const toolCalls = resp.content.filter((c: any) => c.type === 'tool_use'); if(toolCalls.length && !controller.signal.aborted) { history.push({role: 'assistant', content: resp.content}); @@ -122,12 +128,14 @@ export class Anthropic extends LLMProvider { requestParams.messages = history; } } while (!controller.signal.aborted && resp.content.some((c: any) => c.type === 'tool_use')); + if(options.stream) options.stream({done: true}); res(this.toStandard([...history, { role: 'assistant', content: resp.content.filter((c: any) => c.type == 'text').map((c: any) => c.text).join('\n\n') }])); }); + return Object.assign(response, {abort: () => controller.abort()}); } }