/** * 运行: cd client/src && npx tsx tests/chat/toolCallParser.test.ts */ import { assistantOutputOffsetForRound, parseToolCallFromWireRound, } from '../../features/chat/toolCallParser'; let passed = 0; let failed = 0; function assert(desc: string, cond: boolean) { if (cond) { console.log(` ✓ ${desc}`); passed++; } else { console.error(` ✗ ${desc}`); failed++; } } const TOOL_CALL = '\n{"name": "get_weather", "arguments": {"city": "北京"}}\n'; assert('round 0 offset = firstPrompt 长度', assistantOutputOffsetForRound(0, 120, 200) === 120); assert('round > 0 offset = 本轮开始前 wire 长度', assistantOutputOffsetForRound(1, 120, 500) === 500); const firstPrompt = '<|im_start|>assistant\n'; const teacherForcing = TOOL_CALL; const wireRound0 = firstPrompt + teacherForcing; const parsedFromTfOnly = parseToolCallFromWireRound( wireRound0, assistantOutputOffsetForRound(0, firstPrompt.length, wireRound0.length) ); assert( '首轮 TF 含完整 tool call、无模型续写 → parsed', parsedFromTfOnly.status === 'parsed' && parsedFromTfOnly.call.name === 'get_weather' ); const tfPrefix = '\n{"name": "get_weather", "arguments": {"city": "'; const modelSuffix = '北京"}}\n'; const wireSplit = firstPrompt + tfPrefix + modelSuffix; const parsedSplit = parseToolCallFromWireRound( wireSplit, assistantOutputOffsetForRound(0, firstPrompt.length, firstPrompt.length + tfPrefix.length) ); assert( 'TF 与模型续写拼成完整 tool call → parsed', parsedSplit.status === 'parsed' && parsedSplit.call.name === 'get_weather' ); const wireRound1 = wireSplit + '<|im_end|>\n<|im_start|>tool\n{}\n<|im_end|>\n<|im_start|>assistant\n'; const round1Output = TOOL_CALL; const wireAfterRound1 = wireRound1 + round1Output; const parsedRound1 = parseToolCallFromWireRound( wireAfterRound1, assistantOutputOffsetForRound(1, firstPrompt.length, wireRound1.length) ); assert( '后续轮仅模型续写含 tool call → parsed', parsedRound1.status === 'parsed' && parsedRound1.call.name === 'get_weather' ); console.log(`\n${passed} passed, ${failed} failed`); process.exit(failed > 0 ? 1 : 0);