Skip to content

Commit 0781c6e

Browse files
committed
quick refactors
1 parent 973224c commit 0781c6e

File tree

12 files changed

+201
-82
lines changed

12 files changed

+201
-82
lines changed

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,83 @@ async function run() {
8484
await graph.invoke({
8585
messages: [{ role: 'user', content: 'What is the weather?' }],
8686
});
87+
88+
// Define mock LLM function that returns with tool calls
89+
let callCount = 0;
90+
const mockLlmWithTools = () => {
91+
callCount++;
92+
93+
// First call - return tool calls
94+
if (callCount === 1) {
95+
return {
96+
messages: [
97+
{
98+
role: 'assistant',
99+
content: '',
100+
response_metadata: {
101+
model_name: 'gpt-4-0613',
102+
finish_reason: 'tool_calls',
103+
tokenUsage: {
104+
promptTokens: 30,
105+
completionTokens: 20,
106+
totalTokens: 50,
107+
},
108+
},
109+
tool_calls: [
110+
{
111+
name: 'get_weather',
112+
args: { city: 'San Francisco' },
113+
id: 'call_123',
114+
type: 'tool_call',
115+
},
116+
],
117+
},
118+
],
119+
};
120+
}
121+
122+
// Second call - return final response after tool execution
123+
return {
124+
messages: [
125+
{
126+
role: 'assistant',
127+
content: 'Based on the weather data, it is sunny and 72 degrees in San Francisco.',
128+
response_metadata: {
129+
model_name: 'gpt-4-0613',
130+
finish_reason: 'stop',
131+
tokenUsage: {
132+
promptTokens: 50,
133+
completionTokens: 20,
134+
totalTokens: 70,
135+
},
136+
},
137+
tool_calls: [],
138+
},
139+
],
140+
};
141+
};
142+
143+
// Create graph with tool calls enabled
144+
const graphWithTools = new StateGraph(MessagesAnnotation)
145+
.addNode('agent', mockLlmWithTools)
146+
.addNode('tools', toolNode)
147+
.addEdge(START, 'agent')
148+
.addConditionalEdges('agent', shouldContinue, {
149+
tools: 'tools',
150+
[END]: END,
151+
})
152+
.addEdge('tools', 'agent')
153+
.compile({ name: 'tool_calling_agent' });
154+
155+
// Invocation that actually calls tools
156+
await graphWithTools.invoke({
157+
messages: [{ role: 'user', content: 'What is the weather in San Francisco?' }],
158+
});
87159
});
88160

89161
await Sentry.flush(2000);
90162
}
91163

92164
run();
165+
166+

dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ describe('LangGraph integration', () => {
105105
const EXPECTED_TRANSACTION_WITH_TOOLS = {
106106
transaction: 'langgraph-tools-test',
107107
spans: expect.arrayContaining([
108-
// create_agent span
108+
// create_agent span for first graph (no tool calls)
109109
expect.objectContaining({
110110
data: {
111111
'gen_ai.operation.name': 'create_agent',
@@ -118,7 +118,7 @@ describe('LangGraph integration', () => {
118118
origin: 'auto.ai.langgraph',
119119
status: 'ok',
120120
}),
121-
// invoke_agent span with tools
121+
// invoke_agent span with tools available but not called
122122
expect.objectContaining({
123123
data: expect.objectContaining({
124124
'gen_ai.operation.name': 'invoke_agent',
@@ -140,6 +140,43 @@ describe('LangGraph integration', () => {
140140
origin: 'auto.ai.langgraph',
141141
status: 'ok',
142142
}),
143+
// create_agent span for second graph (with tool calls)
144+
expect.objectContaining({
145+
data: {
146+
'gen_ai.operation.name': 'create_agent',
147+
'sentry.op': 'gen_ai.create_agent',
148+
'sentry.origin': 'auto.ai.langgraph',
149+
'gen_ai.agent.name': 'tool_calling_agent',
150+
},
151+
description: 'create_agent tool_calling_agent',
152+
op: 'gen_ai.create_agent',
153+
origin: 'auto.ai.langgraph',
154+
status: 'ok',
155+
}),
156+
// invoke_agent span with tool calls and execution
157+
expect.objectContaining({
158+
data: expect.objectContaining({
159+
'gen_ai.operation.name': 'invoke_agent',
160+
'sentry.op': 'gen_ai.invoke_agent',
161+
'sentry.origin': 'auto.ai.langgraph',
162+
'gen_ai.agent.name': 'tool_calling_agent',
163+
'gen_ai.pipeline.name': 'tool_calling_agent',
164+
'gen_ai.request.available_tools': expect.stringContaining('get_weather'),
165+
'gen_ai.request.messages': expect.stringContaining('San Francisco'),
166+
'gen_ai.response.model': 'gpt-4-0613',
167+
'gen_ai.response.finish_reasons': ['stop'],
168+
'gen_ai.response.text': expect.stringMatching(/"role":"tool"/),
169+
// Verify tool_calls are captured
170+
'gen_ai.response.tool_calls': expect.stringContaining('get_weather'),
171+
'gen_ai.usage.input_tokens': 50,
172+
'gen_ai.usage.output_tokens': 20,
173+
'gen_ai.usage.total_tokens': 70,
174+
}),
175+
description: 'invoke_agent tool_calling_agent',
176+
op: 'gen_ai.invoke_agent',
177+
origin: 'auto.ai.langgraph',
178+
status: 'ok',
179+
}),
143180
]),
144181
};
145182

packages/astro/src/index.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export {
9595
onUnhandledRejectionIntegration,
9696
openAIIntegration,
9797
langChainIntegration,
98-
langgraphIntegration,
98+
langGraphIntegration,
9999
parameterize,
100100
pinoIntegration,
101101
postgresIntegration,

packages/aws-serverless/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export {
5858
onUnhandledRejectionIntegration,
5959
openAIIntegration,
6060
langChainIntegration,
61-
langgraphIntegration,
61+
langGraphIntegration,
6262
modulesIntegration,
6363
contextLinesIntegration,
6464
nodeContextIntegration,

packages/bun/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export {
7979
onUnhandledRejectionIntegration,
8080
openAIIntegration,
8181
langChainIntegration,
82-
langgraphIntegration,
82+
langGraphIntegration,
8383
modulesIntegration,
8484
contextLinesIntegration,
8585
nodeContextIntegration,

packages/core/src/tracing/ai/gen-ai-attributes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,11 @@ export const GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE_ATTRIBUTE = 'gen_ai.usage.inp
164164
*/
165165
export const GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE = 'gen_ai.usage.input_tokens.cached';
166166

167+
/**
168+
* The span operation name for invoking an agent
169+
*/
170+
export const GEN_AI_INVOKE_AGENT_OPERATION_ATTRIBUTE = 'gen_ai.invoke_agent';
171+
167172
// =============================================================================
168173
// OPENAI-SPECIFIC ATTRIBUTES
169174
// =============================================================================

packages/core/src/tracing/langgraph/index.ts

Lines changed: 5 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,28 @@
11
import { captureException } from '../../exports';
22
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes';
33
import { SPAN_STATUS_ERROR } from '../../tracing';
4-
import type { Span } from '../../types-hoist/span';
54
import {
65
GEN_AI_AGENT_NAME_ATTRIBUTE,
6+
GEN_AI_INVOKE_AGENT_OPERATION_ATTRIBUTE,
77
GEN_AI_OPERATION_NAME_ATTRIBUTE,
88
GEN_AI_PIPELINE_NAME_ATTRIBUTE,
99
GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE,
1010
GEN_AI_REQUEST_MESSAGES_ATTRIBUTE,
11-
GEN_AI_RESPONSE_TEXT_ATTRIBUTE,
12-
GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE,
1311
} from '../../utils/ai/gen-ai-attributes';
1412
import { truncateGenAiMessages } from '../../utils/ai/messageTruncation';
1513
import type { LangChainMessage } from '../../utils/langchain/types';
1614
import { normalizeLangChainMessages } from '../../utils/langchain/utils';
1715
import { startSpan } from '../trace';
1816
import { LANGGRAPH_ORIGIN } from './constants';
19-
import type { CompiledGraph, LangGraphOptions, LangGraphTool } from './types';
20-
import { extractModelMetadata, extractTokenUsageFromMetadata, extractToolCalls } from './utils';
17+
import type { CompiledGraph, LangGraphOptions } from './types';
18+
import { extractToolsFromCompiledGraph, setResponseAttributes } from './utils';
2119

2220
/**
2321
* Instruments StateGraph's compile method to create spans for agent creation and invocation
2422
*
2523
* Wraps the compile() method to:
2624
* - Create a `gen_ai.create_agent` span when compile() is called
27-
* - Automatically wrap the invoke() method on the returned compiled graph
25+
* - Automatically wrap the invoke() method on the returned compiled graph with a `gen_ai.invoke_agent` span
2826
*
2927
*/
3028
export function instrumentStateGraphCompile(
@@ -101,7 +99,7 @@ function instrumentCompiledGraphInvoke(
10199
name: 'invoke_agent',
102100
attributes: {
103101
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: LANGGRAPH_ORIGIN,
104-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.invoke_agent',
102+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: GEN_AI_INVOKE_AGENT_OPERATION_ATTRIBUTE,
105103
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'invoke_agent',
106104
},
107105
},
@@ -156,64 +154,3 @@ function instrumentCompiledGraphInvoke(
156154
},
157155
}) as (...args: unknown[]) => Promise<unknown>;
158156
}
159-
160-
/**
161-
* Extract tools from compiled graph structure
162-
*
163-
* Tools are stored in: compiledGraph.builder.nodes.tools.runnable.tools
164-
*/
165-
function extractToolsFromCompiledGraph(compiledGraph: CompiledGraph): unknown[] | null {
166-
if (!compiledGraph.builder?.nodes?.tools?.runnable?.tools) {
167-
return null;
168-
}
169-
170-
const tools = compiledGraph.builder?.nodes?.tools?.runnable?.tools;
171-
172-
if (!tools || !Array.isArray(tools) || tools.length === 0) {
173-
return null;
174-
}
175-
176-
// Extract name, description, and schema from each tool's lc_kwargs
177-
return tools.map((tool: LangGraphTool) => ({
178-
name: tool.lc_kwargs?.name,
179-
description: tool.lc_kwargs?.description,
180-
schema: tool.lc_kwargs?.schema,
181-
}));
182-
}
183-
184-
/**
185-
* Set response attributes on the span
186-
*/
187-
function setResponseAttributes(span: Span, inputMessages: LangChainMessage[] | null, result: unknown): void {
188-
// Extract messages from result
189-
const resultObj = result as { messages?: LangChainMessage[] } | undefined;
190-
const outputMessages = resultObj?.messages;
191-
192-
if (!outputMessages || !Array.isArray(outputMessages)) {
193-
return;
194-
}
195-
196-
// Get new messages (delta between input and output)
197-
const inputCount = inputMessages?.length ?? 0;
198-
const newMessages = outputMessages.length > inputCount ? outputMessages.slice(inputCount) : [];
199-
200-
if (newMessages.length === 0) {
201-
return;
202-
}
203-
204-
// Normalize the new messages
205-
const normalizedNewMessages = normalizeLangChainMessages(newMessages);
206-
span.setAttribute(GEN_AI_RESPONSE_TEXT_ATTRIBUTE, JSON.stringify(normalizedNewMessages));
207-
208-
// Extract and set tool calls from new messages
209-
const toolCalls = extractToolCalls(normalizedNewMessages);
210-
if (toolCalls) {
211-
span.setAttribute(GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, JSON.stringify(toolCalls));
212-
}
213-
214-
// Extract metadata from messages
215-
for (const message of newMessages) {
216-
extractTokenUsageFromMetadata(span, message);
217-
extractModelMetadata(span, message);
218-
}
219-
}

packages/core/src/tracing/langgraph/utils.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ import type { Span } from '../../types-hoist/span';
22
import {
33
GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE,
44
GEN_AI_RESPONSE_MODEL_ATTRIBUTE,
5+
GEN_AI_RESPONSE_TEXT_ATTRIBUTE,
6+
GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE,
57
GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE,
68
GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE,
79
GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE,
810
} from '../../utils/ai/gen-ai-attributes';
911
import type { LangChainMessage } from '../../utils/langchain/types';
12+
import { normalizeLangChainMessages } from '../../utils/langchain/utils';
13+
import type { CompiledGraph, LangGraphTool } from './types';
1014

1115
/**
1216
* Extract tool calls from messages
@@ -87,3 +91,65 @@ export function extractModelMetadata(span: Span, message: LangChainMessage): voi
8791
}
8892
}
8993
}
94+
95+
/**
96+
* Extract tools from compiled graph structure
97+
*
98+
* Tools are stored in: compiledGraph.builder.nodes.tools.runnable.tools
99+
*/
100+
export function extractToolsFromCompiledGraph(compiledGraph: CompiledGraph): unknown[] | null {
101+
if (!compiledGraph.builder?.nodes?.tools?.runnable?.tools) {
102+
return null;
103+
}
104+
105+
const tools = compiledGraph.builder?.nodes?.tools?.runnable?.tools;
106+
107+
if (!tools || !Array.isArray(tools) || tools.length === 0) {
108+
return null;
109+
}
110+
111+
// Extract name, description, and schema from each tool's lc_kwargs
112+
return tools.map((tool: LangGraphTool) => ({
113+
name: tool.lc_kwargs?.name,
114+
description: tool.lc_kwargs?.description,
115+
schema: tool.lc_kwargs?.schema,
116+
}));
117+
}
118+
119+
/**
120+
* Set response attributes on the span
121+
*/
122+
export function setResponseAttributes(span: Span, inputMessages: LangChainMessage[] | null, result: unknown): void {
123+
// Extract messages from result
124+
const resultObj = result as { messages?: LangChainMessage[] } | undefined;
125+
const outputMessages = resultObj?.messages;
126+
127+
if (!outputMessages || !Array.isArray(outputMessages)) {
128+
return;
129+
}
130+
131+
// Get new messages (delta between input and output)
132+
const inputCount = inputMessages?.length ?? 0;
133+
const newMessages = outputMessages.length > inputCount ? outputMessages.slice(inputCount) : [];
134+
135+
if (newMessages.length === 0) {
136+
return;
137+
}
138+
139+
// Extract and set tool calls from new messages BEFORE normalization
140+
// (normalization strips tool_calls, so we need to extract them first)
141+
const toolCalls = extractToolCalls(newMessages as Array<Record<string, unknown>>);
142+
if (toolCalls) {
143+
span.setAttribute(GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, JSON.stringify(toolCalls));
144+
}
145+
146+
// Normalize the new messages
147+
const normalizedNewMessages = normalizeLangChainMessages(newMessages);
148+
span.setAttribute(GEN_AI_RESPONSE_TEXT_ATTRIBUTE, JSON.stringify(normalizedNewMessages));
149+
150+
// Extract metadata from messages
151+
for (const message of newMessages) {
152+
extractTokenUsageFromMetadata(span, message);
153+
extractModelMetadata(span, message);
154+
}
155+
}

packages/google-cloud-serverless/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export {
5858
onUnhandledRejectionIntegration,
5959
openAIIntegration,
6060
langChainIntegration,
61-
langgraphIntegration,
61+
langGraphIntegration,
6262
modulesIntegration,
6363
contextLinesIntegration,
6464
nodeContextIntegration,

packages/node/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export { openAIIntegration } from './integrations/tracing/openai';
2828
export { anthropicAIIntegration } from './integrations/tracing/anthropic-ai';
2929
export { googleGenAIIntegration } from './integrations/tracing/google-genai';
3030
export { langChainIntegration } from './integrations/tracing/langchain';
31-
export { langgraphIntegration } from './integrations/tracing/langgraph';
31+
export { langGraphIntegration } from './integrations/tracing/langgraph';
3232
export {
3333
launchDarklyIntegration,
3434
buildLaunchDarklyFlagUsedHandler,

0 commit comments

Comments
 (0)