Skip to content

Commit b4b6895

Browse files
Vikhyath MondretiVikhyath Mondreti
authored andcommitted
add tool calls to trace span
1 parent 5b7c07c commit b4b6895

File tree

2 files changed

+358
-91
lines changed

2 files changed

+358
-91
lines changed
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
import { describe, expect, test } from 'vitest'
2+
import type { ExecutionResult } from '@/executor/types'
3+
import { buildTraceSpans, stripCustomToolPrefix } from './trace-spans'
4+
5+
describe('buildTraceSpans', () => {
6+
test('should extract tool calls from agent block output with toolCalls.list format', () => {
7+
const mockExecutionResult: ExecutionResult = {
8+
success: true,
9+
output: { content: 'Final output' },
10+
logs: [
11+
{
12+
blockId: 'agent-1',
13+
blockName: 'Test Agent',
14+
blockType: 'agent',
15+
startedAt: '2024-01-01T10:00:00.000Z',
16+
endedAt: '2024-01-01T10:00:05.000Z',
17+
durationMs: 5000,
18+
success: true,
19+
input: { userPrompt: 'Test prompt' },
20+
output: {
21+
content: 'Agent response',
22+
model: 'gpt-4o',
23+
tokens: { prompt: 10, completion: 20, total: 30 },
24+
providerTiming: {
25+
duration: 4000,
26+
startTime: '2024-01-01T10:00:00.500Z',
27+
endTime: '2024-01-01T10:00:04.500Z',
28+
},
29+
toolCalls: {
30+
list: [
31+
{
32+
name: 'custom_test_tool',
33+
arguments: { input: 'test input' },
34+
result: { output: 'test output' },
35+
duration: 1000,
36+
startTime: '2024-01-01T10:00:01.000Z',
37+
endTime: '2024-01-01T10:00:02.000Z',
38+
},
39+
{
40+
name: 'http_request',
41+
arguments: { url: 'https://api.example.com' },
42+
result: { status: 200, data: 'response' },
43+
duration: 2000,
44+
startTime: '2024-01-01T10:00:02.000Z',
45+
endTime: '2024-01-01T10:00:04.000Z',
46+
},
47+
],
48+
count: 2,
49+
},
50+
},
51+
},
52+
],
53+
}
54+
55+
const { traceSpans } = buildTraceSpans(mockExecutionResult)
56+
57+
expect(traceSpans).toHaveLength(1)
58+
const agentSpan = traceSpans[0]
59+
expect(agentSpan.type).toBe('agent')
60+
expect(agentSpan.toolCalls).toBeDefined()
61+
expect(agentSpan.toolCalls).toHaveLength(2)
62+
63+
// Check first tool call
64+
const firstToolCall = agentSpan.toolCalls![0]
65+
expect(firstToolCall.name).toBe('test_tool') // custom_ prefix should be stripped
66+
expect(firstToolCall.duration).toBe(1000)
67+
expect(firstToolCall.status).toBe('success')
68+
expect(firstToolCall.input).toEqual({ input: 'test input' })
69+
expect(firstToolCall.output).toEqual({ output: 'test output' })
70+
71+
// Check second tool call
72+
const secondToolCall = agentSpan.toolCalls![1]
73+
expect(secondToolCall.name).toBe('http_request')
74+
expect(secondToolCall.duration).toBe(2000)
75+
expect(secondToolCall.status).toBe('success')
76+
expect(secondToolCall.input).toEqual({ url: 'https://api.example.com' })
77+
expect(secondToolCall.output).toEqual({ status: 200, data: 'response' })
78+
})
79+
80+
test('should extract tool calls from agent block output with direct toolCalls array format', () => {
81+
const mockExecutionResult: ExecutionResult = {
82+
success: true,
83+
output: { content: 'Final output' },
84+
logs: [
85+
{
86+
blockId: 'agent-2',
87+
blockName: 'Test Agent 2',
88+
blockType: 'agent',
89+
startedAt: '2024-01-01T10:00:00.000Z',
90+
endedAt: '2024-01-01T10:00:03.000Z',
91+
durationMs: 3000,
92+
success: true,
93+
input: { userPrompt: 'Test prompt' },
94+
output: {
95+
content: 'Agent response',
96+
model: 'gpt-4o',
97+
providerTiming: {
98+
duration: 2500,
99+
startTime: '2024-01-01T10:00:00.250Z',
100+
endTime: '2024-01-01T10:00:02.750Z',
101+
},
102+
toolCalls: [
103+
{
104+
name: 'serper_search',
105+
arguments: { query: 'test search' },
106+
result: { results: ['result1', 'result2'] },
107+
duration: 1500,
108+
startTime: '2024-01-01T10:00:00.500Z',
109+
endTime: '2024-01-01T10:00:02.000Z',
110+
},
111+
],
112+
},
113+
},
114+
],
115+
}
116+
117+
const { traceSpans } = buildTraceSpans(mockExecutionResult)
118+
119+
expect(traceSpans).toHaveLength(1)
120+
const agentSpan = traceSpans[0]
121+
expect(agentSpan.toolCalls).toBeDefined()
122+
expect(agentSpan.toolCalls).toHaveLength(1)
123+
124+
const toolCall = agentSpan.toolCalls![0]
125+
expect(toolCall.name).toBe('serper_search')
126+
expect(toolCall.duration).toBe(1500)
127+
expect(toolCall.status).toBe('success')
128+
expect(toolCall.input).toEqual({ query: 'test search' })
129+
expect(toolCall.output).toEqual({ results: ['result1', 'result2'] })
130+
})
131+
132+
test('should extract tool calls from streaming response with executionData format', () => {
133+
const mockExecutionResult: ExecutionResult = {
134+
success: true,
135+
output: { content: 'Final output' },
136+
logs: [
137+
{
138+
blockId: 'agent-3',
139+
blockName: 'Streaming Agent',
140+
blockType: 'agent',
141+
startedAt: '2024-01-01T10:00:00.000Z',
142+
endedAt: '2024-01-01T10:00:04.000Z',
143+
durationMs: 4000,
144+
success: true,
145+
input: { userPrompt: 'Test prompt' },
146+
output: {
147+
content: 'Agent response',
148+
model: 'gpt-4o',
149+
executionData: {
150+
output: {
151+
toolCalls: {
152+
list: [
153+
{
154+
name: 'custom_analysis_tool',
155+
arguments: { data: 'sample data' },
156+
result: { analysis: 'completed' },
157+
duration: 2000,
158+
startTime: '2024-01-01T10:00:01.000Z',
159+
endTime: '2024-01-01T10:00:03.000Z',
160+
},
161+
],
162+
},
163+
},
164+
},
165+
},
166+
},
167+
],
168+
}
169+
170+
const { traceSpans } = buildTraceSpans(mockExecutionResult)
171+
172+
expect(traceSpans).toHaveLength(1)
173+
const agentSpan = traceSpans[0]
174+
expect(agentSpan.toolCalls).toBeDefined()
175+
expect(agentSpan.toolCalls).toHaveLength(1)
176+
177+
const toolCall = agentSpan.toolCalls![0]
178+
expect(toolCall.name).toBe('analysis_tool') // custom_ prefix should be stripped
179+
expect(toolCall.duration).toBe(2000)
180+
expect(toolCall.status).toBe('success')
181+
expect(toolCall.input).toEqual({ data: 'sample data' })
182+
expect(toolCall.output).toEqual({ analysis: 'completed' })
183+
})
184+
185+
test('should handle tool calls with errors', () => {
186+
const mockExecutionResult: ExecutionResult = {
187+
success: true,
188+
output: { content: 'Final output' },
189+
logs: [
190+
{
191+
blockId: 'agent-4',
192+
blockName: 'Error Agent',
193+
blockType: 'agent',
194+
startedAt: '2024-01-01T10:00:00.000Z',
195+
endedAt: '2024-01-01T10:00:02.000Z',
196+
durationMs: 2000,
197+
success: true,
198+
input: { userPrompt: 'Test prompt' },
199+
output: {
200+
content: 'Agent response',
201+
model: 'gpt-4o',
202+
toolCalls: {
203+
list: [
204+
{
205+
name: 'failing_tool',
206+
arguments: { input: 'test' },
207+
error: 'Tool execution failed',
208+
duration: 1000,
209+
startTime: '2024-01-01T10:00:00.500Z',
210+
endTime: '2024-01-01T10:00:01.500Z',
211+
},
212+
],
213+
count: 1,
214+
},
215+
},
216+
},
217+
],
218+
}
219+
220+
const { traceSpans } = buildTraceSpans(mockExecutionResult)
221+
222+
expect(traceSpans).toHaveLength(1)
223+
const agentSpan = traceSpans[0]
224+
expect(agentSpan.toolCalls).toBeDefined()
225+
expect(agentSpan.toolCalls).toHaveLength(1)
226+
227+
const toolCall = agentSpan.toolCalls![0]
228+
expect(toolCall.name).toBe('failing_tool')
229+
expect(toolCall.status).toBe('error')
230+
expect(toolCall.error).toBe('Tool execution failed')
231+
expect(toolCall.input).toEqual({ input: 'test' })
232+
})
233+
234+
test('should handle blocks without tool calls', () => {
235+
const mockExecutionResult: ExecutionResult = {
236+
success: true,
237+
output: { content: 'Final output' },
238+
logs: [
239+
{
240+
blockId: 'text-1',
241+
blockName: 'Text Block',
242+
blockType: 'text',
243+
startedAt: '2024-01-01T10:00:00.000Z',
244+
endedAt: '2024-01-01T10:00:01.000Z',
245+
durationMs: 1000,
246+
success: true,
247+
input: { content: 'Hello world' },
248+
output: { content: 'Hello world' },
249+
},
250+
],
251+
}
252+
253+
const { traceSpans } = buildTraceSpans(mockExecutionResult)
254+
255+
expect(traceSpans).toHaveLength(1)
256+
const textSpan = traceSpans[0]
257+
expect(textSpan.type).toBe('text')
258+
expect(textSpan.toolCalls).toBeUndefined()
259+
})
260+
})
261+
262+
describe('stripCustomToolPrefix', () => {
263+
test('should strip custom_ prefix from tool names', () => {
264+
expect(stripCustomToolPrefix('custom_test_tool')).toBe('test_tool')
265+
expect(stripCustomToolPrefix('custom_analysis')).toBe('analysis')
266+
})
267+
268+
test('should leave non-custom tool names unchanged', () => {
269+
expect(stripCustomToolPrefix('http_request')).toBe('http_request')
270+
expect(stripCustomToolPrefix('serper_search')).toBe('serper_search')
271+
expect(stripCustomToolPrefix('regular_tool')).toBe('regular_tool')
272+
})
273+
})

0 commit comments

Comments
 (0)