Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,14 @@ Sentry.init({
- (Vercel AI) The internal JSON-stringify workaround for array span attributes was removed.
- AI integrations are no longer available in the browser SDK. They remain available in the server-side SDKs.
- The AI instrumentation code moved out of `@sentry/core` into `@sentry/server-utils`. If you imported any AI helper **directly from `@sentry/core`**, import it from `@sentry/server-utils` instead (or keep importing it from your platform SDK, e.g. `@sentry/node`, if it re-exported that helper before — platform SDK availability is unchanged from v10). Affected helpers: `instrumentOpenAiClient`, `instrumentAnthropicAiClient`, `instrumentGoogleGenAIClient`, `instrumentWorkersAiClient`, `createLangChainCallbackHandler`, `instrumentLangChainEmbeddings`, `instrumentStateGraph`, `instrumentStateGraphCompile`, `instrumentCreateReactAgent`, `addVercelAiProcessors`.
- The `VercelAiOptions` type exported from `@sentry/server-utils` was removed. All AI integrations now share a single `GenAiOptions` type (`{ recordInputs?, recordOutputs? }`). Import `GenAiOptions` instead:

```js
// Before (v10)
import type { VercelAiOptions } from '@sentry/server-utils';
// After (v11)
import type { GenAiOptions } from '@sentry/server-utils';
```
- The following low-level AI exports are no longer part of the public API (they were provider-instrumentation internals exported from `@sentry/core`):
- Attribute/stream/util helpers: `extractOpenAiRequestAttributes`, `addOpenAiRequestAttributes`, `addOpenAiResponseAttributes`, `extractOpenAiRequestParameters`, `instrumentOpenAiStream`, `extractAnthropicRequestAttributes`, `addAnthropicRequestAttributes`, `addAnthropicResponseAttributes`, `instrumentAsyncIterableStream`, `instrumentMessageStream`, `extractGoogleGenAIRequestAttributes`, `addGoogleGenAIRequestAttributes`, `addGoogleGenAIResponseAttributes`, `instrumentGoogleGenAIStream`, `getProviderMetadataAttributes`, `getTruncatedJsonString`, `shouldEnableTruncation`, `resolveAIRecordingOptions`, `wrapToolsWithSpans`, `extractLLMFromParams`, `extractAgentNameFromParams`, `instrumentCompiledGraphInvoke`.
- Integration-name constants: `OPENAI_INTEGRATION_NAME`, `ANTHROPIC_AI_INTEGRATION_NAME`, `GOOGLE_GENAI_INTEGRATION_NAME`, `LANGCHAIN_INTEGRATION_NAME`, `LANGGRAPH_INTEGRATION_NAME`.
Expand Down
4 changes: 2 additions & 2 deletions packages/cloudflare/src/integrations/tracing/vercelai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@

import type { IntegrationFn } from '@sentry/core';
import { defineIntegration, extendIntegration } from '@sentry/core';
import { addVercelAiProcessors, vercelAiIntegration, type VercelAiOptions } from '@sentry/server-utils';
import { addVercelAiProcessors, vercelAiIntegration, type GenAiOptions } from '@sentry/server-utils';

const _vercelAIIntegration = ((options: VercelAiOptions = {}) => {
const _vercelAIIntegration = ((options: GenAiOptions = {}) => {
const inner = vercelAiIntegration(options);

return extendIntegration(inner, {
Expand Down
4 changes: 2 additions & 2 deletions packages/deno/src/integrations/tracing/vercelai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { defineIntegration, extendIntegration } from '@sentry/core';
import {
addVercelAiProcessors,
vercelAiIntegration as serverUtilsVercelAiIntegration,
type VercelAiOptions,
type GenAiOptions,
} from '@sentry/server-utils';

const _vercelAIIntegration = ((options: VercelAiOptions = {}) => {
const _vercelAIIntegration = ((options: GenAiOptions = {}) => {
const inner = serverUtilsVercelAiIntegration(options);

return extendIntegration(inner, {
Expand Down
11 changes: 6 additions & 5 deletions packages/server-utils/src/ai/anthropic-ai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '@sentry/conventions/attributes';
import { GEN_AI_REQUEST_STREAM_ATTRIBUTE } from '../core/gen-ai-attributes';
import type { InstrumentedMethodEntry } from '../core/utils';
import type { GenAiOptions } from '../core/utils';
import {
getGenAiSpanOp,
resolveAIRecordingOptions,
Expand All @@ -33,7 +34,7 @@ import {
} from '../core/utils';
import { ANTHROPIC_METHOD_REGISTRY } from './constants';
import { instrumentAsyncIterableStream, instrumentMessageStream } from './streaming';
import type { AnthropicAiOptions, AnthropicAiResponse, AnthropicAiStreamingEvent, ContentBlock } from './types';
import type { AnthropicAiResponse, AnthropicAiStreamingEvent, ContentBlock } from './types';
import { handleResponseError, messagesFromParams, setMessagesAttribute } from './utils';

// Set only while a streaming helper (e.g. `messages.stream()`) synchronously delegates to the
Expand Down Expand Up @@ -200,7 +201,7 @@ function handleStreamingRequest<T extends unknown[], R>(
operationName: string,
methodPath: string,
params: Record<string, unknown> | undefined,
options: AnthropicAiOptions,
options: GenAiOptions,
isStreamRequested: boolean,
isStreamingMethod: boolean,
): R | Promise<R> {
Expand Down Expand Up @@ -267,7 +268,7 @@ function instrumentMethod<T extends unknown[], R>(
methodPath: string,
instrumentedMethod: InstrumentedMethodEntry,
context: unknown,
options: AnthropicAiOptions,
options: GenAiOptions,
): (...args: T) => R | Promise<R> {
return new Proxy(originalMethod, {
apply(target, thisArg, args: T): R | Promise<R> {
Expand Down Expand Up @@ -360,7 +361,7 @@ function instrumentMethod<T extends unknown[], R>(
* `instanceof` checks behave exactly as on an uninstrumented client, and non-instrumented
* methods are left untouched.
*/
function instrumentClientInPlace<T extends object>(client: T, options: AnthropicAiOptions): T {
function instrumentClientInPlace<T extends object>(client: T, options: GenAiOptions): T {
for (const methodPath of Object.keys(ANTHROPIC_METHOD_REGISTRY) as Array<keyof typeof ANTHROPIC_METHOD_REGISTRY>) {
const segments = methodPath.split('.');
const methodName = segments.pop() as string;
Expand Down Expand Up @@ -402,6 +403,6 @@ function instrumentClientInPlace<T extends object>(client: T, options: Anthropic
* @param options - Optional configuration for recording inputs and outputs
* @returns The instrumented client with the same type as the input
*/
export function instrumentAnthropicAiClient<T extends object>(anthropicAiClient: T, options?: AnthropicAiOptions): T {
export function instrumentAnthropicAiClient<T extends object>(anthropicAiClient: T, options?: GenAiOptions): T {
return instrumentClientInPlace(anthropicAiClient, resolveAIRecordingOptions(options));
}
14 changes: 2 additions & 12 deletions packages/server-utils/src/ai/anthropic-ai/types.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
import type { GenAiOptions } from '../core/utils';
import type { ANTHROPIC_METHOD_REGISTRY } from './constants';

export interface AnthropicAiOptions {
/**
* Enable or disable input recording.
*/
recordInputs?: boolean;
/**
* Enable or disable output recording.
*/
recordOutputs?: boolean;
}

export type Message = {
role: 'user' | 'assistant';
content: string | unknown[];
Expand Down Expand Up @@ -81,7 +71,7 @@ export interface AnthropicAiClient {
*/
export interface AnthropicAiIntegration {
name: string;
options: AnthropicAiOptions;
options: GenAiOptions;
}

/**
Expand Down
14 changes: 11 additions & 3 deletions packages/server-utils/src/ai/core/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@ import {
} from '@sentry/conventions/attributes';
import { GENERAL_FUNCTION_SPAN_OP } from '@sentry/conventions/op';

export interface AIRecordingOptions {
export interface GenAiOptions {
/**
* Record input messages/prompts on gen_ai spans. Defaults to the global
* `dataCollection.genAI.inputs` setting; an explicit value here takes precedence.
*/
recordInputs?: boolean;
/**
* Record output text/responses on gen_ai spans. Defaults to the global
* `dataCollection.genAI.outputs` setting; an explicit value here takes precedence.
*/
recordOutputs?: boolean;
}

Expand Down Expand Up @@ -60,13 +68,13 @@ export function getGenAiSpanOp(operationName: string): string {
* Resolves AI recording options by falling back to the client's `dataCollection.genAI` settings.
* Precedence: explicit option > dataCollection.genAI > true (genAI data collected by default)
*/
export function resolveAIRecordingOptions<T extends AIRecordingOptions>(options?: T): T & Required<AIRecordingOptions> {
export function resolveAIRecordingOptions<T extends GenAiOptions>(options?: T): T & Required<GenAiOptions> {
const genAI = getClient()?.getDataCollectionOptions().genAI;
return {
...options,
recordInputs: options?.recordInputs ?? genAI?.inputs ?? true,
recordOutputs: options?.recordOutputs ?? genAI?.outputs ?? true,
} as T & Required<AIRecordingOptions>;
} as T & Required<GenAiOptions>;
}

/**
Expand Down
9 changes: 5 additions & 4 deletions packages/server-utils/src/ai/google-genai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ import {
GEN_AI_USAGE_TOTAL_TOKENS,
} from '@sentry/conventions/attributes';
import type { InstrumentedMethodEntry } from '../core/utils';
import type { GenAiOptions } from '../core/utils';
import { buildMethodPath, extractSystemInstructions, getGenAiSpanOp, resolveAIRecordingOptions } from '../core/utils';
import { GOOGLE_GENAI_METHOD_REGISTRY, GOOGLE_GENAI_SYSTEM_NAME } from './constants';
import { instrumentStream } from './streaming';
import type { Candidate, ContentPart, GoogleGenAIOptions, GoogleGenAIResponse } from './types';
import type { Candidate, ContentPart, GoogleGenAIResponse } from './types';
import type { ContentListUnion, Message, PartListUnion } from './utils';
import { contentUnionToMessages } from './utils';

Expand Down Expand Up @@ -260,7 +261,7 @@ function instrumentMethod<T extends unknown[], R>(
methodPath: string,
instrumentedMethod: InstrumentedMethodEntry,
context: unknown,
options: GoogleGenAIOptions,
options: GenAiOptions,
): (...args: T) => R | Promise<R> {
const isEmbeddings = instrumentedMethod.operation === 'embeddings';

Expand Down Expand Up @@ -339,7 +340,7 @@ function instrumentMethod<T extends unknown[], R>(
* Create a deep proxy for Google GenAI client instrumentation
* Recursively instruments methods and handles special cases like chats.create
*/
function createDeepProxy<T extends object>(target: T, currentPath = '', options: GoogleGenAIOptions): T {
function createDeepProxy<T extends object>(target: T, currentPath = '', options: GenAiOptions): T {
return new Proxy(target, {
get: (t, prop, receiver) => {
const value = Reflect.get(t, prop, receiver);
Expand Down Expand Up @@ -406,6 +407,6 @@ function createDeepProxy<T extends object>(target: T, currentPath = '', options:
* const response = await chat.sendMessage({ message: 'Hello' });
* ```
*/
export function instrumentGoogleGenAIClient<T extends object>(client: T, options?: GoogleGenAIOptions): T {
export function instrumentGoogleGenAIClient<T extends object>(client: T, options?: GenAiOptions): T {
return createDeepProxy(client, '', resolveAIRecordingOptions(options));
}
11 changes: 0 additions & 11 deletions packages/server-utils/src/ai/google-genai/types.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
import type { GOOGLE_GENAI_METHOD_REGISTRY } from './constants';

export interface GoogleGenAIOptions {
/**
* Enable or disable input recording.
*/
recordInputs?: boolean;
/**
* Enable or disable output recording.
*/
recordOutputs?: boolean;
}

/**
* Google GenAI Content Part
* @see https://ai.google.dev/api/rest/v1/Content#Part
Expand Down
8 changes: 4 additions & 4 deletions packages/server-utils/src/ai/langchain/embeddings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import {
GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE,
GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE,
} from '../core/gen-ai-attributes';
import type { GenAiOptions } from '../core/utils';
import { resolveAIRecordingOptions } from '../core/utils';
import { LANGCHAIN_ORIGIN } from './constants';
import type { LangChainOptions } from './types';

/**
* Infers the AI provider system name from the embedding class instance.
Expand Down Expand Up @@ -70,7 +70,7 @@ function extractEmbeddingAttributes(instance: unknown): Record<string, unknown>
export function _INTERNAL_getLangChainEmbeddingsSpanOptions(
instance: unknown,
input: unknown,
options: LangChainOptions = {},
options: GenAiOptions = {},
): { name: string; op: string; attributes: Record<string, SpanAttributeValue> } {
const { recordInputs } = resolveAIRecordingOptions(options);
const attributes = extractEmbeddingAttributes(instance);
Expand All @@ -94,7 +94,7 @@ export function _INTERNAL_getLangChainEmbeddingsSpanOptions(
*/
export function instrumentEmbeddingMethod(
originalMethod: (...args: unknown[]) => Promise<unknown>,
options: LangChainOptions = {},
options: GenAiOptions = {},
): (...args: unknown[]) => Promise<unknown> {
return new Proxy(originalMethod, {
apply(target, thisArg, args: unknown[]): Promise<unknown> {
Expand Down Expand Up @@ -128,7 +128,7 @@ export function instrumentEmbeddingMethod(
* await embeddings.embedDocuments(['doc1', 'doc2']);
* ```
*/
export function instrumentLangChainEmbeddings<T extends object>(instance: T, options?: LangChainOptions): T {
export function instrumentLangChainEmbeddings<T extends object>(instance: T, options?: GenAiOptions): T {
const embeddingsInstance = instance as Record<string, unknown>;

if (typeof embeddingsInstance.embedQuery === 'function') {
Expand Down
11 changes: 3 additions & 8 deletions packages/server-utils/src/ai/langchain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,10 @@ import {
GEN_AI_TOOL_DEFINITIONS,
GEN_AI_TOOL_NAME,
} from '@sentry/conventions/attributes';
import type { GenAiOptions } from '../core/utils';
import { resolveAIRecordingOptions } from '../core/utils';
import { LANGCHAIN_ORIGIN } from './constants';
import type {
LangChainCallbackHandler,
LangChainLLMResult,
LangChainMessage,
LangChainOptions,
LangChainSerialized,
} from './types';
import type { LangChainCallbackHandler, LangChainLLMResult, LangChainMessage, LangChainSerialized } from './types';
import {
extractChatModelRequestAttributes,
extractLLMRequestAttributes,
Expand All @@ -40,7 +35,7 @@ import {
*
* This is a stateful handler that tracks spans across multiple LangChain executions.
*/
export function createLangChainCallbackHandler(options: LangChainOptions = {}): LangChainCallbackHandler {
export function createLangChainCallbackHandler(options: GenAiOptions = {}): LangChainCallbackHandler {
const { recordInputs, recordOutputs } = resolveAIRecordingOptions(options);

// Internal state - single instance tracks all spans
Expand Down
19 changes: 2 additions & 17 deletions packages/server-utils/src/ai/langchain/types.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,4 @@
/**
* Options for LangChain integration
*/
export interface LangChainOptions {
/**
* Whether to record input messages/prompts
* @default false (respects `dataCollection.genAI.inputs`)
*/
recordInputs?: boolean;

/**
* Whether to record output text and responses
* @default false (respects `dataCollection.genAI.outputs`)
*/
recordOutputs?: boolean;
}
import type { GenAiOptions } from '../core/utils';

/**
* LangChain Serialized type (compatible with @langchain/core)
Expand Down Expand Up @@ -107,7 +92,7 @@ export interface LangChainLLMResult {
*/
export interface LangChainIntegration {
name: string;
options: LangChainOptions;
options: GenAiOptions;
}

/**
Expand Down
11 changes: 6 additions & 5 deletions packages/server-utils/src/ai/langgraph/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import {
GEN_AI_TOOL_DEFINITIONS,
} from '@sentry/conventions/attributes';
import { GEN_AI_INVOKE_AGENT_OPERATION_ATTRIBUTE } from '../core/gen-ai-attributes';
import type { GenAiOptions } from '../core/utils';
import { extractSystemInstructions, resolveAIRecordingOptions } from '../core/utils';
import { createLangChainCallbackHandler } from '../langchain';
import type { BaseChatModel, LangChainMessage } from '../langchain/types';
import { normalizeLangChainMessages } from '../langchain/utils';
import { LANGGRAPH_ORIGIN } from './constants';
import type { CompiledGraph, LangGraphOptions } from './types';
import type { CompiledGraph } from './types';
import {
extractAgentNameFromParams,
extractLLMFromParams,
Expand All @@ -43,7 +44,7 @@ const SENTRY_PATCHED = '__sentry_patched__';
*/
export function instrumentStateGraphCompile(
originalCompile: (...args: unknown[]) => CompiledGraph,
options: LangGraphOptions,
options: GenAiOptions,
): (...args: unknown[]) => CompiledGraph {
if (Object.prototype.hasOwnProperty.call(originalCompile, SENTRY_PATCHED)) {
return originalCompile;
Expand Down Expand Up @@ -91,7 +92,7 @@ export function instrumentCompiledGraphInvoke(
originalInvoke: (...args: unknown[]) => Promise<unknown>,
graphInstance: CompiledGraph,
compileOptions: Record<string, unknown>,
options: LangGraphOptions,
options: GenAiOptions,
llm?: BaseChatModel | null,
sentryCallbackHandler?: unknown,
): (...args: unknown[]) => Promise<unknown> {
Expand Down Expand Up @@ -203,7 +204,7 @@ export function instrumentCompiledGraphInvoke(
*/
export function instrumentCreateReactAgent(
originalCreateReactAgent: (...args: unknown[]) => CompiledGraph,
options?: LangGraphOptions,
options?: GenAiOptions,
): (...args: unknown[]) => CompiledGraph {
if (Object.prototype.hasOwnProperty.call(originalCreateReactAgent, SENTRY_PATCHED)) {
return originalCreateReactAgent;
Expand Down Expand Up @@ -284,7 +285,7 @@ export function instrumentCreateReactAgent(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function instrumentStateGraph<T extends { compile: (...args: any[]) => any }>(
stateGraph: T,
options?: LangGraphOptions,
options?: GenAiOptions,
): T {
stateGraph.compile = instrumentStateGraphCompile(stateGraph.compile, resolveAIRecordingOptions(options));

Expand Down
13 changes: 2 additions & 11 deletions packages/server-utils/src/ai/langgraph/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
export interface LangGraphOptions {
/**
* Enable or disable input recording.
*/
recordInputs?: boolean;
/**
* Enable or disable output recording.
*/
recordOutputs?: boolean;
}
import type { GenAiOptions } from '../core/utils';

/**
* LangGraph Tool definition from lc_kwargs
Expand Down Expand Up @@ -81,5 +72,5 @@ export interface CompiledGraph {
*/
export interface LangGraphIntegration {
name: string;
options: LangGraphOptions;
options: GenAiOptions;
}
5 changes: 3 additions & 2 deletions packages/server-utils/src/ai/langgraph/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import { GEN_AI_EXECUTE_TOOL_OPERATION_ATTRIBUTE, GEN_AI_TOOL_CALL_ID_ATTRIBUTE
import type { BaseChatModel, LangChainMessage } from '../langchain/types';
import { normalizeLangChainMessages } from '../langchain/utils';
import { LANGGRAPH_ORIGIN } from './constants';
import type { CompiledGraph, LangGraphOptions, LangGraphTool } from './types';
import type { GenAiOptions } from '../core/utils';
import type { CompiledGraph, LangGraphTool } from './types';

/**
* Extract LLM model object from createReactAgent params
Expand Down Expand Up @@ -59,7 +60,7 @@ export function extractAgentNameFromParams(args: unknown[]): string | null {
*
* Wraps each tool's invoke() method in place. A marker prevents double-wrapping.
*/
export function wrapToolsWithSpans(tools: unknown[], options: LangGraphOptions, agentName?: string): unknown[] {
export function wrapToolsWithSpans(tools: unknown[], options: GenAiOptions, agentName?: string): unknown[] {
const SENTRY_WRAPPED = '__sentry_tool_wrapped__';

for (const tool of tools) {
Expand Down
Loading
Loading