Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { instrumentOpenAiClient } from '@sentry/core';
import * as Sentry from '@sentry/node';

class MockOpenAIToolCalls {
Expand Down Expand Up @@ -240,7 +239,7 @@ async function run() {
apiKey: 'mock-api-key',
});

const client = instrumentOpenAiClient(mockClient);
const client = Sentry.instrumentOpenAiClient(mockClient);

const weatherTool = {
type: 'function',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
GEN_AI_USAGE_OUTPUT_TOKENS,
GEN_AI_USAGE_TOTAL_TOKENS,
} from '@sentry/conventions/attributes';
import { GEN_AI_REQUEST_STREAM_ATTRIBUTE } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes';
import { GEN_AI_REQUEST_STREAM_ATTRIBUTE } from '../../../../../../packages/server-utils/src/ai/core/gen-ai-attributes';
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner';

describe('OpenAI Tool Calls integration', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE,
GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE,
GEN_AI_REQUEST_STREAM_ATTRIBUTE,
} from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes';
} from '../../../../../packages/server-utils/src/ai/core/gen-ai-attributes';
import { getStringAttributeValue } from '../../../utils';
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { instrumentOpenAiClient } from '@sentry/core';
import * as Sentry from '@sentry/node';

class MockOpenAI {
Expand Down Expand Up @@ -45,7 +44,7 @@ async function run() {
apiKey: 'mock-api-key',
});

const client = instrumentOpenAiClient(mockClient, { enableTruncation: true, recordInputs: true });
const client = Sentry.instrumentOpenAiClient(mockClient, { enableTruncation: true, recordInputs: true });

// Test 1: Given an array of messages only the last message should be kept
// The last message should be truncated to fit within the 20KB limit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { instrumentOpenAiClient } from '@sentry/core';
import * as Sentry from '@sentry/node';

class MockOpenAI {
Expand Down Expand Up @@ -77,7 +76,7 @@ async function run() {
apiKey: 'mock-api-key',
});

const client = instrumentOpenAiClient(mockClient, { enableTruncation: true, recordInputs: true });
const client = Sentry.instrumentOpenAiClient(mockClient, { enableTruncation: true, recordInputs: true });

// Create 1 large message that gets truncated to fit within the 20KB limit
const largeContent = 'A'.repeat(25000) + 'B'.repeat(25000); // ~50KB gets truncated to include only As
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE,
GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE,
GEN_AI_REQUEST_STREAM_ATTRIBUTE,
} from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes';
} from '../../../../../../packages/server-utils/src/ai/core/gen-ai-attributes';
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner';

describe('OpenAI integration (V6)', () => {
Expand Down
3 changes: 1 addition & 2 deletions packages/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ export {
functionToStringIntegration,
// eslint-disable-next-line typescript/no-deprecated
inboundFiltersIntegration,
instrumentOpenAiClient,
instrumentGoogleGenAIClient,
instrumentAnthropicAiClient,
eventFiltersIntegration,
Expand Down Expand Up @@ -128,7 +127,7 @@ export { httpServerIntegration } from './integrations/httpServer';
export { fetchIntegration } from './integrations/fetch';
export { spotlightIntegration } from './integrations/spotlight';
export { vercelAIIntegration } from './integrations/tracing/vercelai';
export { prismaIntegration } from '@sentry/server-utils';
export { prismaIntegration, instrumentOpenAiClient } from '@sentry/server-utils';

// eslint-disable-next-line typescript/no-deprecated
export { instrumentD1WithSentry } from './instrumentations/worker/instrumentD1';
Expand Down
13 changes: 0 additions & 13 deletions packages/core/src/server-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,6 @@ export { addVercelAiProcessors, getProviderMetadataAttributes } from './tracing/
export { getTruncatedJsonString, shouldEnableTruncation, resolveAIRecordingOptions } from './tracing/ai/utils';
export { _INTERNAL_getSpanContextForToolCallId, _INTERNAL_cleanupToolCallSpanContext } from './tracing/vercel-ai/utils';
export { toolCallSpanContextMap as _INTERNAL_toolCallSpanContextMap } from './tracing/vercel-ai/constants';
export {
instrumentOpenAiClient,
extractRequestAttributes as extractOpenAiRequestAttributes,
addRequestAttributes as addOpenAiRequestAttributes,
} from './tracing/openai';
export {
addResponseAttributes as addOpenAiResponseAttributes,
extractRequestParameters as extractOpenAiRequestParameters,
} from './tracing/openai/utils';
export { instrumentStream as instrumentOpenAiStream } from './tracing/openai/streaming';
export { OPENAI_INTEGRATION_NAME } from './tracing/openai/constants';
export {
instrumentAnthropicAiClient,
extractRequestAttributes as extractAnthropicRequestAttributes,
Expand Down Expand Up @@ -104,8 +93,6 @@ export { LANGGRAPH_INTEGRATION_NAME } from './tracing/langgraph/constants';
export type { LangGraphOptions, LangGraphIntegration, CompiledGraph } from './tracing/langgraph/types';
export { instrumentWorkersAiClient } from './tracing/workers-ai';
export type { WorkersAiClient, WorkersAiOptions } from './tracing/workers-ai/types';
// eslint-disable-next-line typescript/no-deprecated
export type { OpenAiClient, OpenAiOptions, InstrumentedMethod } from './tracing/openai/types';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: Are these just dropped?

export type {
AnthropicAiClient,
AnthropicAiOptions,
Expand Down
3 changes: 1 addition & 2 deletions packages/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export {
vercelAiIntegration as vercelAIIntegration,
} from '@sentry/server-utils/orchestrion';
export { redisIntegration } from './integrations/tracing/redis';
export { prismaIntegration } from '@sentry/server-utils';
export { prismaIntegration, instrumentOpenAiClient } from '@sentry/server-utils';
export { setupHapiErrorHandler } from './integrations/tracing/hapi';
export { setupKoaErrorHandler } from './integrations/tracing/koa';
export {
Expand Down Expand Up @@ -133,7 +133,6 @@ export {
updateSpanName,
supabaseIntegration,
instrumentSupabaseClient,
instrumentOpenAiClient,
instrumentAnthropicAiClient,
instrumentGoogleGenAIClient,
zodErrorsIntegration,
Expand Down
4 changes: 1 addition & 3 deletions packages/server-utils/src/ai/core/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
/**
* Shared utils for AI integrations (OpenAI, Anthropic, Verce.AI, etc.)
*/
import { captureException } from '@sentry/core';
import { getClient } from '@sentry/core';
import { captureException, getClient, isThenable } from '@sentry/core';
import type { Span } from '@sentry/core';
import { isThenable } from '@sentry/core';
import {
GEN_AI_RESPONSE_FINISH_REASONS,
GEN_AI_RESPONSE_ID,
Expand Down
7 changes: 7 additions & 0 deletions packages/server-utils/src/ai/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* AI/gen-ai instrumentation logic for server runtimes.
*
* @module
*/

export { instrumentOpenAiClient } from './openai';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { InstrumentedMethodRegistry } from '../ai/utils';
import type { InstrumentedMethodRegistry } from '../core/utils';

export const OPENAI_INTEGRATION_NAME = 'OpenAI' as const;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
/* eslint-disable typescript-eslint/no-deprecated */
import { DEBUG_BUILD } from '../../debug-build';
import { captureException } from '../../exports';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes';
import { SPAN_STATUS_ERROR } from '../../tracing';
import { startSpan, startSpanManual } from '../../tracing/trace';
import type { Span, SpanAttributeValue } from '../../types/span';
import { debug } from '../../utils/debug-logger';
import {
captureException,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SPAN_STATUS_ERROR,
startSpan,
startSpanManual,
debug,
stringify,
} from '@sentry/core';
import type { Span, SpanAttributeValue } from '@sentry/core';
import {
GEN_AI_EMBEDDINGS_INPUT,
GEN_AI_INPUT_MESSAGES,
Expand All @@ -15,16 +19,15 @@ import {
GEN_AI_SYSTEM,
GEN_AI_SYSTEM_INSTRUCTIONS,
} from '@sentry/conventions/attributes';
import type { InstrumentedMethodEntry } from '../ai/utils';
import { stringify } from '../../utils/string';
import type { InstrumentedMethodEntry } from '../core/utils';
import {
buildMethodPath,
extractSystemInstructions,
getTruncatedJsonString,
resolveAIRecordingOptions,
shouldEnableTruncation,
wrapPromiseWithMethods,
} from '../ai/utils';
} from '../core/utils';
import { OPENAI_METHOD_REGISTRY } from './constants';
import { instrumentStream } from './streaming';
import type { ChatCompletionChunk, OpenAiOptions, OpenAIStream, ResponseStreamingEvent } from './types';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { captureException } from '../../exports';
import { SPAN_STATUS_ERROR } from '../../tracing';
import type { Span } from '../../types/span';
import { endStreamSpan } from '../ai/utils';
import { captureException, SPAN_STATUS_ERROR } from '@sentry/core';
import type { Span } from '@sentry/core';
import { endStreamSpan } from '../core/utils';
import { RESPONSE_EVENT_TYPES } from './constants';
import type {
ChatCompletionChunk,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable typescript-eslint/no-deprecated */
import type { Span } from '../../types/span';
import type { SpanAttributeValue } from '../../types/span';
import type { Span, SpanAttributeValue } from '@sentry/core';
import {
GEN_AI_CONVERSATION_ID,
GEN_AI_REQUEST_FREQUENCY_PENALTY,
Expand All @@ -21,7 +20,7 @@ import {
GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE,
GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE,
GEN_AI_REQUEST_STREAM_ATTRIBUTE,
} from '../ai/gen-ai-attributes';
} from '../core/gen-ai-attributes';
import type { ChatCompletionChunk, ResponseStreamingEvent } from './types';

/**
Expand Down
1 change: 1 addition & 0 deletions packages/server-utils/src/exports.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Shared exports not using diagnostics channels
export { setHttpServerSpanRouteAttribute } from './utils/setHttpServerSpanRouteAttribute';
export { setAsyncLocalStorageAsyncContextStrategy } from './async-context';
export * from './ai';
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import {
defineIntegration,
GOOGLE_GENAI_INTEGRATION_NAME,
LANGCHAIN_INTEGRATION_NAME,
OPENAI_INTEGRATION_NAME,
startInactiveSpan,
} from '@sentry/core';
import { OPENAI_INTEGRATION_NAME } from '../../ai/openai/constants';
import { CHANNELS } from '../../orchestrion/channels';
import { langchainEmbeddingsChannels } from '../../orchestrion/config/langchain';
import { bindTracingChannelToSpan } from '../../tracing-channel';
Expand Down
25 changes: 12 additions & 13 deletions packages/server-utils/src/integrations/tracing-channel/openai.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import * as diagnosticsChannel from 'node:diagnostics_channel';
import type { IntegrationFn, OpenAiOptions, Span, SpanAttributeValue } from '@sentry/core';
import type { IntegrationFn, Span, SpanAttributeValue } from '@sentry/core';
import {
_INTERNAL_shouldSkipAiProviderWrapping,
addOpenAiRequestAttributes,
addOpenAiResponseAttributes,
defineIntegration,
extractOpenAiRequestAttributes,
instrumentOpenAiStream,
resolveAIRecordingOptions,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
shouldEnableTruncation,
startInactiveSpan,
} from '@sentry/core';
import { resolveAIRecordingOptions, shouldEnableTruncation } from '../../ai/core/utils';
import { addRequestAttributes, extractRequestAttributes } from '../../ai/openai';
import { instrumentStream } from '../../ai/openai/streaming';
import type { OpenAiOptions } from '../../ai/openai/types';
import { addResponseAttributes } from '../../ai/openai/utils';
import { CHANNELS } from '../../orchestrion/channels';
import { bindTracingChannelToSpan } from '../../tracing-channel';
import { openaiModuleNames } from '../../orchestrion/config/openai';
Expand Down Expand Up @@ -55,7 +54,7 @@ function instrumentOpenai(options: OpenAiOptions): void {
data => createGenAiSpan(data, operation, options),
{
beforeSpanEnd: (span, data) => {
addOpenAiResponseAttributes(span, data.result, resolveAIRecordingOptions(options).recordOutputs);
addResponseAttributes(span, data.result, resolveAIRecordingOptions(options).recordOutputs);
},
// Streaming: the result is a `Stream` consumed later, so instrument it and let it end the span.
deferSpanEnd: ({ span, data }) => wrapStreamResult(span, data, options),
Expand All @@ -81,7 +80,7 @@ function createGenAiSpan(data: OpenAiChatChannelContext, operation: string, opti
const { recordInputs } = resolveAIRecordingOptions(options);
const enableTruncation = shouldEnableTruncation(options.enableTruncation);

const attributes = extractOpenAiRequestAttributes(args, operation);
const attributes = extractRequestAttributes(args, operation);
attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] = ORIGIN;
const model = (params?.model as string) || 'unknown';

Expand All @@ -92,7 +91,7 @@ function createGenAiSpan(data: OpenAiChatChannelContext, operation: string, opti
});

if (recordInputs && params) {
addOpenAiRequestAttributes(span, params, operation, enableTruncation);
addRequestAttributes(span, params, operation, enableTruncation);
}

return span;
Expand All @@ -108,9 +107,9 @@ function isAsyncIterable(value: unknown): value is AsyncIterableStream {
* For a streaming `create({ stream: true })` the result is a `Stream` the caller consumes later. We can't
* swap what `create` returns, but the `Stream` in `data.result` is the same instance the caller holds and
* `asyncEnd` fires before the caller iterates — so we patch its async iterator in place to run through
* `instrumentOpenAiStream`, which accumulates the streamed attributes and ends the span when iteration finishes.
* `instrumentStream`, which accumulates the streamed attributes and ends the span when iteration finishes.
* Only a streaming call resolves to an async-iterable, so that check alone distinguishes it. Returns `true`
* to hand span-ending ownership to `instrumentOpenAiStream`; `false` for non-streaming/errored results, which end
* to hand span-ending ownership to `instrumentStream`; `false` for non-streaming/errored results, which end
* via the normal `beforeSpanEnd` path.
*/
function wrapStreamResult(span: Span, data: OpenAiChatChannelContext, options: OpenAiOptions): boolean {
Expand All @@ -121,7 +120,7 @@ function wrapStreamResult(span: Span, data: OpenAiChatChannelContext, options: O

const { recordOutputs } = resolveAIRecordingOptions(options);
const iterate = result[Symbol.asyncIterator].bind(result);
const instrumented = instrumentOpenAiStream({ [Symbol.asyncIterator]: iterate }, span, recordOutputs ?? false);
const instrumented = instrumentStream({ [Symbol.asyncIterator]: iterate }, span, recordOutputs ?? false);
result[Symbol.asyncIterator] = () => instrumented;

return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { buildMethodPath } from '../../../src/tracing/ai/utils';
import { isChatCompletionChunk, isResponsesApiStreamEvent } from '../../../src/tracing/openai/utils';
import { buildMethodPath } from '../../../../src/ai/core/utils';
import { isChatCompletionChunk, isResponsesApiStreamEvent } from '../../../../src/ai/openai/utils';

describe('openai-utils', () => {
describe('buildMethodPath', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it } from 'vitest';
import type { OpenAiClient } from '../../src';
import { instrumentOpenAiClient } from '../../src/tracing/openai';
import type { OpenAiClient } from '../../../src/ai/openai/types';
import { instrumentOpenAiClient } from '../../../src/ai/openai';

interface FullOpenAIClient {
chat: {
Expand Down
3 changes: 2 additions & 1 deletion packages/vercel-edge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.1",
"@sentry/core": "10.67.0"
"@sentry/core": "10.67.0",
"@sentry/server-utils": "10.67.0"
},
"devDependencies": {
"@edge-runtime/types": "4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/vercel-edge/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ export {
functionToStringIntegration,
// eslint-disable-next-line typescript/no-deprecated
inboundFiltersIntegration,
instrumentOpenAiClient,
instrumentStateGraph,
instrumentGoogleGenAIClient,
instrumentAnthropicAiClient,
Expand Down Expand Up @@ -108,6 +107,7 @@ export {
withStreamedSpan,
spanStreamingIntegration,
} from '@sentry/core';
export { instrumentOpenAiClient } from '@sentry/server-utils/no-diagnostic-channels';

export { VercelEdgeClient } from './client';
export { getDefaultIntegrations, init } from './sdk';
Expand Down
Loading