perf: 优化 Agent 流式会话处理

- 合并流式通知与持久化写入,限制浏览器会话缓存

- 增量投影轮次事件并稳定关联异步工具任务
This commit is contained in:
2026-07-27 19:41:11 +08:00
parent 5497931abd
commit b08eb009bb
10 changed files with 539 additions and 47 deletions

View File

@@ -241,4 +241,158 @@ describe('useAgentTryoutRawRounds', () => {
toolCallId: 'call-approval',
});
});
it('异步工作流轮询事件始终归并到首张审批卡', () => {
const store = useAgentTryoutRawRounds({
mode: 'draft',
sessionId: 'session-raw-async-tool',
});
const roundId = store.createRound('生成文档');
store.recordEvent(roundId, {
domain: 'TOOL',
payload: {
input: { user_input: '写一篇小作文' },
requestId: 'req-async',
resumeToken: 'resume-async',
toolCallId: 'submit-call-1',
toolName: '文档生成',
},
type: 'FORM_REQUEST',
});
store.recordEvent(roundId, {
domain: 'TOOL',
payload: {
asyncTool: true,
phase: 'submit',
sourceToolCallId: 'submit-call-1',
status: 'RUNNING',
taskId: 'task-1',
toolCallId: 'task-1',
toolName: '文档生成',
},
type: 'TOOL_RESULT',
});
for (const sourceToolCallId of ['observe-call-1', 'observe-call-2']) {
store.recordEvent(roundId, {
domain: 'TOOL',
payload: {
asyncTool: true,
input: { taskId: 'task-1' },
phase: 'observe',
sourceToolCallId,
status: 'RUNNING',
taskId: 'task-1',
toolCallId: 'task-1',
toolName: '文档生成',
},
type: 'TOOL_CALL',
});
}
store.recordEvent(roundId, {
domain: 'TOOL',
payload: {
asyncTool: true,
phase: 'result',
sourceToolCallId: 'result-call-1',
status: 'SUCCEEDED',
taskId: 'task-1',
toolCallId: 'task-1',
toolName: '文档生成',
},
type: 'TOOL_RESULT',
});
const tools = store
.buildTimelineItems()
.filter((item) => item.type === 'tool');
expect(tools).toHaveLength(1);
expect(tools[0]).toMatchObject({
mode: 'approval',
status: 'success',
taskId: 'task-1',
toolCallId: 'task-1',
toolName: '文档生成',
});
});
it('连续文本增量压缩后刷新内容保持一致', () => {
const sessionId = 'stream-compaction';
const store = useAgentTryoutRawRounds({
mode: 'draft',
sessionId,
});
const roundId = store.createRound('你好');
const liveItems = store.buildTimelineItems();
const assistantText = (
items: ReturnType<typeof store.buildTimelineItems>,
) =>
items
.flatMap((item) =>
item.type === 'message' && item.role === 'assistant'
? item.parts
.filter((part) => part.type === 'text')
.map((part) => part.content)
: [],
)
.join('');
for (const delta of ['你', '好', '', '世界']) {
const event = store.recordEvent(roundId, {
domain: 'LLM',
payload: { delta },
type: 'MESSAGE',
});
if (!event) {
throw new Error('流式事件记录失败');
}
store.projectEvent(liveItems, roundId, event);
}
expect(store.currentVariant(roundId)?.runtimeEvents).toHaveLength(1);
expect(store.currentVariant(roundId)?.runtimeEvents[0]?.payload.delta).toBe(
'你好,世界',
);
expect(assistantText(liveItems)).toBe('你好,世界');
store.flush();
const restored = useAgentTryoutRawRounds({
mode: 'draft',
sessionId,
});
expect(assistantText(restored.buildTimelineItems())).toBe('你好,世界');
});
it('存在待持久化增量时结束事件仍立即落盘', () => {
const sessionId = 'terminal-persist';
const store = useAgentTryoutRawRounds({
mode: 'draft',
sessionId,
});
const roundId = store.createRound('结束测试');
store.recordEvent(roundId, {
domain: 'LLM',
payload: { reasoning: '思考中' },
type: 'THINKING',
});
store.recordEvent(roundId, {
domain: 'SYSTEM',
payload: {},
type: 'DONE',
});
const restored = useAgentTryoutRawRounds({
mode: 'draft',
sessionId,
});
expect(restored.currentVariant(roundId)?.status).toBe('completed');
expect(
restored
.currentVariant(roundId)
?.runtimeEvents.some(
(event) => event.domain === 'SYSTEM' && event.type === 'DONE',
),
).toBe(true);
});
});