# springAIAgent **Repository Path**: VictorInfo/spring-aiagent ## Basic Information - **Project Name**: springAIAgent - **Description**: 这是基于spring ai alibaba 1.1.2 构建的agent - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-27 - **Last Updated**: 2026-10-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Spring AI Alibaba 多智能体 Demo:规划 → 执行 → 反思 基于 **Spring Boot 3.4 + Spring AI Alibaba 1.1.2** 的多智能体示例,演示一个完整的 **Planner → Executor → Reflector(可多轮)** 工作流,并同时接入: - ✅ **短期记忆**:同一 `threadId` 下的多轮对话上下文 - ✅ **长期记忆**:按 `userId` 持久化的用户画像/偏好,跨会话、跨重启生效 - ✅ **RAG 检索**:基于 DashScope Embedding + `SimpleVectorStore` 的企业知识库检索 --- ## 一、整体流程 ``` 用户问题 (question / userId / threadId) │ ▼ ┌────────────────────────────────────────┐ │ 1. 长期记忆召回(Utf8FileSystemStore) │ │ 2. RAG 预检索(SimpleVectorStore) │ │ 3. 短期记忆渲染(最近 3 轮对话) │ └───────────────────┬────────────────────┘ ▼ ┌─────────────────┐ │ Planner 规划 │ 输出 2~5 步执行计划 └────────┬────────┘ ▼ ┌────────────────────────────────┐ ┌────▶│ Executor 执行 │ │ │ 按计划作答;可调用知识库检索工具 │ │ └───────────────┬────────────────┘ │ ▼ │ ┌────────────────────────────────┐ │ │ Reflector 反思 │ │ │ 输出 JSON:评分/问题/建议/记忆候选 │ │ └───────────────┬────────────────┘ │ ▼ │ passed == true ? │ │ │ │ 否 是 └──── 反思意见 ──┘ │ 回到 Executor ▼ ┌──────────────────────────────┐ │ 写入长期记忆 / 记录短期记忆 / 返回 │ └──────────────────────────────┘ ``` - 反思不通过时,会把 `issues + suggestions` 作为 **上一轮反思意见** 带回 Executor 重写。 - 默认最多 2 轮(`MAX_REFLECTION_ROUNDS` 可配),避免无限循环。 - 每轮草稿和反思结果都会出现在响应的 `rounds` 字段中,便于观察效果。 --- ## 二、三种记忆 / 检索能力怎么实现的 ### 1. 短期记忆 | 层次 | 实现 | 作用 | | --- | --- | --- | | Agent 内部 | Agent 框架 `MemorySaver`(每个 Agent 一个) | 按 `userId::threadId::role` 保存完整消息历史 | | 编排层 | `ConversationMemoryService` | 按 `threadId` 保存最近 8 轮“问题 + 最终答案”,渲染最近 3 轮注入 Prompt | 相关代码: - `MultiAgentConfiguration`:三个 Agent 均配置 `.saver(new MemorySaver())` - `ConversationMemoryService`:`ConcurrentHashMap>` - `MultiAgentOrchestrator#run`:调用前 `render(threadId)`,结束后 `record(...)` > 短期记忆保存在进程内存中,应用重启后会清空;长期记忆不受影响。 ### 2. 长期记忆 - 存储:`Utf8FileSystemStore`(继承 `FileSystemStore`,修复中文环境下写入/读取字符集不一致的问题),默认目录 `./data/long-term-memory` - 命名空间:`users/{safeUserId}/memories` - 写入:每轮 Reflector 输出 `memories` 候选 → 过滤长度/去重 → 任务结束时统一写入 - 召回:用问题做文本匹配 + 最近记忆补充 → 按中文二元组命中数和更新时间排序 → 取 Top N 注入 Planner/Executor Prompt - `userId` 会被清洗(只保留字母、数字、`_`、`-`),避免非法路径 相关代码: - `MultiAgentConfiguration#longTermMemoryStore` - `LongTermMemoryService`(`recall` / `remember` / `listMemories`) - `AgentPrompts.REFLECTOR`:要求 Reflector 以 JSON 返回 `memories` ### 3. RAG 检索 - 知识库文件:`src/main/resources/knowledge/*.md|*.txt`(示例包含员工手册、产品 FAQ、研发规范) - 切分:`TokenTextSplitter`(chunkSize=400,保留分隔符) - 向量化:DashScope `EmbeddingModel`(默认 `text-embedding-v3`) - 向量库:`SimpleVectorStore`(内存型,首次检索时懒加载建索引) - 检索:`SearchRequest` + 相似度阈值;阈值过滤后为空时自动降级重查一次 - 使用方式: 1. **预检索**:编排器先检索一次,把结果放进 Planner/Executor Prompt; 2. **工具检索**:Executor 也可主动调用 `searchKnowledgeBase` 工具补充检索。 相关代码: - `KnowledgeBaseService` - `KnowledgeBaseTools`(`@Tool(name = "searchKnowledgeBase")`) - `MultiAgentConfiguration#knowledgeVectorStore` --- ## 三、代码结构 ``` src/main/java/com/agent/multiagent/ ├── config/ │ ├── MultiAgentConfiguration.java # 3 个 Agent + Store + VectorStore Bean │ └── MultiAgentProperties.java # app.multi-agent.* 配置 ├── controller/ │ └── MultiAgentController.java # HTTP 接口 ├── exception/ │ └── AgentRunException.java ├── model/ │ ├── AgentChatRequest.java │ ├── AgentRunResult.java │ ├── ConversationTurn.java │ ├── MemoryItem.java │ ├── ReflectionResult.java │ ├── RetrievedDocument.java │ └── RoundTrace.java ├── prompt/ │ └── AgentPrompts.java # Planner / Executor / Reflector 系统提示词 ├── service/ │ ├── ConversationMemoryService.java # 短期记忆 │ ├── KnowledgeBaseService.java # RAG │ ├── LongTermMemoryService.java # 长期记忆 │ └── MultiAgentOrchestrator.java # 编排:规划 → 执行 → 反思 ├── store/ │ └── Utf8FileSystemStore.java # 长期记忆持久化(UTF-8 修正) ├── tool/ │ └── KnowledgeBaseTools.java # Executor 可调用的检索工具 └── util/ └── ReflectionParser.java # 解析 Reflector 的 JSON 输出 ``` 知识库与配置: ``` src/main/resources/ ├── application.yaml └── knowledge/ ├── company-handbook.md ├── product-faq.md └── tech-stack.md ``` --- ## 四、快速开始 ### 1. 环境要求 - JDK **17**(项目 `pom.xml` 中 `java.version=17`) - Maven 3.8+ - DashScope API Key(默认读取 `spring.ai.dashscope.api-key`) > 注意:本机 Maven 默认可能指向 JDK 8,编译前请确认 `java -version` 为 17。 > Windows 下可临时指定: > > ```bash > JAVA_HOME="C:/Users/victor/.jdks/ms-17.0.18" mvn spring-boot:run > ``` > > PowerShell: > > ```powershell > $env:JAVA_HOME="C:\Users\victor\.jdks\ms-17.0.18"; mvn spring-boot:run > ``` ### 2. 启动 ```bash mvn -DskipTests compile mvn spring-boot:run ``` 服务端口:`8091`(见 `application.yaml`)。 ### 3. 关于现有 MCP 配置 `application.yaml` 中保留了原有的 MCP 客户端配置(`http://127.0.0.1:8090`), 它只服务于项目里原有的 `jokeAgent` / `ChatServiceImpl`。**本 Demo 的 `/api/multi-agent/**` 接口不依赖 MCP**。 为了避免本地没有 MCP 服务时应用启动失败,默认已关闭 MCP 客户端: ```yaml spring: ai: mcp: client: enabled: ${MCP_CLIENT_ENABLED:false} ``` 如果 8090 端口有可用的 MCP 服务,并希望使用旧版 MCP 工具: ```bash MCP_CLIENT_ENABLED=true mvn spring-boot:run ``` 同时旧代码已做了兼容处理:MCP 缺失时 `jokeAgent` 按需创建,`ChatServiceImpl` 以“无 MCP 工具”模式启动,不会影响多智能体接口。 ### 4. API Key 当前 `application.yaml` 中直接写入了 API Key,建议改为环境变量: ```yaml spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} ``` ```bash export DASHSCOPE_API_KEY=sk-xxx ``` --- ## 五、接口说明 | 方法 | 路径 | 说明 | | --- | --- | --- | | POST | `/api/multi-agent/chat` | 主入口:规划 → 执行 → 反思 | | GET | `/api/multi-agent/chat` | 主入口 GET 便捷版 | | GET | `/api/multi-agent/memories?userId=xxx` | 查看长期记忆 | | GET | `/api/multi-agent/history?threadId=xxx` | 查看短期对话记忆 | | GET | `/api/multi-agent/knowledge/search?q=xxx&topK=4` | 直接测试 RAG 检索 | | POST | `/api/multi-agent/knowledge/reindex` | 重建知识库索引 | ### 请求体 ```json { "question": "公司年假有多少天?", "userId": "demo-user", "threadId": "demo-thread-1" } ``` - `userId` 缺省为 `anonymous` - `threadId` 缺省自动生成 UUID;**同一会话请复用同一个 threadId** - 响应中的 `threadId` 可直接用于后续请求 ### 响应示例(截断) ```json { "threadId": "demo-thread-1", "userId": "demo-user", "question": "公司年假有多少天?", "plan": "1. 检索员工手册中年假条款\n2. 汇总不同工龄对应的天数\n3. 给出申请方式", "rounds": [ { "round": 1, "draft": "根据《星云科技员工手册》……", "reflection": { "passed": true, "score": 9, "issues": [], "suggestions": "", "memories": [] } } ], "finalAnswer": "……", "retrievedDocuments": [ { "source": "company-handbook.md", "text": "……", "score": 0.83 } ], "recalledMemories": [], "savedMemories": [], "warnings": [], "elapsedMs": 12345 } ``` 字段说明: - `rounds[]`:每一轮执行/反思的完整轨迹,`reflection.passed=false` 时会进入下一轮 - `retrievedDocuments`:本轮 RAG 预检索命中的分片 - `recalledMemories`:本轮注入 Prompt 的长期记忆 - `savedMemories`:本轮任务结束后新写入的长期记忆 - `warnings`:RAG 或记忆读写失败时的降级提示(请求本身仍会成功) --- ## 六、演示脚本 可直接使用仓库中的 `HTTP/multi-agent.http`(IntelliJ HTTP Client),或参考下面的 curl。 ### 场景 1:RAG 检索(问公司制度) ```bash curl -s http://localhost:8091/api/multi-agent/chat \ -H "Content-Type: application/json" \ -d '{ "question": "星云科技的年假怎么算?远程办公一周可以几天?", "userId": "demo-user", "threadId": "demo-thread-rag" }' ``` 预期: - `retrievedDocuments` 中出现 `company-handbook.md` - `finalAnswer` 引用员工手册中的年假/远程办公条款 - `rounds` 展示规划、草稿与反思评分 ### 场景 2:长期记忆(跨 threadId) 第一步,让 Reflector 提取用户信息: ```bash curl -s http://localhost:8091/api/multi-agent/chat \ -H "Content-Type: application/json" \ -d '{ "question": "我叫李雷,是技术部后端工程师,以后回答请尽量简洁。", "userId": "demo-user", "threadId": "demo-thread-profile" }' ``` 查看已保存的长期记忆: ```bash curl -s "http://localhost:8091/api/multi-agent/memories?userId=demo-user" ``` 第二步,**换一个 threadId**,仍使用相同 `userId`: ```bash curl -s http://localhost:8091/api/multi-agent/chat \ -H "Content-Type: application/json" \ -d '{ "question": "根据我的岗位,推荐一个合适的学习方向。", "userId": "demo-user", "threadId": "demo-thread-2" }' ``` 预期:响应中的 `recalledMemories` 包含“李雷 / 后端工程师 / 回答简洁”等信息, 说明长期记忆跨会话生效。长期记忆落盘在 `./data/long-term-memory`,重启后仍在。 ### 场景 3:短期记忆(同一 threadId 多轮) ```bash curl -s http://localhost:8091/api/multi-agent/chat \ -H "Content-Type: application/json" \ -d '{ "question": "公司年假有多少天?", "userId": "demo-user", "threadId": "demo-thread-chat" }' curl -s http://localhost:8091/api/multi-agent/chat \ -H "Content-Type: application/json" \ -d '{ "question": "那病假呢?", "userId": "demo-user", "threadId": "demo-thread-chat" }' curl -s "http://localhost:8091/api/multi-agent/history?threadId=demo-thread-chat" ``` 预期:第二个问题能理解“那”指代年假/休假制度;`/history` 返回两轮问答。 ### 场景 4:直接验证 RAG ```bash curl -s "http://localhost:8091/api/multi-agent/knowledge/search?q=专业版价格&topK=3" ``` 预期返回 `product-faq.md` 中与价格相关的分片及相似度分数。 ### 场景 5:观察反思循环 把 `MAX_REFLECTION_ROUNDS` 调大(如 3),提一个需要多步推理的问题: ```bash MAX_REFLECTION_ROUNDS=3 mvn spring-boot:run ``` 响应中的 `rounds` 长度大于 1 时,表示第一轮反思未通过,Executor 已根据 `reflection.issues` / `reflection.suggestions` 重写答案。 --- ## 七、配置项 `application.yaml` 中的 `app.multi-agent.*`: | 配置 | 环境变量 | 默认值 | 说明 | | --- | --- | --- | --- | | `max-reflection-rounds` | `MAX_REFLECTION_ROUNDS` | `2` | 最多“执行 → 反思”轮数 | | `rag-top-k` | `RAG_TOP_K` | `4` | 预检索返回分片数 | | `rag-similarity-threshold` | `RAG_SIMILARITY_THRESHOLD` | `0.2` | 相似度阈值 | | `memory-recall-limit` | `MEMORY_RECALL_LIMIT` | `5` | 每轮召回长期记忆条数 | | `memory-save-limit` | `MEMORY_SAVE_LIMIT` | `5` | 单次任务最多写入条数 | | `long-term-memory-path` | `LONG_TERM_MEMORY_PATH` | `./data/long-term-memory` | 长期记忆落盘目录 | | `enable-agent-logging` | `ENABLE_AGENT_LOGGING` | `false` | 打印 Agent 内部 Prompt/轮次日志 | 模型配置: | 配置 | 环境变量 | 默认值 | | --- | --- | --- | | `spring.ai.dashscope.chat.options.model` | `DATAAGENT_MODEL_NAME` | `qwen-plus` | | `spring.ai.dashscope.embedding.options.model` | `EMBEDDING_MODEL` | `text-embedding-v3` | --- ## 八、测试 已包含不依赖网络的单元测试: ```bash JAVA_HOME="C:/Users/victor/.jdks/ms-17.0.18" \ mvn -Dtest='ReflectionParserTest,LongTermMemoryServiceTest,ConversationMemoryServiceTest,Utf8FileSystemStoreTest' test ``` - `ReflectionParserTest`:纯 JSON、Markdown 代码块包裹、非法输出降级 - `LongTermMemoryServiceTest`:写入/召回、去重、用户隔离(使用内存 `MemoryStore`) - `ConversationMemoryServiceTest`:会话内记录、会话隔离 - `Utf8FileSystemStoreTest`:中文长期记忆写入后能正确读回 --- ## 九、实现要点与可扩展方向 1. **为什么不用一个 Agent 全包?** 规划、执行、审查拆成三个 `ReactAgent`,职责单一、Prompt 更稳定,也便于单独替换模型。 2. **反思结果为什么要 JSON?** `ReflectionParser` 需要结构化拿到 `passed/score/issues/suggestions/memories`, 非法输出会降级为“未通过”,保证循环不会因解析失败而中断。 3. **降级策略** RAG 预检索失败、长期记忆读写失败都只记录 `warnings`,不阻断主流程; Executor 仍可通过 `searchKnowledgeBase` 工具再次尝试检索。 4. **可扩展** - 把 `SimpleVectorStore` 换成 `Milvus` / `pgvector` 持久化向量库 - 把 `Utf8FileSystemStore` 换成 Redis/数据库实现的 `Store` - 给 Reflector 增加更多维度(事实性、引用完整性、安全性) - 增加人工审批 Hook 或流式输出(参考原有 `ChatServiceImpl`)