package com.vincent.rsf.server.ai.service.impl.chat;
|
|
import com.vincent.rsf.server.ai.dto.AiChatThinkingEventDto;
|
import org.springframework.util.StringUtils;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
|
import java.time.Instant;
|
import java.util.Objects;
|
|
public class AiThinkingTraceEmitter {
|
|
private final AiSseEventPublisher aiSseEventPublisher;
|
private final SseEmitter emitter;
|
private final String requestId;
|
private final Long sessionId;
|
private String currentPhase;
|
private String currentStatus;
|
|
public AiThinkingTraceEmitter(AiSseEventPublisher aiSseEventPublisher, SseEmitter emitter, String requestId, Long sessionId) {
|
this.aiSseEventPublisher = aiSseEventPublisher;
|
this.emitter = emitter;
|
this.requestId = requestId;
|
this.sessionId = sessionId;
|
}
|
|
public void startAnalyze() {
|
if (currentPhase != null) {
|
return;
|
}
|
currentPhase = "ANALYZE";
|
currentStatus = "STARTED";
|
emitThinkingEvent("ANALYZE", "STARTED", "正在分析问题",
|
"已接收你的问题,正在理解意图并判断是否需要调用工具。", null);
|
}
|
|
public void onToolStart(String toolName, String toolCallId) {
|
switchPhase("TOOL_CALL", "STARTED", "正在调用工具", "已判断需要调用工具,正在查询相关信息。", null);
|
currentStatus = "UPDATED";
|
emitThinkingEvent("TOOL_CALL", "UPDATED", "正在调用工具",
|
"正在调用工具 " + safeLabel(toolName, "未知工具") + " 获取所需信息。", toolCallId);
|
}
|
|
public void onToolResult(String toolName, String toolCallId, boolean failed) {
|
currentPhase = "TOOL_CALL";
|
currentStatus = failed ? "FAILED" : "UPDATED";
|
emitThinkingEvent("TOOL_CALL", failed ? "FAILED" : "UPDATED",
|
failed ? "工具调用失败" : "工具调用完成",
|
failed
|
? "工具 " + safeLabel(toolName, "未知工具") + " 调用失败,正在评估失败影响并整理可用信息。"
|
: "工具 " + safeLabel(toolName, "未知工具") + " 已返回结果,正在继续分析并提炼关键信息。",
|
toolCallId);
|
}
|
|
public void startAnswer() {
|
switchPhase("ANSWER", "STARTED", "正在整理答案", "已完成分析,正在组织最终回复内容。", null);
|
}
|
|
public void completeCurrentPhase() {
|
if (!StringUtils.hasText(currentPhase) || isTerminalStatus(currentStatus)) {
|
return;
|
}
|
currentStatus = "COMPLETED";
|
emitThinkingEvent(currentPhase, "COMPLETED", resolveCompleteTitle(currentPhase),
|
resolveCompleteContent(currentPhase), null);
|
}
|
|
public void markTerminated(String terminalStatus) {
|
if (!StringUtils.hasText(currentPhase) || isTerminalStatus(currentStatus)) {
|
return;
|
}
|
currentStatus = terminalStatus;
|
emitThinkingEvent(currentPhase, terminalStatus,
|
"ABORTED".equals(terminalStatus) ? "思考已中止" : "思考失败",
|
"ABORTED".equals(terminalStatus)
|
? "本轮对话已被中止,思考过程提前结束。"
|
: "本轮对话在生成答案前失败,当前思考过程已停止。",
|
null);
|
}
|
|
private void switchPhase(String nextPhase, String nextStatus, String title, String content, String toolCallId) {
|
if (!Objects.equals(currentPhase, nextPhase)) {
|
completeCurrentPhase();
|
}
|
currentPhase = nextPhase;
|
currentStatus = nextStatus;
|
emitThinkingEvent(nextPhase, nextStatus, title, content, toolCallId);
|
}
|
|
private void emitThinkingEvent(String phase, String status, String title, String content, String toolCallId) {
|
aiSseEventPublisher.emitSafely(emitter, "thinking", AiChatThinkingEventDto.builder()
|
.requestId(requestId)
|
.sessionId(sessionId)
|
.phase(phase)
|
.status(status)
|
.title(title)
|
.content(content)
|
.toolCallId(toolCallId)
|
.timestamp(Instant.now().toEpochMilli())
|
.build());
|
}
|
|
private boolean isTerminalStatus(String status) {
|
return "COMPLETED".equals(status) || "FAILED".equals(status) || "ABORTED".equals(status);
|
}
|
|
private String resolveCompleteTitle(String phase) {
|
if ("ANSWER".equals(phase)) {
|
return "答案整理完成";
|
}
|
if ("TOOL_CALL".equals(phase)) {
|
return "工具分析完成";
|
}
|
return "问题分析完成";
|
}
|
|
private String resolveCompleteContent(String phase) {
|
if ("ANSWER".equals(phase)) {
|
return "最终答复已生成完成。";
|
}
|
if ("TOOL_CALL".equals(phase)) {
|
return "工具调用阶段已结束,相关信息已整理完毕。";
|
}
|
return "问题意图和处理方向已分析完成。";
|
}
|
|
private String safeLabel(String value, String fallback) {
|
return StringUtils.hasText(value) ? value : fallback;
|
}
|
}
|