fix: 兼容旧模型内联思考标签
- 在结构化 reasoning 为空时解析正文开头的 think/thinking 标签 - 将思考与正文映射为现有流式事件并覆盖 Agent 与 Bot 链路 - 补充跨分片和旁路条件测试
This commit is contained in:
@@ -23,6 +23,7 @@ import tech.easyflow.core.runtime.ChatRuntimeContext;
|
||||
import tech.easyflow.core.runtime.ChatRuntimeExtKeys;
|
||||
import tech.easyflow.core.runtime.ChatRuntimeManager;
|
||||
import tech.easyflow.core.runtime.ChatRuntimeMessage;
|
||||
import tech.easyflow.core.runtime.LegacyThinkingTagParser;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
@@ -43,6 +44,7 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
private final ChatRuntimeManager chatRuntimeManager;
|
||||
private final ChatRuntimeContext runtimeContext;
|
||||
private final ChatAssistantAccumulator assistantAccumulator;
|
||||
private final LegacyThinkingTagParser legacyThinkingTagParser = new LegacyThinkingTagParser();
|
||||
// 核心标记:是否允许执行onStop业务逻辑(仅最后一次无后续工具调用时为true)
|
||||
private boolean canStop = true;
|
||||
// 辅助标记:是否进入过工具调用(避免重复递归判断)
|
||||
@@ -65,6 +67,7 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
|
||||
@Override
|
||||
public void onStart(StreamContext context) {
|
||||
legacyThinkingTagParser.reset();
|
||||
StreamResponseListener.super.onStart(context);
|
||||
}
|
||||
|
||||
@@ -80,6 +83,7 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
return;
|
||||
}
|
||||
if (aiMessage.isFinalDelta() && aiMessageResponse.hasToolCalls()) {
|
||||
flushLegacyThinkingSegments();
|
||||
this.canStop = false; // 工具调用期间,禁止执行onStop
|
||||
this.hasToolCall = true; // 标记已进入过工具调用
|
||||
List<ToolCall> toolCalls = aiMessage.getToolCalls();
|
||||
@@ -103,20 +107,7 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
if (this.hasToolCall) {
|
||||
this.canStop = true;
|
||||
}
|
||||
String reasoningContent = aiMessage.getReasoningContent();
|
||||
if (reasoningContent != null && !reasoningContent.isEmpty()) {
|
||||
assistantAccumulator.appendReasoning(reasoningContent);
|
||||
chatRuntimeManager.recordAssistantDelta(runtimeContext, buildAssistantDeltaMessage(reasoningContent, ChatType.THINKING));
|
||||
sendChatEnvelope(sseEmitter, reasoningContent, ChatType.THINKING);
|
||||
} else {
|
||||
String delta = aiMessage.getContent();
|
||||
if (delta != null && !delta.isEmpty()) {
|
||||
assistantAccumulator.appendContent(delta);
|
||||
chatRuntimeManager.recordAssistantDelta(runtimeContext, buildAssistantDeltaMessage(delta, ChatType.MESSAGE));
|
||||
sendChatEnvelope(sseEmitter, delta, ChatType.MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
handleAssistantDelta(aiMessage);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Chat stream onMessage failed, conversationId={}, message={}, exception={}",
|
||||
@@ -137,6 +128,7 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
sendSystemError(sseEmitter, context.getThrowable().getMessage(), context.getThrowable());
|
||||
return;
|
||||
}
|
||||
flushLegacyThinkingSegments();
|
||||
memoryPrompt.addMessage(context.getFullMessage());
|
||||
chatRuntimeManager.recordAssistantCompleted(runtimeContext, buildAssistantCompletedMessage(context));
|
||||
chatRuntimeManager.recordCompleted(runtimeContext);
|
||||
@@ -186,6 +178,49 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 归一化并发送单个模型增量。
|
||||
*
|
||||
* @param aiMessage 模型增量消息
|
||||
*/
|
||||
private void handleAssistantDelta(AiMessage aiMessage) {
|
||||
String reasoningContent = aiMessage.getReasoningContent();
|
||||
if (StringUtil.hasText(reasoningContent)) {
|
||||
emitAssistantSegments(legacyThinkingTagParser.acceptReasoning(reasoningContent));
|
||||
return;
|
||||
}
|
||||
emitAssistantSegments(legacyThinkingTagParser.acceptContent(aiMessage.getContent()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送兼容解析后的思考与正文片段。
|
||||
*
|
||||
* @param segments 解析片段
|
||||
*/
|
||||
private void emitAssistantSegments(List<LegacyThinkingTagParser.Segment> segments) {
|
||||
for (LegacyThinkingTagParser.Segment segment : segments) {
|
||||
String text = segment.getText();
|
||||
if (segment.getType() == LegacyThinkingTagParser.SegmentType.REASONING) {
|
||||
assistantAccumulator.appendReasoning(text);
|
||||
chatRuntimeManager.recordAssistantDelta(runtimeContext,
|
||||
buildAssistantDeltaMessage(text, ChatType.THINKING));
|
||||
sendChatEnvelope(sseEmitter, text, ChatType.THINKING);
|
||||
continue;
|
||||
}
|
||||
assistantAccumulator.appendContent(text);
|
||||
chatRuntimeManager.recordAssistantDelta(runtimeContext,
|
||||
buildAssistantDeltaMessage(text, ChatType.MESSAGE));
|
||||
sendChatEnvelope(sseEmitter, text, ChatType.MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收口并发送旧标签解析器仍保留的少量前缀。
|
||||
*/
|
||||
private void flushLegacyThinkingSegments() {
|
||||
emitAssistantSegments(legacyThinkingTagParser.finish());
|
||||
}
|
||||
|
||||
private void sendToolCallEnvelope(ToolCall toolCall) {
|
||||
if (toolCall == null) {
|
||||
return;
|
||||
@@ -330,7 +365,10 @@ public class ChatStreamListener implements StreamResponseListener {
|
||||
message.setRole("assistant");
|
||||
message.setContentType("TEXT");
|
||||
String fullContent = context != null && context.getFullMessage() != null ? context.getFullMessage().getContent() : null;
|
||||
message.setContentText(StringUtil.hasText(fullContent) ? fullContent : assistantAccumulator.getContent());
|
||||
String normalizedContent = assistantAccumulator.getContent();
|
||||
message.setContentText(legacyThinkingTagParser.isLegacyFormatDetected()
|
||||
? normalizedContent
|
||||
: (StringUtil.hasText(fullContent) ? fullContent : normalizedContent));
|
||||
message.setContentPayload(assistantAccumulator.buildPayload(message.getContentText()));
|
||||
message.setCreatedAt(new Date());
|
||||
message.setSenderId(runtimeContext.getAssistantId());
|
||||
|
||||
Reference in New Issue
Block a user