Skip to content

Commit 1baed6e

Browse files
committed
quick refactor
1 parent c6c2071 commit 1baed6e

File tree

5 files changed

+57
-177
lines changed

5 files changed

+57
-177
lines changed

dev-packages/node-integration-tests/suites/tracing/anthropic/scenario.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Anthropic from '@anthropic-ai/sdk';
22
import * as Sentry from '@sentry/node';
33
import express from 'express';
44

5-
const PORT = 3333;
5+
const PORT = 3335;
66

77
function startMockAnthropicServer() {
88
const app = express();

dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-streaming.mjs

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function startMockGoogleGenAIServer() {
88
const app = express();
99
app.use(express.json());
1010

11-
// Streaming endpoint for models.generateContentStream
11+
// Streaming endpoint for models.generateContentStream and chat.sendMessageStream
1212
app.post('/v1beta/models/:model\\:streamGenerateContent', (req, res) => {
1313
const model = req.params.model;
1414

@@ -39,32 +39,6 @@ function startMockGoogleGenAIServer() {
3939
sendChunk();
4040
});
4141

42-
// Streaming endpoint for chat.sendMessageStream
43-
app.post('/v1beta/models/:model\\:streamGenerateContent', (req, res) => {
44-
const model = req.params.model;
45-
46-
// Set headers for streaming response
47-
res.setHeader('Content-Type', 'application/json');
48-
res.setHeader('Transfer-Encoding', 'chunked');
49-
50-
// Create a mock stream
51-
const mockStream = createMockStream(model);
52-
53-
// Send chunks
54-
const sendChunk = async () => {
55-
const { value, done } = await mockStream.next();
56-
if (done) {
57-
res.end();
58-
return;
59-
}
60-
61-
res.write(`data: ${JSON.stringify(value)}\n\n`);
62-
setTimeout(sendChunk, 10); // Small delay between chunks
63-
};
64-
65-
sendChunk();
66-
});
67-
6842
return app.listen(PORT);
6943
}
7044

dev-packages/node-integration-tests/suites/tracing/google-genai/scenario-tools.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { GoogleGenAI } from '@google/genai';
22
import * as Sentry from '@sentry/node';
33
import express from 'express';
44

5-
const PORT = 3335; // Different port to avoid conflicts
5+
const PORT = 3337;
66

77
function startMockGoogleGenAIServer() {
88
const app = express();
@@ -91,6 +91,7 @@ function startMockGoogleGenAIServer() {
9191
});
9292

9393
// Streaming endpoint for models.generateContentStream
94+
// And chat.sendMessageStream
9495
app.post('/v1beta/models/:model\\:streamGenerateContent', (req, res) => {
9596
const { tools } = req.body;
9697

dev-packages/node-integration-tests/suites/tracing/google-genai/scenario.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { GoogleGenAI } from '@google/genai';
22
import * as Sentry from '@sentry/node';
33
import express from 'express';
44

5-
const PORT = 3333;
5+
const PORT = 3336;
66

77
function startMockGoogleGenAIServer() {
88
const app = express();
Lines changed: 52 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { captureException } from '../../exports';
22
import { SPAN_STATUS_ERROR } from '../../tracing';
3-
import type { Span } from '../../types-hoist/span';
3+
import type { Span, SpanAttributeValue } from '../../types-hoist/span';
44
import {
55
GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE,
66
GEN_AI_RESPONSE_ID_ATTRIBUTE,
@@ -23,15 +23,15 @@ interface StreamingState {
2323
/** Reasons for finishing the response, as reported by the API. */
2424
finishReasons: string[];
2525
/** The response ID. */
26-
responseId: string;
26+
responseId?: string;
2727
/** The model name. */
28-
responseModel: string;
28+
responseModel?: string;
2929
/** Number of prompt/input tokens used. */
30-
promptTokens: number | undefined;
30+
promptTokens?: number;
3131
/** Number of completion/output tokens used. */
32-
completionTokens: number | undefined;
32+
completionTokens?: number;
3333
/** Number of total tokens used. */
34-
totalTokens: number | undefined;
34+
totalTokens?: number;
3535
/** Accumulated tool calls (finalized) */
3636
toolCalls: Array<Record<string, unknown>>;
3737
}
@@ -43,25 +43,14 @@ interface StreamingState {
4343
* @returns Whether an error occurred
4444
*/
4545
function isErrorChunk(chunk: GoogleGenAIResponse, span: Span): boolean {
46-
// Check for errors in the response
47-
if (chunk && typeof chunk === 'object') {
48-
// Google GenAI may include error information in promptFeedback
49-
if (chunk.promptFeedback && typeof chunk.promptFeedback === 'object') {
50-
const feedback = chunk.promptFeedback;
51-
if (feedback.blockReason && typeof feedback.blockReason === 'string') {
52-
// Use blockReasonMessage if available (more descriptive), otherwise use blockReason (enum)
53-
const errorMessage = feedback.blockReasonMessage ? feedback.blockReasonMessage : feedback.blockReason;
54-
55-
span.setStatus({ code: SPAN_STATUS_ERROR, message: `Content blocked: ${errorMessage}` });
56-
captureException(`Content blocked: ${errorMessage}`, {
57-
mechanism: {
58-
handled: false,
59-
type: 'auto.ai.google_genai',
60-
},
61-
});
62-
return true;
63-
}
64-
}
46+
const feedback = chunk?.promptFeedback;
47+
if (feedback?.blockReason) {
48+
const message = feedback.blockReasonMessage ?? feedback.blockReason;
49+
span.setStatus({ code: SPAN_STATUS_ERROR, message: `Content blocked: ${message}` });
50+
captureException(`Content blocked: ${message}`, {
51+
mechanism: { handled: false, type: 'auto.ai.google_genai' },
52+
});
53+
return true;
6554
}
6655
return false;
6756
}
@@ -72,30 +61,14 @@ function isErrorChunk(chunk: GoogleGenAIResponse, span: Span): boolean {
7261
* @param state - The state of the streaming process
7362
*/
7463
function handleResponseMetadata(chunk: GoogleGenAIResponse, state: StreamingState): void {
75-
if (!chunk || typeof chunk !== 'object') return;
76-
77-
// Extract response ID
78-
if (chunk.responseId && typeof chunk.responseId === 'string') {
79-
state.responseId = chunk.responseId;
80-
}
81-
82-
// Extract model version
83-
if (chunk.modelVersion && typeof chunk.modelVersion === 'string') {
84-
state.responseModel = chunk.modelVersion;
85-
}
86-
87-
// Extract usage metadata
88-
if (chunk.usageMetadata && typeof chunk.usageMetadata === 'object') {
89-
const usage = chunk.usageMetadata;
90-
if (typeof usage.promptTokenCount === 'number') {
91-
state.promptTokens = usage.promptTokenCount;
92-
}
93-
if (typeof usage.candidatesTokenCount === 'number') {
94-
state.completionTokens = usage.candidatesTokenCount;
95-
}
96-
if (typeof usage.totalTokenCount === 'number') {
97-
state.totalTokens = usage.totalTokenCount;
98-
}
64+
if (typeof chunk.responseId === 'string') state.responseId = chunk.responseId;
65+
if (typeof chunk.modelVersion === 'string') state.responseModel = chunk.modelVersion;
66+
67+
const usage = chunk.usageMetadata;
68+
if (usage) {
69+
if (typeof usage.promptTokenCount === 'number') state.promptTokens = usage.promptTokenCount;
70+
if (typeof usage.candidatesTokenCount === 'number') state.completionTokens = usage.candidatesTokenCount;
71+
if (typeof usage.totalTokenCount === 'number') state.totalTokens = usage.totalTokenCount;
9972
}
10073
}
10174

@@ -106,46 +79,24 @@ function handleResponseMetadata(chunk: GoogleGenAIResponse, state: StreamingStat
10679
* @param recordOutputs - Whether to record outputs
10780
*/
10881
function handleCandidateContent(chunk: GoogleGenAIResponse, state: StreamingState, recordOutputs: boolean): void {
109-
// Check for direct functionCalls getter first
110-
if (chunk.functionCalls && Array.isArray(chunk.functionCalls)) {
111-
const functionCalls = chunk.functionCalls;
112-
for (const functionCall of functionCalls) {
113-
state.toolCalls.push(functionCall);
114-
}
82+
if (Array.isArray(chunk.functionCalls)) {
83+
state.toolCalls.push(...chunk.functionCalls);
11584
}
11685

117-
if (!chunk?.candidates) return;
118-
119-
for (const candidate of chunk.candidates) {
120-
if (!candidate || typeof candidate !== 'object') continue;
121-
122-
// Extract finish reason
123-
if (candidate.finishReason) {
124-
if (!state.finishReasons.includes(candidate.finishReason)) {
125-
state.finishReasons.push(candidate.finishReason);
126-
}
86+
for (const candidate of chunk.candidates ?? []) {
87+
if (candidate?.finishReason && !state.finishReasons.includes(candidate.finishReason)) {
88+
state.finishReasons.push(candidate.finishReason);
12789
}
12890

129-
// Extract content
130-
if (candidate.content) {
131-
const content = candidate.content;
132-
if (content.parts) {
133-
for (const part of content.parts) {
134-
// Extract text content for output recording
135-
if (recordOutputs && part.text) {
136-
state.responseTexts.push(part.text);
137-
}
138-
139-
// Extract function calls (fallback method)
140-
if (part.functionCall) {
141-
state.toolCalls.push({
142-
type: 'function',
143-
id: part.functionCall?.id,
144-
name: part.functionCall?.name,
145-
arguments: part.functionCall?.args,
146-
});
147-
}
148-
}
91+
for (const part of candidate?.content?.parts ?? []) {
92+
if (recordOutputs && part.text) state.responseTexts.push(part.text);
93+
if (part.functionCall) {
94+
state.toolCalls.push({
95+
type: 'function',
96+
id: part.functionCall.id,
97+
name: part.functionCall.name,
98+
arguments: part.functionCall.args,
99+
});
149100
}
150101
}
151102
}
@@ -159,14 +110,7 @@ function handleCandidateContent(chunk: GoogleGenAIResponse, state: StreamingStat
159110
* @param span - The span to update
160111
*/
161112
function processChunk(chunk: GoogleGenAIResponse, state: StreamingState, recordOutputs: boolean, span: Span): void {
162-
if (!chunk || typeof chunk !== 'object') {
163-
return;
164-
}
165-
166-
const isError = isErrorChunk(chunk, span);
167-
// No further metadata or content will be sent to process
168-
if (isError) return;
169-
113+
if (!chunk || isErrorChunk(chunk, span)) return;
170114
handleResponseMetadata(chunk, state);
171115
handleCandidateContent(chunk, state, recordOutputs);
172116
}
@@ -184,11 +128,6 @@ export async function* instrumentStream(
184128
const state: StreamingState = {
185129
responseTexts: [],
186130
finishReasons: [],
187-
responseId: '',
188-
responseModel: '',
189-
promptTokens: undefined,
190-
completionTokens: undefined,
191-
totalTokens: undefined,
192131
toolCalls: [],
193132
};
194133

@@ -198,61 +137,27 @@ export async function* instrumentStream(
198137
yield chunk;
199138
}
200139
} finally {
201-
// Set common response attributes if available once the stream is finished
202-
if (state.responseId) {
203-
span.setAttributes({
204-
[GEN_AI_RESPONSE_ID_ATTRIBUTE]: state.responseId,
205-
});
206-
}
207-
if (state.responseModel) {
208-
span.setAttributes({
209-
[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: state.responseModel,
210-
});
211-
}
212-
213-
// Set token usage attributes
214-
if (state.promptTokens !== undefined) {
215-
span.setAttributes({
216-
[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: state.promptTokens,
217-
});
218-
}
219-
if (state.completionTokens !== undefined) {
220-
span.setAttributes({
221-
[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: state.completionTokens,
222-
});
223-
}
224-
if (state.totalTokens !== undefined) {
225-
span.setAttributes({
226-
[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: state.totalTokens,
227-
});
228-
}
229-
230-
// Mark as streaming response
231-
span.setAttributes({
140+
const attrs: Record<string, SpanAttributeValue> = {
232141
[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]: true,
233-
});
142+
};
234143

235-
// Set finish reasons if available
236-
if (state.finishReasons.length > 0) {
237-
span.setAttributes({
238-
[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: JSON.stringify(state.finishReasons),
239-
});
240-
}
144+
if (state.responseId) attrs[GEN_AI_RESPONSE_ID_ATTRIBUTE] = state.responseId;
145+
if (state.responseModel) attrs[GEN_AI_RESPONSE_MODEL_ATTRIBUTE] = state.responseModel;
146+
if (state.promptTokens !== undefined) attrs[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE] = state.promptTokens;
147+
if (state.completionTokens !== undefined) attrs[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE] = state.completionTokens;
148+
if (state.totalTokens !== undefined) attrs[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE] = state.totalTokens;
241149

242-
// Set response text if recording outputs
243-
if (recordOutputs && state.responseTexts.length > 0) {
244-
span.setAttributes({
245-
[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: state.responseTexts.join(''),
246-
});
150+
if (state.finishReasons.length) {
151+
attrs[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE] = JSON.stringify(state.finishReasons);
247152
}
248-
249-
// Set tool calls if any were captured
250-
if (recordOutputs && state.toolCalls.length > 0) {
251-
span.setAttributes({
252-
[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]: JSON.stringify(state.toolCalls),
253-
});
153+
if (recordOutputs && state.responseTexts.length) {
154+
attrs[GEN_AI_RESPONSE_TEXT_ATTRIBUTE] = state.responseTexts.join('');
155+
}
156+
if (recordOutputs && state.toolCalls.length) {
157+
attrs[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE] = JSON.stringify(state.toolCalls);
254158
}
255159

160+
span.setAttributes(attrs);
256161
span.end();
257162
}
258163
}

0 commit comments

Comments
 (0)