UNPKG

ai

Version:

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.

1,323 lines (1,208 loc) • 64.9 kB
import type { LanguageModelV4Content, LanguageModelV4GenerateResult, LanguageModelV4ToolCall, } from '@ai-sdk/provider'; import { asArray, createIdGenerator, getErrorMessage, withUserAgentSuffix, type Arrayable, type Context, type Experimental_SandboxSession as SandboxSession, type IdGenerator, type InferToolSetContext, type ProviderOptions, type ToolSet, } from '@ai-sdk/provider-utils'; import { NoOutputGeneratedError } from '../error'; import { ToolCallNotFoundForApprovalError } from '../error/tool-call-not-found-for-approval-error'; import { logWarnings } from '../logger/log-warnings'; import { resolveLanguageModel } from '../model/resolve-model'; import type { ModelMessage } from '../prompt'; import { cloneModelMessages } from '../prompt/clone-model-message'; import { convertToLanguageModelPrompt } from '../prompt/convert-to-language-model-prompt'; import { createToolModelOutput } from '../prompt/create-tool-model-output'; import type { LanguageModelCallOptions } from '../prompt/language-model-call-options'; import { prepareLanguageModelCallOptions } from '../prompt/prepare-language-model-call-options'; import { prepareToolChoice } from '../prompt/prepare-tool-choice'; import { prepareTools } from '../prompt/prepare-tools'; import type { Prompt } from '../prompt/prompt'; import { getStepTimeoutMs, getTotalTimeoutMs, type RequestOptions, type TimeoutConfiguration, } from '../prompt/request-options'; import { standardizePrompt } from '../prompt/standardize-prompt'; import { wrapGatewayError } from '../prompt/wrap-gateway-error'; import type { Telemetry, TelemetryDispatcher } from '../telemetry/telemetry'; import type { TelemetryOptions } from '../telemetry/telemetry-options'; import type { LanguageModel, LanguageModelRequestMetadata, ToolChoice, } from '../types'; import { addLanguageModelUsage, asLanguageModelUsage, type LanguageModelUsage, } from '../types/usage'; import type { DownloadFunction } from '../util/download/download-function'; import { getOwn } from '../util/get-own'; import { mergeAbortSignals } from '../util/merge-abort-signals'; import { mergeObjects } from '../util/merge-objects'; import { now as originalNow } from '../util/now'; import { notify } from '../util/notify'; import { prepareRetries } from '../util/prepare-retries'; import { setAbortTimeout } from '../util/set-abort-timeout'; import { VERSION } from '../version'; import type { ActiveTools } from './active-tools'; import { calculateTokensPerSecond } from './calculate-tokens-per-second'; import { collectToolApprovals } from './collect-tool-approvals'; import type { ContentPart } from './content-part'; import { executeToolCall } from './execute-tool-call'; import { filterActiveTools, type ActiveToolSubset, } from './filter-active-tools'; import type { GenerateTextOnEndCallback, GenerateTextOnStartCallback, GenerateTextOnStepEndCallback, GenerateTextOnStepFinishCallback, GenerateTextOnStepStartCallback, } from './generate-text-events'; import type { GenerateTextResult } from './generate-text-result'; import { DefaultGeneratedFile } from './generated-file'; import type { OnLanguageModelCallEndCallback, OnLanguageModelCallStartCallback, } from './language-model-events'; import { text, type Output } from './output'; import type { InferCompleteOutput } from './output-utils'; import { parseToolCall } from './parse-tool-call'; import type { PrepareStepFunction } from './prepare-step'; import { convertToReasoningOutputs } from './reasoning-output'; import { resolveToolApproval } from './resolve-tool-approval'; import type { ResponseMessage } from './response-message'; import { createRestrictedTelemetryDispatcher } from './restricted-telemetry-dispatcher'; import { DefaultStepResult, type StepResult, type StepResultPerformance, } from './step-result'; import { isStepCount, isStopConditionMet, type StopCondition, } from './stop-condition'; import { sumTokenCounts } from './sum-token-counts'; import { toResponseMessages } from './to-response-messages'; import type { ToolApprovalConfiguration } from './tool-approval-configuration'; import type { ToolApprovalRequestOutput } from './tool-approval-request-output'; import type { ToolApprovalResponseOutput } from './tool-approval-response-output'; import type { TypedToolCall } from './tool-call'; import type { ToolCallRepairFunction } from './tool-call-repair-function'; import type { TypedToolError } from './tool-error'; import type { OnToolExecutionEndCallback, OnToolExecutionStartCallback, } from './tool-execution-events'; import type { ToolInputRefinement } from './tool-input-refinement'; import type { ToolOrder } from './tool-order'; import type { ToolOutput } from './tool-output'; import type { TypedToolResult } from './tool-result'; import type { ToolsContextParameter } from './tools-context-parameter'; import { maybeSignApproval } from './tool-approval-signature'; import { validateApprovedToolApprovals } from './validate-tool-approvals'; const originalGenerateId = createIdGenerator({ prefix: 'aitxt', size: 24, }); const originalGenerateCallId = createIdGenerator({ prefix: 'call', size: 24, }); export type GenerateTextInclude = { /** * Whether to retain the request body in step results. * The request body can be large when sending images or files. * * @default false */ requestBody?: boolean; /** * Whether to retain the request messages in step results. * The request messages can be large when sending images or files. * * @default false */ requestMessages?: boolean; /** * Whether to retain the response body in step results. * * @default false */ responseBody?: boolean; }; /** * Generate a text and call tools for a given prompt using a language model. * * This function does not stream the output. If you want to stream the output, use `streamText` instead. * * @param model - The language model to use. * * @param tools - Tools that are accessible to and can be called by the model. The model needs to support calling tools. * @param toolChoice - The tool choice strategy. Default: 'auto'. * @param toolOrder - Controls the order in which tools are sent to the provider. Tools not listed are appended alphabetically. * * @param system - A system message that will be part of the prompt. * @param prompt - A simple text prompt. You can either use `prompt` or `messages` but not both. * @param messages - A list of messages. You can either use `prompt` or `messages` but not both. * @param allowSystemInMessages - Whether system messages are allowed in the `prompt` or `messages` fields. Default: false. * * @param maxOutputTokens - Maximum number of tokens to generate. * @param temperature - Temperature setting. * The value is passed through to the provider. The range depends on the provider and model. * It is recommended to set either `temperature` or `topP`, but not both. * @param topP - Nucleus sampling. * The value is passed through to the provider. The range depends on the provider and model. * It is recommended to set either `temperature` or `topP`, but not both. * @param topK - Only sample from the top K options for each subsequent token. * Used to remove "long tail" low probability responses. * Recommended for advanced use cases only. You usually only need to use temperature. * @param presencePenalty - Presence penalty setting. * It affects the likelihood of the model to repeat information that is already in the prompt. * The value is passed through to the provider. The range depends on the provider and model. * @param frequencyPenalty - Frequency penalty setting. * It affects the likelihood of the model to repeatedly use the same words or phrases. * The value is passed through to the provider. The range depends on the provider and model. * @param stopSequences - Stop sequences. * If set, the model will stop generating text when one of the stop sequences is generated. * @param seed - The seed (integer) to use for random sampling. * If set and supported by the model, calls will generate deterministic results. * * @param maxRetries - Maximum number of retries. Set to 0 to disable retries. Default: 2. * @param abortSignal - An optional abort signal that can be used to cancel the call. * @param timeout - An optional timeout in milliseconds. The call will be aborted if it takes longer than the specified timeout. * @param headers - Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers. * * @param experimental_sandbox - The sandbox environment that is passed through to tool execution. * @param runtimeContext - User-defined runtime context that flows through the entire generation lifecycle. * @param experimental_refineToolInput - Optional mapping of tool names to functions that refine parsed tool inputs before tools are executed and before outputs, callbacks, and telemetry are recorded. * @param onStart - Callback invoked when generation begins, before any LLM calls. * @param experimental_onStart - Deprecated alias for `onStart`. * @param onStepStart - Callback invoked when each step begins, before the provider is called. * @param experimental_onStepStart - Deprecated alias for `onStepStart`. * Receives step number, messages (in ModelMessage format), tools, and runtimeContext. * @param onLanguageModelCallStart - Callback invoked immediately before each provider model call begins. * @param experimental_onLanguageModelCallStart - Deprecated alias for `onLanguageModelCallStart`. * @param onLanguageModelCallEnd - Callback invoked after each provider model call response is normalized and parsed. * @param experimental_onLanguageModelCallEnd - Deprecated alias for `onLanguageModelCallEnd`. * @param onToolExecutionStart - Callback invoked before each tool execution begins. * Receives tool name, call ID, input, and context. * @param experimental_onToolCallStart - Deprecated alias for `onToolExecutionStart`. * @param onToolExecutionEnd - Callback invoked after each tool execution completes. * Uses a discriminated union: check `success` to determine if `output` or `error` is present. * @param experimental_onToolCallFinish - Deprecated alias for `onToolExecutionEnd`. * @param onStepFinish - Callback that is called when each step (LLM call) is finished, including intermediate steps. * @param onEnd - Callback that is called when all steps are finished and the response is complete. * @param onFinish - Deprecated alias for `onEnd`. * * @returns * A result object that contains the generated text, the results of the tool calls, and additional information. */ export async function generateText< TOOLS extends ToolSet, RUNTIME_CONTEXT extends Context = Context, OUTPUT extends Output = Output<string, string>, >({ model: modelArg, tools, toolChoice, instructions, system, prompt, messages, allowSystemInMessages, maxRetries: maxRetriesArg, abortSignal, timeout, headers, stopWhen = isStepCount(1), experimental_sandbox: sandbox, output, toolApproval, experimental_toolApprovalSecret, experimental_telemetry, telemetry = experimental_telemetry, providerOptions, activeTools, toolOrder, prepareStep, experimental_repairToolCall, repairToolCall = experimental_repairToolCall, experimental_refineToolInput: refineToolInput, experimental_download: download, runtimeContext = {} as RUNTIME_CONTEXT, toolsContext = {} as InferToolSetContext<TOOLS>, experimental_include, include = experimental_include, _internal: { generateId = originalGenerateId, generateCallId = originalGenerateCallId, now = originalNow, } = {}, onStart, experimental_onStart, onStepStart, experimental_onStepStart, onLanguageModelCallStart, experimental_onLanguageModelCallStart, onLanguageModelCallEnd, experimental_onLanguageModelCallEnd, onToolExecutionStart, onToolExecutionEnd, experimental_onToolCallStart, experimental_onToolCallFinish, onStepEnd, onStepFinish, onFinish, onEnd = onFinish, ...settings }: LanguageModelCallOptions & RequestOptions<TOOLS> & Prompt & ToolsContextParameter<TOOLS> & { /** * The language model to use. */ model: LanguageModel; /** * The tool choice strategy. Default: 'auto'. */ toolChoice?: ToolChoice<NoInfer<TOOLS>>; /** * Condition for stopping the generation when there are tool results in the last step. * When the condition is an array, any of the conditions can be met to stop the generation. * * @default isStepCount(1) */ stopWhen?: Arrayable<StopCondition<NoInfer<TOOLS>, RUNTIME_CONTEXT>>; /** * Optional telemetry configuration. */ telemetry?: TelemetryOptions<RUNTIME_CONTEXT, NoInfer<TOOLS>>; /** * Optional telemetry configuration. * * @deprecated Use `telemetry` instead. This alias will be removed in a future major release. */ experimental_telemetry?: TelemetryOptions<RUNTIME_CONTEXT, NoInfer<TOOLS>>; /** * Additional provider-specific options. They are passed through * to the provider from the AI SDK and enable provider-specific * functionality that can be fully encapsulated in the provider. */ providerOptions?: ProviderOptions; /** * The sandbox environment that is passed through to tool execution. */ experimental_sandbox?: SandboxSession; /** * Runtime context. Treat runtime context as immutable. * If you need to mutate runtime context, update it in `prepareStep`. */ runtimeContext?: RUNTIME_CONTEXT; /** * Limits the tools that are available for the model to call without * changing the tool call and result types in the result. */ activeTools?: ActiveTools<NoInfer<TOOLS>>; /** * 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. This can improve provider-side * caching by keeping tool definitions in a stable order. */ toolOrder?: ToolOrder<NoInfer<TOOLS>>; /** * Optional specification for parsing structured outputs from the LLM response. */ output?: OUTPUT; /** * Optional tool approval configuration. * * This configuration takes precedence over tool-defined approval settings. */ toolApproval?: ToolApprovalConfiguration<TOOLS, RUNTIME_CONTEXT>; /** * Secret for HMAC-signing tool approval requests. When set, the server * signs each approval request at issuance and verifies the signature when * the approval is replayed, preventing client-forged approvals. */ experimental_toolApprovalSecret?: string | Uint8Array; /** * Custom download function to use for URLs. * * By default, files are downloaded if the model does not support the URL for the given media type. */ experimental_download?: DownloadFunction | undefined; /** * Optional function that you can use to provide different settings for a step. */ prepareStep?: PrepareStepFunction<NoInfer<TOOLS>, RUNTIME_CONTEXT>; /** * A function that attempts to repair a tool call that failed to parse. */ repairToolCall?: ToolCallRepairFunction<NoInfer<TOOLS>>; /** * A function that attempts to repair a tool call that failed to parse. * * @deprecated Use `repairToolCall` instead. */ experimental_repairToolCall?: ToolCallRepairFunction<NoInfer<TOOLS>>; /** * Optional mapping of tool names to functions that refine parsed tool inputs. * * The refined input must have the same type shape as the tool input. Refined * inputs are used for tool execution, outputs, callbacks, and telemetry. */ experimental_refineToolInput?: ToolInputRefinement<NoInfer<TOOLS>>; /** * Callback that is called when the generateText operation begins, * before any LLM calls are made. */ onStart?: GenerateTextOnStartCallback< NoInfer<TOOLS>, NoInfer<RUNTIME_CONTEXT>, NoInfer<OUTPUT> >; /** * Callback that is called when the generateText operation begins, * before any LLM calls are made. * * @deprecated Use `onStart` instead. */ experimental_onStart?: GenerateTextOnStartCallback< NoInfer<TOOLS>, NoInfer<RUNTIME_CONTEXT>, NoInfer<OUTPUT> >; /** * Callback that is called when a step (LLM call) begins, * before the provider is called. */ onStepStart?: GenerateTextOnStepStartCallback< NoInfer<TOOLS>, NoInfer<RUNTIME_CONTEXT>, NoInfer<OUTPUT> >; /** * Callback that is called when a step (LLM call) begins, * before the provider is called. * * @deprecated Use `onStepStart` instead. */ experimental_onStepStart?: GenerateTextOnStepStartCallback< NoInfer<TOOLS>, NoInfer<RUNTIME_CONTEXT>, NoInfer<OUTPUT> >; /** * Callback that is called immediately before the provider model call begins. */ onLanguageModelCallStart?: OnLanguageModelCallStartCallback; /** * Callback that is called immediately before the provider model call begins. * * @deprecated Use `onLanguageModelCallStart` instead. */ experimental_onLanguageModelCallStart?: OnLanguageModelCallStartCallback; /** * Callback that is called after the model response has been normalized and parsed, * but before any client-side tool execution begins. */ onLanguageModelCallEnd?: OnLanguageModelCallEndCallback<NoInfer<TOOLS>>; /** * Callback that is called after the model response has been normalized and parsed, * but before any client-side tool execution begins. * * @deprecated Use `onLanguageModelCallEnd` instead. */ experimental_onLanguageModelCallEnd?: OnLanguageModelCallEndCallback< NoInfer<TOOLS> >; /** * Callback that is called right before a tool's execute function runs. */ onToolExecutionStart?: OnToolExecutionStartCallback<NoInfer<TOOLS>>; /** * Callback that is called right before a tool's execute function runs. * * @deprecated Use `onToolExecutionStart` instead. */ experimental_onToolCallStart?: OnToolExecutionStartCallback<NoInfer<TOOLS>>; /** * Callback that is called right after a tool's execute function completes (or errors). */ onToolExecutionEnd?: OnToolExecutionEndCallback<NoInfer<TOOLS>>; /** * Callback that is called right after a tool's execute function completes (or errors). * * @deprecated Use `onToolExecutionEnd` instead. */ experimental_onToolCallFinish?: OnToolExecutionEndCallback<NoInfer<TOOLS>>; /** * Callback that is called when each step (LLM call) ends, including intermediate steps. */ onStepEnd?: GenerateTextOnStepEndCallback< NoInfer<TOOLS>, NoInfer<RUNTIME_CONTEXT> >; /** * Callback that is called when each step (LLM call) ends, including intermediate steps. * * @deprecated Use `onStepEnd` instead. */ onStepFinish?: GenerateTextOnStepFinishCallback< NoInfer<TOOLS>, NoInfer<RUNTIME_CONTEXT> >; /** * Callback that is called when all steps are finished and the response is complete. */ onEnd?: GenerateTextOnEndCallback<NoInfer<TOOLS>, NoInfer<RUNTIME_CONTEXT>>; /** * Callback that is called when all steps are finished and the response is complete. * * @deprecated Use `onEnd` instead. */ onFinish?: GenerateTextOnEndCallback< NoInfer<TOOLS>, NoInfer<RUNTIME_CONTEXT> >; /** * Settings for controlling what data is included in step results. * Disabling inclusion can help reduce memory usage when processing * large payloads like images. * * By default, request bodies, request messages, and response bodies are * excluded. */ include?: GenerateTextInclude; /** * Settings for controlling what data is included in step results. * * @deprecated Use `include` instead. */ experimental_include?: GenerateTextInclude; /** * Internal. For test use only. May change without notice. */ _internal?: { generateId?: IdGenerator; generateCallId?: IdGenerator; now?: () => number; }; }): Promise<GenerateTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>> { // assign default values to include: include = { requestBody: include?.requestBody ?? false, requestMessages: include?.requestMessages ?? false, responseBody: include?.responseBody ?? false, }; const model = resolveLanguageModel(modelArg); const stopConditions = asArray(stopWhen); const resolvedOnStart = onStart ?? experimental_onStart; const resolvedOnStepStart = onStepStart ?? experimental_onStepStart; const resolvedOnLanguageModelCallStart = onLanguageModelCallStart ?? experimental_onLanguageModelCallStart; const resolvedOnLanguageModelCallEnd = onLanguageModelCallEnd ?? experimental_onLanguageModelCallEnd; const resolvedOnToolExecutionStart = onToolExecutionStart ?? experimental_onToolCallStart; const resolvedOnToolExecutionEnd = onToolExecutionEnd ?? experimental_onToolCallFinish; const resolvedOnStepEnd = onStepEnd ?? onStepFinish; const totalTimeoutMs = getTotalTimeoutMs(timeout); const stepTimeoutMs = getStepTimeoutMs(timeout); const stepAbortController = stepTimeoutMs != null ? new AbortController() : undefined; const mergedAbortSignal = mergeAbortSignals( abortSignal, totalTimeoutMs, stepAbortController?.signal, ); const { maxRetries, retry } = prepareRetries({ maxRetries: maxRetriesArg, abortSignal: mergedAbortSignal, }); const callSettings = prepareLanguageModelCallOptions(settings); const headersWithUserAgent = withUserAgentSuffix( headers ?? {}, `ai/${VERSION}`, ); const initialPrompt = await standardizePrompt({ instructions, system, prompt, messages, allowSystemInMessages, } as Prompt); const callId = generateCallId(); const telemetryDispatcher = createRestrictedTelemetryDispatcher< TOOLS, RUNTIME_CONTEXT, OUTPUT >({ telemetry, includeRuntimeContext: telemetry?.includeRuntimeContext, includeToolsContext: telemetry?.includeToolsContext, }); const runInTracingChannelSpan = telemetryDispatcher.runInTracingChannelSpan ?? (async <T>({ execute }: { execute: () => PromiseLike<T> }) => await execute()); const generateTextStartEvent = { callId, operationId: 'ai.generateText', provider: model.provider, modelId: model.modelId, instructions: initialPrompt.instructions, messages: initialPrompt.messages, tools, toolChoice, activeTools, toolOrder, maxOutputTokens: callSettings.maxOutputTokens, temperature: callSettings.temperature, topP: callSettings.topP, topK: callSettings.topK, presencePenalty: callSettings.presencePenalty, frequencyPenalty: callSettings.frequencyPenalty, stopSequences: callSettings.stopSequences, seed: callSettings.seed, reasoning: callSettings.reasoning, maxRetries, timeout, headers: headersWithUserAgent, providerOptions, output, runtimeContext, toolsContext, }; const executeGenerateText = async () => { await notify({ event: generateTextStartEvent, callbacks: [resolvedOnStart, telemetryDispatcher.onStart], }); try { const initialMessages = initialPrompt.messages; const initialResponseMessages: Array<ResponseMessage> = []; const { approvedToolApprovals, deniedToolApprovals: collectedDeniedToolApprovals, } = collectToolApprovals<TOOLS>({ messages: initialMessages }); const { approvedToolApprovals: localApprovedToolApprovals, deniedToolApprovals: revalidationDeniedToolApprovals, } = await validateApprovedToolApprovals<TOOLS, RUNTIME_CONTEXT>({ approvedToolApprovals: approvedToolApprovals.filter( toolApproval => !toolApproval.toolCall.providerExecuted, ), tools, toolApproval, messages: initialMessages, toolsContext, runtimeContext, toolApprovalSecret: experimental_toolApprovalSecret, }); const deniedToolApprovals = [ ...collectedDeniedToolApprovals, ...revalidationDeniedToolApprovals, ]; if ( deniedToolApprovals.length > 0 || localApprovedToolApprovals.length > 0 ) { const toolResults = await executeTools({ toolCalls: localApprovedToolApprovals.map( toolApproval => toolApproval.toolCall, ), tools: tools as TOOLS, callId, messages: initialMessages, abortSignal: mergedAbortSignal, timeout, experimental_sandbox: sandbox, toolsContext, onToolExecutionStart: event => notify({ event, callbacks: [ resolvedOnToolExecutionStart, telemetryDispatcher.onToolExecutionStart, ], }), onToolExecutionEnd: event => notify({ event, callbacks: [ resolvedOnToolExecutionEnd, telemetryDispatcher.onToolExecutionEnd, ], }), executeToolInTelemetryContext: telemetryDispatcher.executeTool, runInTracingChannelSpan, }); const toolContent: Array<any> = []; // add regular tool results for approved tool calls: for (const result of toolResults) { const output = result.output; const modelOutput = await createToolModelOutput({ toolCallId: output.toolCallId, input: output.input, tool: getOwn(tools, output.toolName), output: output.type === 'tool-result' ? output.output : output.error, errorMode: output.type === 'tool-error' ? 'text' : 'none', }); toolContent.push({ type: 'tool-result' as const, toolCallId: output.toolCallId, toolName: output.toolName, output: modelOutput, }); } // add execution denied tool results for all denied tool approvals: for (const toolApproval of deniedToolApprovals) { toolContent.push({ type: 'tool-result' as const, toolCallId: toolApproval.toolCall.toolCallId, toolName: toolApproval.toolCall.toolName, output: { type: 'execution-denied' as const, reason: toolApproval.approvalResponse.reason, // For provider-executed tools, include approvalId so provider can correlate ...(toolApproval.toolCall.providerExecuted && { providerOptions: { openai: { approvalId: toolApproval.approvalResponse.approvalId, }, }, }), }, }); } initialResponseMessages.push({ role: 'tool', content: toolContent, }); } const callSettings = prepareLanguageModelCallOptions(settings); let currentModelResponse: LanguageModelV4GenerateResult & { response: { id: string; timestamp: Date; modelId: string }; }; let clientToolCalls: Array<TypedToolCall<TOOLS>> = []; let clientToolOutputs: Array<ToolOutput<TOOLS>> = []; let toolApprovalResponses: Array<ToolApprovalResponseOutput<TOOLS>> = []; let deniedToolApprovalResponses: Array< ToolApprovalResponseOutput<TOOLS> > = []; const steps: GenerateTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>['steps'] = []; let instructionsForNextStep = initialPrompt.instructions; let messagesForNextStep = [ ...initialMessages, ...initialResponseMessages, ]; // Track provider-executed tool calls that support deferred results // (e.g., code_execution in programmatic tool calling scenarios). // These tools may not return their results in the same turn as their call. const pendingDeferredToolCalls = new Map<string, { toolName: string }>(); do { // Set up step timeout if configured const stepTimeoutId = setAbortTimeout({ abortController: stepAbortController, label: 'Step', timeoutMs: stepTimeoutMs, }); const stepNumber = steps.length; try { await runInTracingChannelSpan({ type: 'step', event: { callId, stepNumber }, execute: async () => { const accumulatedResponseMessages = [ ...initialResponseMessages, ...steps.flatMap(step => step.response.messages), ]; const stepInputMessages = messagesForNextStep; const prepareStepResult = await prepareStep?.({ model, steps, stepNumber: steps.length, instructions: instructionsForNextStep, initialInstructions: initialPrompt.instructions, messages: stepInputMessages, initialMessages, responseMessages: accumulatedResponseMessages, runtimeContext, toolsContext, experimental_sandbox: sandbox, }); const stepSandbox = prepareStepResult?.experimental_sandbox ?? sandbox; const stepModel = resolveLanguageModel( prepareStepResult?.model ?? model, ); const stepInstructions = prepareStepResult?.instructions ?? prepareStepResult?.system ?? instructionsForNextStep; const promptMessages = await convertToLanguageModelPrompt({ prompt: { instructions: stepInstructions, messages: prepareStepResult?.messages ?? stepInputMessages, }, supportedUrls: await stepModel.supportedUrls, download, provider: stepModel.provider.split('.')[0], }); runtimeContext = prepareStepResult?.runtimeContext ?? runtimeContext; toolsContext = prepareStepResult?.toolsContext ?? toolsContext; const stepActiveTools = filterActiveTools({ tools, activeTools: prepareStepResult?.activeTools ?? activeTools, }); const stepToolOrder = prepareStepResult?.toolOrder ?? toolOrder; const stepTools = await prepareTools({ tools: stepActiveTools, toolOrder: stepToolOrder as ToolOrder< ActiveToolSubset<TOOLS, ActiveTools<NoInfer<TOOLS>>> >, // active tools context is a subset of the tools context, so we can cast to the unknown type toolsContext: toolsContext as unknown as InferToolSetContext< ActiveToolSubset<TOOLS, ActiveTools<NoInfer<TOOLS>>> >, experimental_sandbox: stepSandbox, }); const stepToolChoice = prepareToolChoice({ toolChoice: prepareStepResult?.toolChoice ?? toolChoice, }); const stepMessages = prepareStepResult?.messages ?? stepInputMessages; const stepProviderOptions = mergeObjects( providerOptions, prepareStepResult?.providerOptions, ); await notify({ event: { callId, provider: stepModel.provider, modelId: stepModel.modelId, stepNumber, instructions: stepInstructions, messages: stepMessages, tools, toolChoice: prepareStepResult?.toolChoice ?? toolChoice, activeTools: prepareStepResult?.activeTools ?? activeTools, toolOrder: stepToolOrder, steps: [...steps], providerOptions: stepProviderOptions, output, runtimeContext, promptMessages, stepTools, stepToolChoice, toolsContext, }, callbacks: [ resolvedOnStepStart, telemetryDispatcher.onStepStart, ], }); const languageModelCallContext = { provider: stepModel.provider, modelId: stepModel.modelId, instructions: stepInstructions, messages: stepMessages, tools: stepTools, ...callSettings, }; const languageModelCallStartEvent = { callId, ...languageModelCallContext, }; const stepStartTimestampMs = now(); await notify({ event: languageModelCallStartEvent, callbacks: [ resolvedOnLanguageModelCallStart, telemetryDispatcher.onLanguageModelCallStart as | undefined | OnLanguageModelCallStartCallback, ], }); const executeLanguageModelCallInTelemetryContext = telemetryDispatcher.executeLanguageModelCall ?? (async <T>({ execute }: { execute: () => PromiseLike<T> }) => await execute()); currentModelResponse = await retry(async () => { const result = await executeLanguageModelCallInTelemetryContext( { ...languageModelCallStartEvent, execute: async () => await stepModel.doGenerate({ ...callSettings, tools: stepTools, toolChoice: stepToolChoice, responseFormat: await output?.responseFormat, prompt: promptMessages, providerOptions: stepProviderOptions, abortSignal: mergedAbortSignal, headers: headersWithUserAgent, }), }, ); const responseData = { id: result.response?.id ?? generateId(), timestamp: result.response?.timestamp ?? new Date(), modelId: result.response?.modelId ?? stepModel.modelId, headers: result.response?.headers, body: result.response?.body, }; return { ...result, response: responseData }; }); const responseTimeMs = now() - stepStartTimestampMs; const stepUsage = asLanguageModelUsage( currentModelResponse.usage, ); // parse tool calls: const stepToolCalls: TypedToolCall<TOOLS>[] = await Promise.all( currentModelResponse.content .filter( (part): part is LanguageModelV4ToolCall => part.type === 'tool-call', ) .map(toolCall => parseToolCall({ toolCall, tools, repairToolCall, refineToolInput, instructions: stepInstructions, messages: stepMessages, }), ), ); const toolApprovalRequests: Record< string, ToolApprovalRequestOutput<TOOLS> > = {}; const stepToolApprovalResponses: Record< string, ToolApprovalResponseOutput<TOOLS> > = {}; const blockedToolCallIds = new Set<string>(); const modelCallContent = asContent({ content: currentModelResponse.content, toolCalls: stepToolCalls, toolOutputs: [], toolApprovalRequests: [], toolApprovalResponses: [], tools, }); await notify({ event: { callId, provider: stepModel.provider, modelId: stepModel.modelId, finishReason: currentModelResponse.finishReason.unified, usage: stepUsage, content: modelCallContent, responseId: currentModelResponse.response.id, performance: { responseTimeMs, effectiveOutputTokensPerSecond: calculateTokensPerSecond({ tokens: stepUsage.outputTokens, durationMs: responseTimeMs, }), outputTokensPerSecond: undefined, inputTokensPerSecond: undefined, effectiveTotalTokensPerSecond: calculateTokensPerSecond({ tokens: sumTokenCounts( stepUsage.inputTokens, stepUsage.outputTokens, ), durationMs: responseTimeMs, }), timeToFirstOutputMs: undefined, }, }, callbacks: [ resolvedOnLanguageModelCallEnd, telemetryDispatcher.onLanguageModelCallEnd as | undefined | OnLanguageModelCallEndCallback<TOOLS>, ], }); // notify the tools that the tool calls are available: for (const toolCall of stepToolCalls) { if (toolCall.invalid) { continue; // ignore invalid tool calls } const tool = getOwn(tools, toolCall.toolName); if (tool == null) { // ignore tool calls for tools that are not available, // e.g. provider-executed dynamic tools continue; } if (tool?.onInputAvailable != null) { await tool.onInputAvailable({ input: toolCall.input, toolCallId: toolCall.toolCallId, messages: stepMessages, abortSignal: mergedAbortSignal, context: runtimeContext, }); } const toolApprovalStatus = await resolveToolApproval({ tools, toolApproval, toolCall, messages: stepMessages, toolsContext, runtimeContext, }); // Tools that don't require approval ('not-applicable') must not // consume an approval id, so that id generation stays stable for // callers that rely on deterministic id sequences. if (toolApprovalStatus.type === 'not-applicable') { continue; } const approvalId = generateId(); const signature = await maybeSignApproval({ secret: experimental_toolApprovalSecret, approvalId, toolCallId: toolCall.toolCallId, toolName: toolCall.toolName, input: toolCall.input, }); switch (toolApprovalStatus.type) { case 'user-approval': { toolApprovalRequests[toolCall.toolCallId] = { type: 'tool-approval-request', approvalId, toolCall, ...(signature != null ? { signature } : {}), }; blockedToolCallIds.add(toolCall.toolCallId); break; } case 'approved': { toolApprovalRequests[toolCall.toolCallId] = { type: 'tool-approval-request', approvalId, toolCall, isAutomatic: true, ...(signature != null ? { signature } : {}), }; stepToolApprovalResponses[toolCall.toolCallId] = { type: 'tool-approval-response', approvalId, toolCall, approved: true, reason: toolApprovalStatus.reason, providerExecuted: toolCall.providerExecuted, }; break; } case 'denied': { toolApprovalRequests[toolCall.toolCallId] = { type: 'tool-approval-request', approvalId, toolCall, isAutomatic: true, ...(signature != null ? { signature } : {}), }; stepToolApprovalResponses[toolCall.toolCallId] = { type: 'tool-approval-response', approvalId, toolCall, approved: false, reason: toolApprovalStatus.reason, providerExecuted: toolCall.providerExecuted, }; blockedToolCallIds.add(toolCall.toolCallId); break; } } } // insert error tool outputs for invalid tool calls: // TODO AI SDK 6: invalid inputs should not require output parts const invalidToolCalls = stepToolCalls.filter( toolCall => toolCall.invalid && toolCall.dynamic, ); clientToolOutputs = []; for (const toolCall of invalidToolCalls) { clientToolOutputs.push({ type: 'tool-error', toolCallId: toolCall.toolCallId, toolName: toolCall.toolName, input: toolCall.input, error: getErrorMessage(toolCall.error!), dynamic: true, }); } // execute client tool calls: clientToolCalls = stepToolCalls.filter( toolCall => !toolCall.providerExecuted, ); toolApprovalResponses = Object.values(stepToolApprovalResponses); deniedToolApprovalResponses = toolApprovalResponses.filter( toolApprovalResponse => toolApprovalResponse.approved === false, ); const toolExecutionMs: Record<string, number> = {}; if (tools != null) { const toolExecutionResults = await executeTools({ toolCalls: clientToolCalls.filter( toolCall => !toolCall.invalid && !blockedToolCallIds.has(toolCall.toolCallId), ), tools, callId, messages: stepMessages, abortSignal: mergedAbortSignal, timeout, experimental_sandbox: stepSandbox, toolsContext, onToolExecutionStart: event => notify({ event, callbacks: [ resolvedOnToolExecutionStart, telemetryDispatcher.onToolExecutionStart, ], }), onToolExecutionEnd: event => notify({ event, callbacks: [ resolvedOnToolExecutionEnd, telemetryDispatcher.onToolExecutionEnd, ], }), executeToolInTelemetryContext: telemetryDispatcher.executeTool, runInTracingChannelSpan, }); for (const result of toolExecutionResults) { toolExecutionMs[result.output.toolCallId] = result.toolExecutionMs; clientToolOutputs.push(result.output); } } const stepTimeMs = now() - stepStartTimestampMs; const stepPerformance: StepResultPerformance = { effectiveOutputTokensPerSecond: calculateTokensPerSecond({ tokens: stepUsage.outputTokens, durationMs: responseTimeMs, }), outputTokensPerSecond: undefined, inputTokensPerSecond: undefined, effectiveTotalTokensPerSecond: calculateTokensPerSecond({ tokens: sumTokenCounts( stepUsage.inputTokens, stepUsage.outputTokens, ), durationMs: responseTimeMs, }), stepTimeMs, responseTimeMs, toolExecutionMs, timeToFirstOutputMs: undefined, }; // Track provider-executed tool calls that support deferred results. // In programmatic tool calling, a server tool (e.g., code_execution) may // trigger a client tool, and the server tool's result is deferred until // the client tool's result is sent back. for (const toolCall of stepToolCalls) { if (!toolCall.providerExecuted) continue; const tool = getOwn(tools, toolCall.toolName); if (tool?.type === 'provider' && tool.supportsDeferredResults) { // Check if this tool call already has a result in the current response const hasResultInResponse = currentModelResponse.content.some( part => part.type === 'tool-result' && part.toolCallId === toolCall.toolCallId, ); if (!hasResultInResponse) { pendingDeferredToolCalls.set(toolCall.toolCallId, { toolName: toolCall.toolName, }); } } } // Mark deferred tool calls as resolved when we receive their results for (const part of currentModelResponse.content) { if (part.type === 'tool-result') { pendingDeferredToolCalls.delete(part.toolCallId); } } // content: const stepContent = asContent({ content: currentModelResponse.content, toolCalls: stepToolCalls, toolOutputs: clientToolOutputs, toolApprovalRequests: Object.values(toolApprovalRequests), toolApprovalResponses, tools, }); const stepResponseMessages = await toResponseMessages({ content: stepContent, tools, }); // Add step information (after response messages are updated): // Conditionally include request.body and response.body based on include settings. // Large payloads (e.g., base64-encoded images) can cause memory issues. const stepRequest: LanguageModelRequestMetadata = { ...currentModelResponse.request, body: include.requestBody ? currentModelResponse.request?.body : undefined, messages: include.requestMessages ? cloneModelMessages(stepMessages) : undefined, }; const stepResponse = { ...currentModelResponse.response, // deep clone msgs to avoid mutating step results in multi-step: messages: cloneModelMessages(stepResponseMessages), // Conditionally include response body: body: include.responseBody ? currentModelResponse.response?.body : undefined, }; const currentStepResult: StepResult<TOOLS, RUNTIME_CONTEXT> = new DefaultStepResult({ callId, stepNumber, provider: stepModel.provider, modelId: stepModel.modelId, runtimeContext, content: stepContent, finishReason: currentModelResponse.finishReason.unified, rawFinishReason: currentModelResponse.finishReason.raw, usage: stepUsage, performance: stepPerformance, warnings: currentModelResponse.warnings, providerMetadata: currentModelResponse.providerMetadata, request: stepRequest, response: stepResponse,