fix: 兼容旧模型内联思考标签
- 在结构化 reasoning 为空时解析正文开头的 think/thinking 标签 - 将思考与正文映射为现有流式事件并覆盖 Agent 与 Bot 链路 - 补充跨分片和旁路条件测试
This commit is contained in:
@@ -355,9 +355,10 @@ public class AgentRunService {
|
||||
AtomicBoolean finished = new AtomicBoolean(false);
|
||||
StringBuilder answer = new StringBuilder();
|
||||
ChatAssistantAccumulator assistantAccumulator = new ChatAssistantAccumulator();
|
||||
LegacyThinkingTagParser legacyThinkingTagParser = new LegacyThinkingTagParser();
|
||||
// 注册 emit 服务
|
||||
registerEmitterCancellation(requestId, chatSseEmitter, chatContext, answer,
|
||||
assistantAccumulator, finished, persistChatlog);
|
||||
assistantAccumulator, legacyThinkingTagParser, finished, persistChatlog);
|
||||
AgentRunLock.Handle lockHandle = initialLockHandle;
|
||||
try {
|
||||
if (persistChatlog) {
|
||||
@@ -394,10 +395,11 @@ public class AgentRunService {
|
||||
owner,
|
||||
lockHandle,
|
||||
event -> handleRuntimeEvent(event, requestId, chatSseEmitter, answer,
|
||||
assistantAccumulator, chatContext, finished, persistChatlog),
|
||||
error -> handleRuntimeError(error, requestId, chatSseEmitter, chatContext, finished, persistChatlog),
|
||||
() -> finishIfNeeded(requestId, chatSseEmitter, chatContext, answer,
|
||||
assistantAccumulator, finished, persistChatlog)
|
||||
assistantAccumulator, legacyThinkingTagParser, chatContext, finished, persistChatlog),
|
||||
error -> handleRuntimeStreamError(error, requestId, chatSseEmitter, chatContext, answer,
|
||||
assistantAccumulator, legacyThinkingTagParser, finished, persistChatlog),
|
||||
() -> finishRuntimeStream(requestId, chatSseEmitter, chatContext, answer,
|
||||
assistantAccumulator, legacyThinkingTagParser, finished, persistChatlog)
|
||||
);
|
||||
agentRunRegistry.register(runContext);
|
||||
lockHandle = null;
|
||||
@@ -469,10 +471,11 @@ public class AgentRunService {
|
||||
ChatRuntimeContext chatContext,
|
||||
StringBuilder answer,
|
||||
ChatAssistantAccumulator assistantAccumulator,
|
||||
LegacyThinkingTagParser legacyThinkingTagParser,
|
||||
AtomicBoolean finished,
|
||||
boolean persistChatlog) {
|
||||
Runnable cancelTask = () -> cancelDisconnectedRun(requestId, chatContext, answer,
|
||||
assistantAccumulator, finished, persistChatlog);
|
||||
assistantAccumulator, legacyThinkingTagParser, finished, persistChatlog);
|
||||
SseEmitter emitter = chatSseEmitter.getEmitter();
|
||||
emitter.onCompletion(cancelTask);
|
||||
emitter.onTimeout(cancelTask);
|
||||
@@ -483,6 +486,7 @@ public class AgentRunService {
|
||||
ChatRuntimeContext chatContext,
|
||||
StringBuilder answer,
|
||||
ChatAssistantAccumulator assistantAccumulator,
|
||||
LegacyThinkingTagParser legacyThinkingTagParser,
|
||||
AtomicBoolean finished,
|
||||
boolean persistChatlog) {
|
||||
if (!finished.compareAndSet(false, true)) {
|
||||
@@ -498,6 +502,7 @@ public class AgentRunService {
|
||||
}
|
||||
agentRunRegistry.remove(requestId);
|
||||
cancelPending(requestId, "客户端连接已断开,Agent 运行已取消", persistChatlog);
|
||||
appendAssistantSegments(legacyThinkingTagParser.finish(), answer, assistantAccumulator);
|
||||
if (!persistChatlog) {
|
||||
return;
|
||||
}
|
||||
@@ -517,32 +522,39 @@ public class AgentRunService {
|
||||
ChatRuntimeContext chatContext,
|
||||
AtomicBoolean finished,
|
||||
boolean persistChatlog) {
|
||||
handleRuntimeEvent(event, requestId, chatSseEmitter, answer, assistantAccumulator,
|
||||
new LegacyThinkingTagParser(), chatContext, finished, persistChatlog);
|
||||
}
|
||||
|
||||
private void handleRuntimeEvent(AgentRuntimeEvent event,
|
||||
String requestId,
|
||||
ChatSseEmitter chatSseEmitter,
|
||||
StringBuilder answer,
|
||||
ChatAssistantAccumulator assistantAccumulator,
|
||||
LegacyThinkingTagParser legacyThinkingTagParser,
|
||||
ChatRuntimeContext chatContext,
|
||||
AtomicBoolean finished,
|
||||
boolean persistChatlog) {
|
||||
if (event == null || event.getEventType() == null) {
|
||||
return;
|
||||
}
|
||||
recordRuntimeEvent(requestId, chatContext, event, persistChatlog);
|
||||
if (event.getEventType() == AgentRuntimeEventType.REASONING_STARTED) {
|
||||
emitAssistantSegments(legacyThinkingTagParser.finish(), requestId, chatSseEmitter, chatContext,
|
||||
answer, assistantAccumulator, legacyThinkingTagParser, finished, persistChatlog);
|
||||
legacyThinkingTagParser.reset();
|
||||
return;
|
||||
}
|
||||
if (event.getEventType() == AgentRuntimeEventType.MESSAGE_DELTA) {
|
||||
String text = stringPayload(event, "text");
|
||||
if (text != null) {
|
||||
answer.append(text);
|
||||
assistantAccumulator.appendContent(text);
|
||||
LOG.debug("Agent runtime message delta, requestId={}, deltaLength={}, answerLength={}, delta={}",
|
||||
requestId, text.length(), answer.length(), toVisibleLogText(text));
|
||||
if (!sendEnvelope(chatSseEmitter, ChatDomain.LLM, ChatType.MESSAGE, Map.of("delta", text, "role", "assistant"))) {
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator, finished, persistChatlog);
|
||||
}
|
||||
}
|
||||
emitAssistantSegments(legacyThinkingTagParser.acceptContent(text), requestId, chatSseEmitter, chatContext,
|
||||
answer, assistantAccumulator, legacyThinkingTagParser, finished, persistChatlog);
|
||||
return;
|
||||
}
|
||||
if (event.getEventType() == AgentRuntimeEventType.REASONING_DELTA) {
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
String reasoning = firstText(stringPayload(event, "reasoning"), stringPayload(event, "text"));
|
||||
assistantAccumulator.appendReasoning(reasoning);
|
||||
payload.put("reasoning", reasoning);
|
||||
payload.put("delta", reasoning);
|
||||
if (!sendEnvelope(chatSseEmitter, ChatDomain.LLM, ChatType.THINKING, payload)) {
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator, finished, persistChatlog);
|
||||
}
|
||||
emitAssistantSegments(legacyThinkingTagParser.acceptReasoning(reasoning), requestId, chatSseEmitter, chatContext,
|
||||
answer, assistantAccumulator, legacyThinkingTagParser, finished, persistChatlog);
|
||||
return;
|
||||
}
|
||||
if (event.getEventType() == AgentRuntimeEventType.TOOL_APPROVAL_REQUIRED) {
|
||||
@@ -550,17 +562,23 @@ public class AgentRunService {
|
||||
agentRunRegistry.registerResumeToken(requestId, resumeToken);
|
||||
recordApprovalRequired(requestId, chatContext, event, persistChatlog);
|
||||
if (!sendEnvelope(chatSseEmitter, ChatDomain.TOOL, ChatType.FORM_REQUEST, buildToolHitlPayload(requestId, event))) {
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator, finished, persistChatlog);
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator,
|
||||
legacyThinkingTagParser, finished, persistChatlog);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isAsyncToolEvent(event.getEventType())) {
|
||||
if (!sendEnvelope(chatSseEmitter, ChatDomain.TOOL, asyncToolChatType(event), buildAsyncToolEventPayload(event))) {
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator, finished, persistChatlog);
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator,
|
||||
legacyThinkingTagParser, finished, persistChatlog);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.getEventType() == AgentRuntimeEventType.TOOL_CALL) {
|
||||
if (!emitAssistantSegments(legacyThinkingTagParser.finish(), requestId, chatSseEmitter, chatContext,
|
||||
answer, assistantAccumulator, legacyThinkingTagParser, finished, persistChatlog)) {
|
||||
return;
|
||||
}
|
||||
LOG.info("Agent runtime tool call, requestId={}, toolCallId={}, payload={}, metadata={}",
|
||||
requestId, event.getToolCallId(), event.getPayload(), event.getMetadata());
|
||||
Map<String, Object> toolPayload = buildToolEventPayload(event);
|
||||
@@ -571,7 +589,8 @@ public class AgentRunService {
|
||||
firstNonNull(toolPayload.get("input"), toolPayload.get("toolInput"))
|
||||
);
|
||||
if (!sendEnvelope(chatSseEmitter, ChatDomain.TOOL, ChatType.TOOL_CALL, toolPayload)) {
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator, finished, persistChatlog);
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator,
|
||||
legacyThinkingTagParser, finished, persistChatlog);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -587,15 +606,19 @@ public class AgentRunService {
|
||||
toolPayload.get("text"))
|
||||
);
|
||||
if (!sendEnvelope(chatSseEmitter, ChatDomain.TOOL, ChatType.TOOL_RESULT, toolPayload)) {
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator, finished, persistChatlog);
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator,
|
||||
legacyThinkingTagParser, finished, persistChatlog);
|
||||
return;
|
||||
}
|
||||
legacyThinkingTagParser.reset();
|
||||
return;
|
||||
}
|
||||
if (event.getEventType() == AgentRuntimeEventType.KNOWLEDGE_RETRIEVAL) {
|
||||
LOG.info("Agent runtime knowledge retrieval, requestId={}, payload={}, metadata={}",
|
||||
requestId, event.getPayload(), event.getMetadata());
|
||||
if (!sendEnvelope(chatSseEmitter, ChatDomain.BUSINESS, ChatType.STATUS, buildKnowledgeRetrievalStatusPayload(event))) {
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator, finished, persistChatlog);
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator,
|
||||
legacyThinkingTagParser, finished, persistChatlog);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -604,7 +627,8 @@ public class AgentRunService {
|
||||
LOG.info("Agent runtime memory compression, requestId={}, eventType={}, payload={}, metadata={}",
|
||||
requestId, event.getEventType(), event.getPayload(), event.getMetadata());
|
||||
if (!sendEnvelope(chatSseEmitter, ChatDomain.BUSINESS, ChatType.STATUS, event.getPayload())) {
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator, finished, persistChatlog);
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator,
|
||||
legacyThinkingTagParser, finished, persistChatlog);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -612,7 +636,8 @@ public class AgentRunService {
|
||||
LOG.info("Agent runtime suspended, requestId={}, payload={}, metadata={}",
|
||||
requestId, event.getPayload(), event.getMetadata());
|
||||
if (!sendEnvelope(chatSseEmitter, ChatDomain.BUSINESS, ChatType.STATUS, buildSuspendedStatusPayload(event))) {
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator, finished, persistChatlog);
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator,
|
||||
legacyThinkingTagParser, finished, persistChatlog);
|
||||
return;
|
||||
}
|
||||
AgentRunRegistry.AgentRunContext runContext = agentRunRegistry.get(requestId);
|
||||
@@ -622,15 +647,20 @@ public class AgentRunService {
|
||||
return;
|
||||
}
|
||||
if (event.getEventType() == AgentRuntimeEventType.COMPLETED) {
|
||||
if (!emitAssistantSegments(legacyThinkingTagParser.finish(), requestId, chatSseEmitter, chatContext,
|
||||
answer, assistantAccumulator, legacyThinkingTagParser, finished, persistChatlog)) {
|
||||
return;
|
||||
}
|
||||
String finalText = stringPayload(event, "text");
|
||||
if (finalText != null && !finalText.isBlank()) {
|
||||
if (!legacyThinkingTagParser.isLegacyFormatDetected() && finalText != null && !finalText.isBlank()) {
|
||||
answer.setLength(0);
|
||||
answer.append(finalText);
|
||||
}
|
||||
List<Map<String, Object>> citations = buildKnowledgeCitationPayload(event);
|
||||
if (!citations.isEmpty()) {
|
||||
if (!sendEnvelope(chatSseEmitter, ChatDomain.BUSINESS, ChatType.CITATIONS, Map.of("items", citations))) {
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator, finished, persistChatlog);
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator,
|
||||
legacyThinkingTagParser, finished, persistChatlog);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -639,15 +669,150 @@ public class AgentRunService {
|
||||
return;
|
||||
}
|
||||
if (event.getEventType() == AgentRuntimeEventType.CANCELLED) {
|
||||
if (!emitAssistantSegments(legacyThinkingTagParser.finish(), requestId, chatSseEmitter, chatContext,
|
||||
answer, assistantAccumulator, legacyThinkingTagParser, finished, persistChatlog)) {
|
||||
return;
|
||||
}
|
||||
handleRuntimeCancelled(event, requestId, chatSseEmitter, chatContext, answer,
|
||||
assistantAccumulator, finished, persistChatlog);
|
||||
return;
|
||||
}
|
||||
if (event.getEventType() == AgentRuntimeEventType.FAILED) {
|
||||
if (!emitAssistantSegments(legacyThinkingTagParser.finish(), requestId, chatSseEmitter, chatContext,
|
||||
answer, assistantAccumulator, legacyThinkingTagParser, finished, persistChatlog)) {
|
||||
return;
|
||||
}
|
||||
handleRuntimeError(new BusinessException(errorMessage(event)), requestId, chatSseEmitter, chatContext, finished, persistChatlog);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将解析后的助手片段累计、持久化并发送到前端。
|
||||
*
|
||||
* @param segments 解析片段
|
||||
* @param requestId 运行请求 ID
|
||||
* @param chatSseEmitter SSE 发送器
|
||||
* @param chatContext 聊天上下文
|
||||
* @param answer 最终正文缓冲
|
||||
* @param assistantAccumulator 结构化消息缓冲
|
||||
* @param legacyThinkingTagParser 旧思考标签解析器
|
||||
* @param finished 完成标记
|
||||
* @param persistChatlog 是否持久化聊天记录
|
||||
* @return 全部片段发送成功时为 {@code true}
|
||||
*/
|
||||
private boolean emitAssistantSegments(List<LegacyThinkingTagParser.Segment> segments,
|
||||
String requestId,
|
||||
ChatSseEmitter chatSseEmitter,
|
||||
ChatRuntimeContext chatContext,
|
||||
StringBuilder answer,
|
||||
ChatAssistantAccumulator assistantAccumulator,
|
||||
LegacyThinkingTagParser legacyThinkingTagParser,
|
||||
AtomicBoolean finished,
|
||||
boolean persistChatlog) {
|
||||
for (LegacyThinkingTagParser.Segment segment : segments) {
|
||||
String text = segment.getText();
|
||||
ChatType chatType;
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
if (segment.getType() == LegacyThinkingTagParser.SegmentType.REASONING) {
|
||||
assistantAccumulator.appendReasoning(text);
|
||||
payload.put("reasoning", text);
|
||||
payload.put("delta", text);
|
||||
chatType = ChatType.THINKING;
|
||||
} else {
|
||||
answer.append(text);
|
||||
assistantAccumulator.appendContent(text);
|
||||
payload.put("delta", text);
|
||||
payload.put("role", "assistant");
|
||||
chatType = ChatType.MESSAGE;
|
||||
LOG.debug("Agent runtime message delta, requestId={}, deltaLength={}, answerLength={}, delta={}",
|
||||
requestId, text.length(), answer.length(), toVisibleLogText(text));
|
||||
}
|
||||
if (!sendEnvelope(chatSseEmitter, ChatDomain.LLM, chatType, payload)) {
|
||||
cancelDisconnectedRun(requestId, chatContext, answer, assistantAccumulator,
|
||||
legacyThinkingTagParser, finished, persistChatlog);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅累计解析片段,用于连接已断开后的部分消息持久化。
|
||||
*
|
||||
* @param segments 解析片段
|
||||
* @param answer 最终正文缓冲
|
||||
* @param assistantAccumulator 结构化消息缓冲
|
||||
*/
|
||||
private void appendAssistantSegments(List<LegacyThinkingTagParser.Segment> segments,
|
||||
StringBuilder answer,
|
||||
ChatAssistantAccumulator assistantAccumulator) {
|
||||
for (LegacyThinkingTagParser.Segment segment : segments) {
|
||||
if (segment.getType() == LegacyThinkingTagParser.SegmentType.REASONING) {
|
||||
assistantAccumulator.appendReasoning(segment.getText());
|
||||
} else {
|
||||
answer.append(segment.getText());
|
||||
assistantAccumulator.appendContent(segment.getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在运行时自然结束但未显式发出完成事件时收口兼容解析器。
|
||||
*
|
||||
* @param requestId 运行请求 ID
|
||||
* @param chatSseEmitter SSE 发送器
|
||||
* @param chatContext 聊天上下文
|
||||
* @param answer 最终正文缓冲
|
||||
* @param assistantAccumulator 结构化消息缓冲
|
||||
* @param legacyThinkingTagParser 旧思考标签解析器
|
||||
* @param finished 完成标记
|
||||
* @param persistChatlog 是否持久化聊天记录
|
||||
*/
|
||||
private void finishRuntimeStream(String requestId,
|
||||
ChatSseEmitter chatSseEmitter,
|
||||
ChatRuntimeContext chatContext,
|
||||
StringBuilder answer,
|
||||
ChatAssistantAccumulator assistantAccumulator,
|
||||
LegacyThinkingTagParser legacyThinkingTagParser,
|
||||
AtomicBoolean finished,
|
||||
boolean persistChatlog) {
|
||||
if (!emitAssistantSegments(legacyThinkingTagParser.finish(), requestId, chatSseEmitter, chatContext,
|
||||
answer, assistantAccumulator, legacyThinkingTagParser, finished, persistChatlog)) {
|
||||
return;
|
||||
}
|
||||
finishIfNeeded(requestId, chatSseEmitter, chatContext, answer,
|
||||
assistantAccumulator, finished, persistChatlog);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在运行时异常结束前发送兼容解析器中尚未收口的片段。
|
||||
*
|
||||
* @param error 运行异常
|
||||
* @param requestId 运行请求 ID
|
||||
* @param chatSseEmitter SSE 发送器
|
||||
* @param chatContext 聊天上下文
|
||||
* @param answer 最终正文缓冲
|
||||
* @param assistantAccumulator 结构化消息缓冲
|
||||
* @param legacyThinkingTagParser 旧思考标签解析器
|
||||
* @param finished 完成标记
|
||||
* @param persistChatlog 是否持久化聊天记录
|
||||
*/
|
||||
private void handleRuntimeStreamError(Throwable error,
|
||||
String requestId,
|
||||
ChatSseEmitter chatSseEmitter,
|
||||
ChatRuntimeContext chatContext,
|
||||
StringBuilder answer,
|
||||
ChatAssistantAccumulator assistantAccumulator,
|
||||
LegacyThinkingTagParser legacyThinkingTagParser,
|
||||
AtomicBoolean finished,
|
||||
boolean persistChatlog) {
|
||||
if (!emitAssistantSegments(legacyThinkingTagParser.finish(), requestId, chatSseEmitter, chatContext,
|
||||
answer, assistantAccumulator, legacyThinkingTagParser, finished, persistChatlog)) {
|
||||
return;
|
||||
}
|
||||
handleRuntimeError(error, requestId, chatSseEmitter, chatContext, finished, persistChatlog);
|
||||
}
|
||||
|
||||
private void finishIfNeeded(String requestId,
|
||||
ChatSseEmitter chatSseEmitter,
|
||||
ChatRuntimeContext chatContext,
|
||||
|
||||
Reference in New Issue
Block a user