Archived
feat(loop-engine): analyze 动词 —— 系统组装入参、声明出参 schema、系统把结果落表
AI 在这里只是一个纯函数:不给工具、不给记忆、不多轮。 行数不由它决定 —— 出参是拿主键去 lookup 的一张查表,它漏一条那一格就是空, required 立刻把它变成 _missing > 0,提交闸与放行门当场拦住。 出参不成形就地重问一次,再不成抛出去交人。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { taskIdSchema } from '@contracts';
|
||||
import { createAnalyzeGateway } from '../wiring/analyze-gateway.ts';
|
||||
|
||||
const TASK = taskIdSchema.parse('11111111-1111-4111-8111-111111111111');
|
||||
const SCHEMA = { type: 'object', required: ['items'], properties: { items: { type: 'array' } } };
|
||||
const call = { taskId: TASK, model: 'deepseek-v4-flash', instruction: '逐条判四态', payload: [{ p: 'A' }], outputSchema: SCHEMA, label: 'S2#1' };
|
||||
|
||||
describe('analyze 网关', () => {
|
||||
it('出参是对象就直接回,入参 JSON 进 prompt、instruction 进 system', async () => {
|
||||
const seen: Array<Record<string, unknown>> = [];
|
||||
const gateway = createAnalyzeGateway({
|
||||
generate: (c: Record<string, unknown>) => {
|
||||
seen.push(c);
|
||||
return Promise.resolve({ text: '', object: { items: [{ p: 'A' }] }, usage: { inputTokens: 1, outputTokens: 1, latencyMs: 1 }, binding: { name: 'x', provider: 'deepseek', model: 'y' }, mode: 'live' });
|
||||
},
|
||||
} as never);
|
||||
const out = await gateway.run(call);
|
||||
expect(out.object).toEqual({ items: [{ p: 'A' }] });
|
||||
expect(seen[0]?.['system']).toBe('逐条判四态');
|
||||
expect(String(seen[0]?.['prompt'])).toContain('"p": "A"');
|
||||
expect(seen[0]?.['reasoning']).toBe('off');
|
||||
});
|
||||
|
||||
it('出参不成形 → **就地重问一次**,第二次成了就用它', async () => {
|
||||
let n = 0;
|
||||
const gateway = createAnalyzeGateway({
|
||||
generate: () => {
|
||||
n += 1;
|
||||
return Promise.resolve(
|
||||
n === 1
|
||||
? { text: 'not json', usage: { inputTokens: 1, outputTokens: 1, latencyMs: 1 }, binding: { name: 'x', provider: 'deepseek', model: 'y' }, mode: 'live' }
|
||||
: { text: '', object: { items: [] }, usage: { inputTokens: 1, outputTokens: 1, latencyMs: 1 }, binding: { name: 'x', provider: 'deepseek', model: 'y' }, mode: 'live' },
|
||||
);
|
||||
},
|
||||
} as never);
|
||||
const out = await gateway.run(call);
|
||||
expect(n).toBe(2);
|
||||
expect(out.object).toEqual({ items: [] });
|
||||
});
|
||||
|
||||
it('重问了还是不成形 → 抛,交人;**不重问第三次**', async () => {
|
||||
let n = 0;
|
||||
const gateway = createAnalyzeGateway({
|
||||
generate: () => {
|
||||
n += 1;
|
||||
return Promise.resolve({ text: 'nope', usage: { inputTokens: 1, outputTokens: 1, latencyMs: 1 }, binding: { name: 'x', provider: 'deepseek', model: 'y' }, mode: 'live' });
|
||||
},
|
||||
} as never);
|
||||
await expect(gateway.run(call)).rejects.toThrow(/出参不成形/);
|
||||
expect(n).toBe(2);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user