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
Expand Up @@ -16,7 +16,7 @@ import {
GEN_AI_USAGE_OUTPUT_TOKENS,
GEN_AI_USAGE_TOTAL_TOKENS,
} from '@sentry/conventions/attributes';
import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes';
import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/server-utils/src/ai/core/gen-ai-attributes';
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner';
import { getStringAttributeValue, isOrchestrionEnabled } from '../../../../utils';

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_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes';
import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/server-utils/src/ai/core/gen-ai-attributes';
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner';
import { isOrchestrionEnabled } from '../../../../utils';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
GEN_AI_USAGE_OUTPUT_TOKENS,
GEN_AI_USAGE_TOTAL_TOKENS,
} from '@sentry/conventions/attributes';
import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes';
import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../packages/server-utils/src/ai/core/gen-ai-attributes';
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';
import { getStringAttributeValue, isOrchestrionEnabled } from '../../../utils';

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_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes';
import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/server-utils/src/ai/core/gen-ai-attributes';
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner';
import { getStringAttributeValue, isOrchestrionEnabled } from '../../../../utils';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
GEN_AI_USAGE_OUTPUT_TOKENS,
GEN_AI_USAGE_TOTAL_TOKENS,
} from '@sentry/conventions/attributes';
import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes';
import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/server-utils/src/ai/core/gen-ai-attributes';
import { cleanupChildProcesses, createEsmAndCjsTests, createEsmTests } from '../../../../utils/runner';
import { isOrchestrionEnabled } from '../../../../utils';

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 @@ -9,8 +9,8 @@
*/

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

const _vercelAIIntegration = ((options: VercelAiOptions = {}) => {
const inner = vercelAiIntegration(options);
Expand Down
3 changes: 0 additions & 3 deletions packages/core/src/server-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,4 @@ export type {

// AI instrumentation is only supported in server runtimes, so these exports are kept out of the browser entry to
// avoid shipping the AI tracing code in browser bundles.
export { addVercelAiProcessors, getProviderMetadataAttributes } from './tracing/vercel-ai';
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';
8 changes: 6 additions & 2 deletions packages/deno/src/integrations/tracing/vercelai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
*/

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

const _vercelAIIntegration = ((options: VercelAiOptions = {}) => {
const inner = serverUtilsVercelAiIntegration(options);
Expand Down
1 change: 1 addition & 0 deletions packages/server-utils/src/ai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export { instrumentGoogleGenAIClient } from './google-genai';
export { instrumentWorkersAiClient } from './workers-ai';
export { createLangChainCallbackHandler, instrumentLangChainEmbeddings } from './langchain';
export { instrumentStateGraph, instrumentStateGraphCompile, instrumentCreateReactAgent } from './langgraph';
export { addVercelAiProcessors } from './vercel-ai';
Comment thread
nicohrubec marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
/* eslint-disable typescript-eslint/no-deprecated */
/* eslint-disable max-lines */
import type { Client } from '../../client';
import { getClient } from '../../currentScopes';
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes';
import { shouldEnableTruncation } from '../ai/utils';
import type { Event } from '../../types/event';
import type { Span, SpanAttributes, SpanAttributeValue, SpanJSON, StreamedSpanJSON } from '../../types/span';
import { _INTERNAL_skipAiProviderWrapping } from '../../utils/ai/providerSkip';
import { spanToJSON } from '../../utils/spanUtils';
import {
_INTERNAL_skipAiProviderWrapping,
getClient,
hasSpanStreamingEnabled,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
spanToJSON,
} from '@sentry/core';
import type { Client, Event, Span, SpanAttributes, SpanAttributeValue, SpanJSON, StreamedSpanJSON } from '@sentry/core';
import { shouldEnableTruncation } from '../core/utils';
import { WORKERS_AI_INTEGRATION_NAME } from '../workers-ai/constants';
import {
GEN_AI_CONVERSATION_ID,
GEN_AI_EMBEDDINGS_INPUT,
Expand All @@ -27,10 +30,9 @@ import {
GEN_AI_USAGE_OUTPUT_TOKENS,
GEN_AI_USAGE_TOTAL_TOKENS,
} from '@sentry/conventions/attributes';
import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../ai/gen-ai-attributes';
import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../core/gen-ai-attributes';
import { SPAN_TO_OPERATION_NAME, toolCallSpanContextMap, toolDescriptionMap } from './constants';
import type { TokenSummary } from './types';
import { hasSpanStreamingEnabled } from '../spans/hasSpanStreamingEnabled';
import {
accumulateTokensForParent,
applyAccumulatedTokens,
Expand Down Expand Up @@ -64,8 +66,6 @@ import {
OPERATION_NAME_ATTRIBUTE,
} from './vercel-ai-attributes';

const WORKERS_AI_INTEGRATION_NAME = 'WorkersAI';

/**
* Post-process spans emitted by the Vercel AI SDK.
* This is supposed to be used in `client.on('spanStart', ...)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable typescript-eslint/no-deprecated */
import type { TraceContext } from '../../types/context';
import type { Span, SpanAttributes, SpanJSON } from '../../types/span';
import { stringify } from '@sentry/core';
import type { Span, SpanAttributes, SpanJSON, TraceContext } from '@sentry/core';
import {
GEN_AI_INPUT_MESSAGES,
GEN_AI_REQUEST_AVAILABLE_TOOLS,
Expand All @@ -10,8 +10,7 @@ import {
GEN_AI_USAGE_INPUT_TOKENS,
GEN_AI_USAGE_OUTPUT_TOKENS,
} from '@sentry/conventions/attributes';
import { extractSystemInstructions, getTruncatedJsonString } from '../ai/utils';
import { stringify } from '../../utils/string';
import { extractSystemInstructions, getTruncatedJsonString } from '../core/utils';
import { toolCallSpanContextMap } from './constants';
import type { TokenSummary, ToolCallSpanContext } from './types';
import { AI_PROMPT_ATTRIBUTE, AI_PROMPT_MESSAGES_ATTRIBUTE } from './vercel-ai-attributes';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,8 @@ import {
captureException,
GEN_AI_CONVERSATION_ID_ATTRIBUTE,
getClient,
getProviderMetadataAttributes,
getTruncatedJsonString,
isObjectLike,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
shouldEnableTruncation,
SPAN_STATUS_ERROR,
spanToJSON,
spanToTraceContext,
Expand All @@ -46,6 +43,9 @@ import {
withScope,
} from '@sentry/core';
import type { TracingChannel } from 'node:diagnostics_channel';
import { getProviderMetadataAttributes } from '../ai/vercel-ai';
import { getTruncatedJsonString, shouldEnableTruncation } from '../ai/core/utils';
import { WORKERS_AI_INTEGRATION_NAME } from '../ai/workers-ai/constants';
import { bindTracingChannelToSpan } from '../tracing-channel';
import { asNumber, asString, isReadableStream, type StreamedModelCallResult, sum, tapModelCallStream } from './util';

Expand All @@ -66,8 +66,6 @@ const GEN_AI_RERANK_OPERATION = 'rerank';
// The model-call op matches the Vercel AI OTel integration (`gen_ai.generate_content`) rather than
// the generic `gen_ai.chat`, so v6 (OTel) and v7 (channel) produce the same spans.
const GEN_AI_GENERATE_CONTENT_OPERATION = 'generate_content';
// TODO(v11): export the constant from server-utils and import it here instead.
const WORKERS_AI_INTEGRATION_NAME = 'WorkersAI';

// Subset of the `vercel.ai.*` passthrough attributes the OTel integration emits that we reproduce.
const VERCEL_AI_OPERATION_ID_ATTRIBUTE = 'vercel.ai.operationId';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest';
import { addVercelAiProcessors } from '../../../src/tracing/vercel-ai';
import type { SpanJSON } from '../../../src/types/span';
import { getDefaultTestClientOptions, TestClient } from '../../mocks/client';
import { addVercelAiProcessors } from '../../../../src/ai/vercel-ai';
import type { SpanJSON } from '@sentry/core';
import { getDefaultTestClientOptions, TestClient } from '../../../mocks/client';

function processSpan(data: SpanJSON['data']): SpanJSON {
const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0 });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest';
import { addVercelAiProcessors } from '../../../src/tracing/vercel-ai';
import type { SpanJSON } from '../../../src/types/span';
import { getDefaultTestClientOptions, TestClient } from '../../mocks/client';
import { addVercelAiProcessors } from '../../../../src/ai/vercel-ai';
import type { SpanJSON } from '@sentry/core';
import { getDefaultTestClientOptions, TestClient } from '../../../mocks/client';

describe('vercel-ai parent span token attributes', () => {
it('should map ai.usage.inputTokens to gen_ai.usage.input_tokens', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { describe, expect, it } from 'vitest';
import { getTruncatedJsonString } from '../../../src/tracing/ai/utils';
import { stringify } from '../../../src/utils/string';
import { getTruncatedJsonString } from '../../../../src/ai/core/utils';
import { stringify } from '@sentry/core';
import { GEN_AI_INPUT_MESSAGES, GEN_AI_SYSTEM_INSTRUCTIONS } from '@sentry/conventions/attributes';
import { requestMessagesFromPrompt } from '../../../src/tracing/vercel-ai/utils';
import { AI_PROMPT_MESSAGES_ATTRIBUTE } from '../../../src/tracing/vercel-ai/vercel-ai-attributes';
import type { Span, SpanAttributes } from '../../../src/types/span';
import { requestMessagesFromPrompt } from '../../../../src/ai/vercel-ai/utils';
import { AI_PROMPT_MESSAGES_ATTRIBUTE } from '../../../../src/ai/vercel-ai/vercel-ai-attributes';
import type { Span, SpanAttributes } from '@sentry/core';

/**
* Minimal span that records the attributes set on it, so we can assert on the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest';
import { addVercelAiProcessors } from '../../../src/tracing/vercel-ai';
import type { SpanJSON } from '../../../src/types/span';
import { getDefaultTestClientOptions, TestClient } from '../../mocks/client';
import { addVercelAiProcessors } from '../../../../src/ai/vercel-ai';
import type { SpanJSON } from '@sentry/core';
import { getDefaultTestClientOptions, TestClient } from '../../../mocks/client';

describe('vercel-ai span status normalization', () => {
function processSpan(status: string): string | undefined {
Expand Down
27 changes: 3 additions & 24 deletions packages/server-utils/test/ai/lib/tracing/workers-ai.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,9 @@ import {
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
_INTERNAL_clearAiProviderSkips,
addVercelAiProcessors,
Client,
createTransport,
getCurrentScope,
getGlobalScope,
getIsolationScope,
resolvedSyncPromise,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
Expand All @@ -25,30 +21,13 @@ import {
spanToJSON,
startSpan,
} from '@sentry/core';
import type { ClientOptions, DataCollection, Span } from '@sentry/core';
import type { DataCollection, Span } from '@sentry/core';
import { addVercelAiProcessors } from '../../../../src/ai/vercel-ai';
import { instrumentWorkersAiClient } from '../../../../src/ai/workers-ai';
import { getDefaultTestClientOptions, TestClient } from '../../../mocks/client';

const AI_OPERATION_ID_ATTRIBUTE = 'ai.operationId';

function getDefaultTestClientOptions(options: Partial<ClientOptions> = {}): ClientOptions {
return {
integrations: [],
transport: () => createTransport({ recordDroppedEvent: () => undefined }, () => resolvedSyncPromise({})),
stackParser: () => [],
...options,
} as ClientOptions;
}

class TestClient extends Client<any> {
public eventFromException(): PromiseLike<any> {
return resolvedSyncPromise({});
}

public eventFromMessage(): PromiseLike<any> {
return resolvedSyncPromise({});
}
}

const MODEL = '@cf/meta/llama-3.1-8b-instruct';

describe('instrumentWorkersAiClient', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { convertUserInputToMessagesFormat } from '../../../src/tracing/vercel-ai/utils';
import { convertUserInputToMessagesFormat } from '../../../../src/ai/vercel-ai/utils';

describe('vercel-ai-utils', () => {
describe('convertUserInputToMessagesFormat', () => {
Expand Down
98 changes: 98 additions & 0 deletions packages/server-utils/test/mocks/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type {
ClientOptions,
Event,
EventHint,
Integration,
ParameterizedString,
Session,
SeverityLevel,
} from '@sentry/core';
import { Client, createTransport, initAndBind, resolvedSyncPromise } from '@sentry/core';

export function getDefaultTestClientOptions(options: Partial<TestClientOptions> = {}): TestClientOptions {
return {
integrations: [],
sendClientReports: true,
traceLifecycle: 'static',
transport: () =>
createTransport(
{
recordDroppedEvent: () => undefined,
}, // noop
_ => resolvedSyncPromise({}),
),
stackParser: () => [],
...options,
};
}

export interface TestClientOptions extends ClientOptions {
test?: boolean;
mockInstallFailure?: boolean;
enableSend?: boolean;
defaultIntegrations?: Integration[] | false;
}

export class TestClient extends Client<TestClientOptions> {
public static instance?: TestClient;
public static sendEventCalled?: (event: Event) => void;

public event?: Event;
public session?: Session;

public constructor(options: TestClientOptions) {
super(options);
TestClient.instance = this;
}

public eventFromException(exception: any): PromiseLike<Event> {
const event: Event = {
exception: {
values: [
{
type: exception.name,
value: exception.message,
},
],
},
};

const frames = this._options.stackParser(exception.stack || '', 1);
if (frames.length && event.exception?.values?.[0]) {
event.exception.values[0] = { ...event.exception.values[0], stacktrace: { frames } };
}

return resolvedSyncPromise(event);
}

public eventFromMessage(message: ParameterizedString, level: SeverityLevel = 'info'): PromiseLike<Event> {
return resolvedSyncPromise({ message, level });
}

public sendEvent(event: Event, hint?: EventHint): void {
this.event = event;

if (this._options.enableSend) {
super.sendEvent(event, hint);
return;
}

// In real life, this will get deleted as part of envelope creation.
delete event.sdkProcessingMetadata;

TestClient.sendEventCalled?.(event);
}

public sendSession(session: Session): void {
this.session = session;
}

// Public proxy for protected method
public _clearOutcomes(): ReturnType<Client['_clearOutcomes']> {
return super._clearOutcomes();
}
}

export function init(options: TestClientOptions): void {
initAndBind(TestClient, options);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@ import {
resolvedSyncPromise,
} from '@sentry/core';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { WORKERS_AI_INTEGRATION_NAME } from '../../src/ai/workers-ai/constants';
import { createSpanFromMessage } from '../../src/vercel-ai/vercel-ai-dc-subscriber';

// Must match `WORKERS_AI_INTEGRATION_NAME` in core's `tracing/workers-ai/constants`.
const WORKERS_AI_INTEGRATION_NAME = 'WorkersAI';

class TestClient extends Client<any> {
public eventFromException(): PromiseLike<any> {
return resolvedSyncPromise({});
Expand Down
Loading
Loading