@lobehub/chat 1.1.7 → 1.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/docs/usage/providers/01ai.mdx +1 -1
  3. package/docs/usage/providers/01ai.zh-CN.mdx +1 -1
  4. package/docs/usage/providers/anthropic.mdx +1 -1
  5. package/docs/usage/providers/anthropic.zh-CN.mdx +1 -1
  6. package/docs/usage/providers/azure.mdx +1 -1
  7. package/docs/usage/providers/azure.zh-CN.mdx +1 -1
  8. package/docs/usage/providers/bedrock.mdx +1 -1
  9. package/docs/usage/providers/bedrock.zh-CN.mdx +1 -1
  10. package/docs/usage/providers/deepseek.mdx +1 -1
  11. package/docs/usage/providers/deepseek.zh-CN.mdx +1 -1
  12. package/docs/usage/providers/gemini.mdx +1 -1
  13. package/docs/usage/providers/gemini.zh-CN.mdx +1 -1
  14. package/docs/usage/providers/groq.mdx +1 -1
  15. package/docs/usage/providers/groq.zh-CN.mdx +1 -1
  16. package/docs/usage/providers/minimax.mdx +1 -1
  17. package/docs/usage/providers/minimax.zh-CN.mdx +1 -1
  18. package/docs/usage/providers/mistral.mdx +1 -1
  19. package/docs/usage/providers/mistral.zh-CN.mdx +1 -1
  20. package/docs/usage/providers/moonshot.mdx +1 -1
  21. package/docs/usage/providers/moonshot.zh-CN.mdx +1 -1
  22. package/docs/usage/providers/openai.mdx +1 -1
  23. package/docs/usage/providers/openai.zh-CN.mdx +1 -1
  24. package/docs/usage/providers/openrouter.mdx +1 -1
  25. package/docs/usage/providers/openrouter.zh-CN.mdx +2 -2
  26. package/docs/usage/providers/perplexity.mdx +1 -1
  27. package/docs/usage/providers/perplexity.zh-CN.mdx +1 -1
  28. package/docs/usage/providers/qwen.mdx +1 -1
  29. package/docs/usage/providers/qwen.zh-CN.mdx +1 -1
  30. package/docs/usage/providers/stepfun.mdx +1 -1
  31. package/docs/usage/providers/stepfun.zh-CN.mdx +2 -2
  32. package/docs/usage/providers/togetherai.mdx +1 -1
  33. package/docs/usage/providers/togetherai.zh-CN.mdx +2 -2
  34. package/docs/usage/providers/zhipu.mdx +1 -1
  35. package/docs/usage/providers/zhipu.zh-CN.mdx +2 -2
  36. package/docs/usage/tools-calling/anthropic.zh-CN.mdx +935 -2
  37. package/docs/usage/tools-calling/openai.zh-CN.mdx +4 -2
  38. package/docs/usage/tools-calling.zh-CN.mdx +15 -1
  39. package/package.json +1 -1
  40. package/src/libs/agent-runtime/utils/anthropicHelpers.test.ts +104 -0
  41. package/src/libs/agent-runtime/utils/anthropicHelpers.ts +28 -7
  42. package/src/libs/agent-runtime/utils/streams/anthropic.test.ts +156 -0
  43. package/src/libs/agent-runtime/utils/streams/anthropic.ts +12 -3
  44. package/src/libs/agent-runtime/utils/streams/protocol.ts +1 -0
@@ -1,6 +1,8 @@
1
1
  ---
2
- title: OpenAI GPT 系列 Tools Calling
3
- description: 使用 LobeChat 测试 OpenAI GPT 系列模型(GPT 3.5-turbo / GPT-4 /GPT-4o) 的工具调用能力,并展现评测结果
2
+ title: OpenAI GPT 系列 Tools Calling 评测
3
+ description: >-
4
+ 使用 LobeChat 测试 OpenAI GPT 系列模型(GPT 3.5-turbo / GPT-4 /GPT-4o) 的工具调用(Function
5
+ Calling)能力,并展现评测结果
4
6
  tags:
5
7
  - Tools Calling
6
8
  - Benchmark
@@ -4,7 +4,7 @@ description: 基于 LobeChat 测试主流支持工具调用(Tool Calling)
4
4
  tags:
5
5
  - Tools Calling
6
6
  - Benchmark
7
- - Function Calling
7
+ - Function Calling 评测
8
8
  - 工具调用
9
9
  - LobeChat 插件
10
10
  ---
@@ -238,4 +238,18 @@ Create images from a text-only prompt.
238
238
 
239
239
  ## 评测结果
240
240
 
241
+ 各模型的评测细节可以点击查看:
242
+
243
+ <Cards>
244
+ <Card href={'/docs/usage/tools-calling/openai'} title={'OpenAI GPT 系列'} />
245
+ <Card href={'/docs/usage/tools-calling/anthropic'} title={'Anthropic Claude 系列'} />
246
+ <Card href={'/docs/usage/tools-calling/google'} title={'【TODO】Google Gemini 系列'} />
247
+ <Card
248
+ href={'/docs/usage/tools-calling/groq'}
249
+ title={'【TODO】Groq 部署的开源模型(Llama 3/Qwen2/Mistral 等)'}
250
+ />
251
+ </Cards>
252
+
253
+ ### 结果汇总
254
+
241
255
  TODO
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -192,6 +192,110 @@ describe('anthropicHelpers', () => {
192
192
  { content: '你好', role: 'user' },
193
193
  ]);
194
194
  });
195
+
196
+ it('should correctly convert OpenAI tool message to Anthropic format', () => {
197
+ const messages: OpenAIChatMessage[] = [
198
+ {
199
+ content: '告诉我杭州和北京的天气,先回答我好的',
200
+ role: 'user',
201
+ },
202
+ {
203
+ content:
204
+ '好的,我会为您查询杭州和北京的天气信息。我现在就开始查询这两个城市的当前天气情况。',
205
+ role: 'assistant',
206
+ tool_calls: [
207
+ {
208
+ function: {
209
+ arguments: '{"city": "\\u676d\\u5dde"}',
210
+ name: 'realtime-weather____fetchCurrentWeather',
211
+ },
212
+ id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
213
+ type: 'function',
214
+ },
215
+ {
216
+ function: {
217
+ arguments: '{"city": "\\u5317\\u4eac"}',
218
+ name: 'realtime-weather____fetchCurrentWeather',
219
+ },
220
+ id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
221
+ type: 'function',
222
+ },
223
+ ],
224
+ },
225
+ {
226
+ content:
227
+ '[{"city":"杭州市","adcode":"330100","province":"浙江","reporttime":"2024-06-24 17:02:14","casts":[{"date":"2024-06-24","week":"1","dayweather":"小雨","nightweather":"中雨","daytemp":"26","nighttemp":"20","daywind":"西","nightwind":"西","daypower":"1-3","nightpower":"1-3","daytemp_float":"26.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"大雨","nightweather":"中雨","daytemp":"23","nighttemp":"19","daywind":"东","nightwind":"东","daypower":"1-3","nightpower":"1-3","daytemp_float":"23.0","nighttemp_float":"19.0"},{"date":"2024-06-26","week":"3","dayweather":"中雨","nightweather":"中雨","daytemp":"24","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"21.0"},{"date":"2024-06-27","week":"4","dayweather":"中雨-大雨","nightweather":"中雨","daytemp":"24","nighttemp":"22","daywind":"南","nightwind":"南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"22.0"}]}]',
228
+ name: 'realtime-weather____fetchCurrentWeather',
229
+ role: 'tool',
230
+ tool_call_id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
231
+ },
232
+ {
233
+ content:
234
+ '[{"city":"北京市","adcode":"110000","province":"北京","reporttime":"2024-06-24 17:03:11","casts":[{"date":"2024-06-24","week":"1","dayweather":"晴","nightweather":"晴","daytemp":"33","nighttemp":"20","daywind":"北","nightwind":"北","daypower":"1-3","nightpower":"1-3","daytemp_float":"33.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"21.0"},{"date":"2024-06-26","week":"3","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"},{"date":"2024-06-27","week":"4","dayweather":"多云","nightweather":"多云","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"}]}]',
235
+ name: 'realtime-weather____fetchCurrentWeather',
236
+ role: 'tool',
237
+ tool_call_id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
238
+ },
239
+ {
240
+ content: '继续',
241
+ role: 'user',
242
+ },
243
+ ];
244
+
245
+ const contents = buildAnthropicMessages(messages);
246
+
247
+ expect(contents).toEqual([
248
+ { content: '告诉我杭州和北京的天气,先回答我好的', role: 'user' },
249
+ {
250
+ content: [
251
+ {
252
+ text: '好的,我会为您查询杭州和北京的天气信息。我现在就开始查询这两个城市的当前天气情况。',
253
+ type: 'text',
254
+ },
255
+ {
256
+ id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
257
+ input: { city: '杭州' },
258
+ name: 'realtime-weather____fetchCurrentWeather',
259
+ type: 'tool_use',
260
+ },
261
+ {
262
+ id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
263
+ input: { city: '北京' },
264
+ name: 'realtime-weather____fetchCurrentWeather',
265
+ type: 'tool_use',
266
+ },
267
+ ],
268
+ role: 'assistant',
269
+ },
270
+ {
271
+ content: [
272
+ {
273
+ content: [
274
+ {
275
+ text: '[{"city":"杭州市","adcode":"330100","province":"浙江","reporttime":"2024-06-24 17:02:14","casts":[{"date":"2024-06-24","week":"1","dayweather":"小雨","nightweather":"中雨","daytemp":"26","nighttemp":"20","daywind":"西","nightwind":"西","daypower":"1-3","nightpower":"1-3","daytemp_float":"26.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"大雨","nightweather":"中雨","daytemp":"23","nighttemp":"19","daywind":"东","nightwind":"东","daypower":"1-3","nightpower":"1-3","daytemp_float":"23.0","nighttemp_float":"19.0"},{"date":"2024-06-26","week":"3","dayweather":"中雨","nightweather":"中雨","daytemp":"24","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"21.0"},{"date":"2024-06-27","week":"4","dayweather":"中雨-大雨","nightweather":"中雨","daytemp":"24","nighttemp":"22","daywind":"南","nightwind":"南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"22.0"}]}]',
276
+ type: 'text',
277
+ },
278
+ ],
279
+ tool_use_id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
280
+ type: 'tool_result',
281
+ },
282
+ {
283
+ content: [
284
+ {
285
+ text: '[{"city":"北京市","adcode":"110000","province":"北京","reporttime":"2024-06-24 17:03:11","casts":[{"date":"2024-06-24","week":"1","dayweather":"晴","nightweather":"晴","daytemp":"33","nighttemp":"20","daywind":"北","nightwind":"北","daypower":"1-3","nightpower":"1-3","daytemp_float":"33.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"21.0"},{"date":"2024-06-26","week":"3","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"},{"date":"2024-06-27","week":"4","dayweather":"多云","nightweather":"多云","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"}]}]',
286
+ type: 'text',
287
+ },
288
+ ],
289
+ tool_use_id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
290
+ type: 'tool_result',
291
+ },
292
+ ],
293
+ role: 'user',
294
+ },
295
+ { content: '_', role: 'assistant' },
296
+ { content: '继续', role: 'user' },
297
+ ]);
298
+ });
195
299
  });
196
300
 
197
301
  describe('buildAnthropicTools', () => {
@@ -95,16 +95,37 @@ export const buildAnthropicMessages = (
95
95
  ): Anthropic.Messages.MessageParam[] => {
96
96
  const messages: Anthropic.Messages.MessageParam[] = [];
97
97
  let lastRole = 'assistant';
98
+ let pendingToolResults: Anthropic.ToolResultBlockParam[] = [];
99
+
100
+ oaiMessages.forEach((message, index) => {
101
+ // refs: https://docs.anthropic.com/claude/docs/tool-use#tool-use-and-tool-result-content-blocks
102
+ if (message.role === 'tool') {
103
+ pendingToolResults.push({
104
+ content: [{ text: message.content as string, type: 'text' }],
105
+ tool_use_id: message.tool_call_id!,
106
+ type: 'tool_result',
107
+ });
108
+
109
+ // If this is the last message or the next message is not a 'tool' message,
110
+ // we add the accumulated tool results as a single 'user' message
111
+ if (index === oaiMessages.length - 1 || oaiMessages[index + 1].role !== 'tool') {
112
+ messages.push({
113
+ content: pendingToolResults,
114
+ role: 'user',
115
+ });
116
+ pendingToolResults = [];
117
+ lastRole = 'user';
118
+ }
119
+ } else {
120
+ const anthropicMessage = buildAnthropicMessage(message);
98
121
 
99
- oaiMessages.forEach((message) => {
100
- const anthropicMessage = buildAnthropicMessage(message);
122
+ if (lastRole === anthropicMessage.role) {
123
+ messages.push({ content: '_', role: lastRole === 'user' ? 'assistant' : 'user' });
124
+ }
101
125
 
102
- if (lastRole === anthropicMessage.role) {
103
- messages.push({ content: '_', role: lastRole === 'user' ? 'assistant' : 'user' });
126
+ lastRole = anthropicMessage.role;
127
+ messages.push(anthropicMessage);
104
128
  }
105
-
106
- lastRole = anthropicMessage.role;
107
- messages.push(anthropicMessage);
108
129
  });
109
130
 
110
131
  return messages;
@@ -227,6 +227,162 @@ describe('AnthropicStream', () => {
227
227
 
228
228
  expect(onToolCallMock).toHaveBeenCalledTimes(5);
229
229
  });
230
+ it('should handle parallel tools use event and ReadableStream input', async () => {
231
+ const streams = [
232
+ {
233
+ type: 'message_start',
234
+ message: {
235
+ id: 'msg_0175ryA67RbGrnRrGBXFQEYK',
236
+ type: 'message',
237
+ role: 'assistant',
238
+ model: 'claude-3-5-sonnet-20240620',
239
+ content: [],
240
+ stop_reason: null,
241
+ stop_sequence: null,
242
+ usage: { input_tokens: 485, output_tokens: 4 },
243
+ },
244
+ },
245
+ { type: 'content_block_start', index: 0, content_block: { type: 'text', text: '' } },
246
+ {
247
+ type: 'content_block_delta',
248
+ index: 0,
249
+ delta: { type: 'text_delta', text: '好的,我会为您查询杭州和北京的天气情况。' },
250
+ },
251
+ {
252
+ type: 'content_block_delta',
253
+ index: 0,
254
+ delta: { type: 'text_delta', text: '请稍等,我现在开始获取这两个城市的天气信息。' },
255
+ },
256
+ { type: 'content_block_stop', index: 0 },
257
+ {
258
+ type: 'content_block_start',
259
+ index: 1,
260
+ content_block: {
261
+ type: 'tool_use',
262
+ id: 'toolu_011NuszmBcxskstLWe4z4z5B',
263
+ name: 'realtime-weather____fetchCurrentWeather',
264
+ input: {},
265
+ },
266
+ },
267
+ {
268
+ type: 'content_block_delta',
269
+ index: 1,
270
+ delta: { type: 'input_json_delta', partial_json: '' },
271
+ },
272
+ {
273
+ type: 'content_block_delta',
274
+ index: 1,
275
+ delta: { type: 'input_json_delta', partial_json: '{"city": "杭州"}' },
276
+ },
277
+ { type: 'content_block_stop', index: 1 },
278
+ {
279
+ type: 'content_block_start',
280
+ index: 2,
281
+ content_block: {
282
+ type: 'tool_use',
283
+ id: 'toolu_01HojNiibMiKnYFvLrJyfX3B',
284
+ name: 'realtime-weather____fetchCurrentWeather',
285
+ input: {},
286
+ },
287
+ },
288
+ {
289
+ type: 'content_block_delta',
290
+ index: 2,
291
+ delta: { type: 'input_json_delta', partial_json: '' },
292
+ },
293
+ {
294
+ type: 'content_block_delta',
295
+ index: 2,
296
+ delta: { type: 'input_json_delta', partial_json: '{"city": "北京"}' },
297
+ },
298
+ { type: 'content_block_stop', index: 2 },
299
+ {
300
+ type: 'message_delta',
301
+ delta: { stop_reason: 'tool_use', stop_sequence: null },
302
+ usage: { output_tokens: 150 },
303
+ },
304
+ { type: 'message_stop' },
305
+ ];
306
+
307
+ const mockReadableStream = new ReadableStream({
308
+ start(controller) {
309
+ streams.forEach((chunk) => {
310
+ controller.enqueue(chunk);
311
+ });
312
+ controller.close();
313
+ },
314
+ });
315
+
316
+ const onToolCallMock = vi.fn();
317
+
318
+ const protocolStream = AnthropicStream(mockReadableStream, {
319
+ onToolCall: onToolCallMock,
320
+ });
321
+
322
+ const decoder = new TextDecoder();
323
+ const chunks = [];
324
+
325
+ // @ts-ignore
326
+ for await (const chunk of protocolStream) {
327
+ chunks.push(decoder.decode(chunk, { stream: true }));
328
+ }
329
+
330
+ expect(chunks).toEqual(
331
+ [
332
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
333
+ 'event: data',
334
+ 'data: {"id":"msg_0175ryA67RbGrnRrGBXFQEYK","type":"message","role":"assistant","model":"claude-3-5-sonnet-20240620","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":485,"output_tokens":4}}\n',
335
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
336
+ 'event: data',
337
+ 'data: ""\n',
338
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
339
+ 'event: text',
340
+ 'data: "好的,我会为您查询杭州和北京的天气情况。"\n',
341
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
342
+ 'event: text',
343
+ 'data: "请稍等,我现在开始获取这两个城市的天气信息。"\n',
344
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
345
+ 'event: data',
346
+ 'data: {"type":"content_block_stop","index":0}\n',
347
+ // Tool calls
348
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
349
+ 'event: tool_calls',
350
+ `data: [{"function":{"arguments":"","name":"realtime-weather____fetchCurrentWeather"},"id":"toolu_011NuszmBcxskstLWe4z4z5B","index":0,"type":"function"}]\n`,
351
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
352
+ 'event: tool_calls',
353
+ `data: [{"function":{"arguments":""},"index":0,"type":"function"}]\n`,
354
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
355
+ 'event: tool_calls',
356
+ `data: [{"function":{"arguments":"{\\"city\\": \\"杭州\\"}"},"index":0,"type":"function"}]\n`,
357
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
358
+ 'event: data',
359
+ `data: {"type":"content_block_stop","index":1}\n`,
360
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
361
+ 'event: tool_calls',
362
+ `data: [{"function":{"arguments":"","name":"realtime-weather____fetchCurrentWeather"},"id":"toolu_01HojNiibMiKnYFvLrJyfX3B","index":1,"type":"function"}]\n`,
363
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
364
+ 'event: tool_calls',
365
+ `data: [{"function":{"arguments":""},"index":1,"type":"function"}]\n`,
366
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
367
+ 'event: tool_calls',
368
+ `data: [{"function":{"arguments":"{\\"city\\": \\"北京\\"}"},"index":1,"type":"function"}]\n`,
369
+
370
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
371
+ 'event: data',
372
+ 'data: {"type":"content_block_stop","index":2}\n',
373
+
374
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
375
+ 'event: stop',
376
+ 'data: "tool_use"\n',
377
+
378
+ 'id: msg_0175ryA67RbGrnRrGBXFQEYK',
379
+ 'event: stop',
380
+ 'data: "message_stop"\n',
381
+ ].map((item) => `${item}\n`),
382
+ );
383
+
384
+ expect(onToolCallMock).toHaveBeenCalledTimes(6);
385
+ });
230
386
 
231
387
  it('should handle ReadableStream input', async () => {
232
388
  const mockReadableStream = new ReadableStream({
@@ -26,17 +26,26 @@ export const transformAnthropicStream = (
26
26
  if (chunk.content_block.type === 'tool_use') {
27
27
  const toolChunk = chunk.content_block;
28
28
 
29
+ // if toolIndex is not defined, set it to 0
30
+ if (typeof stack.toolIndex === 'undefined') {
31
+ stack.toolIndex = 0;
32
+ }
33
+ // if toolIndex is defined, increment it
34
+ else {
35
+ stack.toolIndex += 1;
36
+ }
37
+
29
38
  const toolCall: StreamToolCallChunkData = {
30
39
  function: {
31
40
  arguments: '',
32
41
  name: toolChunk.name,
33
42
  },
34
43
  id: toolChunk.id,
35
- index: 0,
44
+ index: stack.toolIndex,
36
45
  type: 'function',
37
46
  };
38
47
 
39
- stack.tool = { id: toolChunk.id, index: 0, name: toolChunk.name };
48
+ stack.tool = { id: toolChunk.id, index: stack.toolIndex, name: toolChunk.name };
40
49
 
41
50
  return { data: [toolCall], id: stack.id, type: 'tool_calls' };
42
51
  }
@@ -55,7 +64,7 @@ export const transformAnthropicStream = (
55
64
 
56
65
  const toolCall: StreamToolCallChunkData = {
57
66
  function: { arguments: delta },
58
- index: 0,
67
+ index: stack.toolIndex || 0,
59
68
  type: 'function',
60
69
  };
61
70
 
@@ -7,6 +7,7 @@ export interface StreamStack {
7
7
  index: number;
8
8
  name: string;
9
9
  };
10
+ toolIndex?: number;
10
11
  }
11
12
 
12
13
  export interface StreamProtocolChunk {