feat(teacher): stream Yuxi reasoning and answer previews
This commit is contained in:
1 parent
f443f3bc66
commit
8ccc4650a2
12 files changed
+364
-42
No files matched your search
@@ -759,6 +759,7 @@ it.each(['suggestions', 'reply-v1', 'discussion-v1'] as const)(
|
||||
let firstReads = 0, resumedReads = 0;
|
||||
const text = vi.fn();
|
||||
const activity = vi.fn();
|
||||
const stream = vi.fn();
|
||||
const transport: TeacherCloudTransport = {
|
||||
json: vi.fn(async (url) => {
|
||||
if (url === '/questions') return { request_id: requestId, run_id: 'before-read' };
|
||||
@@ -783,18 +784,31 @@ it.each(['suggestions', 'reply-v1', 'discussion-v1'] as const)(
|
||||
payload: { items: [{ stream_event: { type: 'message_delta', message_id: id, content } }] },
|
||||
});
|
||||
if (url.includes('/before-read/')) {
|
||||
accept('message', { thread_id: 'teacher-thread', payload: { items: [{ stream_event: {
|
||||
type: 'message_delta', message_id: 'preamble', reasoning_content: '需要先读取项目。',
|
||||
} }] } }, '0-1');
|
||||
accept('message', message('teacher-thread', 'preamble', '我先检查重力。'), '1-0');
|
||||
accept('message', message('teacher-thread', 'draft', '{"reply":"读取前的草案"}'), '2-0');
|
||||
} else {
|
||||
accept('message', message('child-thread', 'child', '子线程内容'), '1-0');
|
||||
accept('message', { thread_id: 'teacher-thread', payload: { items: [{ stream_event: {
|
||||
type: 'message_delta', message_id: 'final', reasoning_content: '已经获得项目证据。',
|
||||
} }] } }, '1-1');
|
||||
accept('message', message('teacher-thread', 'final', finalOutput.slice(0, 12)), '2-0');
|
||||
expect(stream).toHaveBeenLastCalledWith({ response: finalOutput.slice(0, 12) });
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
}
|
||||
}),
|
||||
};
|
||||
await prepareCloudTeacher(
|
||||
f.account, f.topic, requestId, f.access, f.progress, f.saveRequest, transport, activity
|
||||
f.account, f.topic, requestId, f.access, f.progress, f.saveRequest, transport, activity, stream
|
||||
).run([{ role: 'user', content: '一起讨论' }], new AbortController().signal, text);
|
||||
expect(text.mock.calls).toEqual([[finalOutput]]);
|
||||
expect(stream.mock.calls).toContainEqual([{ response: '' }]);
|
||||
expect(stream.mock.calls).toContainEqual([{ response: finalOutput.slice(0, 12) }]);
|
||||
expect(stream.mock.calls.flatMap(([update]) => update.reasoning ?? []).join(''))
|
||||
.toBe('需要先读取项目。\n\n已经获得项目证据。');
|
||||
expect(JSON.stringify(stream.mock.calls)).not.toContain('子线程内容');
|
||||
expect(activity.mock.calls).toEqual([
|
||||
[{ id: requestId + ':file', name: 'read_project_file', status: 'running' }],
|
||||
[{ id: requestId + ':file', name: 'read_project_file', status: 'completed' }],
|
||||
@@ -870,3 +884,38 @@ it('keeps only the cloud main-thread answer while advancing past child events on
|
||||
expect(transport.events).toHaveBeenCalledTimes(2);
|
||||
expect(streamUrls[1]).toContain('after_seq=2-0');
|
||||
});
|
||||
|
||||
it('streams main-thread reasoning through reconnect and drains a completed run before finalizing', async () => {
|
||||
const f = await fixture();
|
||||
f.topic.requests.push({ id: requestId, text: '检查', references: [], createdAt: 'now',
|
||||
sourceCursor: f.access.source.cursor, sourceCapturedAt: 'now', includedSourceMessageIds: [],
|
||||
omittedMessages: 0, status: 'running', response: '', presentation: 'reply-v1' });
|
||||
let reads = 0;
|
||||
const stream = vi.fn(), text = vi.fn();
|
||||
const transport: TeacherCloudTransport = {
|
||||
json: vi.fn(async url => url === '/questions' ? { request_id: requestId, run_id: 'one' }
|
||||
: ++reads === 1 ? { status: 'running', thread_id: 'teacher-thread' }
|
||||
: { status: 'completed', thread_id: 'teacher-thread', output: '{"reply":"最后正文","quickReplies":[]}' }),
|
||||
events: vi.fn(async (url, _signal, accept) => {
|
||||
const event = (thread_id: string, message_id: string, fields: Record<string, string>) => ({
|
||||
thread_id, payload: { chunk: { stream_event: { type: 'message_delta', message_id, ...fields } } },
|
||||
});
|
||||
if (url.endsWith('0-0')) {
|
||||
accept('message', event('teacher-thread', 'answer', { reasoning_content: '先看代码。' }), '1-0');
|
||||
expect(stream).toHaveBeenLastCalledWith({ reasoning: '先看代码。' });
|
||||
accept('message', event('child-thread', 'child', { reasoning_content: 'child-private' }), '2-0');
|
||||
throw new Error('stream interrupted');
|
||||
}
|
||||
expect(url).toContain('after_seq=2-0');
|
||||
accept('message', event('teacher-thread', 'answer', { additional_reasoning_content: '再确认结果。' }), '3-0');
|
||||
accept('message', event('teacher-thread', 'answer', { content: '{"reply":"最后' }), '4-0');
|
||||
expect(stream).toHaveBeenLastCalledWith({ response: '{"reply":"最后' });
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
}),
|
||||
};
|
||||
await prepareCloudTeacher(f.account, f.topic, requestId, f.access, f.progress, f.saveRequest,
|
||||
transport, undefined, stream).run([], new AbortController().signal, text);
|
||||
expect(stream.mock.calls.flatMap(([update]) => update.reasoning ?? []).join('')).toBe('先看代码。再确认结果。');
|
||||
expect(text.mock.calls).toEqual([['{"reply":"最后正文","quickReplies":[]}']]);
|
||||
expect(transport.events).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
Reference in new issue
Block a user