Skip to content

对话流程

本文档介绍 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            // 完成
    }
}

响应类型说明

类型说明前端展示
CONTENTLLM 生成的文本内容正常显示
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);
}

下一步