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); }); });