Browse Source

fix 流式响应

Grizzly 4 months ago
parent
commit
138dae7bbd

+ 41 - 5
src/main/java/edu/nju/software/aipaasagent/agent/core/reactagent/ToolCallAgent.java

@@ -22,6 +22,7 @@ import org.springframework.ai.chat.model.ChatModel;
 import org.springframework.ai.chat.model.ChatResponse;
 import org.springframework.ai.chat.prompt.ChatOptions;
 import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.model.tool.ToolCallingChatOptions;
 import org.springframework.ai.model.tool.ToolCallingManager;
 import org.springframework.ai.model.tool.ToolExecutionResult;
 import org.springframework.ai.tool.ToolCallback;
@@ -294,7 +295,7 @@ public class ToolCallAgent extends ReActAgent {
                 .map(this::eventToSse)
                 .doOnCancel(() -> {
                     log.warn("[ToolCallAgent] 客户端取消订阅 - chatId: {}", chatId);
-                    state = AgentState.IDLE;
+                    cleanup();
                 });
     }
 
@@ -420,7 +421,7 @@ public class ToolCallAgent extends ReActAgent {
         return sink.asFlux()
                 .doOnCancel(() -> {
                     log.warn("[ToolCallAgent] SSE流式 - 客户端取消订阅 - chatId: {}", chatId);
-                    state = AgentState.IDLE;
+                    cleanup();
                 });
     }
 
@@ -438,7 +439,19 @@ public class ToolCallAgent extends ReActAgent {
                 // 获取最后一条消息作为结果
                 Message lastMsg = CollUtil.getLast(currentConversationMessages);
                 if (lastMsg instanceof AssistantMessage) {
-                    emitEvent(StreamEvent.contentChunk(((AssistantMessage) lastMsg).getText()));
+                    String fullText = ((AssistantMessage) lastMsg).getText();
+                    if (fullText != null && !fullText.isEmpty()) {
+                        // 打字机效果:按单个字符分片发射(模拟打字机)
+                        for (int i = 0; i < fullText.length(); i++) {
+                            emitEvent(StreamEvent.contentChunk(String.valueOf(fullText.charAt(i))));
+                            // 可选:加个小延迟让打字效果更明显(但不要太长影响体验)
+                            try {
+                                Thread.sleep(10);
+                            } catch (InterruptedException e) {
+                                Thread.currentThread().interrupt();
+                            }
+                        }
+                    }
                 }
                 state = AgentState.FINISHED;
                 return false;
@@ -490,11 +503,17 @@ public class ToolCallAgent extends ReActAgent {
         try {
             String enhancedSystemPrompt = buildEnhancedSystemPrompt();
 
-            // 使用完整的对话历史
+            // 【关键】注册工具让 AI 知道,但设置 .internalToolExecutionEnabled(false) 不让 Spring AI 自动执行!
+            // 工具执行交给 act() 方法手动执行
+            ToolCallingChatOptions chatOptions = ToolCallingChatOptions.builder()
+                    .toolCallbacks(toolRegister.getTools().toArray(new ToolCallback[0]))
+                    .internalToolExecutionEnabled(false)  // 【关键】禁用自动执行!
+                    .build();
+            
             ChatResponse chatResponse = chatClient.prompt()
                     .system(enhancedSystemPrompt)
                     .messages(currentConversationMessages)
-                    .toolCallbacks(toolRegister.getTools().toArray(new ToolCallback[0]))
+                    .options(chatOptions)
                     .call()
                     .chatResponse();
 
@@ -669,4 +688,21 @@ public class ToolCallAgent extends ReActAgent {
                 .doOnNext(chunk -> log.debug("[ToolCallAgent] 流式输出 - chatId: {}, chunk: {}", chatId, chunk))
                 .doOnComplete(() -> log.info("[ToolCallAgent] 流式对话完成 - chatId: {}", chatId));
     }
+
+    /**
+     * 清理资源,重置状态
+     * 每次对话结束后调用,确保可以进行下一次对话
+     */
+    @Override
+    protected void cleanup() {
+        log.info("[ToolCallAgent] 清理资源,重置状态");
+        // 重置状态
+        this.state = AgentState.IDLE;
+        // 清空对话上下文
+        this.currentConversationMessages.clear();
+        // 重置当前步数
+        this.currentStep = 0;
+        // 清空 eventSink
+        this.eventSink = null;
+    }
 }