从架构图到落地的第一步,往往是最能体现工程思维的环节。上次我们聊完了智能体的六种架构模式,并得出结论:对于天机AI助理这种多领域、独立并行的业务场景,路由工作流智能体是最合适的骨架。今天,我们就正式动手,把之前那个单兵作战的增强型智能体,改造成一个由“意图路由”驱动的多智能体协同系统。

一、实现流程

📚 流程说明:

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

二、打好地基:接口与抽象类

5个智能体必然会有重复代码,我们必须先建立一套统一的抽象。

2.1类型枚举

让每个智能体都有明确的身份标识:

package com.edu.enums;

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

/**
 * 智能体类型枚举
 */
@Getter
public enum AgentTypeEnum {
    ROUTE("ROUTE", "路由智能体"),
    NORMALCHAT("NORMALCHAT", "普通聊天智能体"),
    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);
    }

}

2.2 Agent接口

一个智能体需要具备哪些能力?

基础能力

  • process:普通对话

  • processStream:流式对话

  • getAgentType:获取类型

  • stop:停止生成

  • systemMessage:系统提示词

扩展能力(与LLM和工具交互):

  • tools:挂载的工具集

  • toolContext:工具运行时上下文

  • advisors:增强顾问列表

  • advisorParams:顾问参数

  • systemMessageParams:提示词动态参数

所有方法都有默认实现,子类只需覆写自己关心的部分:

package com.edu.agent;

import com.edu.enums.AgentTypeEnum;
import com.edu.vo.ChatEventVO;
import org.apache.logging.log4j.util.Strings;
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 {

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

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

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


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

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

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

    /**
     * 获取工具列表,默认返回空数组。子类需根据需求覆盖此方法。
     * 此处需要返回数组,不能返回List (list会出现异常)
     */
    default Object[] tools() {
        return new Object[0];
    }

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

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

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

}

2.3 抽象基类 AbstractAgent

把与大模型交互的通用逻辑(构建 ChatClient 请求、处理流式输出、生成中断处理等)全部封装在抽象基类中。子类只需要关注自己独特的提示词和工具配置。

这其中还处理了几个关键问题:

  • 生成中断:通过 ConcurrentHashMap 维护会话的生成状态,实现随时停止

  • 工具结果传递:通过 ToolResultHolder 把工具执行结果以参数事件的形式追加到流末尾

package com.edu.agent;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.edu.enums.ChatEventTypeEnum;
import com.edu.holder.ToolResultHolder;
import com.edu.vo.ChatEventVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.AssistantMessage;
import reactor.core.publisher.Flux;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public abstract class AbstractAgent implements Agent {

    @Resource
    private ChatClient chatClient;
    @Resource
    private ChatMemory chatMemory;

    // 输出结束的标记
    public static final ChatEventVO STOP_EVENT = ChatEventVO.builder()
            .eventType(ChatEventTypeEnum.STOP.getValue())
            .build();

    // 存储大模型的生成状态,这里采用ConcurrentHashMap是确保线程安全
    // 目前的版本暂时用Map实现,如果考虑分布式环境的话,可以考虑用redis来实现
    public static final Map<String, Boolean> GENERATE_STATUS = new ConcurrentHashMap<>();


    @Override
    public String process(String question, String sessionId) {
        // 获取用户id
        //var userId = UserContext.getUser();
        var requestId = this.generateRequestId();

        //更新会话时间和标题
        //this.chatSessionService.update(sessionId, question, userId);

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

    @Override
    public Flux<ChatEventVO> processStream(String question, String sessionId) {
        // 获取用户id
        //var userId = UserContext.getUser();
        var requestId = this.generateRequestId();
        // 大模型输出内容的缓存器,用于在输出中断后的数据存储
        var outputBuilder = new StringBuilder();

        //更新会话时间和标题
        //this.chatSessionService.update(sessionId, question, userId);

        return this.getChatClientRequest(sessionId, requestId, question)
                .stream()
                .chatResponse()
                .doFirst(() -> GENERATE_STATUS.put(sessionId, true)) // 第一次输出内容时执行
                .doOnError(throwable -> GENERATE_STATUS.remove(sessionId)) // 出现异常时,删除标识
                .doOnComplete(() -> GENERATE_STATUS.remove(sessionId)) // 完成时执行,删除标识
                .doOnCancel(() -> {
                    // 当输出被取消时,保存输出的内容到历史记录中
                    this.saveStopHistoryRecord(sessionId, outputBuilder.toString());
                })
                .takeWhile(response -> { // 通过返回值来控制Flux流是否继续,true:继续,false:终止
                    return GENERATE_STATUS.getOrDefault(sessionId, false);
                })
                .map(chatResponse -> {
                    var finishReason = chatResponse.getResult().getMetadata().getFinishReason();
                    if (StrUtil.equals(ChatEventTypeEnum.STOP.name(), finishReason)) {
                        var messageId = chatResponse.getMetadata().getId();
                        ToolResultHolder.put(messageId, "requestId", requestId);
                    }

                    // 获取大模型的输出的内容
                    var text = chatResponse.getResult().getOutput().getText();
                    // 追加到输出内容中
                    outputBuilder.append(text);
                    // 封装响应对象
                    return ChatEventVO.builder()
                            .eventData(text)
                            .eventType(ChatEventTypeEnum.DATA.getValue())
                            .build();
                })
                .concatWith(Flux.defer(() -> {
                    // 通过请求id获取到参数列表,如果不为空,就将其追加到返回结果中
                    var map = ToolResultHolder.get(requestId);
                    if (CollUtil.isNotEmpty(map)) {
                        ToolResultHolder.remove(requestId); // 清除参数列表

                        // 响应给前端的参数数据
                        var chatEventVO = ChatEventVO.builder()
                                .eventData(map)
                                .eventType(ChatEventTypeEnum.PARAM.getValue())
                                .build();
                        return Flux.just(chatEventVO, STOP_EVENT);
                    }
                    return Flux.just(STOP_EVENT);
                }));
    }

    private ChatClient.ChatClientRequestSpec getChatClientRequest(String sessionId, String requestId, String question) {
        ChatClient.ChatClientRequestSpec chatClientRequestSpec = this.chatClient.prompt()
                .system(promptSystem -> promptSystem
                        .text(this.systemPrompt())
                        .params(this.systemPromptParams())
                )
                .advisors(advisor -> advisor
                        .advisors(this.advisors(sessionId))
                        .params(this.advisorParams(sessionId, requestId))
                );
        if (ArrayUtils.isNotEmpty(this.tools())) {
            chatClientRequestSpec.tools(this.tools())
                    .toolContext(this.toolContext(sessionId, requestId));
        }
        return chatClientRequestSpec
                .user(question);
    }

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

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

    @Override
    public void stop(String sessionId) {
        GENERATE_STATUS.remove(sessionId);
    }

    @Override
    public List<Advisor> advisors(String sessionId) {
        List<Advisor> advisors = new ArrayList<>();
        advisors.add(MessageChatMemoryAdvisor.builder(chatMemory)
                .conversationId(sessionId)
                .build());
        return advisors;
    }

    @Override
    public Map<String, Object> advisorParams(String sessionId, String requestId) {
        return Map.of(ChatMemory.CONVERSATION_ID, sessionId);
    }


}

三、逐个击破:5个智能体实现

3.1 RouteAgent(路由智能体)

3.1.1系统提示词核心设计

route-agent-system-message.st
# 角色
天机AI意图分析师

## 能力
1. 识别用户意图并匹配对应编号:
   - RECOMMEND(课程推荐)
   - BUY(课程购买)
   - CONSULT(课程咨询)
   - KNOWLEDGE(知识讲解)
   - NORMALCHAT(普通聊天)
2. 特殊场景处理:
   - 识别关键词触发意图:
     - BUY: 确认购买/下单/是的确认
     - RECOMMEND: 包含年龄/学历/兴趣信息

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

## 输出
- 匹配意图时返回编号
- 无匹配时则返回NORMALCHAT

## 示例
输入:20岁本科想学Java → RECOMMEND
输入:现在要下单 → BUY
输入:这个课程多少钱 → CONSULT
输入:java是什么 → KNOWLEDGE
输入:你好 → NORMALCHAT
输入:今天天气 → NORMALCHAT

3.1.2提示词资源

package com.edu.resources;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

/**
 * 提示词资源
 */
@Component
@Data
public class AiPromptResources {

    /**
     * 系统聊天提示词
     */
    @Value("classpath:prompts/system-chat-message.st")
    private Resource systemChatMessage;


    /**
     * 路由提示词
     */
    @Value("classpath:prompts/route-agent-system-message.st")
    private Resource routeAgentSystemMessage;

    /**
     * 课程推荐提示词
     */
    @Value("classpath:prompts/recommend-agent-system-message.st")
    private Resource recommendAgentSystemMessage;

    /**
     * 课程咨询提示词
     */
    @Value("classpath:prompts/consult-agent-system-message.st")
    private Resource consultAgentSystemMessage;

    /**
     * 知识讲解提示词
     */
    @Value("classpath:prompts/knowledge-agent-system-message.st")
    private Resource knowledgeAgentSystemMessage;


    /**
     * 聊天客服提示词
     */
    @Value("classpath:prompts/normal-chat.st")
    private Resource normalChatMessage;

}

3.1.3. 编写智能体

package com.edu.agent;

import com.edu.enums.AgentTypeEnum;
import com.edu.resources.AiPromptResources;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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

    private final AiPromptResources aiPromptResources;

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

    @SneakyThrows
    @Override
    public String systemPrompt() {
        return StreamUtils.copyToString(
                this.aiPromptResources.getRouteAgentSystemMessage().getInputStream(),
                StandardCharsets.UTF_8);
    }

    /**
     * 路由智能体的顾问,返回空 不记录chatMemory
     *
     * @param sessionId
     * @return
     */
    @Override
    public List<Advisor> advisors(String sessionId) {
        return Collections.emptyList();
    }


}

3.1.4. 编写控制器

package com.edu.controller;

import com.edu.agent.RouteAgent;
import com.edu.dto.ChatDTO;
import com.edu.vo.ChatEventVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/route")
public class RouteController {

    @Autowired
    private RouteAgent routeAgent;

    @PostMapping("/simple")
    public String simpleChat(@RequestBody ChatDTO chatDTO) {
        return routeAgent.process(chatDTO.getQuestion(), chatDTO.getSessionId());
    }


    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ChatEventVO> stream(@RequestBody ChatDTO chatDTO) {
        return routeAgent.processStream(chatDTO.getQuestion(), chatDTO.getSessionId());
    }





}

3.2 NormalChatAgent(普通聊天智能体)

3.2.1系统提示词核心设计

normal-chat.st
# 角色说明

作为在线教育平台的聊天客服,你的主要职责是回复学员提问的问题。

3.2.2编写智能体

package com.edu.agent;

import com.edu.enums.AgentTypeEnum;
import com.edu.resources.AiPromptResources;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 普通聊天智能体
 */
@Component
@RequiredArgsConstructor
public class NormalChatAgent extends AbstractAgent {

    private final AiPromptResources aiPromptResources;

    @SneakyThrows
    @Override
    public String systemPrompt() {
        return StreamUtils.copyToString(
                this.aiPromptResources.getNormalChatMessage().getInputStream(),
                StandardCharsets.UTF_8);
    }

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



}

3.3 RecommendAgent(课程推荐智能体)

3.3.1系统提示词核心设计

recommend-agent-system-message.st
# 在线教育客服&讲师指南

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

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

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

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

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

3.3.2编写智能体

package com.edu.agent;

import com.edu.enums.AgentTypeEnum;
import com.edu.resources.AiPromptResources;
import com.edu.tools.CourseTools;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
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 org.springframework.util.StreamUtils;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * 课程推荐智能体
 */
@Component
@RequiredArgsConstructor
public class RecommendAgent extends AbstractAgent {

    private final AiPromptResources aiPromptResources;
    private final VectorStore vectorStore;
    private final CourseTools courseTools;

    @SneakyThrows
    @Override
    public String systemPrompt() {
        return StreamUtils.copyToString(
                this.aiPromptResources.getRecommendAgentSystemMessage().getInputStream(),
                StandardCharsets.UTF_8);
    }

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

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

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

    @Override
    public Map<String, Object> toolContext(String sessionId, String requestId) {
        return Map.of(
                "requestId", requestId  // 设置请求id参数
        );
    }

}

3.4 KnowledgeAgent(知识讲解智能体)

3.4.1系统提示词核心设计

knowledge-agent-system-message.st
# 角色说明

作为在线教育平台的资深客服代表兼讲师,你的主要职责是解答学员关于IT相关知识点的疑问,并提供详细讲解和示例。

## 技能要求

### 知识讲解
- 针对学员提出的IT知识点问题,进行详细的解析并给出实际案例辅助理解。

## 限制条件

- 仅限回答与课程内容及IT知识点相关的问题。如果学员提出与课程或IT知识无关的问题,请告知其你只能回答相关问题,并鼓励他们提出课程或IT领域的疑问。

3.4.2编写智能体

package com.edu.agent;

import com.edu.enums.AgentTypeEnum;
import com.edu.resources.AiPromptResources;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;

import java.nio.charset.StandardCharsets;

/**
 * 知识讲解智能体
 */
@Component
@RequiredArgsConstructor
public class KnowledgeAgent extends AbstractAgent {

    private final AiPromptResources aiPromptResources;

    @SneakyThrows
    @Override
    public String systemPrompt() {
        return StreamUtils.copyToString(
                this.aiPromptResources.getKnowledgeAgentSystemMessage().getInputStream(),
                StandardCharsets.UTF_8);
    }

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

}

3.5 ConsultAgent(课程咨询智能体)

3.5.1系统提示词核心设计

consult-agent-system-message.st
# 角色说明

作为在线教育平台的资深客服代表兼讲师,你的主要职责是为学员提供关于课程的咨询服务。

## 技能:课程咨询

### 课程推荐与信息查询
- 当学员询问课程内容时,根据知识库匹配合适的课程,并获取课程ID以查询详细信息。确保回复全面且具有引导性,鼓励学员报名购买。
- 若未能找到相关课程,请礼貌通知学员未检索到相关内容,并建议联系人工客服(电话:010-12345678)。
- 对于课程有效期的咨询,将当前时间{now}与课程有效期相加后告知学员具体日期;若有效期为999天,则视为永久有效。

### 注意事项
- 所有推荐课程必须源自知识库,严禁编造。
- 确保回答逻辑清晰、内容详尽无遗漏。
- 仅限回答与课程和IT知识点相关的问题。如遇无关问题,应告知学员无法作答,并引导其提出与课程或IT相关的疑问。
- 学员询问课程ID时,解释无法直接提供课程ID,并邀请他们探讨其他感兴趣的话题。

3.5.2编写智能体

package com.edu.agent;

import cn.hutool.core.date.DateUtil;
import com.edu.enums.AgentTypeEnum;
import com.edu.resources.AiPromptResources;
import com.edu.tools.CourseTools;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
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 org.springframework.util.StreamUtils;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * 课程咨询智能体
 */
@Component
@RequiredArgsConstructor
public class ConsultAgent extends AbstractAgent {

    private final AiPromptResources aiPromptResources;
    private final VectorStore vectorStore;
    private final CourseTools courseTools;

    @SneakyThrows
    @Override
    public String systemPrompt() {
        return StreamUtils.copyToString(
                this.aiPromptResources.getConsultAgentSystemMessage().getInputStream(),
                StandardCharsets.UTF_8);
    }

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

    @Override
    public List<Advisor> advisors(String sessionId) {
        // 创建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) {
        return Map.of(
                "requestId", requestId  // 设置请求id参数
        );
    }

    @Override
    public Map<String, Object> systemPromptParams() {
        return Map.of("now", DateUtil.now());
    }

}

四、多智能体协调工作

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

package com.edu.controller;

import com.edu.agent.Agent;
import com.edu.agent.RouteAgent;
import com.edu.dto.ChatDTO;
import com.edu.enums.AgentTypeEnum;
import com.edu.enums.ChatEventTypeEnum;
import com.edu.vo.ChatEventVO;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@RestController
@Slf4j
@RequestMapping("/route")
public class RouteController {

    @Autowired
    private RouteAgent routeAgent;

    @PostMapping("/simple")
    public String simpleChat(@RequestBody ChatDTO chatDTO) {
        return routeAgent.process(chatDTO.getQuestion(), chatDTO.getSessionId());
    }


    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ChatEventVO> stream(@RequestBody ChatDTO chatDTO) {
        return routeAgent.processStream(chatDTO.getQuestion(), chatDTO.getSessionId());
    }


    @Autowired
    private List<Agent> agentList;
    private Map<AgentTypeEnum, Agent> agentMap = new HashMap<>();

    @PostConstruct
    public void initAgentMap() {
        for (Agent agent : agentList) {
            agentMap.put(agent.getAgentType(), agent);
        }
    }


    /**
     * 多智能体协作
     */
    @PostMapping(value = "/multi-agent", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ChatEventVO> multiAgent(@RequestBody ChatDTO chatDTO) {
        //String routeResponse = routeAgent.process(chatDTO.getQuestion(), chatDTO.getSessionId());
        String routeResponse = routeAgent.processStream(chatDTO.getQuestion(), chatDTO.getSessionId())
                .filter(vo -> vo.getEventType() == ChatEventTypeEnum.DATA.getValue())
                .map(vo -> String.valueOf(vo.getEventData()))
                .collectList()
                .map(list -> String.join(Strings.EMPTY, list))
                .block();
        log.info("路由智能体,结果:{}", routeResponse);
        AgentTypeEnum agentTypeEnum = AgentTypeEnum.agentNameOf(routeResponse);
        Agent agent = agentMap.get(agentTypeEnum);
        return agent.processStream(chatDTO.getQuestion(), chatDTO.getSessionId());
    }


}

五、chatMemory多条记录问题

如果遇到这个问题,可见:https://www.yuque.com/zhangzhijun-91vgw/javaai1.0/lokiyqtciar0ep4f#CUzgc

文档访问密码:nxyt

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐