Files
openmaic/OpenMAIC/tests/qa/agent.test.ts
2026-08-16 14:58:47 +08:00

83 lines
2.8 KiB
TypeScript

import { describe, expect, test, vi } from 'vitest';
import { buildQaSystemPrompt, runQaAgent, type QaStreamEvent } from '@/lib/qa/agent';
import type { CoursewareKnowledge, RetrievedChunk } from '@/lib/qa/knowledge';
vi.mock('@/lib/ai/llm', () => ({
streamLLM: (_params: unknown, _source: string) => ({
textStream: (async function* () {
yield '你';
yield '好';
})(),
}),
}));
const courseware: CoursewareKnowledge = {
coursewareId: 'cw-1',
version: 1,
language: 'zh-CN',
title: '示例课件:光合作用',
knowledge: { coursewareId: 'cw-1', scenes: [] },
quiz: { scenes: [] },
};
const chunks: RetrievedChunk[] = [
{
sceneId: 's1',
order: 1,
title: '光合作用概述',
type: 'slide',
text: '叶绿体是光合作用的场所。',
narration: '今天我们来学习光合作用。',
score: 4,
},
];
describe('buildQaSystemPrompt', () => {
test('embeds courseware title, language, chunks and profile', () => {
const prompt = buildQaSystemPrompt(courseware, chunks, '目标是理解光合作用;初二学生');
expect(prompt).toContain('示例课件:光合作用');
expect(prompt).toContain('请使用zh-CN回答');
expect(prompt).toContain('光合作用概述');
expect(prompt).toContain('叶绿体是光合作用的场所');
expect(prompt).toContain('学习者档案');
expect(prompt).toContain('初二学生');
});
test('omits profile section when absent', () => {
const prompt = buildQaSystemPrompt(courseware, [], undefined);
expect(prompt).not.toContain('学习者档案');
expect(prompt).toContain('(本次未检索到相关课件内容)');
});
});
describe('runQaAgent', () => {
test('streams text deltas and a done event (no tools)', async () => {
const events: QaStreamEvent[] = [];
for await (const event of runQaAgent(
{ model: {} as never, courseware, chunks },
[{ role: 'user', content: '光合作用发生在哪里?' }],
)) {
events.push(event);
}
const text = events.filter((e) => e.type === 'text');
expect(text.map((e) => (e as { delta: string }).delta).join('')).toBe('你好');
const done = events.find((e) => e.type === 'done') as { type: 'done'; sources: RetrievedChunk[]; toolCalls: number };
expect(done.sources).toEqual(chunks);
expect(done.toolCalls).toBe(0);
});
test('bounded context: keeps only the last QA_MAX_CONTEXT_TURNS turns', async () => {
const turns = Array.from({ length: 20 }, (_, i) => ({
role: 'user' as const,
content: `消息${i}`,
}));
const events: QaStreamEvent[] = [];
for await (const event of runQaAgent({ model: {} as never, courseware, chunks: [] }, turns)) {
events.push(event);
}
// Stream ran to completion without error; the mock ignores messages.
expect(events.some((e) => e.type === 'done')).toBe(true);
});
});