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.

210 lines (189 loc) • 7.06 kB
import { asArray } from '@ai-sdk/provider-utils'; import type { Callback } from '../util/callback'; import { mergeCallbacks } from '../util/merge-callbacks'; import type { InferTelemetryEvent, Telemetry, TelemetryDispatcher, } from './telemetry'; import { openTelemetryChannelSpanContext, runWithTracingChannelSpan, } from './tracing-channel-publisher'; import { getGlobalTelemetryIntegrations } from './telemetry-registry'; import type { TelemetryOptions } from './telemetry-options'; /** * The subset of `TelemetryDispatcher` keys whose values are Callback callbacks. * This excludes dispatcher-only helpers and non-Callback properties such as * `executeLanguageModelCall` and `executeTool`. */ type TelemetryCallbackKey = Exclude< keyof { [K in keyof TelemetryDispatcher as TelemetryDispatcher[K] extends | Callback<any> | undefined ? K : never]: true; }, 'runInTracingChannelSpan' | 'startTracingChannelContext' >; /** * Resolves the public event type accepted by a telemetry callback key. */ type TelemetryEvent<K extends TelemetryCallbackKey> = TelemetryDispatcher[K] extends Callback<infer EVENT> | undefined ? EVENT : never; function augmentEvent<EVENT>( event: EVENT, telemetry: Pick< TelemetryOptions, 'recordInputs' | 'recordOutputs' | 'functionId' >, ): InferTelemetryEvent<EVENT> { return Object.assign( Object.create(Object.getPrototypeOf(event)), event, telemetry, ); } /** * Creates a telemetry dispatcher that sends telemetry events * to the resolved set of integrations. * * When per-call integrations are provided, they take precedence over the globally * registered integrations for that call. When no per-call integrations are * provided, the globally registered integrations are used. * * @param args.telemetry - Optional per-call telemetry settings and integrations. * * @returns A telemetry dispatcher that fans out lifecycle events to the * resolved set of integrations. */ export function createTelemetryDispatcher({ telemetry, }: { telemetry?: TelemetryOptions; // operationId: string; }): TelemetryDispatcher { // When telemetry is explicitly disabled, return a dispatcher // that performs no work and lets tool execution pass through unwrapped. if (telemetry?.isEnabled === false) { return {}; } const localIntegrations = telemetry?.integrations; const integrations: Array<Telemetry> = localIntegrations != null ? asArray(localIntegrations) : getGlobalTelemetryIntegrations(); const telemetryMetadata = { recordInputs: telemetry?.recordInputs, recordOutputs: telemetry?.recordOutputs, functionId: telemetry?.functionId, }; const mergeTelemetryCallback = <KEY extends TelemetryCallbackKey>( key: KEY, ): Callback<TelemetryEvent<KEY>> => { const integrationCallbacks = ( integrations .map(integration => integration[key]?.bind(integration)) .filter(Boolean) as Array< Callback<InferTelemetryEvent<TelemetryEvent<KEY>>> > ).map( callback => ((event: TelemetryEvent<KEY>) => callback(augmentEvent(event, telemetryMetadata))) as Callback< TelemetryEvent<KEY> >, ); const mergedIntegrationCallback = mergeCallbacks(...integrationCallbacks); return async (event: TelemetryEvent<KEY>) => { await mergedIntegrationCallback(event); }; }; const executeLanguageModelCallWrappers = integrations .map(integration => integration.executeLanguageModelCall?.bind(integration)) .filter(Boolean) as Array< NonNullable<Telemetry['executeLanguageModelCall']> >; const executeToolWrappers = integrations .map(integration => integration.executeTool?.bind(integration)) .filter(Boolean) as Array<NonNullable<Telemetry['executeTool']>>; return { runInTracingChannelSpan: async ({ type, event, execute }) => await runWithTracingChannelSpan( { type, event: augmentEvent(event, telemetryMetadata), }, execute, ), startTracingChannelContext: ({ type, event, completion }) => openTelemetryChannelSpanContext({ message: { type, event: augmentEvent(event, telemetryMetadata), }, completion, }), onStart: mergeTelemetryCallback('onStart'), onStepStart: mergeTelemetryCallback('onStepStart'), onLanguageModelCallStart: mergeTelemetryCallback( 'onLanguageModelCallStart', ), onLanguageModelCallEnd: mergeTelemetryCallback('onLanguageModelCallEnd'), onToolExecutionStart: mergeTelemetryCallback('onToolExecutionStart'), onToolExecutionEnd: mergeTelemetryCallback('onToolExecutionEnd'), // Fan out step-end events to both the new `onStepEnd` callback and the // deprecated `onStepFinish` callback so integrations that still implement // only `onStepFinish` keep receiving step-end events during the deprecation // window. onStepEnd: mergeCallbacks( mergeTelemetryCallback('onStepEnd'), mergeTelemetryCallback('onStepFinish'), ), onObjectStepStart: mergeTelemetryCallback('onObjectStepStart'), onObjectStepEnd: mergeTelemetryCallback('onObjectStepEnd'), onEmbedStart: mergeTelemetryCallback('onEmbedStart'), onEmbedEnd: mergeTelemetryCallback('onEmbedEnd'), onRerankStart: mergeTelemetryCallback('onRerankStart'), onRerankEnd: mergeTelemetryCallback('onRerankEnd'), onEnd: mergeTelemetryCallback('onEnd'), onAbort: mergeTelemetryCallback('onAbort'), onError: mergeTelemetryCallback('onError'), /** * Runs provider calls inside integration-specific context so * auto-instrumented provider requests can be associated with model work. */ executeLanguageModelCall: async ({ execute, ...event }) => { const augmentedEvent = augmentEvent(event, telemetryMetadata); let wrappedExecute = execute; for (const executeWrapper of executeLanguageModelCallWrappers) { const innerExecute = wrappedExecute; wrappedExecute = () => executeWrapper({ ...augmentedEvent, execute: innerExecute }); } return await runWithTracingChannelSpan( { type: 'languageModelCall', event: augmentedEvent }, wrappedExecute, ); }, /** * Composes all `executeTool` wrappers around the original tool execution. * Each wrapper receives an `execute` function that calls the next wrapper in * the chain, so integrations can establish nested telemetry context before * delegating to the underlying tool. */ executeTool: async ({ execute, ...event }) => { const augmentedEvent = augmentEvent(event, telemetryMetadata); let wrappedExecute = execute; for (const executeWrapper of executeToolWrappers) { const innerExecute = wrappedExecute; wrappedExecute = () => executeWrapper({ ...augmentedEvent, execute: innerExecute }); } return await wrappedExecute(); }, }; }