对话流程
本文档介绍 Xiuxian 模块的对话处理流程,包括 SSE 流式输出、会话历史管理等。
SSE 流式输出
为什么使用 SSE
传统 HTTP 请求需要等待 LLM 完整响应后才能返回,用户体验差。SSE(Server-Sent Events)支持流式输出,让用户实时看到 AI 的思考和执行过程。
接口定义
java
@PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter chatStream(@RequestBody ChatRequest request, HttpServletResponse response) {
response.setHeader("X-Accel-Buffering", "no"); // 禁用缓冲
response.setHeader("Cache-Control", "no-cache");
return toolAgentService.chatSse(request.getConversationId(), request.getMessage(), request.getMockUserId());
}流式处理核心逻辑
java
public SseEmitter chatSse(String conversationId, String message, String mockUserId) {
SseEmitter emitter = new SseEmitter(60000L); // 60秒超时
CompletableFuture.runAsync(() -> doChatSse(convId, message, emitter, context, user));
emitter.onTimeout(() -> { /* 超时处理 */ });
return emitter;
}响应类型
AgentResponse
java
@Data
@Builder
public class AgentResponse {
private Type type;
private String content;
private Step step;
public enum Type {
CONTENT, // 文本内容
THINKING, // 思考/进度
STEP_START, // 步骤开始
STEP_COMPLETE, // 步骤完成
ERROR, // 错误
DONE // 完成
}
}响应类型说明
| 类型 | 说明 | 前端展示 |
|---|---|---|
CONTENT | LLM 生成的文本内容 | 正常显示 |
THINKING | 思考/进度状态 | 灰色/斜体 |
STEP_START | 步骤开始 | 进度指示器 |
STEP_COMPLETE | 步骤完成 | 更新进度状态 |
ERROR | 错误信息 | 红色提示 |
DONE | 对话完成 | 隐藏加载状态 |
创建响应
java
// 内容响应
AgentResponse.content("查询结果如下...")
// 思考响应
AgentResponse.thinking("正在查询用户信息...")
// 错误响应
AgentResponse.error("未找到该用户")
// 完成响应
AgentResponse.done()
// 步骤响应
AgentResponse.stepStart("step1", "query_user", "查询用户")
AgentResponse.stepComplete("step1", "query_user", "查询用户", "completed")进度通知
发送进度
工具执行过程中可以发送进度通知:
java
@Tool(description = "查询修士信息")
public String queryUserBasic(String userName) {
notifyProgress("正在查阅【%s】的基本信息...", userName);
XiuxianUserEntity user = userService.fuzzyGetByName(userName);
notifyProgress("成功查阅到【%s】的基本信息", user.getName());
return render(USER_INFO, params);
}实现原理
java
// BaseToolSupport.java
protected void notifyProgress(String message) {
AgentContext ctx = AgentContextHolder.getContext();
if (ctx != null) {
ctx.sendProgress(message);
}
}
// AgentContext.java
public void sendProgress(String message) {
if (sseEmitter != null) {
sseEmitter.send(SseEmitter.event()
.name("message")
.data(AgentResponse.thinking(message + "\n")));
}
}前端展示
用户:查询张三的信息
AI:正在思考...
正在查阅【张三】的基本信息...
成功查阅到【张三】的基本信息
### 【张三】修士信息
| 属性 | 值 |
|------|------|
| 境界 | 筑基初期 |
...会话历史管理
核心操作
java
@Service
public class XiuxianConversationService {
// 获取或创建会话
public XiuxianChatConversation getOrCreateConversation(String conversationId, String userId) { ... }
// 保存用户消息
public void saveUserMessage(String conversationId, String content) { ... }
// 保存助手消息
public void saveAssistantMessage(String conversationId, String content) { ... }
}历史加载
java
private List<Message> getConversationHistory(String conversationId, int limit) {
// 从数据库加载最近 N 条消息,转换为 Spring AI 的 Message 对象
}对话流程
System Prompt 构建
模板结构
java
private static final String SYSTEM_PROMPT_TEMPLATE = """
你是一个修仙世界的智能助手,服务于宗门管理者。
## 当前用户信息
%s
## 可用技能(复杂任务,优先匹配)
%s
## 可用工具(原子操作,通过 Meta-Tool 间接调用)
%s
## Meta-Tool(唯一正确的调用方式)
1. **getSkillGuide** - 获取技能的详细执行指南
2. **getToolSchema** - 获取某个工具的详细参数格式
3. **executeTool** - 执行指定的工具
## 工作流程
1. 分析意图
2. 匹配技能
3. 获取参数
4. 执行工具
5. 回复用户
## 严格禁止
- 禁止直接调用任何列出的工具
- 禁止在回复中假设工具执行结果
- 禁止编造参数值
""";动态内容
java
private String buildSystemPrompt(AgentContext context, XiuxianUserEntity user) {
// 根据身份过滤可用工具
String toolDescriptions = capabilityRegistry.getToolDescriptions(context.getRole());
// 构建用户信息
String userInfo = buildUserInfo(context, user);
// 获取技能列表
String skillList = capabilityRegistry.getSkillList();
return String.format(SYSTEM_PROMPT_TEMPLATE, userInfo, skillList, toolDescriptions);
}ToolContext 传递
设置 ToolContext
java
Map<String, Object> toolContextMap = new HashMap<>();
toolContextMap.put(XiuxianMetaTools.AGENT_CONTEXT_KEY, context);
chatClient.prompt()
.tools(metaTools)
.toolContext(toolContextMap)
.call();获取 ToolContext
java
@Tool(description = "执行工具")
public String executeTool(String toolName, Map<String, Object> parameters, ToolContext toolContext) {
// 从 toolContext 获取 AgentContext
AgentContext ctx = (AgentContext) toolContext.getContext().get(AGENT_CONTEXT_KEY);
// 设置到 ThreadLocal
AgentContextHolder.setContext(ctx);
try {
return capabilityRegistry.executeTool(toolName, parameters);
} finally {
AgentContextHolder.clear();
}
}完整流程图
最佳实践
1. 及时发送进度
让用户知道 AI 正在做什么:
java
notifyProgress("正在查询...");
notifyProgress("查询完成,正在分析...");2. 合理设置超时
根据业务复杂度设置 SSE 超时时间:
java
// 简单查询:30秒
SseEmitter emitter = new SseEmitter(30000L);
// 复杂任务:120秒
SseEmitter emitter = new SseEmitter(120000L);3. 限制历史消息
避免上下文过长:
java
List<Message> history = getConversationHistory(conversationId, 10); // 最近10条4. 错误处理
优雅地处理错误:
java
try {
// ...
} catch (Exception e) {
emitter.send(AgentResponse.error("处理失败:" + e.getMessage()));
emitter.completeWithError(e);
}下一步
- Meta-Tool 模式 - 理解工具调用机制
- 工具开发 - 开发业务工具