/** * Pi 运行时用例(任务 A3.3)。 * * 1. 未配置密钥时必须快速失败并给出指引(不是静默挂住) * 2. 真实跑一轮:需要 APS_PI_INTEGRATION=1 且提供密钥(网络调用,默认跳过) * —— 密钥有效时断言收到文本;无效时断言错误被归到六分类之一;两者都必须以 turn_end 收尾 */ import assert from 'node:assert/strict'; import { test } from 'node:test'; import { classifyPiFailure, mapPiEvent } from '@aps/server/agent/pi/runtime'; const integrationEnabled = process.env.APS_PI_INTEGRATION === '1'; const apiKey = process.env.APS_LLM_API_KEY ?? process.env.DEEPSEEK_API_KEY ?? ''; test('未配置模型密钥时快速失败并给出配置指引', async () => { // 环境变量也算配置来源,这里临时清掉,确保「真的没有密钥」 const saved = process.env.DEEPSEEK_API_KEY; delete process.env.DEEPSEEK_API_KEY; try { const { createPiRuntime } = await import('@aps/server/agent/pi/runtime'); const runtime = createPiRuntime({ provider: 'deepseek', model: 'deepseek-chat', apiKey: '' }); await assert.rejects( () => runtime.createSession({ sessionId: 'no-key', subject: { userId: 'u', tenantId: 't' }, projectId: null, tools: [] }), /未配置模型密钥/, ); } finally { if (saved !== undefined) process.env.DEEPSEEK_API_KEY = saved; } }); test('错误文本按六分类归一', () => { assert.equal(classifyPiFailure('401: Authentication Fails'), 'forbidden'); assert.equal(classifyPiFailure('429 rate limit exceeded'), 'rate_limited'); assert.equal(classifyPiFailure('connect ETIMEDOUT'), 'unavailable'); assert.equal(classifyPiFailure('something odd'), 'unavailable'); }); test('真实跑一轮:Pi 子进程接入', { skip: !integrationEnabled ? '需要 APS_PI_INTEGRATION=1 与密钥(会真实调用模型)' : false }, async () => { const { createPiRuntime } = await import('@aps/server/agent/pi/runtime'); const runtime = createPiRuntime({ provider: 'deepseek', model: 'deepseek-chat', apiKey, turnTimeoutMs: 60_000 }); const session = await runtime.createSession({ sessionId: `it-${Date.now()}`, subject: { userId: 'it', tenantId: 'it' }, projectId: null, tools: [], }); const events = []; try { for await (const event of session.send('只用一句话回答:1+1 等于几')) { events.push(event); if (event.type === 'turn_end') break; } } finally { await session.dispose(); } const types = events.map((event) => event.type); assert.ok(types.includes('turn_end'), `必须以 turn_end 收尾,实际:${types.join(',')}`); const failed = events.find((event) => event.type === 'error'); const text = events.filter((event) => event.type === 'text').map((event) => event.delta).join(''); if (failed) { assert.ok( ['invalid_argument', 'forbidden', 'conflict', 'rate_limited', 'unavailable', 'unknown_result'].includes(failed.kind), `错误分类必须合法,实际 ${failed.kind}`, ); console.log(`(本轮模型调用失败,已按 ${failed.kind} 归一)`); } else { assert.ok(text.length > 0 || types.includes('message_end'), `既没有文本也没有错误,事件:${types.join(',')}`); } }); test('Pi 事件映射:工具名还原成能力 id,执行号取平台回执里的那个', () => { const resolve = (toolName) => (toolName === 'fake_echo' ? 'fake.echo' : toolName); const start = mapPiEvent({ type: 'tool_execution_start', toolCallId: 'call_1', toolName: 'fake_echo', args: { text: '你好' } }, resolve); assert.deepEqual(start, [{ type: 'tool_call', callId: 'call_1', capabilityId: 'fake.echo', args: { text: '你好' } }]); const end = mapPiEvent( { type: 'tool_execution_end', toolCallId: 'call_1', toolName: 'fake_echo', isError: false, result: { content: [{ type: 'text', text: '{}' }], details: { executionId: 'exec-9', status: 'succeeded', result: { echo: '你好' } } }, }, resolve, ); assert.equal(end[0].type, 'tool_result'); assert.equal(end[0].receipt.executionId, 'exec-9', '执行号必须取平台回执里的,而不是 Pi 的 callId'); assert.equal(end[0].receipt.status, 'succeeded'); const failed = mapPiEvent( { type: 'tool_execution_end', toolCallId: 'call_2', toolName: 'fake_echo', isError: true, result: { details: { executionId: 'exec-10', status: 'failed', errorKind: 'forbidden' } } }, resolve, ); assert.equal(failed[0].receipt.status, 'failed'); assert.equal(failed[0].receipt.errorKind, 'forbidden'); const text = mapPiEvent({ type: 'message_update', assistantMessageEvent: { type: 'text_delta', delta: '你好' } }, resolve); assert.deepEqual(text, [{ type: 'text', delta: '你好' }]); assert.deepEqual(mapPiEvent({ type: 'agent_settled' }, resolve), [{ type: 'turn_end', reason: 'stop' }]); });