ai 7.0.139 → 7.0.141
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.
- package/CHANGELOG.md +18 -0
- package/dist/index.d.ts +27 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +148 -73
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +28 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +55 -3
- package/dist/internal/index.js.map +1 -1
- package/docs/03-agents/07-workflow-agent.mdx +20 -0
- package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +29 -0
- package/docs/03-ai-sdk-core/17-mcp-events.mdx +4 -1
- package/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +7 -0
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +7 -0
- package/docs/07-reference/01-ai-sdk-core/16-tool-loop-agent.mdx +7 -0
- package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +14 -0
- package/package.json +11 -11
- package/src/agent/tool-loop-agent-settings.ts +10 -0
- package/src/generate-text/collect-tool-approvals.ts +11 -0
- package/src/generate-text/execute-tools-from-stream.ts +10 -4
- package/src/generate-text/generate-text.ts +40 -19
- package/src/generate-text/stream-text.ts +27 -4
- package/src/generate-text/tool-call-concurrency.ts +80 -0
|
@@ -881,3 +881,23 @@ Other options carry over with the same names: `prepareStep`, `onStart`, `onStepS
|
|
|
881
881
|
- [WorkflowChatTransport API Reference](/docs/reference/ai-sdk-workflow/workflow-chat-transport) for stream reconnection options
|
|
882
882
|
- [Building Agents](/docs/agents/building-agents) for the in-memory `ToolLoopAgent` alternative
|
|
883
883
|
- [Loop Control](/docs/agents/loop-control) for advanced stop conditions
|
|
884
|
+
|
|
885
|
+
## Tool execution concurrency
|
|
886
|
+
|
|
887
|
+
Set `toolCallConcurrency` to limit how many local tool calls execute concurrently
|
|
888
|
+
within a step. Use `1` when calls must execute sequentially in model order:
|
|
889
|
+
|
|
890
|
+
```ts
|
|
891
|
+
const agent = new WorkflowAgent({
|
|
892
|
+
model,
|
|
893
|
+
tools,
|
|
894
|
+
toolCallConcurrency: 1,
|
|
895
|
+
});
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
The limit applies to both `generate()` and `stream()`, including approved tool
|
|
899
|
+
calls resumed from message history. It must be a positive integer. By default,
|
|
900
|
+
all local calls execute concurrently; provider-executed tools are unaffected.
|
|
901
|
+
You can also return the setting from `prepareCall` or pass it to `generate()` or
|
|
902
|
+
`stream()`. Explicit call options take precedence over `prepareCall`, which takes
|
|
903
|
+
precedence over the constructor default.
|
|
@@ -511,6 +511,35 @@ const result = await generateText({
|
|
|
511
511
|
});
|
|
512
512
|
```
|
|
513
513
|
|
|
514
|
+
### Tool call execution concurrency
|
|
515
|
+
|
|
516
|
+
By default, when a model generates multiple tool calls in one step,
|
|
517
|
+
`generateText`, `streamText`, and `ToolLoopAgent` execute those calls
|
|
518
|
+
concurrently. Set `toolCallConcurrency` to a positive integer to limit how many
|
|
519
|
+
SDK-executed tool calls run at once.
|
|
520
|
+
|
|
521
|
+
Use `toolCallConcurrency: 1` when later calls need to observe side effects from
|
|
522
|
+
earlier calls. The SDK starts calls sequentially in the order the model
|
|
523
|
+
generated them:
|
|
524
|
+
|
|
525
|
+
```ts highlight="5"
|
|
526
|
+
const result = await generateText({
|
|
527
|
+
model: __MODEL__,
|
|
528
|
+
tools,
|
|
529
|
+
prompt: 'Update the file, then read it.',
|
|
530
|
+
toolCallConcurrency: 1,
|
|
531
|
+
});
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
This option controls execution of tool calls that the SDK runs locally. It does
|
|
535
|
+
not change how many tool calls the model may generate, and it does not schedule
|
|
536
|
+
provider-executed tools or client-side tools. Provider options such as
|
|
537
|
+
`parallelToolCalls` control model generation instead.
|
|
538
|
+
|
|
539
|
+
When the operation is aborted, active tools receive the abort signal and queued
|
|
540
|
+
tool calls are not started. A streaming tool holds its concurrency slot until
|
|
541
|
+
its final output or error.
|
|
542
|
+
|
|
514
543
|
### Tool execution lifecycle callbacks
|
|
515
544
|
|
|
516
545
|
You can use `onToolExecutionStart` and `onToolExecutionEnd` to observe tool execution.
|
|
@@ -154,13 +154,16 @@ try {
|
|
|
154
154
|
}
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
After catalog discovery and before sending `events/subscribe`, the SDK generates and saves
|
|
158
158
|
`delivery.secret`. It appends a `mcp_event_subscription` query parameter to the
|
|
159
159
|
callback URL so the webhook can find that secret even while verification is
|
|
160
160
|
pending. Preserve this parameter and pass the raw request body to the handler.
|
|
161
161
|
|
|
162
162
|
## Runnable example
|
|
163
163
|
|
|
164
|
+
For a step-by-step walkthrough of receiving events and choosing a subscription
|
|
165
|
+
backend, see the [MCP events recipe](/cookbook/node/mcp-events).
|
|
166
|
+
|
|
164
167
|
The [local example](https://github.com/vercel/ai/tree/main/examples/mcp/src/events)
|
|
165
168
|
uses `@modelcontextprotocol/server` to emit a comment and the AI SDK to subscribe,
|
|
166
169
|
verify, and receive it. It needs no model API key. Local HTTP callbacks require
|
|
@@ -43,5 +43,5 @@ The AI SDK includes the following harness adapters:
|
|
|
43
43
|
| [fx](/providers/ai-sdk-harnesses/fx) | Sandbox via ACP | <Check /> | <Check /> | <Cross /> | <Check /> | <Cross /> | <Cross /> |
|
|
44
44
|
| [GitHub Copilot](/providers/ai-sdk-harnesses/github-copilot) | Sandbox via ACP | <Check /> | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> |
|
|
45
45
|
| [Grok Build](/providers/ai-sdk-harnesses/grok-build) | Sandbox via ACP | <Check /> | <Check /> | <Check /> | <Cross /> | <Cross /> | <Cross /> |
|
|
46
|
-
| [OpenCode](/providers/ai-sdk-harnesses/opencode) | Sandbox bridge | <Check /> | <Check /> | <Check /> | <Check /> | <Check />
|
|
46
|
+
| [OpenCode](/providers/ai-sdk-harnesses/opencode) | Sandbox bridge | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Cross /> |
|
|
47
47
|
| [Pi](/providers/ai-sdk-harnesses/pi) | Host process | <Check /> | <Check /> | <Cross /> | <Check /> | <Check /> | <Cross /> |
|
|
@@ -571,6 +571,13 @@ To see `generateText` in action, check out [these examples](#examples).
|
|
|
571
571
|
description:
|
|
572
572
|
'Controls the order in which tools are sent to the provider. The list can be partial. Tools not listed in `toolOrder` are sent after the listed tools, sorted alphabetically. Tool names are restricted to the string keys of the tool set.',
|
|
573
573
|
},
|
|
574
|
+
{
|
|
575
|
+
name: 'toolCallConcurrency',
|
|
576
|
+
type: 'number',
|
|
577
|
+
isOptional: true,
|
|
578
|
+
description:
|
|
579
|
+
'Maximum number of tool calls that the SDK executes concurrently within a step. Set to `1` to execute calls sequentially in model-generated order. Must be a positive integer. By default, all tool calls execute concurrently.',
|
|
580
|
+
},
|
|
574
581
|
{
|
|
575
582
|
name: 'toolApproval',
|
|
576
583
|
type: 'ToolApprovalConfiguration<TOOLS, RUNTIME_CONTEXT>',
|
|
@@ -622,6 +622,13 @@ To see `streamText` in action, check out [these examples](#examples).
|
|
|
622
622
|
description:
|
|
623
623
|
'Controls the order in which tools are sent to the provider. The list can be partial. Tools not listed in `toolOrder` are sent after the listed tools, sorted alphabetically. Tool names are restricted to the string keys of the tool set.',
|
|
624
624
|
},
|
|
625
|
+
{
|
|
626
|
+
name: 'toolCallConcurrency',
|
|
627
|
+
type: 'number',
|
|
628
|
+
isOptional: true,
|
|
629
|
+
description:
|
|
630
|
+
'Maximum number of tool calls that the SDK executes concurrently within a step. Set to `1` to execute calls sequentially in model-generated order. Must be a positive integer. By default, all tool calls execute concurrently.',
|
|
631
|
+
},
|
|
625
632
|
{
|
|
626
633
|
name: 'toolApproval',
|
|
627
634
|
type: 'ToolApprovalConfiguration<TOOLS, RUNTIME_CONTEXT>',
|
|
@@ -104,6 +104,13 @@ To see `ToolLoopAgent` in action, check out [these examples](#examples).
|
|
|
104
104
|
description:
|
|
105
105
|
'Controls the order in which tools are sent to the provider. The list can be partial. Tools not listed in `toolOrder` are sent after the listed tools, sorted alphabetically. Tool names are restricted to the string keys of the tool set.',
|
|
106
106
|
},
|
|
107
|
+
{
|
|
108
|
+
name: 'toolCallConcurrency',
|
|
109
|
+
type: 'number',
|
|
110
|
+
isOptional: true,
|
|
111
|
+
description:
|
|
112
|
+
'Maximum number of tool calls that the SDK executes concurrently within an agent step. Set to `1` to execute calls sequentially in model-generated order. Must be a positive integer. By default, all tool calls execute concurrently.',
|
|
113
|
+
},
|
|
107
114
|
{
|
|
108
115
|
name: 'toolApproval',
|
|
109
116
|
type: 'ToolApprovalConfiguration<TOOLS, RUNTIME_CONTEXT>',
|
|
@@ -101,6 +101,13 @@ To see `WorkflowAgent` in action, check out [these examples](#examples).
|
|
|
101
101
|
description:
|
|
102
102
|
'Default stop condition for the agent loop. When omitted, generate defaults to 20 steps, while stream has no maximum step count. Use `isStepCount()` to bound execution. Per-call values override this default.',
|
|
103
103
|
},
|
|
104
|
+
{
|
|
105
|
+
name: 'toolCallConcurrency',
|
|
106
|
+
type: 'number',
|
|
107
|
+
isOptional: true,
|
|
108
|
+
description:
|
|
109
|
+
'Maximum number of local tool calls executing concurrently within a step, including approved calls. Must be a positive integer. Set to 1 for sequential execution in model order. Defaults to unlimited concurrency. Can be changed by prepareCall; explicit generate/stream call options take precedence. Provider-executed tools are unaffected.',
|
|
110
|
+
},
|
|
104
111
|
{
|
|
105
112
|
name: 'activeTools',
|
|
106
113
|
type: 'ActiveTools<TTools>',
|
|
@@ -569,6 +576,13 @@ const result = await agent.stream({
|
|
|
569
576
|
isOptional: true,
|
|
570
577
|
description: "Override the tool choice strategy for this call. Default: 'auto'.",
|
|
571
578
|
},
|
|
579
|
+
{
|
|
580
|
+
name: 'toolCallConcurrency',
|
|
581
|
+
type: 'number',
|
|
582
|
+
isOptional: true,
|
|
583
|
+
description:
|
|
584
|
+
'Override the local tool call concurrency limit for this call. Must be a positive integer. Set to 1 for sequential execution in model order, including approved calls. Takes precedence over the constructor and prepareCall.',
|
|
585
|
+
},
|
|
572
586
|
{
|
|
573
587
|
name: 'activeTools',
|
|
574
588
|
type: 'ActiveTools<TTools>',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.141",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,20 +42,20 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.114",
|
|
46
46
|
"@ai-sdk/provider": "4.0.27",
|
|
47
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.60"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@ai-sdk/amazon-bedrock": "5.0.
|
|
51
|
-
"@ai-sdk/deepseek": "3.0.
|
|
52
|
-
"@ai-sdk/google": "4.0.
|
|
53
|
-
"@ai-sdk/groq": "4.0.
|
|
54
|
-
"@ai-sdk/huggingface": "2.0.
|
|
55
|
-
"@ai-sdk/moonshotai": "3.0.
|
|
56
|
-
"@ai-sdk/openai": "4.0.
|
|
50
|
+
"@ai-sdk/amazon-bedrock": "5.0.116",
|
|
51
|
+
"@ai-sdk/deepseek": "3.0.65",
|
|
52
|
+
"@ai-sdk/google": "4.0.96",
|
|
53
|
+
"@ai-sdk/groq": "4.0.61",
|
|
54
|
+
"@ai-sdk/huggingface": "2.0.69",
|
|
55
|
+
"@ai-sdk/moonshotai": "3.0.69",
|
|
56
|
+
"@ai-sdk/openai": "4.0.94",
|
|
57
57
|
"@ai-sdk/test-server": "2.0.4",
|
|
58
|
-
"@ai-sdk/xai": "5.0.
|
|
58
|
+
"@ai-sdk/xai": "5.0.22",
|
|
59
59
|
"@edge-runtime/vm": "^5.0.0",
|
|
60
60
|
"@smithy/eventstream-codec": "^4.3.3",
|
|
61
61
|
"@smithy/util-utf8": "^4.3.3",
|
|
@@ -121,6 +121,14 @@ export type ToolLoopAgentSettings<
|
|
|
121
121
|
*/
|
|
122
122
|
toolOrder?: ToolOrder<NoInfer<TOOLS>>;
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Maximum number of tool calls that may execute concurrently within a step.
|
|
126
|
+
*
|
|
127
|
+
* Set to `1` to execute tool calls sequentially in the order they were
|
|
128
|
+
* generated. By default, all tool calls execute concurrently.
|
|
129
|
+
*/
|
|
130
|
+
toolCallConcurrency?: number;
|
|
131
|
+
|
|
124
132
|
/**
|
|
125
133
|
* Optional specification for generating structured outputs.
|
|
126
134
|
*/
|
|
@@ -370,6 +378,7 @@ export type ToolLoopAgentSettings<
|
|
|
370
378
|
| 'experimental_telemetry'
|
|
371
379
|
| 'activeTools'
|
|
372
380
|
| 'toolOrder'
|
|
381
|
+
| 'toolCallConcurrency'
|
|
373
382
|
| 'toolApproval'
|
|
374
383
|
| 'experimental_toolCallers'
|
|
375
384
|
| 'experimental_toolApprovalSecret'
|
|
@@ -412,6 +421,7 @@ export type ToolLoopAgentSettings<
|
|
|
412
421
|
| 'experimental_telemetry'
|
|
413
422
|
| 'activeTools'
|
|
414
423
|
| 'toolOrder'
|
|
424
|
+
| 'toolCallConcurrency'
|
|
415
425
|
| 'toolApproval'
|
|
416
426
|
| 'experimental_toolCallers'
|
|
417
427
|
| 'experimental_toolApprovalSecret'
|
|
@@ -50,12 +50,15 @@ export function collectToolApprovals<TOOLS extends ToolSet>({
|
|
|
50
50
|
string,
|
|
51
51
|
TypedToolCall<TOOLS>
|
|
52
52
|
> = Object.create(null);
|
|
53
|
+
const toolCallOrder: Record<string, number> = Object.create(null);
|
|
54
|
+
let nextToolCallIndex = 0;
|
|
53
55
|
for (const message of messages) {
|
|
54
56
|
if (message.role === 'assistant' && typeof message.content !== 'string') {
|
|
55
57
|
const content = message.content;
|
|
56
58
|
for (const part of content) {
|
|
57
59
|
if (part.type === 'tool-call') {
|
|
58
60
|
toolCallsByToolCallId[part.toolCallId] = part as TypedToolCall<TOOLS>;
|
|
61
|
+
toolCallOrder[part.toolCallId] = nextToolCallIndex++;
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
}
|
|
@@ -130,5 +133,13 @@ export function collectToolApprovals<TOOLS extends ToolSet>({
|
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
135
|
|
|
136
|
+
// Approval responses can arrive in a different order than the model's calls.
|
|
137
|
+
// Restore model order before either generation API schedules their execution.
|
|
138
|
+
approvedToolApprovals.sort(
|
|
139
|
+
(a, b) =>
|
|
140
|
+
toolCallOrder[a.toolCall.toolCallId] -
|
|
141
|
+
toolCallOrder[b.toolCall.toolCallId],
|
|
142
|
+
);
|
|
143
|
+
|
|
133
144
|
return { approvedToolApprovals, deniedToolApprovals };
|
|
134
145
|
}
|
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
OnToolExecutionStartCallback,
|
|
27
27
|
} from './tool-execution-events';
|
|
28
28
|
import type { StaticToolOutputDenied } from './tool-output-denied';
|
|
29
|
+
import { mapWithConcurrency } from './tool-call-concurrency';
|
|
29
30
|
|
|
30
31
|
export type ToolExecutionEndStreamPart = {
|
|
31
32
|
type: 'tool-execution-end';
|
|
@@ -63,6 +64,7 @@ export function executeToolsFromStream<
|
|
|
63
64
|
onToolExecutionEnd,
|
|
64
65
|
executeToolInTelemetryContext,
|
|
65
66
|
runInTracingChannelSpan,
|
|
67
|
+
toolCallConcurrency,
|
|
66
68
|
}: {
|
|
67
69
|
stream: ReadableStream<ExecuteToolsInputStreamPart<TOOLS>>;
|
|
68
70
|
tools: TOOLS | undefined;
|
|
@@ -82,6 +84,7 @@ export function executeToolsFromStream<
|
|
|
82
84
|
runInTracingChannelSpan?: NonNullable<
|
|
83
85
|
TelemetryDispatcher['runInTracingChannelSpan']
|
|
84
86
|
>;
|
|
87
|
+
toolCallConcurrency?: number;
|
|
85
88
|
}): ReadableStream<ExecuteToolsStreamPart<TOOLS>> {
|
|
86
89
|
const toolCallsToExecute: Array<TypedToolCall<TOOLS>> = [];
|
|
87
90
|
|
|
@@ -226,8 +229,11 @@ export function executeToolsFromStream<
|
|
|
226
229
|
return;
|
|
227
230
|
}
|
|
228
231
|
|
|
229
|
-
await
|
|
230
|
-
toolCallsToExecute
|
|
232
|
+
await mapWithConcurrency({
|
|
233
|
+
items: toolCallsToExecute,
|
|
234
|
+
concurrency: toolCallConcurrency,
|
|
235
|
+
abortSignal,
|
|
236
|
+
execute: async toolCall => {
|
|
231
237
|
try {
|
|
232
238
|
// Note: we don't await the tool execution here (by leaving out 'await' on recordSpan),
|
|
233
239
|
// because we want to process the next chunk as soon as possible.
|
|
@@ -263,8 +269,8 @@ export function executeToolsFromStream<
|
|
|
263
269
|
error,
|
|
264
270
|
});
|
|
265
271
|
}
|
|
266
|
-
}
|
|
267
|
-
);
|
|
272
|
+
},
|
|
273
|
+
});
|
|
268
274
|
}
|
|
269
275
|
}
|
|
270
276
|
},
|
|
@@ -124,6 +124,10 @@ import type { ToolOutput } from './tool-output';
|
|
|
124
124
|
import type { ToolsContextParameter } from './tools-context-parameter';
|
|
125
125
|
import { maybeSignApproval } from './tool-approval-signature';
|
|
126
126
|
import { validateApprovedToolApprovals } from './validate-tool-approvals';
|
|
127
|
+
import {
|
|
128
|
+
mapWithConcurrency,
|
|
129
|
+
prepareToolCallConcurrency,
|
|
130
|
+
} from './tool-call-concurrency';
|
|
127
131
|
|
|
128
132
|
const originalGenerateId = createIdGenerator({
|
|
129
133
|
prefix: 'aitxt',
|
|
@@ -170,6 +174,7 @@ export type GenerateTextInclude = {
|
|
|
170
174
|
* @param tools - Tools that are accessible to and can be called by the model. The model needs to support calling tools.
|
|
171
175
|
* @param toolChoice - The tool choice strategy. Default: 'auto'.
|
|
172
176
|
* @param toolOrder - Controls the order in which tools are sent to the provider. Tools not listed are appended alphabetically.
|
|
177
|
+
* @param toolCallConcurrency - Maximum number of tool calls that may execute concurrently within a step. Set to 1 for sequential execution. Default: unlimited.
|
|
173
178
|
*
|
|
174
179
|
* @param system - A system message that will be part of the prompt.
|
|
175
180
|
* @param prompt - A simple text prompt. You can either use `prompt` or `messages` but not both.
|
|
@@ -255,6 +260,7 @@ export async function generateText<
|
|
|
255
260
|
providerOptions,
|
|
256
261
|
activeTools,
|
|
257
262
|
toolOrder,
|
|
263
|
+
toolCallConcurrency,
|
|
258
264
|
prepareStep,
|
|
259
265
|
experimental_repairToolCall,
|
|
260
266
|
repairToolCall = experimental_repairToolCall,
|
|
@@ -353,6 +359,14 @@ export async function generateText<
|
|
|
353
359
|
*/
|
|
354
360
|
toolOrder?: ToolOrder<NoInfer<TOOLS>>;
|
|
355
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Maximum number of tool calls that may execute concurrently within a step.
|
|
364
|
+
*
|
|
365
|
+
* Set to `1` to execute tool calls sequentially in the order they were
|
|
366
|
+
* generated. By default, all tool calls execute concurrently.
|
|
367
|
+
*/
|
|
368
|
+
toolCallConcurrency?: number;
|
|
369
|
+
|
|
356
370
|
/**
|
|
357
371
|
* Optional specification for parsing structured outputs from the LLM response.
|
|
358
372
|
*/
|
|
@@ -571,6 +585,8 @@ export async function generateText<
|
|
|
571
585
|
responseBody: include?.responseBody ?? false,
|
|
572
586
|
};
|
|
573
587
|
|
|
588
|
+
const resolvedToolCallConcurrency =
|
|
589
|
+
prepareToolCallConcurrency(toolCallConcurrency);
|
|
574
590
|
const model = resolveLanguageModel(modelArg);
|
|
575
591
|
const resolvedToolCallers = resolveToolCallerConfiguration({
|
|
576
592
|
tools,
|
|
@@ -770,6 +786,7 @@ export async function generateText<
|
|
|
770
786
|
}),
|
|
771
787
|
executeToolInTelemetryContext: telemetryDispatcher.executeTool,
|
|
772
788
|
runInTracingChannelSpan,
|
|
789
|
+
toolCallConcurrency: resolvedToolCallConcurrency,
|
|
773
790
|
});
|
|
774
791
|
|
|
775
792
|
const toolContent: Array<any> = [];
|
|
@@ -1371,6 +1388,7 @@ export async function generateText<
|
|
|
1371
1388
|
executeToolInTelemetryContext:
|
|
1372
1389
|
telemetryDispatcher.executeTool,
|
|
1373
1390
|
runInTracingChannelSpan,
|
|
1391
|
+
toolCallConcurrency: resolvedToolCallConcurrency,
|
|
1374
1392
|
});
|
|
1375
1393
|
|
|
1376
1394
|
for (const result of toolExecutionResults) {
|
|
@@ -1643,6 +1661,7 @@ async function executeTools<TOOLS extends ToolSet>({
|
|
|
1643
1661
|
onToolExecutionEnd,
|
|
1644
1662
|
executeToolInTelemetryContext,
|
|
1645
1663
|
runInTracingChannelSpan,
|
|
1664
|
+
toolCallConcurrency,
|
|
1646
1665
|
}: {
|
|
1647
1666
|
toolCalls: Array<TypedToolCall<TOOLS>>;
|
|
1648
1667
|
tools: TOOLS;
|
|
@@ -1658,31 +1677,33 @@ async function executeTools<TOOLS extends ToolSet>({
|
|
|
1658
1677
|
runInTracingChannelSpan?: NonNullable<
|
|
1659
1678
|
TelemetryDispatcher['runInTracingChannelSpan']
|
|
1660
1679
|
>;
|
|
1680
|
+
toolCallConcurrency: number | undefined;
|
|
1661
1681
|
}): Promise<
|
|
1662
1682
|
Array<{
|
|
1663
1683
|
output: ToolOutput<TOOLS>;
|
|
1664
1684
|
toolExecutionMs: number;
|
|
1665
1685
|
}>
|
|
1666
1686
|
> {
|
|
1667
|
-
const toolResults = await
|
|
1668
|
-
toolCalls
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1687
|
+
const toolResults = await mapWithConcurrency({
|
|
1688
|
+
items: toolCalls,
|
|
1689
|
+
concurrency: toolCallConcurrency,
|
|
1690
|
+
abortSignal,
|
|
1691
|
+
execute: async toolCall =>
|
|
1692
|
+
await executeToolCall({
|
|
1693
|
+
toolCall,
|
|
1694
|
+
tools,
|
|
1695
|
+
callId,
|
|
1696
|
+
messages,
|
|
1697
|
+
abortSignal,
|
|
1698
|
+
timeout,
|
|
1699
|
+
experimental_sandbox: sandbox,
|
|
1700
|
+
toolsContext,
|
|
1701
|
+
onToolExecutionStart,
|
|
1702
|
+
onToolExecutionEnd,
|
|
1703
|
+
executeToolInTelemetryContext,
|
|
1704
|
+
runInTracingChannelSpan,
|
|
1705
|
+
}),
|
|
1706
|
+
});
|
|
1686
1707
|
|
|
1687
1708
|
return toolResults.filter(
|
|
1688
1709
|
(result): result is NonNullable<typeof result> => result != null,
|
|
@@ -166,6 +166,10 @@ import type { ToolOutput } from './tool-output';
|
|
|
166
166
|
import type { StaticToolOutputDenied } from './tool-output-denied';
|
|
167
167
|
import type { ToolsContextParameter } from './tools-context-parameter';
|
|
168
168
|
import { validateApprovedToolApprovals } from './validate-tool-approvals';
|
|
169
|
+
import {
|
|
170
|
+
mapWithConcurrency,
|
|
171
|
+
prepareToolCallConcurrency,
|
|
172
|
+
} from './tool-call-concurrency';
|
|
169
173
|
|
|
170
174
|
const originalGenerateId = createIdGenerator({
|
|
171
175
|
prefix: 'aitxt',
|
|
@@ -288,6 +292,7 @@ export type StreamTextOnAbortCallback<
|
|
|
288
292
|
* @param model - The language model to use.
|
|
289
293
|
* @param tools - Tools that are accessible to and can be called by the model. The model needs to support calling tools.
|
|
290
294
|
* @param toolOrder - Controls the order in which tools are sent to the provider. Tools not listed are appended alphabetically.
|
|
295
|
+
* @param toolCallConcurrency - Maximum number of tool calls that may execute concurrently within a step. Set to 1 for sequential execution. Default: unlimited.
|
|
291
296
|
*
|
|
292
297
|
* @param system - A system message that will be part of the prompt.
|
|
293
298
|
* @param prompt - A simple text prompt. You can either use `prompt` or `messages` but not both.
|
|
@@ -377,6 +382,7 @@ export function streamText<
|
|
|
377
382
|
providerOptions,
|
|
378
383
|
activeTools,
|
|
379
384
|
toolOrder,
|
|
385
|
+
toolCallConcurrency,
|
|
380
386
|
experimental_repairToolCall,
|
|
381
387
|
repairToolCall = experimental_repairToolCall,
|
|
382
388
|
experimental_refineToolInput: refineToolInput,
|
|
@@ -479,6 +485,14 @@ export function streamText<
|
|
|
479
485
|
*/
|
|
480
486
|
toolOrder?: ToolOrder<NoInfer<TOOLS>>;
|
|
481
487
|
|
|
488
|
+
/**
|
|
489
|
+
* Maximum number of tool calls that may execute concurrently within a step.
|
|
490
|
+
*
|
|
491
|
+
* Set to `1` to execute tool calls sequentially in the order they were
|
|
492
|
+
* generated. By default, all tool calls execute concurrently.
|
|
493
|
+
*/
|
|
494
|
+
toolCallConcurrency?: number;
|
|
495
|
+
|
|
482
496
|
/**
|
|
483
497
|
* Optional specification for parsing structured outputs from the LLM response.
|
|
484
498
|
*/
|
|
@@ -769,6 +783,8 @@ export function streamText<
|
|
|
769
783
|
generateCallId?: IdGenerator;
|
|
770
784
|
};
|
|
771
785
|
}): StreamTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT> {
|
|
786
|
+
const resolvedToolCallConcurrency =
|
|
787
|
+
prepareToolCallConcurrency(toolCallConcurrency);
|
|
772
788
|
const totalTimeoutMs = getTotalTimeoutMs(timeout);
|
|
773
789
|
const stepTimeoutMs = getStepTimeoutMs(timeout);
|
|
774
790
|
const firstChunkTimeoutMs = getFirstChunkTimeoutMs(timeout);
|
|
@@ -828,6 +844,7 @@ export function streamText<
|
|
|
828
844
|
transforms: asArray(transform),
|
|
829
845
|
activeTools,
|
|
830
846
|
toolOrder,
|
|
847
|
+
toolCallConcurrency: resolvedToolCallConcurrency,
|
|
831
848
|
repairToolCall,
|
|
832
849
|
refineToolInput,
|
|
833
850
|
stopConditions: asArray(stopWhen),
|
|
@@ -1212,6 +1229,7 @@ class DefaultStreamTextResult<
|
|
|
1212
1229
|
transforms,
|
|
1213
1230
|
activeTools,
|
|
1214
1231
|
toolOrder,
|
|
1232
|
+
toolCallConcurrency,
|
|
1215
1233
|
repairToolCall,
|
|
1216
1234
|
refineToolInput,
|
|
1217
1235
|
stopConditions,
|
|
@@ -1268,6 +1286,7 @@ class DefaultStreamTextResult<
|
|
|
1268
1286
|
transforms: Array<StreamTextTransform<TOOLS>>;
|
|
1269
1287
|
activeTools: ActiveTools<TOOLS>;
|
|
1270
1288
|
toolOrder: ToolOrder<TOOLS>;
|
|
1289
|
+
toolCallConcurrency: number | undefined;
|
|
1271
1290
|
repairToolCall: ToolCallRepairFunction<TOOLS> | undefined;
|
|
1272
1291
|
refineToolInput: ToolInputRefinement<TOOLS> | undefined;
|
|
1273
1292
|
stopConditions: Array<
|
|
@@ -2075,8 +2094,11 @@ class DefaultStreamTextResult<
|
|
|
2075
2094
|
|
|
2076
2095
|
const toolOutputs: Array<ToolOutput<TOOLS>> = [];
|
|
2077
2096
|
|
|
2078
|
-
await
|
|
2079
|
-
localApprovedToolApprovals
|
|
2097
|
+
await mapWithConcurrency({
|
|
2098
|
+
items: localApprovedToolApprovals,
|
|
2099
|
+
concurrency: toolCallConcurrency,
|
|
2100
|
+
abortSignal,
|
|
2101
|
+
execute: async toolApproval => {
|
|
2080
2102
|
const result = await executeToolCall({
|
|
2081
2103
|
toolCall: toolApproval.toolCall,
|
|
2082
2104
|
tools,
|
|
@@ -2105,8 +2127,8 @@ class DefaultStreamTextResult<
|
|
|
2105
2127
|
toolExecutionStepStreamController?.enqueue(result.output);
|
|
2106
2128
|
toolOutputs.push(result.output);
|
|
2107
2129
|
}
|
|
2108
|
-
}
|
|
2109
|
-
);
|
|
2130
|
+
},
|
|
2131
|
+
});
|
|
2110
2132
|
|
|
2111
2133
|
// Local tool results (approved + denied) are sent as tool results:
|
|
2112
2134
|
if (
|
|
@@ -2709,6 +2731,7 @@ class DefaultStreamTextResult<
|
|
|
2709
2731
|
|
|
2710
2732
|
executeToolInTelemetryContext: telemetryDispatcher.executeTool,
|
|
2711
2733
|
runInTracingChannelSpan: runInTracingChannelSpanInStep,
|
|
2734
|
+
toolCallConcurrency,
|
|
2712
2735
|
});
|
|
2713
2736
|
|
|
2714
2737
|
// Conditionally include request.body based on include settings.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { InvalidArgumentError } from '../error/invalid-argument-error';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validates the maximum number of tool calls that may execute concurrently.
|
|
5
|
+
*/
|
|
6
|
+
export function prepareToolCallConcurrency(
|
|
7
|
+
toolCallConcurrency: number | undefined,
|
|
8
|
+
): number | undefined {
|
|
9
|
+
if (toolCallConcurrency == null) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!Number.isInteger(toolCallConcurrency)) {
|
|
14
|
+
throw new InvalidArgumentError({
|
|
15
|
+
parameter: 'toolCallConcurrency',
|
|
16
|
+
value: toolCallConcurrency,
|
|
17
|
+
message: 'toolCallConcurrency must be an integer',
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (toolCallConcurrency < 1) {
|
|
22
|
+
throw new InvalidArgumentError({
|
|
23
|
+
parameter: 'toolCallConcurrency',
|
|
24
|
+
value: toolCallConcurrency,
|
|
25
|
+
message: 'toolCallConcurrency must be >= 1',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return toolCallConcurrency;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Maps items with an optional concurrency limit while preserving result order.
|
|
34
|
+
*
|
|
35
|
+
* Workers claim items in input order. When the signal is aborted or an
|
|
36
|
+
* execution rejects, no additional queued items are started.
|
|
37
|
+
*/
|
|
38
|
+
export async function mapWithConcurrency<INPUT, OUTPUT>({
|
|
39
|
+
items,
|
|
40
|
+
concurrency,
|
|
41
|
+
abortSignal,
|
|
42
|
+
execute,
|
|
43
|
+
}: {
|
|
44
|
+
items: Array<INPUT>;
|
|
45
|
+
concurrency: number | undefined;
|
|
46
|
+
abortSignal?: AbortSignal;
|
|
47
|
+
execute: (item: INPUT, index: number) => Promise<OUTPUT>;
|
|
48
|
+
}): Promise<Array<OUTPUT>> {
|
|
49
|
+
if (concurrency == null || concurrency >= items.length) {
|
|
50
|
+
return Promise.all(items.map(execute));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const results = new Array<OUTPUT>(items.length);
|
|
54
|
+
let nextIndex = 0;
|
|
55
|
+
let stopped = false;
|
|
56
|
+
|
|
57
|
+
const worker = async () => {
|
|
58
|
+
while (!stopped) {
|
|
59
|
+
abortSignal?.throwIfAborted();
|
|
60
|
+
|
|
61
|
+
const index = nextIndex++;
|
|
62
|
+
if (index >= items.length) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
results[index] = await execute(items[index], index);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
stopped = true;
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
await Promise.all(
|
|
76
|
+
Array.from({ length: Math.min(concurrency, items.length) }, worker),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return results;
|
|
80
|
+
}
|