Files
openmaic/OpenMAIC/tests/lib/chat/pi/director-tool-wiring.test.ts
2026-08-16 14:58:47 +08:00

174 lines
5.3 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import type { AgentConfig } from '@/lib/orchestration/registry/types';
import { buildDirectorPrompt } from '@/lib/chat/pi/prompts';
import { buildReadSceneTool } from '@/lib/chat/pi/tools/read-scene';
import { buildDirectorWebSearchTool } from '@/lib/chat/pi/tools/web-search';
import type { StatelessChatRequest } from '@/lib/types/chat';
const teacher: AgentConfig = {
id: 'teacher-1',
name: 'Teacher',
role: 'teacher',
persona: 'Teach from evidence.',
avatar: '',
color: '#3366ff',
allowedActions: [],
priority: 10,
createdAt: new Date('2026-01-01T00:00:00Z'),
updatedAt: new Date('2026-01-01T00:00:00Z'),
isDefault: true,
};
function makeBody(question: string): StatelessChatRequest {
return {
messages: [
{
id: 'user-1',
role: 'user',
parts: [{ type: 'text', text: question }],
},
],
storeState: {
stage: {
id: 'stage-1',
name: 'Quadratic Functions',
createdAt: 1,
updatedAt: 77,
},
outlines: [
{
id: 'outline-1',
type: 'slide',
title: 'Vertex form',
description: 'The current slide introduces the vertex form.',
keyPoints: ['y=a(x-h)^2+k'],
order: 1,
},
{
id: 'outline-2',
type: 'slide',
title: 'Axis of symmetry',
description: 'The next slide derives the axis of symmetry.',
keyPoints: ['x=h'],
order: 2,
},
],
scenes: [
{
id: 'scene-1',
outlineId: 'outline-1',
stageId: 'stage-1',
title: 'Vertex form',
order: 1,
type: 'slide',
content: { type: 'slide', canvas: { elements: [] } as never },
},
{
id: 'scene-2',
outlineId: 'outline-2',
stageId: 'stage-1',
title: 'Axis of symmetry',
order: 2,
type: 'slide',
updatedAt: 88,
content: {
type: 'slide',
canvas: {
elements: [
{
id: 'next-slide-proof',
type: 'text',
content: 'For y=a(x-h)^2+k, the axis of symmetry is x=h.',
left: 40,
top: 80,
width: 480,
height: 60,
},
],
} as never,
},
},
],
currentSceneId: 'scene-1',
mode: 'autonomous',
whiteboardOpen: false,
},
config: {
agentIds: [teacher.id],
agentConfigs: [teacher],
sessionType: 'qa',
},
apiKey: '',
} as StatelessChatRequest;
}
describe('Pi Director scene and web tool wiring', () => {
it('can resolve a question whose evidence exists only on the next slide', async () => {
const body = makeBody('下一页才会讲到的对称轴是什么?');
const prompt = buildDirectorPrompt(body, [teacher], 4);
const tool = buildReadSceneTool({ body });
const result = await tool.execute('read-next', { sceneId: 'scene-2' });
const text = result.content[0]?.type === 'text' ? result.content[0].text : '';
expect(prompt).toContain('order=2, sceneId="scene-2"');
expect(prompt).toContain('another scene, or the next scene');
expect(text).toContain('the axis of symmetry is x=h');
expect(result.details).toMatchObject({
status: 'ok',
sceneId: 'scene-2',
revision: '88',
source: 'request_start_snapshot',
});
});
it('uses sourced web evidence for a latest World Cup result boundary', async () => {
const body = makeBody('最新一届世界杯决赛结果是什么?');
const prompt = buildDirectorPrompt(body, [teacher], 4);
const searchResponses = vi.fn(async () => ({
answer: 'Verified current result.',
query: 'latest World Cup final result',
responseTime: 0.2,
sources: [
{
title: 'Official result',
url: 'https://example.test/official-world-cup-result',
content: 'Official match result.',
score: 1,
},
],
}));
const tool = buildDirectorWebSearchTool({
resolveConfig: () => ({
providerId: 'responses',
apiKey: 'test-key',
baseUrl: 'https://responses-proxy.test/v1',
model: 'search-model',
}),
searchResponses,
now: () => new Date('2026-07-21T08:00:00.000Z'),
});
const result = await tool.execute('search-latest', {
query: 'latest World Cup final result',
});
const text = result.content[0]?.type === 'text' ? result.content[0].text : '';
expect(prompt).toContain('current, recent, or externally verifiable information');
expect(searchResponses).toHaveBeenCalledOnce();
expect(text).toContain('https://example.test/official-world-cup-result');
expect(text).toContain('retrievedAt=2026-07-21T08:00:00.000Z');
});
it('keeps ordinary course questions on the scene path instead of the web path', () => {
const prompt = buildDirectorPrompt(
makeBody('二次函数顶点式中的 h 和 k 分别表示什么?'),
[teacher],
4,
);
expect(prompt).toContain('Do not call `web_search` for ordinary course-content questions');
expect(prompt).toContain('call `read_scene` with the exact sceneId');
});
});