Java 篇-项目实战-AI 天机学堂(从 0 到 1)-day4

java 篇: 1.基础地基 2.设计原理 3.项目实战


多智能体协同工作-历史对话-异步设置标题:

历史对话功能是可以让用户查询之前的对话记录,最多查询 30 条,并且按照当天、最近 30 天、最近 1 年、1 年以上分类,还支持更新标题、删除功能,如下:

代码实现:

在 `ChatSessionService` 中新增方法:

java 复制代码
/**
     * 更新会话更新时间
     *
     * @param sessionId 会话ID,用于标识特定的聊天会话
     * @param title     新的会话标题,如果为空则不进行更新
     * @param userId    用户ID
     */
    void update(String sessionId, String title, Long userId);

编写接口实现类:(注意,这里采用了异步更新,主要是确保 AI 对话聊天时的用户体验)

java 复制代码
/**
     * 异步更新聊天会话的标题
     *
     * @param sessionId 会话ID,用于标识特定的聊天会话
     * @param title     新的会话标题,如果为空则不进行更新
     * @param userId    用户ID
     */
    @Async
    @Override
    public void update(String sessionId, String title, Long userId) {
        // 查询符合条件的聊天会话列表
        List<ChatSession> list = super.lambdaQuery()
                .eq(ChatSession::getSessionId, sessionId)
                .eq(ChatSession::getUserId, userId)
                .list();
        // 如果列表为空,直接返回,无需进一步处理
        if (CollUtil.isEmpty(list)) {
            return;
        }

        // 获取列表中的第一个聊天会话实例
        ChatSession chatSession = list.get(0);
        // 如果聊天会话的标题为空,并且新标题不为空,则更新标题
        if (StrUtil.isEmpty(chatSession.getTitle()) && !StrUtil.isEmpty(title)) {
            chatSession.setTitle(StrUtil.sub(title, 0, 100));
        }
        // 设置更新字段为updateTime为当前时间
        chatSession.setUpdateTime(LocalDateTimeUtil.now());
        // 更新数据库中的聊天会话信息
        super.updateById(chatSession);
    }

这里标题只有为空才更新,但是时间会每次问 ai 都会更新,因为历史记录当中是根据更新时间来的。比如 1 年前的对话,我今天问了句,那它就变成当天的了。

ChatServiceImpl 当中加下:

前端测试一下:

发现 title 写进去了,然后 create_time 和 update_time 也是不一样的,测试通过。

多智能体协同工作-历史对话-查询历史会话记录:

效果如下:

响应结构:

java 复制代码
{
    "code": 200,
    "msg": "OK",
    "data": {
        "1年以上": [
            {
                "sessionId": "03b6491d3a1949c98cf0f8c37aa623fc",
                "title": "水水水水谁谁谁水水水水谁谁谁水水水水水水水水",
                "updateTime": "2023-02-26 15:45:31"
            }
        ],
        "最近1年": [
            {
                "sessionId": "53349594acff4a0fb92f71541491dc1b",
                "title": "帮我推荐课程",
                "updateTime": "2025-01-18 21:33:55"
            },
            {
                "sessionId": "695fdea704254c089da454133a1c17a8",
                "title": "你是谁",
                "updateTime": "2025-01-18 21:33:37"
            }
        ],
        "最近30天": [
            {
                "sessionId": "e380350f97174313898c214afb37d6d8",
                "title": "22222",
                "updateTime": "2025-02-25 13:44:44"
            }
        ],
        "当天": [
            {
                "sessionId": "fa046bdb4ffe48fba4915e490e1e0b0e",
                "title": "xxxxxx",
                "updateTime": "2025-02-26 15:44:01"
            }
        ]
    },
    "requestId": "bc8d535241104da7802e5d27f229d219"
}

代码实现:

先定义一个 VO:

java 复制代码
package com.tianji.aigc.vo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChatSessionVO {

    /**
     * 会话id
     */
    private String sessionId;

    /**
     * 会话标题
     */
    private String title;

    private LocalDateTime updateTime;

}

Controller

java 复制代码
/**
     * 查询历史会话列表
     */
    @GetMapping("/history")
    public Map<String, List<ChatSessionVO>> queryHistorySession() {
        return this.chatSessionService.queryHistorySession();
    }

Service

java 复制代码
/**
     * 查询历史会话列表
     */
    Map<String, List<ChatSessionVO>> queryHistorySession();

Impl

java 复制代码
@Override
    public Map<String, List<ChatSessionVO>> queryHistorySession() {
        var userId = UserContext.getUser();
        // 查询历史会话,限制返回条数
        var list = super.lambdaQuery()
                .eq(ChatSession::getUserId, UserContext.getUser())
                .isNotNull(ChatSession::getTitle)
                .orderByDesc(ChatSession::getUpdateTime)
                .last("LIMIT 30")
                .list();

        if (CollUtil.isEmpty(list)) {
            log.info("No chat sessions found for user: {}", userId);
            return Map.of();
        }


        // 转换为 ChatSessionVO 列表
        var chatSessionVOS = CollStreamUtil.toList(list, chatSession ->
                ChatSessionVO.builder()
                        .sessionId(chatSession.getSessionId())
                        .title(chatSession.getTitle())
                        .updateTime(chatSession.getUpdateTime())
                        .build()
        );

        final var TODAY = "当天";
        final var LAST_30_DAYS = "最近30天";
        final var LAST_YEAR = "最近1年";
        final var MORE_THAN_YEAR = "1年以上";

        // 当前时间
        var now = LocalDateTime.now().toLocalDate();

        // 按照更新时间分组
        return CollStreamUtil.groupByKey(chatSessionVOS, vo -> {
            // 计算两个日期之间的天数差
            long between = Math.abs(ChronoUnit.DAYS.between(vo.getUpdateTime().toLocalDate(), now));
            if (between == 0) {
                return TODAY;
            } else if (between <= 30) {
                return LAST_30_DAYS;
            } else if (between <= 365) {
                return LAST_YEAR;
            } else {
                return MORE_THAN_YEAR;
            }
        });
    }

这段代码的作用是查询用户的历史会话记录,并按时间分组(当天/最近 30 天/最近 1 年/1 年以上)。

查询数据库

最终 SQL:

转换为 VO

转换示例:

定义时间分组常量

按时间分组(核心逻辑)

分组规则

|------------|-------|-----------|
| 天数差 | 分组 | 说明 |
| `0` | 当天 | 今天更新的会话 |
| `1-30` | 最近30天 | 30天内更新的会话 |
| `31-365` | 最近1年 | 1年内更新的会话 |
| `>365` | 1年以上 | 超过1年的会话 |

执行示例

输入数据

输出结果

测试一下:

Map>,这里的 String 就是分组时间。

并且点击可以调整对应的对话。这里也可以验证之前课程卡片的 bug。

继续对话后,更新时间更新

测试通过。

多智能体协同工作-历史对话-删除历史会话:

对于历史对话的删除,实现物理删除即可,但是要注意,Redis 中的对话数据也要相应的删除。

代码实现:

Controller:

java 复制代码
/**
     * 删除历史会话列表
     */
    @DeleteMapping("/history")
    public void deleteHistorySession(@RequestParam("sessionId") String sessionId) {
        this.chatSessionService.deleteHistorySession(sessionId);
    }

Service:

javascript 复制代码
/**
     * 删除历史会话
     *
     * @param sessionId 会话id
     */
    void deleteHistorySession(String sessionId);

Impl:

java 复制代码
@Override
    public void deleteHistorySession(String sessionId) {
        //删除数据库的数据
        var queryWrapper = Wrappers.<ChatSession>lambdaQuery()
                .eq(ChatSession::getSessionId, sessionId)
                .eq(ChatSession::getUserId, UserContext.getUser());
        super.remove(queryWrapper);

        //删除redis中的数据
        var conversationId = ChatService.getConversationId(sessionId);
        this.chatMemory.clear(conversationId);
    }

前端测试一下:

然后看数据库,没了这记录,测试成功,redis 当中也没有了记忆,成功。

多智能体协同工作-实战任务:

练习 1:

历史会话的标题,默认是用户第一次发出的问题,后面用户可以修改标题,如下:

点击编辑按钮:

接口文档

参数有:sessionId 和 title 。

代码实现:

Controller:

java 复制代码
/**
     * 更新历史会话标题
     */
    @PutMapping("/history")
    public void updateTitle(@RequestParam("sessionId") String sessionId,
                            @RequestParam("title") String title) {
        this.chatSessionService.updateTitle(sessionId, title);
    }

Service:

java 复制代码
/**
     * 更新历史会话标题
     *
     * @param sessionId 会话id
     * @param title     标题
     */
    void updateTitle(String sessionId, String title);

Impl:

java 复制代码
@Override
    public void updateTitle(String sessionId, String title) {
        //更新数据
        super.lambdaUpdate()
                // 设置更新条件, 更新字段为title(最多设置前100个字符),更新条件为sessionId和userId
                .set(ChatSession::getTitle, StrUtil.sub(title, 0, 100))
                .eq(ChatSession::getSessionId, sessionId)
                .eq(ChatSession::getUserId, UserContext.getUser())
                .update();
    }

测试一下:

测试通过

多智能体协同工作-智能体架构模型:

前面我们已经完成了天机 AI 助手智能体功能的开发,实际上我们实现的方式只是最为基础的一种模式,一般应用系统中的智能体架构有 6 种,分别是:

  • 增强型智能体
  • 链式工作流智能体
  • 路由工作流智能体
  • 并行工作流智能体
  • 协调器工作流智能体
  • 评估优化工作流智能体

下面,我们一起来了解下这 6 种架构模式,重点要关注:**路由工作流智能体**。

**1.增强型智能体**

增强型智能体的基本构建块是一个增强的 LLM,其中包含检索、工具和记忆等增强功能。

因为复杂的场景,那调用的工具,以及提示词会很多,就会很臃肿。

**2.链式工作流智能体**

工作流智能体模式,就是将任务分解为一系列步骤,其中每个 LLM 调用处理上一个步骤的输出,通过多步骤 LLM 调用分阶段处理复杂任务。

用意就是不同阶段,调用不同的模型,因为每个模型擅长点不同。

**3.路由工作流智能体**

路由工作流智能体,这种模式是将输入通过 **LLM Call Router **对意图识别,再交由下游的 **LLM **执行。

例如智能客服,根据不同需求,转发到不同的智能体,相当于微服务网关。这样一个智能体就不会那么臃肿。

**4.并行工作流智能体**

并行工作智能体,是值一个输入同时交给多个 LLM 去执行,再将这些大模型的输出进行汇总处理,再输出。

①:为了效率,可以将复杂任务拆分,给多个大模型执行不同的任务。

②:为了准确,每个模型执行相同任务,最后再去整合。

**5.协调器工作流智能体**

协调器工作流智能体,这种模式是,由 `Orchestrator LLM` 作为智能调度中心,**动态生成**子任务列表,子任务可以是并行或串行执行,结果由 `Synthesizer` 进行聚合输出。

前面的两种模式,简单的任务也会执行多个。现在就是为了优化这点,所以可以判断,选择用几个模型。

**评估优化工作流智能体**

评估优化工作流智能体,是这一种 生成 → 评估 → 反馈 循环反馈的机制。

**总结**

|----------------|--------------|--------------|-------------|--------------------|---------------|
| **模式名称** | **控制方式** | **延迟水平** | **可靠性** | **典型应用场景** | **开发复杂度** |
| **增强型智能体** | 直接输出 | 最低 | 低 | 简单问答、内容润色 | 简单 |
| 链式工作流智能体 | 线性顺序执行 | 中等 | 中高 | 分阶段任务(如大纲→内容→格式优化) | 中等 |
| 路由工作流智能体 | 条件分支选择 | 低-中等 | 中 | 多领域处理(如客服分流转人工) | 中等 |
| 并行工作流智能体 | 多模型并发执行 | 中等 | 高 | 可靠性敏感任务(如医疗诊断辅助) | 较高 |
| 协调器工作流智能体 | 动态任务分解+调度 | 高 | 最高 | 复杂业务(如商业智能分析系统) | 极高 |
| 评估优化工作流智能体 | 迭代优化+反馈修正 | 最高 | 极高 | 质量敏感场景(如法律文件生成) | 高 |

**模式选型建议,根据业务需求选择:**

  • **简单任务** → 增强型智能体 / 链式工作流智能体
  • **多分支处理** → 路由模式
  • **高实时性** → 并行化(需任务可拆分)
  • **超复杂任务** → 协调器工作流智能体
  • **超高可靠性** → 评估优化工作流智能体

多智能体协同工作-路由工作流智能体实现:

根据前面的分析,我们的天机 AI 助理,比较适合用路由工作流模式,接下来,我们将把之前的增强型智能体,改造成路由工作流模式。

实现流程如下:

  • 我们把原来的单一智能体改成了 5 个智能体一起协同工作。
  • 当用户提出问题时,首先会发送给【意图分析智能体】,它会判断用户是想让我们推荐课程、查询课程信息还是购买课程。
  • 一旦明确了用户的意图,就会根据不同的需求调用相应的智能体来完成任务,比如推荐课程或购买课程等。
  • 这样做的好处是每个智能体都有明确的任务分工,并且只有在需要时才会调用特定的工具,不需要所有智能体都配备全套工具。这样一来,整个系统变得更加灵活高效了。

代码实现:

定义类型枚举:

不同的智能体,是需要通过类型来区分的,比较好的一种方式就是定义类型枚举。

java 复制代码
package com.tianji.aigc.enums;

import cn.hutool.core.util.EnumUtil;
import lombok.Getter;

/**
 * 智能体类型
 */
@Getter
public enum AgentTypeEnum {
    ROUTE("ROUTE", "路由智能体"),
    RECOMMEND("RECOMMEND", "课程推荐智能体"),
    CONSULT("CONSULT", "课程咨询智能体"),
    BUY("BUY", "课程购买智能体"),
    KNOWLEDGE("KNOWLEDGE", "知识讲解智能体");

    private final String agentName;
    private final String desc;

    AgentTypeEnum(String agentName, String desc) {
        this.agentName = agentName;
        this.desc = desc;
    }

    @Override
    public String toString() {
        return this.name();
    }


    /**
     * 通过智能体的名称查找枚举
     */
    public static AgentTypeEnum agentNameOf(String agentName) {
        return EnumUtil.getBy(AgentTypeEnum::getAgentName, agentName);
    }

}

定义 Agent 接口:

我们可以想一下,每个智能体都有什么相关的方法,就把他们抽象出来,形成一个 `Agent interface`,子类只需要实现接口即可。

应该有的方法:

  • process (普通对话)
  • processStream (流式对话)
  • getAgentType (获取智能体类型)
  • stop (停止方法)
  • systemMessage (获取系统提示词方法)

以上这些都是基本的操作方法。实际上,对于一个智能体而言,与大模型或 Tools 交互,还需要一些设定,比如 toolContext、advisors 等,所以还需要额外的加一个方法:

  • tools (工具集)
  • toolContext (工具上下文参数)
  • advisors (Advisor 列表)
  • advisorParams (Advisor 参数列表)
  • systemMessageParams (系统提示词中的参数列表)

所以,基于上面的分析,就可以定义 `Agent interface` 了:

java 复制代码
package com.tianji.aigc.agent;

import com.tianji.aigc.enums.AgentTypeEnum;
import com.tianji.aigc.vo.ChatEventVO;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.Map;

/**
 * AI代理接口,定义处理聊天事件和会话的核心能力
 */
public interface Agent {

    /**
     * 表示空参数的预定义数组
     */
    Object[] EMPTY_OBJECTS = new Object[0];

    /**
     * 处理流式请求(如流式回答)
     *
     * @param question  用户输入的问题
     * @param sessionId 会话唯一标识
     * @return 包含中间结果的反应式事件流(Flux)
     */
    Flux<ChatEventVO> processStream(String question, String sessionId);

    /**
     * 处理标准请求(非流式)
     *
     * @param question  用户输入的问题
     * @param sessionId 会话唯一标识
     * @return 最终处理结果字符串
     */
    String process(String question, String sessionId);

    /**
     * 获取智能体类型标识
     *
     * @return 代理类型枚举值(如:ROUTE、RECOMMEND等)
     */
    AgentTypeEnum getAgentType();

    /**
     * 停止指定会话的处理
     *
     * @param sessionId 需要终止的会话ID
     */
    void stop(String sessionId);

    /**
     * 获取系统提示信息模板,默认为空字符串,子类可以覆盖重写该方法以返回自定义的系统提示信息。
     *
     * @return 系统提示的文本模板
     */
    default String systemMessage() {
        return "";
    }


    /**
     * 获取工具列表,默认返回空数组。子类需根据需求覆盖此方法。
     */
    default Object[] tools() {
        return EMPTY_OBJECTS;
    }

    /**
     * 创建并返回一个工具上下文的空Map对象。
     *
     * @param sessionId 会话标识符
     * @param requestId 请求标识符
     * @return 默认返回一个空的Map对象,子类可以覆盖重写该方法以返回自定义的工具上下文。
     */
    default Map<String, Object> toolContext(String sessionId, String requestId) {
        return Map.of();
    }

    /**
     * Advisor列表,默认返回空对象
     */
    default List<Advisor> advisors() {
        return List.of();
    }

    /**
     * 创建并返回一个Advisor的空Map对象。
     *
     * @param sessionId 会话标识符
     * @param requestId 请求标识符
     * @return 默认返回一个空的Map对象,子类可以覆盖重写该方法以返回自定义的工具上下文。
     */
    default Map<String, Object> advisorParams(String sessionId, String requestId) {
        return Map.of();
    }

    /**
     * 获取系统提示信息模板的参数,默认为空Map,子类可以覆盖重写该方法以返回自定义的系统提示信息参数。
     */
    default Map<String, Object> systemMessageParams() {
        return Map.of();
    }

}

编写抽象类:

去实现通用的方法:

java 复制代码
package com.tianji.aigc.agent;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.tianji.aigc.config.ToolResultHolder;
import com.tianji.aigc.constants.Constant;
import com.tianji.aigc.enums.ChatEventTypeEnum;
import com.tianji.aigc.service.ChatService;
import com.tianji.aigc.service.ChatSessionService;
import com.tianji.aigc.vo.ChatEventVO;
import com.tianji.common.utils.UserContext;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.data.redis.core.StringRedisTemplate;
import reactor.core.publisher.Flux;

import java.util.Map;

@Slf4j
public abstract class AbstractAgent implements Agent {

    public static final ChatEventVO _STOP_EVENT _= ChatEventVO.builder().eventType(ChatEventTypeEnum._STOP_.getValue()).build();

    @Resource
    private ChatClient chatClient;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private ChatMemory chatMemory;
    @Resource
    private ChatSessionService chatSessionService;

    private static final String _GENERATE_STATUS_KEY _= "GENERATE_STATUS";

    @Override
    public Flux<ChatEventVO> processStream(String question, String sessionId) {
        // 生成请求id
        var requestId = this.generateRequestId();
        var hashOps = this.stringRedisTemplate.boundHashOps(_GENERATE_STATUS_KEY_);
        // 将会话id转化为对话id
        var conversationId = ChatService._getConversationId_(sessionId);
        // 大模型输出内容的缓存器,用于在输出中断后的数据存储
        var outputBuilder = new StringBuilder();
        // 获取到当前登录的用户id
        var userId = UserContext.getUser();
        //更新会话时间
        this.chatSessionService.update(sessionId, question, userId);

        return this.getChatClientRequest(question, sessionId, requestId)
                .stream()
                .chatResponse()
                .doFirst(() -> hashOps.put(sessionId, "true")) // 生成开始时,设置标识
                .doOnError(throwable -> hashOps.delete(sessionId)) // 异常结束时,删除标识
                .doOnComplete(() -> hashOps.delete(sessionId)) // 正常结束时,删除标识
                .doOnCancel(() -> {
                    // 当输出被取消时,保存输出的内容到历史记录中
                    this.saveStopHistoryRecord(conversationId, outputBuilder.toString());
                }) // 打断输出的事件
                .takeWhile(response -> hashOps.get(sessionId) != null) // 后续生成的条件,true:继续生成,false:停止生成
                .map(chatResponse -> {
                    // 大模型生成的内容
                    var text = chatResponse.getResult().getOutput().getText();
                    // 追加到输出内容中
                    outputBuilder.append(text);

                    // 获取到消息的结束原因
                    var finishReason = chatResponse.getResult().getMetadata().getFinishReason();
                    if (StrUtil.equals(finishReason, Constant._STOP_)) {
                        // 获取到消息id
                        var messageId = chatResponse.getMetadata().getId();
                        // 将消息id与请求id进行关联
                        ToolResultHolder._put_(messageId, Constant._REQUEST_ID_, requestId);
                    }

                    return ChatEventVO.builder()
                            .eventData(text)
                            .eventType(ChatEventTypeEnum._DATA_.getValue())
                            .build();
                })
                .concatWith(Flux.defer(() -> {
                    var result = ToolResultHolder._get_(requestId);
                    if (ObjectUtil.isNotEmpty(result)) {
                        ToolResultHolder._remove_(requestId);
                        // 工具被调用了,需要向前端传递参数
                        return Flux.just(ChatEventVO.builder()
                                .eventType(ChatEventTypeEnum._PARAM_.getValue())
                                .eventData(result)
                                .build(), _STOP_EVENT_);
                    }
                    return Flux.just(_STOP_EVENT_); // 结束标识
                }));
    }

    @Override
    public String process(String question, String sessionId) {
        // 生成请求id
        var requestId = this.generateRequestId();
        // 获取到当前登录的用户id
        var userId = UserContext.getUser();
        //更新会话时间
        this.chatSessionService.update(sessionId, question, userId);

        return this.getChatClientRequest(question, sessionId, requestId)
                .call()
                .content();
    }

    private ChatClient.ChatClientRequestSpec getChatClientRequest(String question, String sessionId, String requestId) {
        return this.chatClient.prompt()
                .system(promptSystemSpec -> promptSystemSpec.text(this.systemMessage()).params(this.systemMessageParams()))
                .advisors(advisorSpec -> advisorSpec.advisors(this.advisors()).params(this.advisorParams(sessionId, requestId)))
                .tools(this.tools())
                .toolContext(this.toolContext(sessionId, requestId))
                .user(question);
    }

    _/**_
_     * 保存停止输出的记录_
_     *_
_     * @param conversationId 会话id_
_     * @param content        大模型输出的内容_
_     */_
_    _private void saveStopHistoryRecord(String conversationId, String content) {
        this.chatMemory.add(conversationId, new AssistantMessage(content));
    }

    private String generateRequestId() {
        return IdUtil.fastSimpleUUID();
    }

    @Override
    public void stop(String sessionId) {
        var hashOps = this.stringRedisTemplate.boundHashOps(_GENERATE_STATUS_KEY_);
        hashOps.delete(sessionId);
    }

    @Override
    public Map<String, Object> advisorParams(String sessionId, String requestId) {
        // 将会话id转化为对话id
        var conversationId = ChatService._getConversationId_(sessionId);
        return Map._of_(ChatMemory.CONVERSATION_ID, conversationId);
    }
}

这是一个抽象 Agent 类,实现了 AI 对话的核心流程,包括流式对话、工具调用、对话中断恢复等功能。

整体架构

依赖注入

常量定义

**公共方法(public)- 对外暴露**

|-------------------|---------------------------------------|------------|-------------|--------------|
| 方法名 | 参数 | 返回值 | 作用 | 调用场景 |
| `processStream` | `String question, String sessionId` | `Flux` | 流式对话,实时返回内容 | 前端需要实时显示AI回答 |
| `process` | `String question, String sessionId` | `String` | 同步对话,等待完整结果 | 后端内部调用、非实时场景 |
| `stop` | `String sessionId` | `void` | 停止当前正在生成的对话 | 用户点击停止按钮 |

**受保护方法(protected)- 子类可覆盖**

|-------------------------|----------------------------------------|----------------|--------------------|--------------------------|
| 方法名 | 参数 | 返回值 | 作用 | 默认实现 |
| `systemMessage` | 无 | `String` | 定义AI的系统提示词 | `null`(子类必须实现) |
| `systemMessageParams` | 无 | `Map` | 系统提示词的参数 | `Map.of()`(空Map) |
| `advisors` | 无 | `Object\[\]` | 定义AI的Advisor(如RAG) | `new Object0`(空数组) |
| `advisorParams` | `String sessionId, String requestId` | `Map` | Advisor的参数 | 返回包含conversationId的Map |
| `tools` | 无 | `Object\[\]` | 定义AI可调用的工具 | `new Object0`(空数组) |
| `toolContext` | `String sessionId, String requestId` | `Map` | 工具调用的上下文 | 返回包含requestId的Map |

私有方法(private)- 内部使用

|---------------------------|---------------------------------------------------------|---------------------------|----------------|
| 方法名 | 参数 | 返回值 | 作用 |
| `generateRequestId` | 无 | `String` | 生成唯一请求ID(UUID) |
| `getChatClientRequest` | `String question, String sessionId, String requestId` | `ChatClientRequestSpec` | 构建AI请求 |
| `saveStopHistoryRecord` | `String conversationId, String content` | `void` | 保存中断时的输出内容 |

**核心方法详解**

1.`processStream()` - 流式对话(核心)

这是最重要的方法,处理 SSE 流式响应。

流式处理链

2.`process()` - 同步对话

3.`stop()` - 停止生成

4.`saveStopHistoryRecord()` - 保存中断记录

当用户手动停止时,把已生成的内容保存到对话历史中。

数据流图

添加路由智能体 nacos 配置:

java 复制代码
# 角色
天机AI意图分析师

## 能力
1. 识别用户意图并匹配对应编号:
   - RECOMMEND(课程推荐)
   - BUY(课程购买)
   - CONSULT(课程咨询)
   - KNOWLEDGE(知识讲解)
2. 特殊场景处理:
   - 识别关键词触发意图:
     - BUY: 确认购买/下单/是的确认
     - RECOMMEND: 包含年龄/学历/兴趣信息
   - 识别问候语并礼貌回应:你好/您好
3. 非相关提问时礼貌拒答

## 约束
精准识别,避免误判

## 输出
- 匹配意图时返回编号
- 问候语场景返回「您好!有什么可以帮您?」
- 无匹配时用自然语言回复

## 示例
输入:20岁本科想学Java → RECOMMEND  
输入:现在要下单 → BUY  
输入:这个课程多少钱 → CONSULT
输入:java是什么 → KNOWLEDGE
输入:你好 → 您好!有什么可以帮您?  
输入:今天天气 → 抱歉我只处理课程相关问题

AIProperties 读取配置:

`application.yml` 中增加配置:

SystemPromptConfig 加载配置:

**路由智能体**

java 复制代码
package com.tianji.aigc.agent;

import com.tianji.aigc.config.SystemPromptConfig;
import com.tianji.aigc.enums.AgentTypeEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

/**
 * 路由智能体
 */
@Component
@RequiredArgsConstructor
public class RouteAgent extends AbstractAgent {

    private final SystemPromptConfig systemPromptConfig;

    @Override
    public String systemMessage() {
        return this.systemPromptConfig.getRouteAgentSystemMessage().get();
    }

    @Override
    public AgentTypeEnum getAgentType() {
        return AgentTypeEnum.ROUTE;
    }

}

测试用例

java 复制代码
package com.tianji.aigc.agent;

import cn.hutool.core.lang.Assert;
import com.tianji.aigc.enums.AgentTypeEnum;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class RouteAgentTest {

    @Resource
    private RouteAgent routeAgent;

    @Test
    public void testChat(){
        Assert.equals(this.routeAgent.process("最新有哪些课程", "1"), AgentTypeEnum.RECOMMEND.getAgentName());
        Assert.equals(this.routeAgent.process("下单购买这个课程", "1"), AgentTypeEnum.BUY.getAgentName());
        Assert.equals(this.routeAgent.process("这个课程是多少钱", "1"), AgentTypeEnum.CONSULT.getAgentName());
        Assert.equals(this.routeAgent.process("java是什么", "1"), AgentTypeEnum.KNOWLEDGE.getAgentName());
    }

}

SpringAIConfig 删除不必要的配置:

删除添加课程工具,因为有些智能体不用工具

为什么要用断言验证?

测试结果:

**推荐智能体**

系统提示词

java 复制代码
# 在线教育客服&讲师指南

## 核心职责
分步精准推荐:信息采集 → 课程匹配 → 执行推荐

## 强制流程
1. **信息采集(必须优先)**
   - 必须收集三项核心数据:
     ▪ 年龄(数字)
     ▪ 最高学历(初中/高中/本科/硕士等)
     ▪ 编程基础(无经验/基础语法/项目经验)
   - 任一信息缺失时:立即停止推荐,礼貌追问直至信息完整

2. **课程匹配
   - 强制:要通过课程id查询课程之后再输出
   - 匹配逻辑:
     1) 精准匹配(年龄+学历+兴趣)
     2) 向下兼容课程(如学历达标但年龄较小)
     3) 关联领域Top3课程

3. **推荐执行
   - 每次推荐必须包含:
     ▪ 数据关联说明(例:"针对25岁本科学历...")
     ▪ 课程适配点(例:"包含实战项目模块...")
   - 禁止推荐未经数据验证的课程

## 关键规则
- 阻断机制:未收齐三项数据前禁用推荐功能
- 数据校验:发现矛盾数据(如"12岁硕士学历")需确认
- 异常处理:无匹配时提供「人工咨询」入口
- 必须要输出课程id、价格、介绍等信息

读取配置:

java 复制代码
package com.tianji.aigc.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@ConfigurationProperties(prefix = "tj.ai.prompt")
public class AIProperties {

    private System system; // 系统提示语,用于课程推荐、购买业务

    @Data
    public static class System {
        private Chat chat; // 系统提示语,用于课程推荐、购买业务
        private Chat routeAgent; // 路由智能体系统提示词
        private Chat recommendAgent; // 推荐智能体系统提示词

        @Data
        public static class Chat {
            private String dataId;
            private String group = "DEFAULT_GROUP";
            private long timeoutMs = 20000L; // 读取的超时时间,单位毫秒
        }
    }
}

`application.yml` 中增加配置:

java 复制代码
tj:
  ai:
    prompt:
      system:
        chat:
          data-id: system-chat-message.txt
          group: DEFAULT_GROUP
          timeout-ms: 20000
        route-agent:
          data-id: route-agent-system-message.txt
        recommend-agent:
          data-id: recommend-agent-system-message.txt

加载配置:

java 复制代码
package com.tianji.aigc.config;
// 省略一些代码........
public class SystemPromptConfig {

    // 省略一些代码........

    // 使用原子引用,保证线程安全
    private final AtomicReference<String> chatSystemMessage = new AtomicReference<>();
    private final AtomicReference<String> routeAgentSystemMessage = new AtomicReference<>();
    private final AtomicReference<String> recommendAgentSystemMessage = new AtomicReference<>();
    
    @PostConstruct // 初始化时加载配置
    public void init() {
        // 读取配置文件
        loadConfig(aiProperties.getSystem().getChat(), chatSystemMessage);
        loadConfig(aiProperties.getSystem().getRouteAgent(), routeAgentSystemMessage);
        loadConfig(aiProperties.getSystem().getRecommendAgent(), recommendAgentSystemMessage);
    }
// 省略一些代码........
}

编写智能体:

java 复制代码
package com.tianji.aigc.agent;

import com.tianji.aigc.config.SystemPromptConfig;
import com.tianji.aigc.constants.Constant;
import com.tianji.aigc.enums.AgentTypeEnum;
import com.tianji.aigc.tools.CourseTools;
import com.tianji.common.utils.UserContext;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

@Component
@RequiredArgsConstructor
public class RecommendAgent extends AbstractAgent {

    private final SystemPromptConfig systemPromptConfig;
    private final VectorStore vectorStore;
    private final CourseTools courseTools;

    @Override
    public String systemMessage() {
        return this.systemPromptConfig.getRecommendAgentSystemMessage().get();
    }

    @Override
    public AgentTypeEnum getAgentType() {
        return AgentTypeEnum.RECOMMEND;
    }

    @Override
    public List<Advisor> advisors() {
        // 创建RAG增强
        var qaAdvisor = QuestionAnswerAdvisor.builder(this.vectorStore)
                .searchRequest(SearchRequest.builder().similarityThreshold(0.6d).topK(6).build())
                .build();
        return List.of(qaAdvisor);
    }

    @Override
    public Object[] tools() {
        return new Object[]{courseTools};
    }

    @Override
    public Map<String, Object> toolContext(String sessionId, String requestId) {
        var userId = UserContext.getUser();
        return Map.of(
                //Constant.USER_ID, userId, // 设置用户id参数
                Constant.REQUEST_ID, requestId  // 设置请求id参数
        );
    }

}

测试用例:

java 复制代码
package com.tianji.aigc.agent;

import com.tianji.aigc.vo.ChatEventVO;
import com.tianji.common.utils.UserContext;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import reactor.core.publisher.Flux;

@SpringBootTest
class RecommendAgentTest {

    @Resource
    private RecommendAgent recommendAgent;

    @Test
    public void processStream() throws InterruptedException {
        String question = "推荐课程,20岁,本科,对java感兴趣";
        String sessionId = "123";
        UserContext.setUser(123L);
        Flux<ChatEventVO> flux = recommendAgent.processStream(question, sessionId);
        flux.subscribe(System.out::println);

        // 阻塞主线程,防止主线程结束,子线程终止
        Thread.sleep(100000);
    }

}

运行结果如下:

也有 params 的输出

**课程购买智能体**

系统提示词

读取配置

java 复制代码
package com.tianji.aigc.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@ConfigurationProperties(prefix = "tj.ai.prompt")
public class AIProperties {

    private System system; // 系统提示语,用于课程推荐、购买业务

    @Data
    public static class System {
        private Chat chat; // 系统提示语,用于课程推荐、购买业务
        private Chat routeAgent; // 路由智能体系统提示词
        private Chat recommendAgent; // 推荐智能体系统提示词
        private Chat buyAgent; // 购买智能体系统提示词

        @Data
        public static class Chat {
            private String dataId;
            private String group = "DEFAULT_GROUP";
            private long timeoutMs = 20000L; // 读取的超时时间,单位毫秒
        }
    }
}

`application.yml` 中增加配置:

java 复制代码
tj:
  ai:
    prompt:
      system:
        chat:
          data-id: system-chat-message.txt
          group: DEFAULT_GROUP
          timeout-ms: 20000
        route-agent:
          data-id: route-agent-system-message.txt
        recommend-agent:
          data-id: recommend-agent-system-message.txt
        buy-agent:
          data-id: buy-agent-system-message.txt

加载配置:

java 复制代码
package com.tianji.aigc.config;
// 省略一些代码........
public class SystemPromptConfig {

    // 省略一些代码........

    // 使用原子引用,保证线程安全
    private final AtomicReference<String> chatSystemMessage = new AtomicReference<>();
    private final AtomicReference<String> routeAgentSystemMessage = new AtomicReference<>();
    private final AtomicReference<String> recommendAgentSystemMessage = new AtomicReference<>();
    private final AtomicReference<String> buyAgentSystemMessage = new AtomicReference<>();
    
    @PostConstruct // 初始化时加载配置
    public void init() {
        // 读取配置文件
        loadConfig(aiProperties.getSystem().getChat(), chatSystemMessage);
        loadConfig(aiProperties.getSystem().getRouteAgent(), routeAgentSystemMessage);
        loadConfig(aiProperties.getSystem().getRecommendAgent(), recommendAgentSystemMessage);
        loadConfig(aiProperties.getSystem().getBuyAgent(), buyAgentSystemMessage);
    }
// 省略一些代码........
}

编写智能体

java 复制代码
package com.tianji.aigc.agent;

import com.tianji.aigc.config.SystemPromptConfig;
import com.tianji.aigc.constants.Constant;
import com.tianji.aigc.enums.AgentTypeEnum;
import com.tianji.aigc.tools.OrderTools;
import com.tianji.common.utils.UserContext;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
@RequiredArgsConstructor
public class BuyAgent extends AbstractAgent {

    private final SystemPromptConfig systemPromptConfig;
    private final OrderTools orderTools;

    @Override
    public String systemMessage() {
        return this.systemPromptConfig.getBuyAgentSystemMessage().get();
    }

    @Override
    public AgentTypeEnum getAgentType() {
        return AgentTypeEnum.BUY;
    }

    @Override
    public Object[] tools() {
        return new Object[]{orderTools};
    }

    @Override
    public Map<String, Object> toolContext(String sessionId, String requestId) {
        var userId = UserContext.getUser();
        return Map.of(
                Constant.USER_ID, userId, // 设置用户id参数
                Constant.REQUEST_ID, requestId  // 设置请求id参数
        );
    }
}

当中涉及到的工具类 copy 一下,常量添加一下。

测试用例

java 复制代码
package com.tianji.aigc.agent;

import com.tianji.aigc.vo.ChatEventVO;
import com.tianji.common.utils.UserContext;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import reactor.core.publisher.Flux;

@SpringBootTest
class BuyAgentTest {

    @Resource
    private BuyAgent buyAgent;

    @Test
    public void processStream() throws InterruptedException {
        String question = "下单购买,课程id为:1589905661084430337";
        String sessionId = "123";
        UserContext.setUser(123L);
        Flux<ChatEventVO> flux = buyAgent.processStream(question, sessionId);
        flux.subscribe(System.out::println);

        // 阻塞主线程,防止主线程结束,子线程终止
        Thread.sleep(100000);
    }

}

测试结果:

测试成功

多智能体协同工作-整合多智能体:

前面已经实现了多个智能体,这些智能体都是独立运行,接下来我们就需要把他们整合起来,一起协调工作,完成天机 AI 助理。

编写 AgentServiceImpl 实现类

java 复制代码
package com.tianji.aigc.service.impl;

import cn.hutool.extra.spring.SpringUtil;
import com.tianji.aigc.agent.AbstractAgent;
import com.tianji.aigc.agent.Agent;
import com.tianji.aigc.enums.AgentTypeEnum;
import com.tianji.aigc.enums.ChatEventTypeEnum;
import com.tianji.aigc.service.ChatService;
import com.tianji.aigc.vo.ChatEventVO;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

@Service
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "tj.ai", name = "chat-type", havingValue = "ROUTE")
public class AgentServiceImpl implements ChatService {

    @Override
    public Flux<ChatEventVO> chat(String question, String sessionId) {
        // 先通过路由智能体,分析用户的意图,再执行后面的逻辑
        var result = this.findAgentByType(AgentTypeEnum.ROUTE).process(question, sessionId);
        var agentTypeEnum = AgentTypeEnum.agentNameOf(result);

        var agent = this.findAgentByType(agentTypeEnum);
        if (agent == null) {
            // 找不到对应的智能体,直接返回结果
            var chatEventVO = ChatEventVO.builder()
                    .eventType(ChatEventTypeEnum.DATA.getValue())
                    .eventData(result)
                    .build();
            return Flux.just(chatEventVO, AbstractAgent.STOP_EVENT);
        }
        // 执行智能体的逻辑
        return agent.processStream(question, sessionId);
    }

    /**
     * 根据代理类型查找对应的Agent实例
     *
     * @param agentTypeEnum 要查找的代理类型
     * @return 与给定类型匹配的Agent实例,如果未找到或类型为null则返回null
     */
    private Agent findAgentByType(AgentTypeEnum agentTypeEnum) {
        if (agentTypeEnum == null) {
            return null;
        }
        var beans = SpringUtil.getBeansOfType(Agent.class);
        // 遍历所有Agent Bean查找匹配类型
        for (var agent : beans.values()) {
            if (agentTypeEnum == agent.getAgentType()) {
                return agent;
            }
        }
        return null;
    }

    /**
     * 停止生成
     *
     * @param sessionId 会话ID
     */
    @Override
    public void stop(String sessionId) {
        this.findAgentByType(AgentTypeEnum.ROUTE).stop(sessionId);
    }
}
  • `SpringUtil`:Hutool 提供的 Spring 工具类
  • `getBeansOfType(Agent.class)`:获取所有类型为 `Agent`(或其子类)的 Bean
  • 返回值:`Map`
  • Key:Bean 的名称(通常是类名首字母小写)
  • Value:Agent 实例

|-----------------------------------------------|-----------------|---------------------------------------------------------------|
| 部分 | 说明 | 示例值 |
| `this.findAgentByType(AgentTypeEnum.ROUTE)` | 获取路由 Agent 实例 | `RouteAgent` 对象 |
| `.process(question, sessionId)` | 调用路由 Agent 处理问题 | 返回意图识别结果 |
| `result` | 意图识别的结果 | `"recommend"` / `"buy"` / `"consult"` / `"knowledge"` |

作用:让路由 Agent 分析用户问题,返回应该由哪个 Agent 处理的标识。

|-------------------------|---------------|---------------------------------------------------------|
| 部分 | 说明 | 示例 |
| `AgentTypeEnum` | 代理类型枚举 | 定义 `RECOMMEND`, `BUY`, `CONSULT`, `KNOWLEDGE` 等 |
| `agentNameOf(result)` | 静态方法,将字符串转为枚举 | `"recommend"` → `AgentTypeEnum.RECOMMEND` |

作用:将路由 Agent 返回的字符串(如 `"recommend"`)转换为对应的枚举类型。

完整执行流程

一个 ChatService 接口有两个实现类

一个是 ChatServiceImpl 一个是 AgentServiceImpl,如果直接注入的话,就会出错,所以这里得设置一下。

在 `application.yml` 配置文件中,增强条件配置:

复制代码
tj:
  ai:
    chat-type: ROUTE # ROUTE / ENHANCE / APP

改造 ChatServiceImpl

在原 `ChatServiceImpl` 中添加条件:

改造 SpringAIConfig

在 SpringAIConfig 中,就不需要设置默认的 Tool 了,需要改造下,如下:

这个在前面已经处理过了

测试一下:

发送"你好"

发送"课程推荐"

多智能体协同工作-对话记录的 bug 修复:

查看对话记录时,路由智能体发送的部分,也出来了。

那如何解决呢,就是想办法把打叉的部分去掉,这里根据是否为类型,来判断。

advisor 放第一个,虽然进去是第一个先进去,但是出来是最后一个出来呀

**第一步:**编写 `RecordOptimizationAdvisor`

java 复制代码
package com.tianji.aigc.advisor;

import cn.hutool.core.map.MapUtil;
import com.tianji.aigc.enums.AgentTypeEnum;
import com.tianji.aigc.memory.MyChatMemoryRepository;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.api.AdvisorChain;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;

/**
 * 记录优化
 */
public class RecordOptimizationAdvisor implements BaseAdvisor {

    private final MyChatMemoryRepository myChatMemoryRepository;

    public RecordOptimizationAdvisor(MyChatMemoryRepository myChatMemoryRepository) {
        this.myChatMemoryRepository = myChatMemoryRepository;
    }

    @Override
    public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
        return chatClientRequest;
    }

    @Override
    public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
        // 获取大模型的响应内容
        var chatResponse = chatClientResponse.chatResponse();
        // 获取大模型的响应内容,判断内容是否是智能体的名称,如果是,优化记录,否则无需优化
        assert chatResponse != null;
        var text = chatResponse.getResult().getOutput().getText();
        var agentType = AgentTypeEnum.agentNameOf(text);
        if (null != agentType) {
            // 需要优化记录
            var conversationId = MapUtil.getStr(chatClientResponse.context(), ChatMemory.CONVERSATION_ID);
            this.myChatMemoryRepository.optimization(conversationId);
        }

        return chatClientResponse;
    }

    @Override
    public int getOrder() {
        return Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER - 100;
    }
}

为了消除 idea 为空的警告,增加 `package-info.java` 文件:

java 复制代码
@NonNullApi
@NonNullFields
package com.tianji.aigc.advisor;

import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

**第二步,**创建 MyChatMemoryRepository 接口,定义 `optimization` 方法:

java 复制代码
package com.tianji.aigc.memory;

public interface MyChatMemoryRepository {

    /**
     * 根据对话ID优化对话记录,删除最后的2条消息,因为这条消息是从路由智能体存储的,请求由后续的智能体处理
     * 为了确保历史消息的完整性,所以需要将中间转发的消息清理掉
     *
     * @param conversationId 对话的唯一标识符
     */
    void optimization(String conversationId);

}

**第三步,**在 `RedisChatMemoryRepository` 中实现 `optimization` 方法:

javascript 复制代码
/**
     * 根据对话ID优化对话记录,删除最后的2条消息,因为这条消息是从路由智能体存储的,请求由后续的智能体处理
     * 为了确保历史消息的完整性,所以需要将中间转发的消息清理掉
     *
     * @param conversationId 对话的唯一标识符
     */
    public void optimization(String conversationId) {
        var redisKey = this.getKey(conversationId);
        var listOps = this.stringRedisTemplate.boundListOps(redisKey);
        // 从Redis列表右侧弹出2个元素
        listOps.rightPop(2);
    }

**第四步,**在 `SpringAIConfig` 中增加配置,使 `Advisor` 生效:

java 复制代码
/**
     * 配置 ChatClient
     */
    @Bean
    public ChatClient chatClient(ChatClient.Builder chatClientBuilder,
                                 Advisor loggerAdvisor,
                                 Advisor messageChatMemoryAdvisor,
                                 Advisor recordOptimizationAdvisor, // 记录优化
                                 CourseTools courseTools, // 课程工具
                                 OrderTools orderTools // 预下单工具
    ) {  // 日志记录器
        return chatClientBuilder
                .defaultAdvisors(loggerAdvisor, messageChatMemoryAdvisor, recordOptimizationAdvisor) //添加 Advisor 功能增强
                // .defaultTools(courseTools, orderTools) //添加默认工具
                .build();
    }


    /**
     * 优化对话历史记录
     */
    @Bean
    public Advisor recordOptimizationAdvisor(MyChatMemoryRepository myChatMemoryRepository) {
        return new RecordOptimizationAdvisor(myChatMemoryRepository);
    }

**第五步,**测试:

发现出现了 Bug

bug 修复:

主要思路:

对于路由智能体,输入是"课程推荐",输出是"RECOMMEND",那输出是不能展示出来的,所以得在 after 方法当中处理

RecordOptimizationAdvisor:

前面是弹出 2,这里改成 1。相当于把路由智能体输出的 RECOMMEND 记录删掉。

那对于 RecommendAgent 输入是"RECOMMEND",然后它也会有输出,但是记录当中输入是不需要的,所以得在 before 方法当中进行处理。

那因为 before 那,还没有记录,所以这里只是做一个标记,到后面 after 方法当中,根据这个标记去执行相应的删除操作。

以上的操作就是下面图中所示:

前提基础知识补充讲解:

首先 RecordOptimizationAdvisor 实现了 BaseAdvisor

而 BaseAdvisor 又继承了 CallAdvisor 和 StreamAdvisor,如果是普通的大模型调用,CallAdvisor 生效。如果是流式的话,StreamAdvisor 生效。所以我们只需要实现 before 和 after 方法就行了。

下面重新进行测试:

这里可能也有小 bug,就是打出你好后,之后再去对话,发现发送不了了,这是因为流没有关闭导致的。

修复:

AgentServiceImpl

但是测下来还是有 bug

修复条件是又把它改成 2 了

我们再分析一波

对于指向智能体的就是 jack 那方,智能体吐出来的就是小黄鸡那方。

推测是这样一个流程,所以是删 2 个。

测试通过

java 复制代码
{
    "code": 200,
    "msg": "OK",
    "data": [
        {
            "type": "USER",
            "content": "你好"
        },
        {
            "type": "ASSISTANT",
            "content": "您好!有什么可以帮您?",
            "params": {}
        },
        {
            "type": "USER",
            "content": "课程推荐"
        },
        {
            "type": "ASSISTANT",
            "content": "您好!有什么可以帮您呢?",
            "params": {}
        }
    ],
    "requestId": "85fe6cff6b4a40acae388601c679fb89"
}

那为啥视频里面是 1 呢

因为视频里面传的是 result 结果,不是 question,因为是有上下文的,所以它也知道问题是什么


如果对你有帮助的话,请点赞,关注,收藏。热爱可抵一切!👍 ❤️ 🔥

相关推荐
shjsjdmmd13 小时前
Claude API 流式输出(SSE)实战指南:从打字机效果到 Agent 工具调用完整落地
状态模式
前端不太难14 小时前
解码大模型的效率革命:当算力不再是唯一瓶颈
状态模式
前端不太难14 小时前
从算力到存力:AI性能的决定性因素正在重构
人工智能·重构·状态模式
Python私教2 天前
从主题闪烁到 Markdown 阅读体验:RuyiBlog v0.1.1 的前端实现复盘
前端·状态模式
ForgeAI码匠2 天前
后台权限不只是菜单隐藏:Forge Admin 的 RBAC 权限链路拆解
java·spring boot·spring·状态模式
前端不太难2 天前
鸿蒙 PC 为什么需要新的组件体系?
华为·状态模式·harmonyos
c++之路2 天前
状态模式(State Pattern)
ui·状态模式
晓杰'2 天前
从0到1实现Balatro游戏后端(4):玩家手牌操作(出牌 / 弃牌 / 补牌)与状态流转设计
后端·websocket·typescript·node.js·状态模式·项目实战·nestjs
dinl_vin2 天前
FastAPI 系列 ·(九):中间件与错误处理:让服务更健壮
中间件·状态模式·fastapi