发布 v1.10 #5
@@ -930,7 +930,7 @@ public class AgentRunService {
|
|||||||
buildAssistantRuntimeMessage(chatContext, finalAnswer, assistantAccumulator, citations));
|
buildAssistantRuntimeMessage(chatContext, finalAnswer, assistantAccumulator, citations));
|
||||||
chatRuntimeManager.recordCompleted(chatContext);
|
chatRuntimeManager.recordCompleted(chatContext);
|
||||||
}
|
}
|
||||||
sendDone(chatSseEmitter);
|
sendDone(chatSseEmitter, finalAnswer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRuntimeError(Throwable error,
|
private void handleRuntimeError(Throwable error,
|
||||||
@@ -1184,10 +1184,30 @@ public class AgentRunService {
|
|||||||
return chatSseEmitter.send(envelope);
|
return chatSseEmitter.send(envelope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送不携带最终正文的完成事件。
|
||||||
|
*
|
||||||
|
* @param chatSseEmitter SSE 发送器
|
||||||
|
* @return 发送成功时为 {@code true}
|
||||||
|
*/
|
||||||
private boolean sendDone(ChatSseEmitter chatSseEmitter) {
|
private boolean sendDone(ChatSseEmitter chatSseEmitter) {
|
||||||
|
return sendDone(chatSseEmitter, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送完成事件,并提供最终正文供前端校正流式增量。
|
||||||
|
*
|
||||||
|
* @param chatSseEmitter SSE 发送器
|
||||||
|
* @param finalText 最终完整正文;取消场景可为空
|
||||||
|
* @return 发送成功时为 {@code true}
|
||||||
|
*/
|
||||||
|
private boolean sendDone(ChatSseEmitter chatSseEmitter, String finalText) {
|
||||||
ChatEnvelope<Map<String, Object>> envelope = new ChatEnvelope<>();
|
ChatEnvelope<Map<String, Object>> envelope = new ChatEnvelope<>();
|
||||||
envelope.setDomain(ChatDomain.SYSTEM);
|
envelope.setDomain(ChatDomain.SYSTEM);
|
||||||
envelope.setType(ChatType.DONE);
|
envelope.setType(ChatType.DONE);
|
||||||
|
if (finalText != null) {
|
||||||
|
envelope.setPayload(Map.of("finalText", finalText));
|
||||||
|
}
|
||||||
return chatSseEmitter.sendDone(envelope);
|
return chatSseEmitter.sendDone(envelope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -229,8 +229,14 @@ public class AgentRunServiceDraftAndHitlTest {
|
|||||||
Assert.assertEquals("先分析", reasoning.toString());
|
Assert.assertEquals("先分析", reasoning.toString());
|
||||||
Assert.assertEquals("\n最终回答", content.toString());
|
Assert.assertEquals("\n最终回答", content.toString());
|
||||||
Assert.assertEquals("\n最终回答", answer.toString());
|
Assert.assertEquals("\n最终回答", answer.toString());
|
||||||
Assert.assertTrue(emitter.envelopes.stream().anyMatch(envelope ->
|
ChatEnvelope<?> done = emitter.envelopes.stream()
|
||||||
envelope.getDomain() == ChatDomain.SYSTEM && envelope.getType() == ChatType.DONE));
|
.filter(envelope -> envelope.getDomain() == ChatDomain.SYSTEM
|
||||||
|
&& envelope.getType() == ChatType.DONE)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> donePayload = (Map<String, Object>) done.getPayload();
|
||||||
|
Assert.assertEquals("\n最终回答", donePayload.get("finalText"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -298,6 +298,38 @@ describe('agentTimelineAdapter', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('reconciles streamed text with the canonical final answer', () => {
|
||||||
|
const items: any[] = [];
|
||||||
|
|
||||||
|
for (const delta of [
|
||||||
|
'http://127.0.0.1:39',
|
||||||
|
'0',
|
||||||
|
'/easyflow/file.docx',
|
||||||
|
]) {
|
||||||
|
applyAgentSseEnvelope(items, {
|
||||||
|
domain: 'LLM',
|
||||||
|
type: 'MESSAGE',
|
||||||
|
payload: { delta },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
applyAgentSseEnvelope(items, {
|
||||||
|
domain: 'SYSTEM',
|
||||||
|
type: 'DONE',
|
||||||
|
payload: {
|
||||||
|
finalText: 'http://127.0.0.1:39000/easyflow/file.docx',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const assistant = items.find(
|
||||||
|
(item): item is ChatTimelineMessageItem =>
|
||||||
|
item.type === 'message' && item.role === 'assistant',
|
||||||
|
);
|
||||||
|
expect(assistant?.parts[0]?.content).toBe(
|
||||||
|
'http://127.0.0.1:39000/easyflow/file.docx',
|
||||||
|
);
|
||||||
|
expect(assistant?.status).toBe('done');
|
||||||
|
});
|
||||||
|
|
||||||
it('applies streaming text, HITL approval and error envelopes', () => {
|
it('applies streaming text, HITL approval and error envelopes', () => {
|
||||||
const items: any[] = [];
|
const items: any[] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -534,6 +534,10 @@ export function applyAgentSseEnvelope(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (domain === 'SYSTEM' && type === 'DONE') {
|
if (domain === 'SYSTEM' && type === 'DONE') {
|
||||||
|
const finalText = asText(payload.finalText ?? payload.text);
|
||||||
|
if (finalText) {
|
||||||
|
ChatTimelineBuilder.replaceMessageContent(items, finalText);
|
||||||
|
}
|
||||||
ChatTimelineBuilder.finalize(items);
|
ChatTimelineBuilder.finalize(items);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user