Ai Agent 智能体笔记
什么是 AI Agent 智能体
AI 智能体是使用 AI 来实现目标并代表用户完成任务的软件系统。其表现出了推理、规划和记忆能力,并且具有一定的自主性,能够自主学习、适应和做出决定。
Spring AI 框架,支持大语言模型构建 AI Agent 实现。AIAgent是整合多种技术手段的智能实体 ,其实现依赖于 Tools、MCP、Memory、RAG(Retrieval 增强检索生成) 等技术组件,但不是非得依赖全部组件才叫 AI Agent。
AI Agents的定义与类型
AI Agents 是系统 ,它通过赋予 大型语言模型(LLMs) 访问工具 和知识 来扩展其能力,从而使 LLMs 能够执行操作。
- 系统:将 Agents 视为一个由许多组件组成的系统,而不仅仅是单个组件,这一点很重要。在基本层面上,AI Agent 的组件包括:
- 环境 - AI Agent 运行的定义空间。例如,如果我们有一个旅行预订 AI Agent,则环境可以是 AI Agent 用来完成任务的旅行预订系统。
- 传感器 - 环境具有信息并提供反馈。AI Agents 使用传感器来收集和解释有关环境当前状态的信息。在旅行预订 Agent 示例中,旅行预订系统可以提供诸如酒店可用性或航班价格之类的信息。
- 执行器 - 一旦 AI Agent 接收到环境的当前状态,对于当前任务,Agent 会确定要执行的操作以更改环境。对于旅行预订 Agent,它可能是为用户预订可用房间。
- 大型语言模型 - Agents 的概念在 LLMs 创建之前就已存在。使用 LLMs 构建 AI Agents 的优势在于它们能够解释人类语言和数据。这种能力使 LLMs 能够解释环境信息并制定改变环境的计划。
- 执行操作 - 在 AI Agent 系统之外,LLMs 仅限于根据用户提示生成内容或信息的情况。在 AI Agent 系统内部,LLMs 可以通过解释用户请求并使用其环境中可用的工具来完成任务。
- 访问工具 - LLM 可以访问哪些工具由 1) 它运行的环境和 2) AI Agent 的开发者定义。对于我们的旅行 Agent 示例,Agent 的工具受预订系统中可用操作的限制,开发者可以将 Agent 的工具访问权限限制为航班。
- 知识 - 除了环境提供的信息外,AI Agents 还可以从其他系统、服务、工具甚至其他 Agents 中检索知识。在旅行 Agent 示例中,此知识可以是位于客户数据库中的用户旅行偏好信息。
【类型】
简单反射、基于模型的反射、基于目标、基于效用、学习、分层、多Agent系统
AI 智能体、AI 助理和聊天器凄然的区别
|AI智能体 | AI助理 |聊天机器人|
|自主执行任务| 协助用户执行任务 | 自动执行简单任务或对话|
|自主性高|被动,响应用户请求|被动,响应命令|
AI 智能体工作原理
每个智能体都定义了角色、个性和沟通风格等
- 角色:定义良好的角色可以让智能体保持一致到底性格
- 记忆:智能体具备短期记忆、长期记忆、共识记忆及情景记忆。短期记忆用于即时互动,长期记忆用于历史数据和对话,情景记忆用于过去的互动,共识记忆用于智能体之间的共享信息。
- 工具:工具是智能体可以利用的函数或外部资源,用于与环境互动并增强功能。工具可让智能体访问信息、处理数据或控制外部系统来执行复杂的任务
- 模型:大语言模型(LLM)是构建AI智能体的基础,为智能体提供理解、推理和行动的能力。LLM充当智能体的“大脑”,使其能够处理和生成语言,而其他组件则促进推理和行动。
AI 智能体类型
【基于互动】
- 互动式合作伙伴:由用户查询触发,并执行用户查询或事务
- 自主后台处理:在后台运行以自动执行常规任务、分析数据等,通常由事件驱动
【基于智能体数量】
- 单个智能体:独立运作来实现特定目标。利用外部工具和资源来完成任务。
- 多智能体:这些系统利用各个智能体的不同能力和角色,来处理复杂的任务。多智能体系统可以在互动场景中模拟人类行为,例如人际沟通。每个智能体都可以拥有最适合其需求的基础模型。
RAG和MCP
RAG (Retrieval-Augmented Generation,检索增强生成):指查知识库,然后将查到的片段喂给大模型
MCP (Model Context Protocol,模型上下文协议):提供接口,用来让模型调用工具
SpringAI框架开发MCP
MCP服务开发
MCP就是暴露指定的方法给AI,给AI提供工具
- 先引入Spring AI MCP依赖
- 然后在Service中需要暴露的方法上加
@Tool(description = "方法作用描述") - 配置类中注册一个Tool工具回调提供者的bean,将Service注入,然后将Service中所有用@Tool注解的方法注册到Tool工具回调提供者中
- 配置文件里面提供关于MCP协议的内容,Spring:ai:下面有很多配置,使用的sse
- MCP服务实现中会看到方法的出入参对象加入大量的注解,LLM会根据这些出入参对象组装对象
MCP代理调用
实现一个 MCP 客户端,用于对接 MCP 服务端,并通过代理 AI 接口的方式完成调用。
- proxy-api定义代理接口:发送Post请求(由Retrofit2Config封装发送请求)
- proxy-trigger实现代理接口:Controller注入代理接口实现,调用接口方法,发送http请求
- proxy-app程序启动入口,之后ApiTest层是测试调用MCP服务
MCP通信协议(json-rpc2)
配置CORS过滤器,配置了全局的跨域
初始化流程:
- 创建Service实例
- 创建ToolCallbackProvider实例,并注入Service实例
- 自动注册Service实例中的@Tool方法,或手动注册方法
- 然后可以开始使用MCP服务了,接收和处理MCP工具调用请求
之后处理MCP请求的流程:
- MCP请求到达端点,请求体包含method和params参数
- 工具路由分发,由ToolCallbackProvider负责路由
- 具体工具执行,例如提问、字体转换工具,返回响应
MCP请求的两个参数也是由MCP客户端通过AI生成的:
- 工具发现:MCP客户端的AI模型事先通过MCP协议从服务端获取可用工具列表
- AI决策:AI模型根据上下文决定调用哪个工具和传入参数,例如提问就调用ask,参数就是用户的具体问题
- 生成决策过程:
- method:用户提问 → AI 理解意图 → 匹配可用工具 → 选择最合适的 method
- params:提取关键信息 → 映射到工具参数结构 → 验证参数完整性 → 生成 params
- SSE是长连接,服务端实时发送数据给客户端(单向)
- 例如ask提问工具就会使用sse连接,模型一边推理生成,一边推送生成的内容给客户端
【架构分工】
服务端 = MCP 工具提供者
- 执行具体的工具方法(如 ask、toUpperCase)
- 提供工具列表接口
- 处理工具调用请求
MCP 客户端 = AI 助手层
- 调用 AI 模型分析用户意图
- 决策选择合适的工具
- 构造工具调用参数
前端 = 用户界面层
- 收集用户输入
- 展示最终结果
- 处理用户交互
MCP客户端的AI主要用来决策
服务端的AI可以作为MCP的工具用来回答问题
MCP和RAG
我跳过这个两个阶段直接去Ai Agent,这俩先只做简单了解
MCP(Model Context Protocol)模型上下文协议
提供了AI模型和工具交互的协议,只要遵循这个协议设计的接口就让这个工具被AI使用
MCP架构:
1 | |
目前支持两种协议
- SSE:长连接、单向推送消息给客户端
- Stdio:标准输入输出流、本地进程通信
项目中的MCP的工作流程:
1 | |
RAG(Retrieval-Augmented Generation)检索增强生成
RAG = 检索 + 生成,让 AI 在回答前先从知识库查找相关信息,再基于这些信息生成答案。
1 | |
之后可以上传文档,进行解析、拆分、添加标签,然后文本转换为向量,存到pg数据库(用于检索),记录订单到MySQL(记录有哪些知识库)
然后就可以进行查询:获取ChatModel、查询RAG标签、向量相似度搜索、拼接文档内容、构建System Prompt(带RAG内容)、最后流式返回
二者结合的使用流程
1 | |
Ai Agent 业务流程、系统架构、库表设计说明
AI 智能体是使用 AI 来实现目标并代表用户完成任务的软件系统。其表现出了推理、规划和记忆能力,并且具有一定的自主性,能够自主学习、适应和做出决定。
这一节是纯介绍直接给的全代码,后面一节才是从0开始搭建
业务流程
用户发起AI对话的请求调用流程
1 | |
系统架构
一样的DDD架构
1 | |
service包结构,几大领域
- IAiAgentChatService - AI 对话服务:普通对话、流式对话
- IAiAgentPreheatService - 服务预热接口:启动时加载所有配置、手动预热单个客户端————构建 ChatClient、ChatModel、Advisor 等 Bean 并注册到 Spring 容器
- IAiAgentRagService - RAG 知识库服务:上传文件到向量数据库————解析、切分、添加knowledge标签、存储到pgvector、记录到数据库
- IAiAgentTaskService - 智能体任务调度:查询所有有效的定时任务、查询所有无效的任务 ID————配合 @Scheduled 注解执行定时 AI 任务
1 | |
装配工厂
1 | |
树形策略链(责任链模式)
1 | |
各个节点功能
- RootNode - 根节点,多线程并发加载所有配置数据,包括(模型配置、MCP工具、顾问配置、系统提示词、客户端基础配置)
- AiClientToolMcpNode - MCP 工具节点,初始化MCP客户端并注册为Bean,支持SSE和Stdio
- AiClientAdvisorNode - 顾问节点,创建 Advisor(增强 AI 行为的组件),支持两种类型(ChatMemory会话记忆,RagAnswerRAG检索增强回答)
- AiClientModelNode - 模型节点,创建 OpenAI 聊天模型并集成 MCP 工具
- AiClientNode - 客户端节点,构建最终的 ChatClient 对象,整合所有配置————添加ChatModel、Advisors、System Prompt(提示词),最后注册为Bean
- RagAnswerAdvisor - RAG 回答顾问(特殊节点),不是树的节点,而是具体的 Advisor 实现
数据模型(VO对象)
- AiClientVO - 客户端配置
- AiClientModelVO - 模型配置(API Key、BaseURL 等)
- AiClientToolMcpVO - MCP 工具配置
- AiClientAdvisorVO - 顾问配置(ChatMemory、RagAnswer)
- AiClientSystemPromptVO - 系统提示词
启动流程
1 | |
库表设计说明
设计了14张库表,来承接自动化 Ai Agent 的构建操作。
ai_agent_task_schedule,智能体任务调度配置表
ai_agent,AI智能体配置表
ai_agent_client,智能体-客户端关联表
ai_client,AI客户端配置表
模型配置组;ai_client_model、ai_client_model_config、ai_client_model_tool_config
工具配置组;ai_client_tool_config、ai_client_tool_mcp
顾问配置组;ai_client_advisor、ai_client_advisor_config
提示词配置;ai_client_system_prompt、ai_client_system_prompt_config
知识库配置;ai_rag_order
从0开始建立项目
看第二节,直接从0开始做
Ai Agent 测试案例
环境搭建
安装环境最外层的两个docker-compose文件
- docker-compose-app.yml启动开发好的mcp服务(一个csdn发帖,一个微信公众号通知)
- docker-compose-environment.yml启动当前项目需要的各种服务(各种数据库以及管理工具)
- phpmyadmin是MySQL的网页端管理工具,8899就能直接访问,redis-admin就是redis的
- 数据库初始化是直接运行指定路径下的sql文件
(创建自己的镜像(可选):先install打成jar包,然后运行dockerfile,创建镜像)
yml文件中配置数据库地址和模型的key,key可以从小傅哥直接购买
然后就可以测试OpenAI的模型的功能了,和解析上传向量数据库
测试代码
OpenAiTest(5 个测试方法)
- test_call() - 基础文本问答(1+1)
- test_call_images() - 多模态图片识别
- test_stream() - 流式输出
- upload() - 文档上传到向量数据库
- chat() - RAG 检索增强生成
AiAgentTest(4 个测试方法)
- test_chat_model_stream_01() - 流式调用
- test_chat_model_call() - 同步调用
- test_02() - ChatClient 对话
- test_client03() - 多轮对话 + 提示词优化 + Agent 任务
【流式响应】
- 先创建一个countDownLatch对象,结尾使用
- 然后构建一个Flux对象,用提示词发起流式请求
- 然后这个对象订阅响应,持续打印出来,
- 流式响应后用countDownLatch.await()阻塞主线程,直到流式响应完全结束
- 比如这里问1+1等于几,面就会一个字符一个字符
1 + 1 = 2
然后测试AiAgent功能,测试时的key和那两个mcp的地址也改了
我的npm可能有问题,重新配置了一下配置方法
这个stdioParams的能访问的地址也要改/Users/fuzhengwei/Desktop,改成windos的C:\Users\26466\Desktop
npx命令找不到,把npx改成npx.cmd
之后分别验证test_chat_model_stream_01、test_chat_model_call、test_02、test_client03
模型和智能体区别
| 方面 | OpenAiTest(普通调用) | AiAgentTest(智能体) |
|---|---|---|
| 调用方式 | openAiChatModel.call(prompt) |
ChatClient.prompt().system().advisors().call() |
| 角色定义 | 简单 System Prompt | 复杂 Agent 角色 + 任务规划 |
| 工具调用 | ❌ 无 | ✅ MCP 工具(文件系统、CSDN、微信) |
| 记忆能力 | ❌ 无(每次独立) | ✅ MessageWindowChatMemory(多轮对话) |
| 知识检索 | 手动 RAG | 自动 RAG(RagAnswerAdvisor) |
模型就很简单,主要讲智能体
智能体可以配置MCP工具回调、角色、顾问
智能体就是模型+工具+角色+记忆+顾问
- 模型是大脑
- 工具就是mcp
- 角色,定义人格设定和专业知识
- 记忆,记住历史对话
- 顾问,RAG自动知识检索等
模型token
token与字数相关,一次问答输入输出的token和就是总消耗
根据 Ai Agent 案例,设计库表
首先,整个代码构建的整个 Ai Agent 最小化单元服务,我们可以根据这样的服务信息设计出库表结构。
第一步,从上到下,OpenAiApi 是最基础单元结构,可以被多个 OpenAiChatModel 使用,它可以被拆分出第一张表。
第二步,构建 OpenAiChatModel,这个阶段,需要 openAiApi、model对话模型、tool mcp 工具。其中model对话模型时一种固定固定资源,可以直接放到 ai_client_model 模型中,而 openAiApi、mcp 工具,都属于复杂配置,则需要额外的外部关联来衔接。也就是后面的 ai_client_config 配置,用于配置衔接关系。
第三步,ChatClient 对话客户端,这部分的实例化过程都是和外部其他的资源关联,本身表设计只要有一个客户端的唯一id和客户端的描述介绍即可。
第四步,给 mcp 增加一个表,mcp 服务是非常重要的,有 mcp 才有 agent 服务。mcp 的启动有 stido、sse 两种方式,每种方式都有对应的配置文件 json 数据。
第五步,defaultSystem 系统提示词,需要单独拆分出来。提示词等于智能体的大脑,也有人说,其实 Ai Agent 就是 prompt 的堆叠,所以写提示词是很重要的。
第六步,advisor 顾问角色,在 Spring Ai 框架中,以顾问的方式,访问记忆上下文,知识库资源,所以这部分也要单独设计库表。
第七步,设计一个 ai_client_config,用于配置;api、model、client、prompt、mcp、advisor的衔接关系。
第八步,设计 ai_agent、ai_agent_flow_config,也就是一个 ai agent,是可以连续调用多个 ai client 客户端的。
第九步,设计 ai_agent_stask_schedule 任务,这是一种触达手段,可以把配置好的任务,让 task 定时执行,如自动发帖、系统异常巡检、舆情风险检测、系统配置变更、运营活动报表等。
第十步,ai_client_rag_order,是知识库表,用于上传知识库做一个记录,这样顾问角色就可以访问知识库内容了。
【表描述】
- api表,基本单元,包含不同url,key等,供多个模型使用
- ai_client_model聊天模型配置表,定义系统可用AI模型,通过关联API配置
- ai_client_tool_mcp表存放工具集成配置,mcp启动有两种方式,sse和stido对应两个JSON字段
- ai_client_config表,用来存上面模型和mcp、api的对应衔接关系,例如:源类型,源ID,目标类型,目标ID,扩展参数。类型都是多样的
- ai_client对话客户端,客户端是功能单元可以独立处理特定类型的AI任务,客户端通过关联配置与模型、提示词、工具等资源建立连接,多个客户端可以组成智能体工作流
- ai_client_system_prompt系统提示词表,描述智能体的角色,决定了AI的行为方式和回答风格,这是核心组成部分,是智能体的“大脑”
- ai_client_advisor顾问角色表,包含名称、顾问类型、扩展参数配置,json记录,在SpringAi框架中,以顾问的方式记忆上下文和访问知识库
- ai_agent表,记录智能体信息,可以关联多个客户端
- ai_agent_flow_config表,记录当前智能体的流程配置,通过sequence字段指定多个客户端执行顺序,形成链路
- ai_agent_stask_schedule任务表,这是一种触达手段,定义任务描述,参数,cron表达式等,让task自动执行
- ai_client_rag_order知识库表,用于给上传知识库做一个记录,这样顾问角色就可以访问知识库内容了
本系统是基于Spring AI框架构建的AI Agent智能体平台,通过数据库驱动的配置化方式实现AI模型、客户端、工具和智能体的解耦和自动实例化。系统支持多种AI模型接入、工具集成(MCP协议)、任务调度等功能。
多数据源和Mapper配置
配置pgvector(向量库)、mysql(业务库)的连接
- 首先添加一个DataSourceConfig配置类,来自己实现数据源的加载,这部分会替代原本配置到yml中的配置,由Spring加载数据源
- 要配置MySQL、MyBatis集成、PgVector向量数据源,
@Bean("mysqlDataSource")写明数据源名称 - MyBatis的集成sqlSessionTemplate中用
@Qualifier("mysqlDataSource"):明确指定注入名为”mysqlDataSource”的Bean,确保MyBatis使用MySQL数据源,而不会误用PgVector数据源 - PgVectorJdbcTemplate中用
@Qualifier("pgVectorDataSource"),明确注入到PgVector数据源。使用JdbcTemplate而不是MyBatis,更适合向量操作的简单SQL - HikariCP是目前性能最优的java的连接池,Spring默认使用,配置参数类似线程池,MySQL和PgVector用的都是这个
- vectorStore向量库配置,指定库表名称,也就是初始化的向量库表vector_store_openai
- 要配置MySQL、MyBatis集成、PgVector向量数据源,
- 之后根据不同类型的数据源,注入到AI向量库使用场景和MyBatis业务使用场景中,类似之前的DB-Router
【多数据源设计优势】
- 数据隔离性: 业务数据和向量数据完全分离,互不影响,提升系统稳定性
- 性能优化: 针对不同数据类型和访问模式优化连接池参数
- 技术栈适配: MySQL使用MyBatis ORM,PgVector使用JdbcTemplate,各取所长
- 扩展性强: 可以轻松添加更多类型的数据源,如Redis、MongoDB等
- 配置灵活: 支持外部配置文件,便于不同环境的参数调整
- 故障隔离: 一个数据源的问题不会影响另一个数据源的正常使用
【Mapper配置】
一个数据库表为了可以让程序使用,主要需要 PO 对象(持久化对象),DAO 文件(数据传输对象)以及 Mapper 配置数据库语句来映射 PO 和 DAO。
mapper里面定义数据库到映射po,然后dao层写一写简单的增删改查接口就可以测试了
数据加载模型设计
首先,整个 Ai Agent 的实例化过程,就是各项组件的创建和组装的过程。那么,为了让整体的实现代码更易于维护,我们可以把这样的创建过程,通过规则树的方式进行串联实现。
domain包下新建agent领域,该领域有adapter、model、service
- adapter:适配器层(之前大营销是Repository层),也就是整个agent需要什么样的数据结构,就会定义对应的接口方法。之后由基础设施层,通过pom引入domain领域层来适配这些接口。好处就是适配和防腐,如果将来基础层数据有变动,也不会影响到领域层的服务方法。
- model:领域对象,这些对象是为了满足service服务创建的对象,包括聚合对象,实体,值对象
- service:具体的逻辑服务实现,这部分要完成agent实例化过程。因为实际使用中,用户可以通过只实例化 api,也可以实例化整个 client,这是不同的操作。所以这块要做一个数据加载策略。
service包下类关系:
- AbstractArmorySupport:抽象装配支撑类,定义了日志,和几个protected属性(Spring上下文、线程池、仓储接口),还有多线程加载方法,然后他还继承了
AbstractMultiThreadStrategyRouter<ArmoryCommandEntity, DefaultArmoryStrategyFactory.DynamicContext, String>,这个是一个外部导入的扳手工程,这个类实现了原来的规则树逻辑。还实现了两个接口一个StrategyHandler接口,接口定义了策略处理方法,一个StrategyMapper接口,接口定义获取下个流程的 StrategyHandler 实现类,详情在扳手工程的第二节 - RootNode:根节点:
- 首先是map注入的加载策略实现
- 然后是重写的多线程数据处理方法,通过map中的加载策略实例获取加载策略,从入参获取命令类型,然后作为参数调用加载策略的加载方法
- doAppley执行入口,调用父类的router方法,流转到下个节点
- 获取策略处理器
- 具体策略节点:注入仓储和线程池属性,和一个加载数据方法
- 先从ArmoryCommandEntity 获取客户端 ID 列表
- 然后用创建多个CompletableFuture异步任务 + 线程池并发执行多个数据库的查询,查询好的结果会放到入参的上下文对象中,都查询完了方法就返回。
- 查询的对象根据加载策略决定,加载客户端对话模型就只需要api和model两个对象,加载一个client,要顺序的加载所有的资源,包括;ai_client_api、ai_client_model、ai_client_tool_mcp、ai_client_system_prompt、ai_client_advisor、ai_client。
RootNode运行时调用流程
1 | |
动态实例化客户端API
service.armory包下新建一个AiClientApiNode类,内部构建Ai Agent
AiClientApiNode 的构建会使用到 RootNode 节点加载的数据
AiClientApiNode 的构建完成后,会使用到 AbstractArmorySupport 中提供的注册 Bean 到 Spring 容器的方法,这个地方使用到了Spring源码
【手动构建bean的方法】
- 第一步:获取Bean工厂,DefaultListableBeanFactory 是Spring容器的核心实现类。通过它可以动态管理Bean的生命周期(注解主要在项目初始化的时候注册Bean)
- 第二步:构建Bean定义,使用 BeanDefinitionBuilder 创建Bean定义。genericBeanDefinition(beanClass, () -> beanInstance) 指定Bean类型和实例供应商。设置作用域为单例模式( SCOPE_SINGLETON )
- 第三步:处理Bean冲突,检查是否已存在同名Bean,如果存在,先移除旧的Bean定义,确保新Bean能够正确注册。
- 第四步:注册新Bean,将新的Bean定义注册到Spring容器。
【AiClientApiNode类doApply方法构建Ai Agent】
- 首先获取上下文对象中的aiClientApiList,并验空
- 然后遍历这个list,获取VO对象,每个VO都构建OpenAiApi,并注册为Bean
- 完成后router到下一个节点继续处理其他节点实例化
- router之后后会走到get方法,目前设置的是 defaultStrategyHandler,也就是不执行下一个节点。后续会随着功能开发来修改。
【agentTest测试类调用流程】
- 先注入工厂类和Spring上下文,然后执行构建方法
- 构建方法先用工厂类获取一个armoryStrategyHandler,默认实现就是rootNode
- mock一个ArmoryCommandEntity(命令类型为client,客户端列表是一个列表元素只有3001),和一个上下问对象用作为入参
- 执行armoryStrategyHandler(rootNode)的apply方法进行装配
- 进入到apply方法后执行multThread进行多线程装配:先获取命令类型,然后获取策略,最后执行加载方法,就是加载策略类的方法
- loadData方法先获取客户端列表,用异步任务+线程池查询当前client所有所需配置,存到上下文对象中
- 然后再执行RootNode的doApply方法内部执行router方法,进行流转,进入下一个节点AiClientApiNode
- AiClientApiNode的doApply方法中开始构建Ai Agent,从上下文对象中获取data验空,然后遍历上下文列表,每一个VO对象都组装成一个OpenAiApi对象并注册为Bean,然后再进入router方法,此时get就为空,返回
- 装配后从Spring容器中取出装配的Bean来验证
【这个框架的方法】
- AbstractMultiThreadStrategyRouter类实现两个接口,一个StrategyMapper,一个StrategyHandler
- router方法:获取下一个strategyHandler(实例是下一个节点类),然后进入获取实例的apply方法,为null就调用默认策略处理器,返回null
- apply方法:multiThread异步加载数据并存到上下文中,然后执行doApply方法,具体实现都交由子类(mutiThread方法在父类默认缺省,就是{},子类不实现就直接略过这个方法了)
- get方法:获取下一个节点,抽象类的接口方法没实现,而是交由子类实现
- 通过这样的处理,从 router -> apply -> multiThread -> doApply 就顺序的把各个流程节点都执行了,帮助我们定义了标准的执行链路结构。
【这个流转的逻辑】
| 步骤 | 节点 | 动作 | 返回值 |
|---|---|---|---|
| 1 | 外部调用 | handler.apply() |
- |
| 2 | RootNode | multiThread() → 查库 |
- |
| 3 | RootNode | doApply() → router() |
- |
| 4 | RootNode | get() → 返回 aiClientApiNode (目前写死) |
- |
| 5 | AiClientApiNode | doApply() → 注册 Bean |
- |
| 6 | AiClientApiNode | router() → get() 返回 defaultStrategyHandler |
- |
| 7 | DEFAULT | doApply() → return null |
null |
| 8 | 返回 | 逐层返回 null |
null |
| 9 | 验证 | 从 Spring 容器获取 Bean | ✅ 成功 |
目前就两个节点
一个RootNode用来加载数据到上下文,然后流转到下一个节点
一个AiClientApiNode用来用上下文对象构建Ai Agent并注册为Bean
动态实例化对话模型
上一节新增了Api节点类,并完成了构建、实例化、注册为Bean的逻辑
这次再新建两个节点类,AiClientToolMcpNode(MCP工具节点)、AiClientModelNode(模型节点处理)
【当前要实现的流程】
- RootNode负责数据加载,将构建节点元素的数据依次加载到内存中
- 之后在上一节完成的API节点处理基础上,开始创建MCP服务的创建,之后是ChatModel对话模块的创建,ChatModel创建需要用到api、mcp两个元素
【改动】
- 首先,在 AbstractArmorySupport 定义 beanName、dataName,让每个节点类都可以实现一个自己的 beanName、dataName 方法,这样更好维护。
- 之后,在 dynamicContext.getValue(dataName()); 使用 dataName 方法,获取 aiClientApiList 数据。
- 最后,AiClientApiNode 节点完成后,要路由到下一个节点,aiClientToolMcpNode。也就是 get 方法里新增加的配置。
【AiClientToolMcpNode】
- 类似api节点,doApply构建MCP服务:上下文获取MCP服务列表验空,然后将每一个VO都用私有方法createMcpSyncClient实例化为MCP,并注册为Bean
- createMcpSyncClient私有方法:根据transportType决定使用sse、stdio两种方式中的哪种创建客户端
【AiClientModelNode】
- 也是类似api节点,看来只有RootNode节点需要统一加载,只有他重写了multiThread方法进行loadData
- 从上下文中获取模型列表验空,然后获取当前模型关联的api Bean对象,还有关联的Tool MCP Bean对象(这个是一个
List<McpSyncClient>) - 然后实例化对话模型,openAiApi属性为获取的Bean,defaultOptions属性中的toolCallbacks为用mcpSyncClients创建的SyncMcpToolCallbackProvider
【测试注意点】
- 这个是直接运用npx命令启动
@modelcontextprotocol/server-filesystem包,这是 MCP 官方提供的标准文件系统服务器,ai用这个包就能创建文件了 - mcp数据库表里面的配置在windos环境要改很多,npx还是必须改成npx.cmd,后面的路径也要改成windos桌面的路径,用双反斜杠
- 测试类里面的命令也是和mcp表中配置的一致
1 | |
1 | |
这段 JSON 数据是 AI 模型返回的原始响应结构,其中包含了元数据(如 Token 用量、模型版本)和实际的回复内容。
提取其中的核心文本信息并格式化后,AI 的实际回复如下:
📂 当前可用目录
您目前授权我可以访问和操作的目录为:
C:\Users\26466\Desktop
在此目录及其子目录中,我可以帮您执行以下操作:
🛠️ 可用功能列表
📖 读取文件:查看文件的具体内容。
✍️ 写入/编辑:创建新文件或修改现有文件内容。
📋 列出目录:查看文件夹下有哪些文件和子文件夹。
🔍 搜索文件:在目录中查找特定名称或类型的文件。
ℹ️ 获取详情:查看文件大小、创建时间等属性。
📁 创建目录:新建文件夹。
🔄 移动/重命名:更改文件位置或文件名。
🖼️ 读取媒体:查看图片或播放音频文件(如果支持)。
💬 下一步
请告诉我您的具体需求,例如:
“帮我在桌面创建一个名为 notes.md 的文件”
“列出桌面上所有的 txt 文件”
“读取 report.pdf 的内容”
我将立即为您执行相应操作。
实例化对话客户端
在上面的基础上再加入两个节点:
- AiClientAdvisorNode顾问角色节点处理
- AiClientNode客户端节点处理:关联了其他各项元素的,所以在构建时,需要在 AiClientNode 节点,从 Spring 容器通过 getBean 的方式,检索到对应的各项元素。
【编码实现】
- 节点那一层新建这两个节点类
- service.armory.factory.element中新建RagAnswerAdvisor顾问类,用于访问知识库
- 查询出系统提示词,并转为map结构方便使用
- model.valobj包下新建一个AiClientAdvisorVO,负责接收服务器的查询
- model.valobj包下新建策略工厂类AiClientAdvisorTypeEnumVO,内部运用策略模式定义了两种顾问类型(匿名内部类,匿名内部类默认继承父类,也可以重写父类方法),map结构,还有创建顾问对象,根据code返回等方法
- CHAT_MEMORY:创建上下文记忆顾问,维护对话历史(基于内存窗口)。内部重写createAdvisor方法创建记忆组件并注入到Advisor中,创建Advisor类实例(来自SpringAI框架,可以保存最近N轮对话,每次提问时自动附带历史消息,超出窗口大小时自动移除最早的对话)
- RAG_ANSWER:创建知识库问答顾问,从向量数据库检索相关信息。也重写了创建方法,创建Advisor实例(从向量数据库(VectorStore)中搜索与用户提问相关的文档,将检索到的内容自动添加到Prompt,让AI基于检索到的事实信息回答,而不是仅靠训练数据)
- createAdvisor:抽象方法,具体实现在上面两个匿名内部类中
- Map:静态属性,存入枚举对象(就是当前类,用父类来代表两个匿名子类的引用)
- 静态代码块:在类初始化时初始化Map,code作为key,枚举类作为value
- getByCode:静态方法,根据code获取枚举对象
【顾问创建】
- AiClientAdvisorNode节点类,doApply方法执行业务逻辑,同样先从上下文中获取list对象,遍历list获取VO对象,根据配置的数据
List<AiClientAdvisorVO> aiClientAdvisorList循环构建顾问角色,并注册为Bean。而 createAdvisor 方法则通过枚举策略进行构建。 - 如果将来还有其他顾问角色,则在 AiClientAdvisorTypeEnumVO 扩展实现方法即可。
【客户端创建】
- 先获取上下文对象:存aiClientVO的list,systemPromptMap
- 预设话术:设置系统提示词,从systemPromptMap获取所有id符合的,然后都拼接到defaultSystem
- 对话模型:直接根据VO的ModelBeanName属性getBean
- MCP服务:从VO获取McpBeanNameList,遍历获取所有MCP的Bean并添加到一个mcpSyncClients
- advisor顾问角色:和上面MCP类似,获取列表后添加进一个advisors列表,然后将列表转成Advisor[](这是一个大小不变的数组,
类型[]就是java的语法) - 构建对话客户端:用建造者模式传参
- 传入defaultSystem系统提示词属性,入参是第2步
- 传入defaultToolCallbacks属性,入参是将第4步的对象转换
new SyncMcpToolCallbackProvider(mcpSyncClients.toArray(new McpSyncClient[]{}))将原来的对象转成Mcp数组后转成MCP工具回调提供者对象 - 传入第5步的顾问列表
- 构建完了将这个ChatClient注册为Bean
- 然后这个客户端类的strategyHandler因为已经是最后了,就返回defaultStrategyHandler就行,返回空节点并返回
【使用构建好的客户端进行对话】
- 这个构建好的客户端BeanName是3001直接获取并转成ChatClient对象(当初构建时就是这个类型,这个是SpringAI官方的类)
- 然后调用chatClient的prompt(Prompt).call()传入一个构建好的Prompt对象就可以进行对话流程
- .call()开始执行,进入ChatClient内部执行链
- 【Advisor处理链】按顺序执行所有顾问
- 聊天记忆顾问:从Memory中加载历史对话,将历史对话添加到当前Prompt中,例如,输入:有哪些工具可以使用。输出:上次说要学SpringAI,有哪些工具可以使用
- RAG问答顾问:执行用户问题查询,搜索相关文档,例如,输入:有哪些工具可以使用。返回:向量数据库返回:文档1:“CSDN发布工具使用说明”。输出:增强提示词,你是AI… 参考知识库:文档1
- 简单日志顾问:记录用户输入,记录模型响应,打印日志到控制台
- 合并系统提示词:将构建时的defaultSystem与Advisor增强后的内容合并:最终的 System Prompt = defaultSystem (预设话术) + RAG 检索的知识库内容 + 其他 Advisor 添加的内容
- 发送请求到ChatModel:发送一个请求,内容是JSON形式
- OpenAI API处理请求;理解System Prompt,分析用户问题,检查可用的tools列表,如果有工具调用的需求,返回tool_calls,否则直接返回文本回复
- 服务端接受响应,也是JSON,如果有tool_calls,则被Spring AI拦截并调用对应的工具:解析tool_calls,查找对应的MCP客户端,调用MCP工具,MCP服务器执行实际操作,返回执行结果给SpringAI,SpringAI将工具执行结果包装并返回给OpenAI,告知其工具已执行,OpenAI根据结果生成最终回复
- Advisor后置处理:某些Advisor会在响应返回后进行处理,例如:保存记忆、记录日志
- 将结果返回给用户
1 | |
Agent执行链路分析
好像我已经在上面分析完了,看看有没有补充的点
【短期记忆和长期记忆】
- 上下文窗口中存一些短期记忆就是上几次的回答,保证对话连续性
- 长期记忆单独存,只保留重要的知识点,保证长时记忆,同时减少token消耗
- 短期记忆通常存在“内存”或“会话状态”中,而长期记忆则存在“数据库”或“向量存储”中。
【Ai Agent处理过程分类】
- 固定N个步骤,这类的一般是配置工作流的,提高任务执行的准确性。如,一些检索资料、发送帖子、处理通知等。
- 顺序循环调用,配置 Agent 要执行的多个 Client 端,以此顺序执行。适合一些简单的任务关系,并已经分配好的动作,类似于1的方式。
- 智能动态决策,这类是目前市面提供给大家使用的 Agent 比较常见的实现方式,它会动态的规划执行动作,完成行动步骤,观察执行结果,判断完成状态和步骤。并最终给出结果。
【AiSearchMCPTest】
- AiSearchMCPTest, 搜索 MCP 服务。
- AutoAgentTest,学习 Agent 执行过程。
先增加一个百度搜索的MCP服务,这个是百度官方的MCP工具,提供搜索功能。
MCPWorld可以找到各种官方MCP工具,这里用百度AI搜索
要先申请百度的key,然后填进去才能建立sse连接,这个key直接走小傅哥的那个第二个网站就行百度智能云。
记得还要改测试里面的模型key
这个测试类就是直接用HttpClientSseClientTransport和百度mcp服务的url和key构建了一个sse连接作为mcp工具,传入了chatModel对象中
【AutoAgentTest测试类】
实现了三种类型的AI Agent,功能主要取决初始提示词的内容:
- Planning Agent:负责任务规划,将用户需求拆解为可执行的任务列表
- Executor Agent:负责执行具体任务,使用工具完成子任务
- React Agent:负责快速响应,处理简单查询和即时交互
在测试完整的 AutoAgent 工作流程中就会先后使用这几个agent,先任务规划,然后任务执行,然后建一个总结用的提示词,快速响应用来获取总结结果
测试多轮对话:用快速响应Agent,使用相同的conversationId保证记忆,进行多轮对话(PromptChatMemoryAdvisor会自动加载历史对话)
动态多轮执行测试:模拟 PlanningAgent 和 ExecutorAgent 的完整动态执行流程:
- 先配置参数,最大执行步数等,初始化执行上下文,就是StringBuilder(执行历史)、String(当前任务状态描述)、boolean(任务是否完成标志位)
- 初始化三个角色,任务分析师、精准执行器、质量监督员
- 然后进入for循环,进行精准多轮执行
- 第一阶段:分析任务,然后进行解析(把 AI 返回的结构化分析结果,以美观的格式打印到控制),如果任务完成度评估为100%,则把标志位改为true,并打破循环
- 第二阶段:精准执行,先建一个执行提示词,然后执行解析并打印到控制台
- 第三阶段:质量监督,先建一个质量监督检查提示词,然后进行质量检查,然后解析并打印到控制台,根据结果判断打印是否需要重新执行
- 更新执行历史,就是新建一个String保存当前各阶段的记录,然后添加进执行历史中
- 提取下一步任务,更新任务状态描述,然后进入下一轮循环
- 循环结束后进行结果总结,输出结果,然后建个提示词,让快速响应生成总结,然后打印出来流程结束
- 此案例的重点在于把 固定步骤 处理循环步骤,注意;
for (int step = 1; step <= maxSteps && !isCompleted; step++)for 循环里会不断的判断任务是否执行完成,如果没有则会进行分析,自主规划,之后执行。这部分东西,最好要执行验证结果。 - 此过程很耗费 token,也依赖于LLM,如果比较差的模型,那么可能最后的结果也就不太理想。
Agent执行链路设计
Service新增execute领域,用来控制agent的执行策略
armory 是装配 agent 所需的元素,execute 是执行 agent 所需的内核。这部分要想清楚。
之后,本节我们先来实现 auto 自动型的 agent 步骤,也就是把 AutoAgentTest 拆成各个节点。节点包括;Root 根节点,用于数据加载,之后是操作步骤节点 Step1~4,分别执行各自的流程步骤。**1-任务分析、2-精准执行、3-质量监督、4-执行总结。**这块的步骤不要过于拘泥,如果市面上各大厂还有更好的步骤分享,也可以新增加一个套执行过程步骤。类似你看到的我们用过的一些 Agent 也有是有版本迭代升级的,付费后就更好用。
ai_agent_flow_config 表,增加 client_name、client_type,用于 AutoAgent 调度不同类型的对话客户端。同时修改对应程序里的 dao、po、mapper。
在 IAgentRepository 增加一个 queryAiAgentClientFlowConfig(String aiAgentId) 方法,来查询 Agent 下,配置的 Client 节点。
在 domain agent model 模型下,把 valobj 下的枚举,专门增加一个枚举的包 enums 存放,方便管理。
增加 ExecuteCommandEntity 执行 agent 请求实体对象,包含,aiAgentId、message、sessionId、maxStep
实现 execute 执行包,以规则树方式实现执行过程,包括;RootNode、Step1AnalyzerNode、Step2PrecisionExecutorNode、Step3QualitySupervisorNode、Step4LogExecutionSummaryNode,各执行节点。这部分可以查看工程代码。
基础层新建一个查询类,根据智能体ID查询流程配置列表,将数据库实体转为VO对象,以 clientType 为 Key,构建Map返回
【节点流程】
- RootNode数据加载节点:加载数据,并把数据保存在上下文中,路由到下个节点(路由就是get获取下一个strategyHandler然后执行他的apply方法并返回(节点实现了父类实现的StrategyHandler接口,所以这里的这个就是下一个节点类))
- Step1AnalyzerNode - 任务分析节点:第一阶段,先结合上下文中的历史记录建立任务分析提示词(首次设为首次执行),然后获取对话客户端,进行分析并打印,将分析结果保存到动态上下文,检查任务是否完成,如果完成就将标志位设置为true,开始路由(执行本节点的get,get方法会先检验任务是否完成、当前是否已到达最大步数,满足了就进入总结阶段,否则执行下一步)
- Step2PrecisionExecutorNode - 精准执行节点:第二阶段,从上下文中获取分析结果,建立执行提示词,获取对话客户端后执行并打印,将结果保存在上下文中,更新历史记录,路由到下个节点,当前节点的get就没有别的逻辑直接返回第三步节点
- Step3QualitySupervisorNode - 质量监督节点:第三阶段,从上下文中获取执行结果,建立监督提示词,获取对话客户端执行监督并打印,将监督结果保存到动态上下文中,根据监督结果决定是否需要重新执行,更新执行历史,增加步骤计数,如果任务已经完成或达到最大步数就进入总结阶段,否则就执行下一阶段(回到节点1)
- Step4LogExecutionSummaryNode - 执行总结节点:获取客户端执行总结,如果任务未完成生成最终总结报告,完成就返回字符串
"ai agent execution summary completed!"
【测试注意】
百度官方MCP的地址配置在数据库了,记得把key改成自己的
数据库模型api也改成自己的,就是sk那个
Navicat的JSON在行里面编辑不方便,可以右键在单元格编辑器中编辑
Agent服务接口和UI对接
开发接口,然后与Ai实现的前端UI界面对接
config包下新建类,实现自动装配
程序启动时,进行自动装配,装配的数据(客户端),配置到 application-dev.yml 中。
trigger层的接口,调用domain领域层的自动agent策略了。trigger接口是sse的异步流式响应接口,所以要渗透到domain领域层增加 ResponseBodyEmitter 发送各个阶段的数据。
【修改说明】
- app 模块下增加 AiAgentAutoConfiguration 自动装配配置服务,通过拿到 yml 配置文件中的 agent 客户端服务列表进行自动装配。
- api 模块下增加 IAiAgentService.autoAgent 服务接口,接口的返参为 ResponseBodyEmitter 异步流式响应。
- trigger 模块下的 http 包,实现 IAiAgentService.autoAgent 服务接口,调用 autoAgentExecuteStrategy 进行结果响应。那么,这里对应的要给 autoAgentExecuteStrategy 入参增加上异步响应参数。
- 定义自动执行参数响应对象 AutoAgentExecuteResultEntity,包括数据类型(type),动作细节(subType),执行步骤(step)、数据内容(content)、是否完成(completed)、时间戳(timestamp)。同时聚合一些创建对象的方法,包括;创建分析阶段结果、创建分析阶段细分结果、创建执行阶段结果、创建执行阶段细分结果、创建监督阶段结果、创建监督阶段细分结果、总结阶段的结果、创建总结阶段细分的结果、创建错误结果、创建完成标识。
- 在 AbstractExecuteSupport 抽取通用方法 sendSseResult,通过这样的方式让 Step1~4步都可以调用进行发送过程中数据。
【自动装配实现】
- dev.yml文件中添加要加载的client-ids
- config包下新建AiAgentAutoConfiguration类实现自动装配功能
- config包下新建AiAgentAutoConfigProperties类,用于保存配置文件属性
类注解:
@Configuration标记为配置类@EnableConfigurationProperties(AiAgentAutoConfigProperties.class)将配置文件中的属性映射到括号里面的类@ConditionalOnProperty(prefix = "spring.ai.agent.auto-config", name = "enabled", havingValue = "true")条件注解,仅当配置文件中的spring.ai.agent.auto-config.enabled=true 时,此配置才生效
自动装配流程:
- 注入AiAgentAutoConfigProperties配置属性类、DefaultArmoryStrategyFactory装配工厂类
- 类实现了
ApplicationListener<ApplicationReadyEvent>重写onApplicationEvent方法,在Spring Boot 应用完全启动后自动触发,重写方法中就是自动配置逻辑 - 从注入的配置属性类中获取并解析client-ids列表
- 从装配工厂类中获取armoryStrategyHandler(RootNode)并调用apply方法开始装配
【异步通知】
- 之前execute领域内的IExecuteStrategy接口,新增入参ResponseBodyEmitter流式响应参数,用于 AutoAgent 策略执行中的结果返回操作。
- ResponseBodyEmitter 是 Spring MVC 提供的一个类,用于异步地向 HTTP 响应体写入数据。它实现了异步非阻塞的响应输出,允许服务器端在请求处理完成之前,分多次发送数据给客户端。
- 定义一个AutoAgentExecuteResultEntity类,封装返回结果
- 抽象方法中新增sendSseResult方法,内部从上下文中获取结果并用emitter发送给前端,然后step1~4节点中就可以调用这个方法,就在每个节点解析结果后就可以调用这个方法
【接口】
- trigger模块下建立AiAgentController
- 注入IExecuteStrategy提供execute方法
- 创建流式输出对象:emitter
- 构建执行命令体:填入请求里的参数
- 异步执行AutoAgent:调用execute
【测试】
打开前端页面,数据库改改mcp的设置,模型api换成自己的就行
【库表回顾】
- ai_client:执行链路里面每一个节点都是一个client,比如任务分析师,需要初始提示词、顾问、模型、MCP工具。这几个属性就分别是几个表,他们的对应关系存在ai_client_cofig中,装配时就用这个查出来
- ai_agent:多个client组成一个链路就成了agent(智能体),agent有个渠道类型字段(目前就agent智能体,chat_stram对话流)
- ai_agent_flow_config:存agent的配置信息包括,agent与client的对应关系,客户端名称,客户端类型,序列号(执行顺序)
- 上面自动装配装的是client不是agent,agent就是装配好的client成链路处理
- ai_client_api:存模型key,模型通用
- ai_agent_task_schedule:智能体定时任务表,例如自动发帖任务,交给对应智能体执行
- ai_client_rag_order:rag:表存知识库信息
Agent-ELK日志分析场景
增加 Agent-ELK 日志分析的实际应用场景,通过 Agent 根据用户诉求,自主分析、规划、执行和输出结果,来帮助我们对日志检索的提效。
ELK(Elasticsearch、Logstash、Kibanna或自研) 是各个互联网公司中都有的一套分布式日志设备,以便于研发在遇到线上系统报警和运营反馈事故问题时,快速检索日志。但往往这种检索的日志的方式都是非常耗时的,所以增加 Agent 方式来辅助提效是非常有必要的。
【ELK日志只能分析系统流程】
- 阶段一:执行模拟脚本,生成模拟日志(创建日志索引的es索引,生成模拟限流日志并写入)
- 阶段二:将日志存到Elasticsearch(日志存储),然后Logstash日志处理、Kibana可视化界面
- 阶段三:Agent配置,配置Agent和MCP连接
- 阶段四:自动智能分析:日志分析、精准执行(执行结果会调用MCP工具存到ES中)、质量监督、执行总结
【编码实现】
ai_client_system_prompt:增加提示词,定义动态限流查询分析师,智能限流查询执行器、监督员、总结师的角色
ai_client_tool_mcp:增加新MCP工具,调用npx工具使用@awesome-ai/elasticsearch-mcp包
【测试】
- 执行模拟日志脚本,但是脚本有中文utf-8编码bug我懒得处理了,直接让ai根据错误修改sh文件了,要将中文全改成英文,然后时间戳生成改成兼容windos的。改好了就能在git bash中./执行了
- 然后Stack Management索引管理那里就能看到这个新建的索引,然后下面索引模式新建这个索引的索引模式模式
es的Management下的Stack Management中可以管理索引
es的Analytics下的Discover中可以查看日志
观察日志http://127.0.0.1:5601/app/discover他给每个日志都进行了分词,然后就可以用词去找对应的日志,上面的那个搜索框都有提示,比如我要搜id等于什么的日志_id : _Or70ZwBYNWvAkmTxUOw
之后就可以在前端ui直接向智能体发信息,例如通过ES查询被限流的用户,输出被限流的用户列表,智能体就会调用elasticsearch-mcp-server工具去查询,这个还要配置es的key,我就先不试了