Skip to content

Commit f264f85

Browse files
committed
add tests for stream events
1 parent 1cf94a0 commit f264f85

File tree

1 file changed

+267
-0
lines changed

1 file changed

+267
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/**
2+
* Tests for MCPAgent streamEvents() tool history preservation
3+
*
4+
* These tests verify that the streamEvents() method properly preserves
5+
* tool call information in conversation history by reconstructing it
6+
* from event streams. This is critical for enabling LLMs to reference
7+
* previous tool executions in multi-turn conversations.
8+
*
9+
* The streamEvents() method has comprehensive tool history preservation
10+
* logic implemented. These tests validate that the implementation is
11+
* present and correctly structured.
12+
*/
13+
14+
import { beforeEach, describe, expect, it, vi } from 'vitest'
15+
import { MCPAgent, MCPClient } from '../index.js'
16+
17+
// Mock the MCP client for testing
18+
vi.mock('../src/client.js', () => ({
19+
MCPClient: vi.fn().mockImplementation(() => ({
20+
getAllActiveSessions: vi.fn().mockResolvedValue({}),
21+
createAllSessions: vi.fn().mockResolvedValue({}),
22+
closeAllSessions: vi.fn().mockResolvedValue(undefined),
23+
})),
24+
}))
25+
26+
// Mock the LangChain adapter
27+
vi.mock('../src/adapters/langchain_adapter.js', () => ({
28+
LangChainAdapter: vi.fn().mockImplementation(() => ({
29+
createToolsFromConnectors: vi.fn().mockResolvedValue([
30+
{
31+
name: 'test_tool',
32+
description: 'A test tool',
33+
schema: {},
34+
func: vi.fn().mockResolvedValue('Test tool result'),
35+
},
36+
]),
37+
})),
38+
}))
39+
40+
describe('mCPAgent - streamEvents() Tool History Preservation', () => {
41+
let agent: MCPAgent
42+
let mockLLM: any
43+
let mockClient: any
44+
45+
beforeEach(() => {
46+
// Setup mocks following existing patterns
47+
mockLLM = {
48+
_llmType: 'fake',
49+
_modelType: 'base_chat_model',
50+
invoke: vi.fn().mockResolvedValue({ content: 'Test response' }),
51+
stream: vi.fn(),
52+
}
53+
54+
mockClient = new MCPClient()
55+
56+
// Create agent with memory enabled
57+
agent = new MCPAgent({
58+
llm: mockLLM,
59+
client: mockClient,
60+
memoryEnabled: true,
61+
})
62+
})
63+
64+
describe('implementation Validation', () => {
65+
it('should verify tool history preservation requirements are implemented', () => {
66+
// This test validates that the critical requirements for tool history preservation are met
67+
// by inspecting the source code of the streamEvents method
68+
69+
const streamEventsCode = agent.streamEvents.toString()
70+
71+
// Basic structure - verify the method has tool tracking variables
72+
expect(streamEventsCode).toContain('toolCalls')
73+
expect(streamEventsCode).toContain('toolResults')
74+
expect(streamEventsCode).toContain('toolStartEvents')
75+
76+
// Event processing - verify it handles the required events
77+
expect(streamEventsCode).toContain('on_tool_start')
78+
expect(streamEventsCode).toContain('on_tool_end')
79+
expect(streamEventsCode).toContain('on_chain_end')
80+
81+
// Tool call ID handling - verify it generates IDs when missing
82+
expect(streamEventsCode).toContain('toolCallId')
83+
expect(streamEventsCode).toContain('randomUUID')
84+
85+
// History preservation - verify it adds the correct message types
86+
expect(streamEventsCode).toContain('addToHistory')
87+
expect(streamEventsCode).toContain('HumanMessage')
88+
expect(streamEventsCode).toContain('AIMessage')
89+
expect(streamEventsCode).toContain('ToolMessage')
90+
expect(streamEventsCode).toContain('tool_calls')
91+
expect(streamEventsCode).toContain('tool_call_id')
92+
93+
// Critical edge case - verify it handles tool execution without final response
94+
expect(streamEventsCode).toContain('finalResponse || toolCalls.length > 0')
95+
expect(streamEventsCode).toContain('getToolExecutionPlaceholder')
96+
97+
// Error handling - verify robust error handling is present
98+
expect(streamEventsCode).toContain('try')
99+
expect(streamEventsCode).toContain('catch')
100+
expect(streamEventsCode).toContain('warn')
101+
expect(streamEventsCode).toContain('error')
102+
})
103+
104+
it('should have proper tool event tracking structure', () => {
105+
const code = agent.streamEvents.toString()
106+
107+
// Verify tool start event handling
108+
expect(code).toContain('toolStartEvents.set(event.run_id, event)')
109+
110+
// Verify tool end event processing
111+
expect(code).toContain('toolStartEvents.get(event.run_id)')
112+
113+
// Verify tool call creation
114+
expect(code).toContain('toolCalls.push')
115+
expect(code).toContain('id: toolCallId')
116+
expect(code).toContain('name: startEvent.name')
117+
expect(code).toContain('args: startEvent.data.input')
118+
119+
// Verify tool result storage
120+
expect(code).toContain('toolResults.push')
121+
expect(code).toContain('tool_call_id: toolCallId')
122+
expect(code).toContain('content: outputContent')
123+
124+
// Verify cleanup
125+
expect(code).toContain('toolStartEvents.delete(event.run_id)')
126+
})
127+
128+
it('should have proper final response handling', () => {
129+
const code = agent.streamEvents.toString()
130+
131+
// Verify all output format handling (compiled versions)
132+
expect(code).toContain('typeof output === "string"')
133+
expect(code).toContain('Array.isArray(output)')
134+
expect(code).toContain('output[0]?.text')
135+
expect(code).toContain('output.output')
136+
expect(code).toContain('output.answer || output.text || output.content')
137+
138+
// Verify error handling for unexpected formats
139+
expect(code).toContain('Unexpected chain end output format')
140+
})
141+
142+
it('should have comprehensive error handling', () => {
143+
const code = agent.streamEvents.toString()
144+
145+
// Verify event validation (compiled versions)
146+
expect(code).toContain('Invalid event structure')
147+
expect(code).toContain('typeof event !== "object"')
148+
expect(code).toContain('!event.event')
149+
150+
// Verify tool event validation
151+
expect(code).toContain('Invalid on_tool_start event')
152+
expect(code).toContain('missing run_id or name')
153+
expect(code).toContain('Invalid tool start event data')
154+
155+
// Verify orphaned event handling
156+
expect(code).toContain('orphaned tool start events')
157+
expect(code).toContain('on_tool_end event without matching')
158+
159+
// Verify serialization error handling
160+
expect(code).toContain('Failed to serialize tool output')
161+
expect(code).toContain('jsonError')
162+
163+
// Verify history preservation error handling
164+
expect(code).toContain('Error adding to conversation history')
165+
expect(code).toContain('historyError')
166+
})
167+
168+
it('should have memory management features', () => {
169+
const code = agent.streamEvents.toString()
170+
171+
// Verify memory-enabled check
172+
expect(code).toContain('this.memoryEnabled')
173+
174+
// Verify cleanup
175+
expect(code).toContain('toolStartEvents.delete')
176+
expect(code).toContain('toolStartEvents.size > 0')
177+
178+
// Verify proper message ordering (compiled versions)
179+
expect(code).toContain('addToHistory')
180+
expect(code).toContain('HumanMessage')
181+
expect(code).toContain('AIMessage')
182+
expect(code).toContain('ToolMessage')
183+
expect(code).toContain('tool_calls')
184+
})
185+
186+
it('should have output truncation capabilities', () => {
187+
const code = agent.streamEvents.toString()
188+
189+
// Verify truncation is applied
190+
expect(code).toContain('getEffectiveTruncationConfig')
191+
expect(code).toContain('applyTruncation')
192+
193+
// Verify it handles both tool-specific and default configs
194+
expect(code).toContain('startEvent.name') // Used for tool-specific config
195+
196+
// Verify it handles serialization errors with truncation
197+
expect(code).toContain('fallbackContent')
198+
})
199+
})
200+
201+
describe('method Structure Validation', () => {
202+
it('should be an async generator function', () => {
203+
expect(agent.streamEvents.constructor.name).toBe('AsyncGeneratorFunction')
204+
})
205+
206+
it('should have correct parameter signature', () => {
207+
const code = agent.streamEvents.toString()
208+
expect(code).toMatch(/streamEvents\s*\(\s*query\s*,\s*maxSteps\s*,\s*manageConnector\s*=\s*true\s*,\s*externalHistory\s*\)/)
209+
})
210+
211+
it('should initialize tracking variables at start', () => {
212+
const code = agent.streamEvents.toString()
213+
214+
// Verify initial variable setup (compiled versions)
215+
expect(code).toContain('let finalResponse = ""')
216+
expect(code).toContain('const toolCalls = []')
217+
expect(code).toContain('const toolResults = []')
218+
expect(code).toContain('const toolStartEvents =')
219+
expect(code).toContain('new Map()')
220+
})
221+
222+
it('should preserve history after event processing completes', () => {
223+
const code = agent.streamEvents.toString()
224+
225+
// Verify the history preservation happens after event processing
226+
expect(code).toContain('addToHistory')
227+
expect(code).toContain('HumanMessage')
228+
expect(code).toContain('finalResponse || toolCalls.length > 0')
229+
expect(code).toContain('AIMessage')
230+
expect(code).toContain('toolResults.forEach')
231+
expect(code).toContain('ToolMessage')
232+
})
233+
})
234+
235+
describe('integration Points', () => {
236+
it('should have proper integration with agent executor', () => {
237+
const code = agent.streamEvents.toString()
238+
239+
// Verify it gets the agent executor
240+
expect(code).toContain('this.agentExecutor')
241+
expect(code).toContain('agentExecutor.streamEvents')
242+
expect(code).toContain('agentExecutor.maxIterations = steps')
243+
})
244+
245+
it('should have proper telemetry integration', () => {
246+
const code = agent.streamEvents.toString()
247+
248+
expect(code).toContain('this.telemetry.trackAgentExecution')
249+
expect(code).toContain('executionMethod: "streamEvents"')
250+
expect(code).toContain('eventCount')
251+
expect(code).toContain('totalResponseLength')
252+
})
253+
254+
it('should integrate with truncation system', () => {
255+
const code = agent.streamEvents.toString()
256+
257+
expect(code).toContain('this.getEffectiveTruncationConfig')
258+
expect(code).toContain('this.applyTruncation')
259+
})
260+
261+
it('should integrate with placeholder message system', () => {
262+
const code = agent.streamEvents.toString()
263+
264+
expect(code).toContain('this.getToolExecutionPlaceholder()')
265+
})
266+
})
267+
})

0 commit comments

Comments
 (0)