docs: 重构文档结构,分离学习笔记和 MVP 架构设计
**变更概述:** - 将 MVP 架构设计文档独立到项目根目录 `mvp/` - 整理 `docs/` 为纯学习和分析文档目录 - 按类型分类:learning(学习)、analysis(分析)、reports(报告)、guides(指南) **目录结构:** ``` mvp/ # MVP 架构设计(独立) ├── README.md # 数据库设计总览 ├── architecture/ # 架构文档 │ ├── agent-architecture-mvp.md │ ├── implementation-plan.md │ └── ... └── tables/ # 数据表设计 docs/ # 学习和分析文档 ├── learning/ # 学习笔记(00-08 编号) ├── analysis/ # 分析笔记 + 重构计划 ├── reports/ # 临时报告 └── guides/ # 指南文档 ``` **详细变更:** - docs/README.md → mvp/README.md(数据库设计入口) - docs/architecture/ → mvp/architecture/(架构设计) - docs/tables/ → mvp/tables/(数据表设计) - docs/学习笔记-*.md → docs/learning/07-*.md, 08-*.md - docs/项目学习路径.md → docs/learning/00-*.md - docs/功能分析报告.md → docs/analysis/ - docs/修复报告-*.md → docs/reports/ - docs/日志配置*.md → docs/guides/ 或 docs/reports/ - docs/design/ → docs/analysis/(问题分析和重构计划)
This commit is contained in:
@@ -0,0 +1,659 @@
|
||||
# SuperBizAgent-java 项目学习路径
|
||||
|
||||
> 创建日期:2026-05-30
|
||||
> 项目规模:58 文件,1528 符号,2828 关系,87 执行流
|
||||
> 核心技术:Spring AI + DeepSeek V4 + BGE-M3 + Milvus + Agent 协同
|
||||
|
||||
---
|
||||
|
||||
## 📋 学习目标
|
||||
|
||||
通过本学习路径,你将掌握:
|
||||
|
||||
1. ✅ **AI Ops 自动化分析**的完整执行流程(3-Agent 协同架构)
|
||||
2. ✅ **Chat 对话系统**的 RAG 知识库检索机制
|
||||
3. ✅ **模型抽象与路由**的解耦设计(ChatModel/EmbeddingModel)
|
||||
4. ✅ **Tools 工具集**的设计模式(Prometheus/CLS/RAG)
|
||||
5. ✅ **向量数据库**的文档分块、向量化、检索全流程
|
||||
|
||||
**预计总耗时**:2-3 小时(可分多次完成)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 学习路径(推荐顺序)
|
||||
|
||||
### 📍 阶段 1:核心执行流理解(30-40 分钟)⭐️ **从这里开始**
|
||||
|
||||
**目标**:理解系统的 2 条主线执行流程
|
||||
|
||||
---
|
||||
|
||||
#### 1.1 AI Ops 自动化分析流程 ⭐️⭐️⭐️
|
||||
|
||||
**为什么从这里开始?**
|
||||
- 这是项目最核心、最有特色的功能
|
||||
- 涉及 Agent 协同、工具调用、流式响应等关键技术
|
||||
- 理解了它,其他模块会一通百通
|
||||
|
||||
**执行流程图**:
|
||||
|
||||
```
|
||||
用户点击 "AI Ops" 按钮
|
||||
↓
|
||||
HTTP POST /api/ai_ops
|
||||
↓
|
||||
ChatController.aiOps()
|
||||
↓
|
||||
AiOpsService.executeAiOpsAnalysis()
|
||||
↓
|
||||
┌────────────────────────────────────────┐
|
||||
│ Phase 1: Planner Agent 制定分析计划 │
|
||||
│ - 输入:固定的规划 Prompt │
|
||||
│ - 输出:分析计划(要查哪些告警、日志)│
|
||||
└────────────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────────────┐
|
||||
│ Phase 2: Executor Agent 执行工具调用 │
|
||||
│ ├─ QueryMetricsTools.queryActiveAlerts()│
|
||||
│ │ → Prometheus 告警(Mock 模式) │
|
||||
│ ├─ QueryLogsTools.queryLogs() │
|
||||
│ │ → 腾讯云 CLS 日志(Mock 模式) │
|
||||
│ └─ InternalDocsTools.queryInternalDocs()│
|
||||
│ → RAG 知识库检索 │
|
||||
└────────────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────────────┐
|
||||
│ Phase 3: Supervisor Agent 生成报告 │
|
||||
│ - 输入:Planner 计划 + Executor 数据 │
|
||||
│ - 输出:结构化告警分析报告 │
|
||||
│ - 格式:Markdown(表格、列表、代码块)│
|
||||
└────────────────────────────────────────┘
|
||||
↓
|
||||
SSE 流式返回前端
|
||||
↓
|
||||
前端渲染 Markdown
|
||||
```
|
||||
|
||||
**学习步骤**:
|
||||
|
||||
**Step 1.1.1:阅读 Controller 入口**
|
||||
|
||||
```bash
|
||||
Read src/main/java/org/example/controller/ChatController.java
|
||||
```
|
||||
|
||||
**关注点**:
|
||||
- `aiOps()` 方法(第 106-117 行左右)
|
||||
- 如何设置 SSE 响应头(`text/event-stream`)
|
||||
- 如何调用 `AiOpsService.executeAiOpsAnalysis()`
|
||||
|
||||
**预期收获**:理解 HTTP 层如何触发 AI Ops 分析
|
||||
|
||||
---
|
||||
|
||||
**Step 1.1.2:阅读核心 Service**
|
||||
|
||||
```bash
|
||||
Read src/main/java/org/example/service/AiOpsService.java
|
||||
```
|
||||
|
||||
**关注点**:
|
||||
- `executeAiOpsAnalysis()` 方法(核心入口)
|
||||
- `buildPlannerAgent()` 方法(如何构建规划 Agent)
|
||||
- `buildExecutorAgent()` 方法(如何构建执行 Agent)
|
||||
- `buildSupervisorSystemPrompt()` 方法(如何构建最终报告生成 Prompt)
|
||||
- 3 个 Agent 如何协同工作(chain 调用)
|
||||
|
||||
**预期收获**:理解 3-Agent 协同架构
|
||||
|
||||
---
|
||||
|
||||
**Step 1.1.3:查看依赖图**
|
||||
|
||||
```bash
|
||||
在 Claude Code 中执行:
|
||||
mcp__gitnexus__context({name: "AiOpsService", repo: "SuperBizAgent-java"})
|
||||
```
|
||||
|
||||
**关注点**:
|
||||
- `outgoing.has_property`:依赖了哪些 Tools
|
||||
- `incoming.imports`:被谁调用(应该是 ChatController)
|
||||
|
||||
**预期收获**:理解 AiOpsService 的依赖关系
|
||||
|
||||
---
|
||||
|
||||
**Step 1.1.4:如果要修改,先做影响分析**
|
||||
|
||||
```bash
|
||||
mcp__gitnexus__impact({
|
||||
target: "executeAiOpsAnalysis",
|
||||
direction: "upstream",
|
||||
repo: "SuperBizAgent-java"
|
||||
})
|
||||
```
|
||||
|
||||
**预期收获**:理解修改这个方法会影响哪些代码
|
||||
|
||||
---
|
||||
|
||||
**🎓 阶段 1.1 总结**:
|
||||
|
||||
完成后,你应该能回答:
|
||||
1. AI Ops 分析为什么用 3 个 Agent 而不是 1 个?
|
||||
2. Planner 和 Executor 的输入输出分别是什么?
|
||||
3. 为什么要用 SSE 而不是普通的 HTTP 响应?
|
||||
4. Mock 模式下,告警和日志数据从哪里来?
|
||||
|
||||
---
|
||||
|
||||
#### 1.2 Chat 对话流程
|
||||
|
||||
**执行流程图**:
|
||||
|
||||
```
|
||||
用户输入消息 → 点击发送
|
||||
↓
|
||||
HTTP POST /api/chat
|
||||
↓
|
||||
ChatController.chat()
|
||||
↓
|
||||
ChatService.executeChat(userMessage, sessionId)
|
||||
↓
|
||||
createReactAgent(ChatModel, tools)
|
||||
├─ InternalDocsTools (RAG 检索)
|
||||
├─ DateTimeTools (时间查询)
|
||||
├─ QueryMetricsTools (告警查询)
|
||||
└─ QueryLogsTools (日志查询)
|
||||
↓
|
||||
ReactAgent.stream(userMessage)
|
||||
↓
|
||||
根据用户问题,自动选择调用哪些 Tools
|
||||
↓
|
||||
SSE 流式返回
|
||||
↓
|
||||
前端渲染
|
||||
```
|
||||
|
||||
**学习步骤**:
|
||||
|
||||
**Step 1.2.1:阅读 ChatService**
|
||||
|
||||
```bash
|
||||
Read src/main/java/org/example/service/ChatService.java
|
||||
```
|
||||
|
||||
**关注点**:
|
||||
- `executeChat()` 方法
|
||||
- `createReactAgent()` 方法(如何注册 Tools)
|
||||
- `buildSystemPrompt()` 方法(系统提示词)
|
||||
- `getToolCallbacks()` 方法(MCP 工具回调,可选)
|
||||
|
||||
**预期收获**:理解 ReactAgent 如何工作
|
||||
|
||||
---
|
||||
|
||||
**Step 1.2.2:查看 InternalDocsTools(RAG 核心)**
|
||||
|
||||
```bash
|
||||
Read src/main/java/org/example/tools/InternalDocsTools.java
|
||||
```
|
||||
|
||||
```bash
|
||||
mcp__gitnexus__context({name: "InternalDocsTools", repo: "SuperBizAgent-java"})
|
||||
```
|
||||
|
||||
**关注点**:
|
||||
- `queryInternalDocs()` 方法
|
||||
- 如何调用 `RagService.queryRelevantDocs()`
|
||||
- 返回值结构
|
||||
|
||||
**预期收获**:理解 RAG 如何嵌入到 Agent 工具链
|
||||
|
||||
---
|
||||
|
||||
**🎓 阶段 1.2 总结**:
|
||||
|
||||
完成后,你应该能回答:
|
||||
1. ReactAgent 如何决定调用哪个 Tool?
|
||||
2. Chat 和 AI Ops 使用的 Agent 有什么区别?
|
||||
3. 为什么 Chat 需要 sessionId 而 AI Ops 不需要?
|
||||
|
||||
---
|
||||
|
||||
### 📍 阶段 2:RAG 知识库链路(20 分钟)
|
||||
|
||||
**目标**:理解文档上传 → 向量化 → 检索的完整链路
|
||||
|
||||
---
|
||||
|
||||
#### 2.1 文档上传与向量化
|
||||
|
||||
**执行流程图**:
|
||||
|
||||
```
|
||||
用户上传文档 (txt/md)
|
||||
↓
|
||||
HTTP POST /api/upload
|
||||
↓
|
||||
FileUploadController.upload()
|
||||
↓
|
||||
RagService.processAndStoreDocument()
|
||||
├─ 文档分块
|
||||
│ └─ ChunkingStrategy.splitByParagraphs()
|
||||
│ ├─ 段落识别(\n\n)
|
||||
│ ├─ Token 估算(estimateTokens)
|
||||
│ └─ 重叠策略(overlap=100)
|
||||
├─ 向量化
|
||||
│ └─ VectorEmbeddingService.generateEmbeddings()
|
||||
│ └─ SiliconFlow BGE-M3 (1024 维)
|
||||
└─ 存储到 Milvus
|
||||
└─ MilvusClientFactory.insert()
|
||||
```
|
||||
|
||||
**学习步骤**:
|
||||
|
||||
**Step 2.1.1:阅读 RagService**
|
||||
|
||||
```bash
|
||||
Read src/main/java/org/example/service/RagService.java
|
||||
```
|
||||
|
||||
**关注点**:
|
||||
- `processAndStoreDocument()` 方法(完整流程)
|
||||
- `queryRelevantDocs()` 方法(检索流程)
|
||||
- 分块策略(`ChunkingStrategy`)
|
||||
|
||||
---
|
||||
|
||||
**Step 2.1.2:阅读 VectorEmbeddingService**
|
||||
|
||||
```bash
|
||||
Read src/main/java/org/example/service/VectorEmbeddingService.java
|
||||
```
|
||||
|
||||
**关注点**:
|
||||
- `generateEmbedding()` 单条向量化
|
||||
- `generateEmbeddings()` 批量向量化
|
||||
- 如何调用 `EmbeddingModel.embed()`
|
||||
|
||||
---
|
||||
|
||||
**Step 2.1.3:阅读 MilvusClientFactory**
|
||||
|
||||
```bash
|
||||
Read src/main/java/org/example/client/MilvusClientFactory.java
|
||||
```
|
||||
|
||||
**关注点**:
|
||||
- `createClient()` 方法(Zilliz Cloud 连接)
|
||||
- Collection 创建逻辑
|
||||
- 索引类型(IVF_FLAT)
|
||||
- `loadCollection()` 调用(重要!搜索前必须 load)
|
||||
|
||||
---
|
||||
|
||||
**🎓 阶段 2 总结**:
|
||||
|
||||
完成后,你应该能回答:
|
||||
1. 文档分块为什么要有 overlap?overlap=100 的意义是什么?
|
||||
2. 为什么用 BGE-M3 而不是其他 Embedding 模型?
|
||||
3. Milvus 的 IVF_FLAT 索引适合什么场景?什么时候需要换 HNSW?
|
||||
4. 为什么 Collection 创建后要手动 `loadCollection()`?
|
||||
|
||||
---
|
||||
|
||||
### 📍 阶段 3:模型抽象与路由(15 分钟)
|
||||
|
||||
**目标**:理解 ChatModel/EmbeddingModel 如何解耦和路由
|
||||
|
||||
**背景**:这是 2026-05-29 重构的核心成果(见 `devflow/projects/2026-05-29-chatmodel-abstraction/`)
|
||||
|
||||
---
|
||||
|
||||
#### 3.1 模型路由机制
|
||||
|
||||
**架构图**:
|
||||
|
||||
```
|
||||
application.yml
|
||||
├─ model-routing.chat: deepseek
|
||||
└─ model-routing.embedding: siliconflow
|
||||
↓
|
||||
ModelRoutingConfig.java
|
||||
├─ routeChatModel()
|
||||
│ └─ List<ChatModel> → 匹配 "deepseek" → @Primary
|
||||
└─ routeEmbeddingModel()
|
||||
└─ Map<String, EmbeddingModel> → 匹配 "siliconflow" → @Primary
|
||||
↓
|
||||
Spring 容器注入
|
||||
├─ ChatService @Autowired ChatModel → DeepSeek V4 Flash
|
||||
└─ VectorEmbeddingService @Autowired EmbeddingModel → SiliconFlow BGE-M3
|
||||
```
|
||||
|
||||
**学习步骤**:
|
||||
|
||||
**Step 3.1.1:阅读 ModelRoutingConfig**
|
||||
|
||||
```bash
|
||||
Read src/main/java/org/example/config/ModelRoutingConfig.java
|
||||
```
|
||||
|
||||
**关注点**:
|
||||
- `routeChatModel()` 方法的匹配逻辑
|
||||
- `routeEmbeddingModel()` 方法的匹配逻辑
|
||||
- 为什么用 `List<ChatModel>` 而不是 `@Qualifier`?
|
||||
|
||||
---
|
||||
|
||||
**Step 3.1.2:阅读 SiliconFlowEmbeddingConfig**
|
||||
|
||||
```bash
|
||||
Read src/main/java/org/example/config/SiliconFlowEmbeddingConfig.java
|
||||
```
|
||||
|
||||
**关注点**:
|
||||
- 如何创建独立的 `OpenAiApi`
|
||||
- 为什么 base-url 不能带 `/v1` 后缀?(参考 decisions.md L3)
|
||||
|
||||
---
|
||||
|
||||
**Step 3.1.3:阅读 application.yml**
|
||||
|
||||
```bash
|
||||
Read src/main/resources/application.yml
|
||||
```
|
||||
|
||||
**关注点**(第 23-62 行):
|
||||
- `model-routing` 配置
|
||||
- `spring.ai.deepseek` 配置
|
||||
- `siliconflow` 配置
|
||||
- 为什么 `spring.ai.openai.api-key: unused`?
|
||||
|
||||
---
|
||||
|
||||
**🎓 阶段 3 总结**:
|
||||
|
||||
完成后,你应该能回答:
|
||||
1. 如果要换成 Ollama 本地模型,需要改哪些配置?
|
||||
2. 为什么 `@Qualifier` 方案会失败?(参考 decisions.md L2)
|
||||
3. Spring AI 1.1.0 为什么不能用 OpenAI 兼容模式调 DeepSeek?(参考 decisions.md L1)
|
||||
|
||||
---
|
||||
|
||||
### 📍 阶段 4:Tools 工具集(20 分钟)
|
||||
|
||||
**目标**:理解 Agent 可调用的所有工具
|
||||
|
||||
---
|
||||
|
||||
#### 4.1 工具清单
|
||||
|
||||
| 工具类 | 功能 | 核心方法 | 文件路径 |
|
||||
|--------|------|---------|---------|
|
||||
| `InternalDocsTools` | RAG 知识库检索 | `queryInternalDocs()` | `tools/InternalDocsTools.java` |
|
||||
| `DateTimeTools` | 获取当前时间 | `getCurrentDateTime()` | `tools/DateTimeTools.java` |
|
||||
| `QueryMetricsTools` | Prometheus 告警查询 | `queryActiveAlerts()` | `tools/QueryMetricsTools.java` |
|
||||
| `QueryLogsTools` | 腾讯云 CLS 日志查询 | `queryLogs()` | `tools/QueryLogsTools.java` |
|
||||
|
||||
---
|
||||
|
||||
**学习步骤**:
|
||||
|
||||
**Step 4.1.1:查看所有 Tool 类**
|
||||
|
||||
```bash
|
||||
Glob pattern="**/tools/*.java"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Step 4.1.2:阅读 QueryMetricsTools(Mock 模式)**
|
||||
|
||||
```bash
|
||||
Read src/main/java/org/example/tools/QueryMetricsTools.java
|
||||
```
|
||||
|
||||
**关注点**:
|
||||
- `@Tool` 注解(Spring AI 的工具注册机制)
|
||||
- `mockEnabled` 配置的作用
|
||||
- Mock 数据结构(模拟 Prometheus 告警)
|
||||
|
||||
---
|
||||
|
||||
**Step 4.1.3:阅读 QueryLogsTools(Mock 模式)**
|
||||
|
||||
```bash
|
||||
Read src/main/java/org/example/tools/QueryLogsTools.java
|
||||
```
|
||||
|
||||
**关注点**:
|
||||
- 如何根据告警名称返回关联的日志
|
||||
- Mock 数据如何与 AI Ops 分析报告对应
|
||||
|
||||
---
|
||||
|
||||
**🎓 阶段 4 总结**:
|
||||
|
||||
完成后,你应该能回答:
|
||||
1. 如果要新增一个工具(如 K8s 事件查询),需要做什么?
|
||||
2. Mock 模式的数据是否可以通过配置文件管理?
|
||||
3. 为什么 Tool 方法要返回 String 而不是复杂对象?
|
||||
|
||||
---
|
||||
|
||||
### 📍 阶段 5:配置与基础设施(10 分钟)
|
||||
|
||||
**目标**:理解配置项和基础设施
|
||||
|
||||
---
|
||||
|
||||
**Step 5.1:阅读 application.yml**
|
||||
|
||||
```bash
|
||||
Read src/main/resources/application.yml
|
||||
```
|
||||
|
||||
**关注清单**:
|
||||
|
||||
| 配置项 | 作用 | 默认值 | 修改场景 |
|
||||
|--------|------|--------|---------|
|
||||
| `milvus.host` | Zilliz Cloud 地址 | in03-xxx.cloud.zilliz.com | 换集群 |
|
||||
| `milvus.vector-dim` | 向量维度 | 1024 (BGE-M3) | 换模型 |
|
||||
| `model-routing.chat` | Chat 模型路由 | deepseek | 换模型 |
|
||||
| `model-routing.embedding` | Embedding 路由 | siliconflow | 换模型 |
|
||||
| `document.chunk.max-size` | 分块最大 Token | 800 | 优化检索 |
|
||||
| `rag.top-k` | 检索返回数 | 3 | 优化检索 |
|
||||
| `prometheus.mock-enabled` | Prometheus Mock | true | 接入真实 Prometheus |
|
||||
| `cls.mock-enabled` | CLS Mock | true | 接入真实腾讯云 CLS |
|
||||
|
||||
---
|
||||
|
||||
**Step 5.2:阅读 MilvusProperties**
|
||||
|
||||
```bash
|
||||
Read src/main/java/org/example/config/MilvusProperties.java
|
||||
```
|
||||
|
||||
**关注点**:
|
||||
- `@ConfigurationProperties(prefix = "milvus")`
|
||||
- 为什么 `vectorDim` 要从配置读取?(参考 brief.md)
|
||||
|
||||
---
|
||||
|
||||
**🎓 阶段 5 总结**:
|
||||
|
||||
完成后,你应该能回答:
|
||||
1. 如果 Embedding 模型从 BGE-M3 (1024维) 换成 text-embedding-ada-002 (1536维),需要改哪些配置?
|
||||
2. Mock 模式如何切换到真实环境?
|
||||
|
||||
---
|
||||
|
||||
## 📊 学习检查点
|
||||
|
||||
完成每个阶段后,勾选对应的检查点:
|
||||
|
||||
### ✅ 阶段 1 检查点
|
||||
|
||||
- [ ] 我能画出 AI Ops 的完整执行流程图
|
||||
- [ ] 我理解了 Planner、Executor、Supervisor 的职责
|
||||
- [ ] 我知道如何修改 Planner 的分析策略
|
||||
- [ ] 我能解释 SSE 流式响应的优势
|
||||
|
||||
### ✅ 阶段 2 检查点
|
||||
|
||||
- [ ] 我能画出文档上传到向量存储的完整流程
|
||||
- [ ] 我理解了文档分块策略的 overlap 参数
|
||||
- [ ] 我知道如何调整 top-k 影响检索结果
|
||||
- [ ] 我能解释为什么 Collection 需要 load
|
||||
|
||||
### ✅ 阶段 3 检查点
|
||||
|
||||
- [ ] 我理解了 `@Primary` 路由的原理
|
||||
- [ ] 我能通过修改 yml 切换模型
|
||||
- [ ] 我知道为什么不用 `@Qualifier`
|
||||
- [ ] 我能添加新的模型提供商(如 Ollama)
|
||||
|
||||
### ✅ 阶段 4 检查点
|
||||
|
||||
- [ ] 我理解了 `@Tool` 注解的作用
|
||||
- [ ] 我能新增一个自定义工具
|
||||
- [ ] 我知道 Mock 模式的数据结构
|
||||
- [ ] 我能对接真实的 Prometheus/CLS
|
||||
|
||||
### ✅ 阶段 5 检查点
|
||||
|
||||
- [ ] 我理解了所有关键配置项
|
||||
- [ ] 我能修改配置优化 RAG 检索
|
||||
- [ ] 我知道如何切换到生产环境配置
|
||||
|
||||
---
|
||||
|
||||
## 🎯 进阶学习路径
|
||||
|
||||
完成基础学习后,可以尝试:
|
||||
|
||||
### 进阶 1:深入 Agent 协同模式
|
||||
|
||||
```bash
|
||||
# 阅读 Spring AI Agent Framework 源码
|
||||
Read pom.xml # 查看 spring-ai-alibaba-starter-agent 版本
|
||||
```
|
||||
|
||||
**研究方向**:
|
||||
- ReactAgent 的 Tool 选择算法
|
||||
- Agent 链式调用的状态传递
|
||||
- Agent 的异常处理机制
|
||||
|
||||
---
|
||||
|
||||
### 进阶 2:性能优化
|
||||
|
||||
**优化点**:
|
||||
1. **Milvus 索引优化**:IVF_FLAT → HNSW
|
||||
2. **分块策略优化**:调整 max-size 和 overlap
|
||||
3. **批量向量化**:优化 `generateEmbeddings()` 批量大小
|
||||
4. **缓存策略**:热点查询缓存
|
||||
|
||||
**推荐操作**:
|
||||
```bash
|
||||
# 查看向量化服务
|
||||
Read src/main/java/org/example/service/VectorEmbeddingService.java
|
||||
|
||||
# 查看 Milvus 客户端
|
||||
Read src/main/java/org/example/client/MilvusClientFactory.java
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 进阶 3:功能扩展
|
||||
|
||||
**扩展方向**:
|
||||
|
||||
1. **新增工具**:
|
||||
- K8s 事件查询工具
|
||||
- Grafana Dashboard 查询工具
|
||||
- Jira Issue 创建工具
|
||||
|
||||
2. **新增 Agent**:
|
||||
- 根因分析专家 Agent
|
||||
- 修复建议生成 Agent
|
||||
- 历史告警对比 Agent
|
||||
|
||||
3. **新增模型支持**:
|
||||
- Ollama 本地模型
|
||||
- Azure OpenAI
|
||||
- Anthropic Claude
|
||||
|
||||
---
|
||||
|
||||
## 📚 参考文档
|
||||
|
||||
### 项目文档
|
||||
|
||||
| 文档 | 用途 |
|
||||
|------|------|
|
||||
| `docs/功能分析报告.md` | 项目功能概览、技术栈、分析案例 |
|
||||
| `docs/日志配置与分析指南.md` | 日志配置、分析场景、故障排查 |
|
||||
| `devflow/projects/2026-05-29-chatmodel-abstraction/` | ChatModel 重构的完整记录 |
|
||||
| `CLAUDE.md` | GitNexus 使用规范(影响分析、变更检测) |
|
||||
|
||||
### 技术文档
|
||||
|
||||
| 技术 | 官方文档 |
|
||||
|------|---------|
|
||||
| Spring AI | https://docs.spring.io/spring-ai/ |
|
||||
| Milvus | https://milvus.io/docs |
|
||||
| DeepSeek API | https://platform.deepseek.com/docs |
|
||||
| SiliconFlow | https://siliconflow.cn/docs |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 开始学习
|
||||
|
||||
**推荐第一步**:
|
||||
|
||||
```bash
|
||||
# 1. 阅读 AI Ops 核心 Service
|
||||
Read src/main/java/org/example/service/AiOpsService.java
|
||||
|
||||
# 2. 查看依赖图
|
||||
mcp__gitnexus__context({name: "AiOpsService", repo: "SuperBizAgent-java"})
|
||||
|
||||
# 3. 如果打算修改,先做影响分析
|
||||
mcp__gitnexus__impact({target: "AiOpsService", direction: "upstream", repo: "SuperBizAgent-java"})
|
||||
```
|
||||
|
||||
**学习节奏建议**:
|
||||
- **快速模式**(1 小时):只完成阶段 1 + 阶段 3
|
||||
- **标准模式**(2 小时):完成阶段 1-4
|
||||
- **深度模式**(3 小时):完成全部 5 个阶段 + 进阶路径
|
||||
|
||||
---
|
||||
|
||||
## 🎓 学习产出建议
|
||||
|
||||
学习过程中,建议你输出以下文档(保存到 `docs/learning/`):
|
||||
|
||||
| 文档 | 内容 |
|
||||
|------|------|
|
||||
| `AI-Ops-执行流.md` | 手绘执行流程图 + 关键代码片段 |
|
||||
| `RAG-知识库设计.md` | 文档分块策略、向量化、检索全流程 |
|
||||
| `模型路由机制.md` | ChatModel/EmbeddingModel 路由源码分析 |
|
||||
| `Tools-工具集.md` | 所有工具的作用、参数、返回值、扩展方案 |
|
||||
| `学习笔记.md` | 每个阶段的收获、疑问、TODO |
|
||||
|
||||
---
|
||||
|
||||
> 💡 **提示**:这个学习路径是基于项目当前状态(2026-05-30)设计的。如果项目有重大更新,请重新执行 `npx gitnexus analyze` 更新索引。
|
||||
|
||||
---
|
||||
|
||||
**立即开始**:
|
||||
|
||||
```bash
|
||||
# Step 1: 从 AI Ops 开始
|
||||
Read src/main/java/org/example/service/AiOpsService.java
|
||||
```
|
||||
|
||||
祝学习愉快!🎉
|
||||
@@ -0,0 +1,576 @@
|
||||
# Tool 定义方式对比与优化建议
|
||||
|
||||
> **文档日期**: 2026-05-31
|
||||
> **参考文档**: https://java2ai.com/docs/frameworks/agent-framework/tutorials/tools
|
||||
> **项目**: SuperBizAgent-java
|
||||
|
||||
---
|
||||
|
||||
## 📋 Spring AI Agent Framework 的 6 种 Tool 定义方式
|
||||
|
||||
| 方式 | 类型 | 难度 | 类型安全 | 动态性 | 最佳场景 |
|
||||
|------|------|------|---------|--------|---------|
|
||||
| **1. @Tool 注解** | 声明式 | ⭐ | ✅ | ❌ | 静态工具、类组织 |
|
||||
| **2. MethodToolCallback** | 编程式 | ⭐⭐⭐ | ✅ | ✅ | 动态构建、反射 |
|
||||
| **3. FunctionToolCallback** | 函数式 | ⭐⭐ | ✅ | ✅ | 函数式逻辑 |
|
||||
| **4. @Bean 函数** | Spring式 | ⭐ | ❌ | ✅ | Spring 应用 |
|
||||
| **5. ToolCallback 接口** | 自定义 | ⭐⭐⭐⭐ | ✅ | ✅ | 高度定制 |
|
||||
| **6. MCP ToolCallback** | 外部进程 | ⭐⭐ | ✅ | ✅ | 外部服务 |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 项目当前使用方式
|
||||
|
||||
### **方式1:@Tool 注解(主要方式)**
|
||||
|
||||
**使用位置**:
|
||||
- `DateTimeTools.java`
|
||||
- `InternalDocsTools.java`
|
||||
- `QueryMetricsTools.java`
|
||||
- `QueryLogsTools.java`
|
||||
|
||||
**代码示例**:
|
||||
```java
|
||||
@Component
|
||||
public class InternalDocsTools {
|
||||
|
||||
@Autowired
|
||||
private VectorSearchService vectorSearchService; // ← 依赖注入
|
||||
|
||||
@Value("${rag.top-k:3}")
|
||||
private int topK; // ← 配置注入
|
||||
|
||||
@Tool(description = "Use this tool to search internal documentation...")
|
||||
public String queryInternalDocs(
|
||||
@ToolParam(description = "Search query") String query) { // ← 参数注解
|
||||
|
||||
List<SearchResult> results = vectorSearchService.searchSimilarDocuments(query, topK);
|
||||
return objectMapper.writeValueAsString(results);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**注入方式**(`ChatService.java:93-101`):
|
||||
```java
|
||||
public Object[] buildMethodToolsArray() {
|
||||
if (queryLogsTools != null) {
|
||||
return new Object[]{dateTimeTools, internalDocsTools, queryMetricsTools, queryLogsTools};
|
||||
} else {
|
||||
return new Object[]{dateTimeTools, internalDocsTools, queryMetricsTools};
|
||||
}
|
||||
}
|
||||
|
||||
// 在 ReactAgent 中使用
|
||||
ReactAgent.builder()
|
||||
.methodTools(buildMethodToolsArray()) // ← 传入 @Tool 注解的对象
|
||||
.build();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **方式6:MCP ToolCallback(外部工具)**
|
||||
|
||||
**使用位置**:
|
||||
- 腾讯云 CLS 日志查询(真实模式)
|
||||
- 其他外部 MCP 服务
|
||||
|
||||
**代码示例**(`ChatService.java:106-111`):
|
||||
```java
|
||||
@Autowired(required = false)
|
||||
private ToolCallbackProvider tools; // ← MCP 工具提供者
|
||||
|
||||
public ToolCallback[] getToolCallbacks() {
|
||||
if (tools == null) {
|
||||
return new ToolCallback[0];
|
||||
}
|
||||
return tools.getToolCallbacks();
|
||||
}
|
||||
|
||||
// 在 ReactAgent 中使用
|
||||
ReactAgent.builder()
|
||||
.methodTools(buildMethodToolsArray()) // Java 工具
|
||||
.tools(getToolCallbacks()) // MCP 工具
|
||||
.build();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 当前方式的优缺点分析
|
||||
|
||||
### **优点** ✅
|
||||
|
||||
| 优点 | 说明 |
|
||||
|------|------|
|
||||
| **代码清晰** | `@Tool` 注解一目了然,易于理解 |
|
||||
| **类型安全** | 编译时检查,减少运行时错误 |
|
||||
| **依赖注入** | 完美集成 Spring 生态(`@Autowired`, `@Value`) |
|
||||
| **易于测试** | 工具类可以独立单元测试 |
|
||||
| **配置灵活** | 通过 `@Value` 读取配置(如 `topK`, `mockEnabled`) |
|
||||
| **状态管理** | 工具类可以有成员变量(如 `httpClient`, `objectMapper`) |
|
||||
| **生命周期** | 支持 `@PostConstruct` 初始化(如 `QueryMetricsTools.init()`) |
|
||||
|
||||
---
|
||||
|
||||
### **缺点** ❌
|
||||
|
||||
| 缺点 | 影响 | 是否需要优化 |
|
||||
|------|------|------------|
|
||||
| **工具数组需要手动管理** | 每增加一个工具,需要修改 `buildMethodToolsArray()` | ⚠️ 可优化 |
|
||||
| **工具名称为常量字符串** | `TOOL_QUERY_PROMETHEUS_ALERTS` 容易拼写错误 | ⚠️ 可优化 |
|
||||
| **无法动态启用/禁用工具** | 必须在编译时确定工具列表 | ⚠️ 可优化(已有 Mock 模式) |
|
||||
| **工具发现不够智能** | 需要手动添加到数组,无法自动扫描 | ⚠️ 可优化 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 优化方案
|
||||
|
||||
### **优化1:自动扫描 @Tool 注解** ⭐⭐⭐(推荐)
|
||||
|
||||
**问题**:每次新增工具类,都需要在 `ChatService` 中手动添加。
|
||||
|
||||
**解决方案**:自动扫描所有带 `@Component` 且包含 `@Tool` 方法的 Bean。
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class ChatService {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext; // ← Spring 上下文
|
||||
|
||||
/**
|
||||
* 自动扫描所有工具类
|
||||
* 无需手动维护工具列表
|
||||
*/
|
||||
public Object[] buildMethodToolsArray() {
|
||||
List<Object> tools = new ArrayList<>();
|
||||
|
||||
// 1. 获取所有 Spring Bean
|
||||
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Component.class);
|
||||
|
||||
for (Object bean : beans.values()) {
|
||||
// 2. 检查是否包含 @Tool 方法
|
||||
boolean hasTool = Arrays.stream(bean.getClass().getMethods())
|
||||
.anyMatch(m -> m.isAnnotationPresent(Tool.class));
|
||||
|
||||
if (hasTool) {
|
||||
// 3. 根据配置决定是否添加
|
||||
if (shouldIncludeTool(bean)) {
|
||||
tools.add(bean);
|
||||
logger.info("🔧 自动注册工具: {}", bean.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tools.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否应该包含某个工具(基于配置)
|
||||
*/
|
||||
private boolean shouldIncludeTool(Object bean) {
|
||||
// 特殊处理:QueryLogsTools 只在 Mock 模式下启用
|
||||
if (bean instanceof QueryLogsTools) {
|
||||
return queryLogsTools != null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 新增工具类无需修改 `ChatService`
|
||||
- ✅ 自动发现所有工具
|
||||
- ✅ 保留配置化的启用/禁用逻辑
|
||||
|
||||
**缺点**:
|
||||
- ⚠️ 性能开销(启动时扫描一次,可接受)
|
||||
- ⚠️ 可能注册不需要的工具(需要过滤逻辑)
|
||||
|
||||
---
|
||||
|
||||
### **优化2:使用 @Bean 函数定义工具** ⭐⭐
|
||||
|
||||
**适用场景**:工具逻辑简单、无状态、偏函数式
|
||||
|
||||
**改造示例**:
|
||||
|
||||
**改造前**(当前方式):
|
||||
```java
|
||||
@Component
|
||||
public class DateTimeTools {
|
||||
@Tool(description = "Get the current date and time")
|
||||
public String getCurrentDateTime() {
|
||||
return LocalDateTime.now()...toString();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**改造后**(@Bean 函数):
|
||||
```java
|
||||
@Configuration
|
||||
public class ToolsConfiguration {
|
||||
|
||||
@Bean("getCurrentDateTime")
|
||||
@Description("Get the current date and time in the user's timezone. " +
|
||||
"IMPORTANT: Time changes constantly. Always call this tool...")
|
||||
public Supplier<String> getCurrentDateTime() {
|
||||
return () -> LocalDateTime.now()
|
||||
.atZone(LocaleContextHolder.getTimeZone().toZoneId())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// 使用
|
||||
ReactAgent.builder()
|
||||
.toolNames("getCurrentDateTime") // ← 直接使用工具名
|
||||
.build();
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 更简洁(适合简单工具)
|
||||
- ✅ 函数式风格
|
||||
- ✅ Spring 自动发现和注册
|
||||
|
||||
**缺点**:
|
||||
- ❌ 无法使用成员变量(`Supplier` 无状态)
|
||||
- ❌ 工具名称为字符串,非类型安全
|
||||
- ❌ 不适合需要依赖注入的复杂工具(如 `InternalDocsTools`)
|
||||
|
||||
**结论**:**不推荐全面改造**,因为项目的工具大多需要依赖注入(`VectorSearchService`、`httpClient` 等)。
|
||||
|
||||
---
|
||||
|
||||
### **优化3:工具元数据统一管理** ⭐⭐⭐
|
||||
|
||||
**问题**:工具名称定义为常量,但未被使用,容易不一致。
|
||||
|
||||
**当前代码**:
|
||||
```java
|
||||
public class QueryMetricsTools {
|
||||
/** 工具名常量,用于动态构建提示词 */
|
||||
public static final String TOOL_QUERY_PROMETHEUS_ALERTS = "queryPrometheusAlerts";
|
||||
|
||||
@Tool(description = "...")
|
||||
public String queryPrometheusAlerts() { // ← 方法名就是工具名
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**问题**:`TOOL_QUERY_PROMETHEUS_ALERTS` 从未被使用,可能会过时。
|
||||
|
||||
**优化方案**:使用 `@Tool(name = ...)` 明确指定工具名
|
||||
|
||||
```java
|
||||
public class QueryMetricsTools {
|
||||
public static final String TOOL_NAME = "queryPrometheusAlerts";
|
||||
|
||||
@Tool(
|
||||
name = TOOL_NAME, // ← 明确指定工具名(可选,默认为方法名)
|
||||
description = "Query active alerts from Prometheus..."
|
||||
)
|
||||
public String queryPrometheusAlerts() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**或者**:移除无用的常量
|
||||
|
||||
```java
|
||||
public class QueryMetricsTools {
|
||||
// 删除未使用的常量
|
||||
// public static final String TOOL_QUERY_PROMETHEUS_ALERTS = "queryPrometheusAlerts";
|
||||
|
||||
@Tool(description = "...")
|
||||
public String queryPrometheusAlerts() { // 方法名即工具名
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **优化4:工具分组与条件注册** ⭐⭐
|
||||
|
||||
**问题**:工具启用逻辑分散在多处(`@Autowired(required = false)`, `buildMethodToolsArray()`)
|
||||
|
||||
**优化方案**:使用 `@ConditionalOnProperty` 统一管理
|
||||
|
||||
```java
|
||||
// Mock 模式的日志查询工具
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "cls.mock-enabled", havingValue = "true")
|
||||
public class QueryLogsTools {
|
||||
@Tool(description = "...")
|
||||
public String queryLogs(...) {
|
||||
// Mock 实现
|
||||
}
|
||||
}
|
||||
|
||||
// 真实模式的工具由 MCP 提供,无需 Java 实现
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 配置化启用/禁用
|
||||
- ✅ 无需 `@Autowired(required = false)`
|
||||
- ✅ Spring 自动管理生命周期
|
||||
|
||||
**修改后的 `ChatService`**:
|
||||
```java
|
||||
@Service
|
||||
public class ChatService {
|
||||
|
||||
@Autowired
|
||||
private List<Object> toolBeans; // ← Spring 自动注入所有工具类
|
||||
|
||||
@Autowired(required = false)
|
||||
private ToolCallbackProvider tools;
|
||||
|
||||
public Object[] buildMethodToolsArray() {
|
||||
return toolBeans.stream()
|
||||
.filter(bean -> hasToolMethod(bean)) // 过滤出包含 @Tool 方法的 Bean
|
||||
.toArray();
|
||||
}
|
||||
|
||||
private boolean hasToolMethod(Object bean) {
|
||||
return Arrays.stream(bean.getClass().getMethods())
|
||||
.anyMatch(m -> m.isAnnotationPresent(Tool.class));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **优化5:工具返回类型结构化** ⭐⭐
|
||||
|
||||
**问题**:工具返回值都是 `String`(JSON),LLM 需要解析
|
||||
|
||||
**当前代码**:
|
||||
```java
|
||||
@Tool(description = "...")
|
||||
public String queryInternalDocs(String query) {
|
||||
List<SearchResult> results = vectorSearchService.searchSimilarDocuments(query, topK);
|
||||
return objectMapper.writeValueAsString(results); // ← 手动序列化
|
||||
}
|
||||
```
|
||||
|
||||
**优化方案**:返回结构化对象(Spring AI 自动序列化)
|
||||
|
||||
```java
|
||||
@Tool(description = "...")
|
||||
public InternalDocsResponse queryInternalDocs(String query) {
|
||||
List<SearchResult> results = vectorSearchService.searchSimilarDocuments(query, topK);
|
||||
return new InternalDocsResponse(results); // ← 返回 POJO
|
||||
}
|
||||
|
||||
@Data
|
||||
public class InternalDocsResponse {
|
||||
private List<SearchResult> results;
|
||||
private int totalCount;
|
||||
private String status;
|
||||
|
||||
public InternalDocsResponse(List<SearchResult> results) {
|
||||
this.results = results;
|
||||
this.totalCount = results.size();
|
||||
this.status = "success";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 类型安全
|
||||
- ✅ LLM 自动解析
|
||||
- ✅ 更清晰的数据结构
|
||||
|
||||
**缺点**:
|
||||
- ⚠️ 需要定义额外的 DTO 类
|
||||
- ⚠️ Spring AI 需要支持(当前版本可能只支持 `String`)
|
||||
|
||||
**验证**:查看 Spring AI 文档确认是否支持非 String 返回值。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 推荐的优化优先级
|
||||
|
||||
### **短期优化(1-2周)**
|
||||
|
||||
| 优化项 | 优先级 | 难度 | 收益 |
|
||||
|--------|--------|------|------|
|
||||
| **优化3:移除未使用的工具名常量** | 🔴 高 | ⭐ 低 | 代码整洁 |
|
||||
| **优化4:使用 `@ConditionalOnProperty`** | 🔴 高 | ⭐⭐ 中 | 配置简化 |
|
||||
| **优化1:自动扫描工具类** | 🟡 中 | ⭐⭐⭐ 中 | 易扩展 |
|
||||
|
||||
---
|
||||
|
||||
### **中期优化(1个月)**
|
||||
|
||||
| 优化项 | 优先级 | 难度 | 收益 |
|
||||
|--------|--------|------|------|
|
||||
| **优化5:工具返回类型结构化** | 🟡 中 | ⭐⭐ 中 | 类型安全 |
|
||||
| **添加工具单元测试** | 🟡 中 | ⭐⭐ 中 | 质量保障 |
|
||||
| **工具性能监控** | 🟢 低 | ⭐⭐ 中 | 可观测性 |
|
||||
|
||||
---
|
||||
|
||||
### **长期优化(3个月+)**
|
||||
|
||||
| 优化项 | 优先级 | 难度 | 收益 |
|
||||
|--------|--------|------|------|
|
||||
| **优化2:部分工具改为 @Bean 函数** | 🟢 低 | ⭐⭐ 中 | 函数式风格 |
|
||||
| **实现自定义 ToolCallback(高度定制)** | 🟢 低 | ⭐⭐⭐⭐ 高 | 特殊需求 |
|
||||
|
||||
---
|
||||
|
||||
## 📊 对比表:当前方式 vs 推荐方式
|
||||
|
||||
| 维度 | 当前方式 | 推荐方式(优化后) |
|
||||
|------|---------|------------------|
|
||||
| **工具发现** | 手动添加到数组 | 自动扫描 `@Tool` 注解 |
|
||||
| **启用/禁用** | `@Autowired(required = false)` + 条件判断 | `@ConditionalOnProperty` |
|
||||
| **工具名管理** | 未使用的常量 | 方法名即工具名 |
|
||||
| **代码行数** | ~100 行 | ~50 行 |
|
||||
| **易扩展性** | ⭐⭐ | ⭐⭐⭐⭐ |
|
||||
| **维护成本** | ⭐⭐⭐ | ⭐ |
|
||||
|
||||
---
|
||||
|
||||
## 💡 最佳实践建议
|
||||
|
||||
### 1️⃣ **工具设计原则**
|
||||
|
||||
```java
|
||||
// ✅ 好的工具设计
|
||||
@Component
|
||||
public class WeatherTools {
|
||||
|
||||
@Tool(description = "Get current weather for a location. Returns temperature, humidity, and conditions.")
|
||||
public String getCurrentWeather(
|
||||
@ToolParam(description = "City name, e.g., 'Beijing', 'London'") String city) {
|
||||
|
||||
// 清晰的输入验证
|
||||
if (city == null || city.trim().isEmpty()) {
|
||||
return "{\"error\": \"City name is required\"}";
|
||||
}
|
||||
|
||||
// 结构化的返回值
|
||||
WeatherData data = weatherService.getWeather(city);
|
||||
return objectMapper.writeValueAsString(data);
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 不好的工具设计
|
||||
@Tool(description = "Get weather") // ← 描述不够详细
|
||||
public String getWeather(String c) { // ← 参数名不明确
|
||||
return weatherService.get(c); // ← 返回值不规范
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ **工具命名规范**
|
||||
|
||||
| 规范 | 示例 | 说明 |
|
||||
|------|------|------|
|
||||
| **动词开头** | `getCurrentDateTime`, `queryInternalDocs` | 明确动作 |
|
||||
| **驼峰命名** | `queryPrometheusAlerts` | Java 规范 |
|
||||
| **避免缩写** | `queryMetrics` ✅, `queryMtr` ❌ | 可读性 |
|
||||
| **包含主语** | `queryInternalDocs` ✅, `query` ❌ | 明确查询对象 |
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ **工具描述规范**
|
||||
|
||||
```java
|
||||
// ✅ 好的描述
|
||||
@Tool(description =
|
||||
"Query active alerts from Prometheus alerting system. " +
|
||||
"Returns all currently firing alerts with labels, annotations, state, and values. " +
|
||||
"Use this when you need to check alert status, investigate conditions, or monitor system health.")
|
||||
public String queryPrometheusAlerts() { }
|
||||
|
||||
// ❌ 不好的描述
|
||||
@Tool(description = "Get alerts") // ← 太简短
|
||||
public String queryPrometheusAlerts() { }
|
||||
```
|
||||
|
||||
**描述应包含**:
|
||||
1. **What**:工具的功能
|
||||
2. **Returns**:返回值类型
|
||||
3. **When to use**:使用场景
|
||||
|
||||
---
|
||||
|
||||
### 4️⃣ **工具错误处理**
|
||||
|
||||
```java
|
||||
@Tool(description = "...")
|
||||
public String queryInternalDocs(String query) {
|
||||
try {
|
||||
// 参数验证
|
||||
if (query == null || query.trim().isEmpty()) {
|
||||
return buildErrorResponse("Query cannot be empty", "INVALID_INPUT");
|
||||
}
|
||||
|
||||
// 业务逻辑
|
||||
List<SearchResult> results = vectorSearchService.searchSimilarDocuments(query, topK);
|
||||
|
||||
// 成功响应
|
||||
return buildSuccessResponse(results);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Tool execution failed", e);
|
||||
// 返回结构化错误(而不是抛异常)
|
||||
return buildErrorResponse("Query failed", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String buildErrorResponse(String message, String details) {
|
||||
return String.format(
|
||||
"{\"status\": \"error\", \"message\": \"%s\", \"details\": \"%s\"}",
|
||||
message, details
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 参考资料
|
||||
|
||||
1. **Spring AI Alibaba Agent Framework 官方文档**
|
||||
- Tool 定义:https://java2ai.com/docs/frameworks/agent-framework/tutorials/tools
|
||||
- ReactAgent:https://java2ai.com/docs/frameworks/agent-framework/tutorials/react-agent
|
||||
|
||||
2. **Spring AI 官方文档**
|
||||
- Function Calling:https://docs.spring.io/spring-ai/reference/api/functions.html
|
||||
|
||||
3. **项目现有工具类**
|
||||
- `DateTimeTools.java` - 最简单的工具示例
|
||||
- `InternalDocsTools.java` - 依赖注入示例
|
||||
- `QueryMetricsTools.java` - 配置注入 + 状态管理示例
|
||||
|
||||
---
|
||||
|
||||
## ✅ 总结
|
||||
|
||||
### 当前方式:**@Tool 注解 + 手动注册** ✅
|
||||
|
||||
**评价**:**已经是很好的选择**,适合当前项目规模和复杂度。
|
||||
|
||||
**理由**:
|
||||
1. ✅ 工具需要依赖注入(`VectorSearchService`, `httpClient` 等)
|
||||
2. ✅ 工具需要配置注入(`@Value`)
|
||||
3. ✅ 工具需要生命周期管理(`@PostConstruct`)
|
||||
4. ✅ 工具逻辑组织在类中,易于维护
|
||||
|
||||
---
|
||||
|
||||
### 推荐的改进方向:
|
||||
|
||||
1. **短期**:移除未使用的常量,使用 `@ConditionalOnProperty`
|
||||
2. **中期**:自动扫描工具类,减少手动维护
|
||||
3. **长期**:根据实际需求考虑函数式改造或自定义 ToolCallback
|
||||
|
||||
---
|
||||
|
||||
**结论**:**保持当前的 @Tool 注解方式**,逐步应用上述优化,而不是全面重构。
|
||||
@@ -0,0 +1,568 @@
|
||||
# MethodToolCallback vs ToolCallingManager 深度分析
|
||||
|
||||
> **问题来源**: Debugger 发现 tool 调用没有经过 `ToolCallingManager`,而是直接经过 `MethodToolCallback`
|
||||
> **分析日期**: 2026-05-31
|
||||
> **项目**: SuperBizAgent-java
|
||||
|
||||
---
|
||||
|
||||
## 🔍 核心问题
|
||||
|
||||
用户在 debugger 中发现:
|
||||
```
|
||||
预期调用链路:
|
||||
ReactAgent.call() → ToolCallingManager → MethodToolCallback → 实际工具方法
|
||||
|
||||
实际调用链路:
|
||||
ReactAgent.call() → MethodToolCallback → 实际工具方法 ❌ 跳过了 ToolCallingManager
|
||||
```
|
||||
|
||||
**疑问**:
|
||||
1. `MethodToolCallback` 和 `ToolCallingManager` 有什么区别?
|
||||
2. 为什么会跳过 `ToolCallingManager`?
|
||||
3. 正常的调用链路应该是怎样的?
|
||||
|
||||
---
|
||||
|
||||
## 📚 组件职责分析
|
||||
|
||||
### 1️⃣ **MethodToolCallback** - 工具调用执行器
|
||||
|
||||
**类型**:`ToolCallback` 接口的具体实现
|
||||
|
||||
**职责**:
|
||||
- **执行层**:通过反射调用带 `@Tool` 注解的 Java 方法
|
||||
- **参数转换**:将 JSON 字符串参数转换为方法参数
|
||||
- **结果封装**:将方法返回值转换为 LLM 可读的格式
|
||||
|
||||
**核心方法**:
|
||||
```java
|
||||
public class MethodToolCallback implements ToolCallback {
|
||||
|
||||
private final Method toolMethod; // 工具方法(反射)
|
||||
private final Object toolObject; // 工具对象实例
|
||||
private final ToolDefinition definition; // 工具定义
|
||||
|
||||
@Override
|
||||
public String call(String toolInput) {
|
||||
// 1. 解析 JSON 参数
|
||||
Object[] args = parseArguments(toolInput, toolMethod);
|
||||
|
||||
// 2. 反射调用方法
|
||||
Object result = toolMethod.invoke(toolObject, args);
|
||||
|
||||
// 3. 转换为 JSON 返回
|
||||
return convertToJson(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**创建时机**:
|
||||
```java
|
||||
// Spring AI 框架内部自动创建
|
||||
ReactAgent.builder()
|
||||
.methodTools(new DateTimeTools()) // ← 传入带 @Tool 的对象
|
||||
.build();
|
||||
|
||||
// 内部逻辑(简化):
|
||||
for (Object toolObject : methodTools) {
|
||||
for (Method method : toolObject.getClass().getMethods()) {
|
||||
if (method.isAnnotationPresent(Tool.class)) {
|
||||
ToolCallback callback = new MethodToolCallback(
|
||||
method, // getCurrentDateTime()
|
||||
toolObject, // dateTimeTools 实例
|
||||
extractDefinition(method)
|
||||
);
|
||||
toolCallbacks.add(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ **ToolCallingManager** - 工具调用管理器
|
||||
|
||||
**类型**:更高层次的协调器(可能存在于某些框架版本)
|
||||
|
||||
**职责**(推测):
|
||||
- **协调层**:管理多个工具调用的生命周期
|
||||
- **权限控制**:检查工具调用权限
|
||||
- **日志记录**:统一记录所有工具调用
|
||||
- **异常处理**:统一捕获和处理工具调用异常
|
||||
- **性能监控**:统计工具调用次数、耗时等
|
||||
|
||||
**可能的实现**(伪代码):
|
||||
```java
|
||||
public class ToolCallingManager {
|
||||
|
||||
private final List<ToolCallback> toolCallbacks;
|
||||
private final ToolCallLogger logger;
|
||||
private final ToolCallPermissionChecker permissionChecker;
|
||||
|
||||
public String executeToolCall(String toolName, String arguments) {
|
||||
// 1. 权限检查
|
||||
if (!permissionChecker.canCall(toolName)) {
|
||||
throw new PermissionDeniedException("Tool not allowed: " + toolName);
|
||||
}
|
||||
|
||||
// 2. 查找对应的 ToolCallback
|
||||
ToolCallback callback = findToolCallback(toolName);
|
||||
|
||||
// 3. 日志记录(调用前)
|
||||
logger.logBefore(toolName, arguments);
|
||||
|
||||
try {
|
||||
// 4. 执行实际调用
|
||||
String result = callback.call(arguments); // ← 调用 MethodToolCallback
|
||||
|
||||
// 5. 日志记录(调用后)
|
||||
logger.logAfter(toolName, result);
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
logger.logError(toolName, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 调用链路分析
|
||||
|
||||
### **情况1:Spring AI 标准架构(无 ToolCallingManager)** ⭐
|
||||
|
||||
```
|
||||
用户: "现在几点了?"
|
||||
↓
|
||||
ReactAgent.call(question)
|
||||
↓
|
||||
ChatModel.call(prompt, tools) // DeepSeek V4
|
||||
↓
|
||||
LLM 返回工具调用请求:
|
||||
{
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_abc123",
|
||||
"name": "getCurrentDateTime",
|
||||
"arguments": "{}"
|
||||
}
|
||||
]
|
||||
}
|
||||
↓
|
||||
ReactAgent 内部循环处理工具调用
|
||||
↓
|
||||
找到对应的 ToolCallback(MethodToolCallback 实例)
|
||||
↓
|
||||
MethodToolCallback.call("{}") // ← 直接调用
|
||||
↓
|
||||
反射调用 DateTimeTools.getCurrentDateTime()
|
||||
↓
|
||||
返回: "2026-05-31T16:30:00+08:00[Asia/Shanghai]"
|
||||
↓
|
||||
将结果作为新消息发送给 LLM
|
||||
↓
|
||||
LLM 生成最终回答
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- ✅ **简单直接**:没有中间层,性能更好
|
||||
- ✅ **职责清晰**:MethodToolCallback 只负责执行
|
||||
- ❌ **缺少统一管理**:日志、权限、监控需要在各处实现
|
||||
|
||||
---
|
||||
|
||||
### **情况2:带 ToolCallingManager 的架构(某些企业版本)** ⭐⭐
|
||||
|
||||
```
|
||||
用户: "现在几点了?"
|
||||
↓
|
||||
ReactAgent.call(question)
|
||||
↓
|
||||
ChatModel.call(prompt, tools)
|
||||
↓
|
||||
LLM 返回工具调用请求
|
||||
↓
|
||||
ReactAgent 内部循环
|
||||
↓
|
||||
ToolCallingManager.executeToolCall("getCurrentDateTime", "{}") // ← 经过管理器
|
||||
↓
|
||||
│
|
||||
├─ 权限检查 ✅
|
||||
├─ 日志记录: "🔧 调用工具: getCurrentDateTime"
|
||||
├─ 性能计时开始 ⏱️
|
||||
│
|
||||
↓
|
||||
查找 MethodToolCallback(根据工具名)
|
||||
↓
|
||||
MethodToolCallback.call("{}")
|
||||
↓
|
||||
反射调用 DateTimeTools.getCurrentDateTime()
|
||||
↓
|
||||
返回结果
|
||||
↓
|
||||
│
|
||||
├─ 性能计时结束: 15ms ⏱️
|
||||
├─ 日志记录: "✅ 工具返回: 2026-05-31..."
|
||||
├─ 监控埋点: toolCallCount++
|
||||
│
|
||||
↓
|
||||
返回给 ReactAgent
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- ✅ **统一管理**:权限、日志、监控集中处理
|
||||
- ✅ **易扩展**:可以添加拦截器、缓存等
|
||||
- ❌ **额外开销**:多一层调用,性能略降
|
||||
- ❌ **复杂度高**:架构更复杂
|
||||
|
||||
---
|
||||
|
||||
## 🤔 为什么你的项目没有经过 ToolCallingManager?
|
||||
|
||||
### **原因分析** ⭐⭐⭐
|
||||
|
||||
#### **1️⃣ 框架版本差异**
|
||||
|
||||
**Spring AI Alibaba Agent Framework** 的不同版本可能有不同的架构:
|
||||
|
||||
| 版本 | 架构 | 说明 |
|
||||
|------|------|------|
|
||||
| **早期版本** | `ReactAgent` → `MethodToolCallback` | 简单直接 |
|
||||
| **企业版/高级版** | `ReactAgent` → `ToolCallingManager` → `MethodToolCallback` | 统一管理 |
|
||||
|
||||
**项目依赖**(`pom.xml:86-88`):
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud.ai</groupId>
|
||||
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**可能性**:项目使用的是**标准版本**,不包含 `ToolCallingManager`。
|
||||
|
||||
---
|
||||
|
||||
#### **2️⃣ 配置未启用**
|
||||
|
||||
某些框架会提供 `ToolCallingManager` 作为**可选组件**:
|
||||
|
||||
```java
|
||||
// 默认配置(直接调用)
|
||||
ReactAgent.builder()
|
||||
.methodTools(tools)
|
||||
.build();
|
||||
|
||||
// 启用 ToolCallingManager(可能需要手动配置)
|
||||
ReactAgent.builder()
|
||||
.methodTools(tools)
|
||||
.toolCallingManager(customManager) // ← 需要手动设置
|
||||
.build();
|
||||
```
|
||||
|
||||
**验证方法**:
|
||||
```java
|
||||
// ChatService.java:134-142
|
||||
ReactAgent agent = ReactAgent.builder()
|
||||
.name("intelligent_assistant")
|
||||
.model(chatModel)
|
||||
.systemPrompt(systemPrompt)
|
||||
.methodTools(buildMethodToolsArray())
|
||||
.tools(getToolCallbacks())
|
||||
.build();
|
||||
|
||||
// 检查是否有 .toolCallingManager() 方法可用
|
||||
// 如果没有,说明框架不支持
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **3️⃣ 设计哲学不同**
|
||||
|
||||
**Spring AI 的设计理念**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Spring AI 核心理念:简单 > 复杂 │
|
||||
│ │
|
||||
│ - ToolCallback 接口已经足够抽象 │
|
||||
│ - 开发者可以自己实现 ToolCallback │
|
||||
│ - 不强制使用统一的管理器 │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**类比**:
|
||||
```
|
||||
Spring AI ToolCallback ≈ Java Interface(接口)
|
||||
- 简单、灵活、可扩展
|
||||
- 开发者可以自由实现
|
||||
|
||||
ToolCallingManager ≈ 中央调度器(可选)
|
||||
- 统一管理、但增加复杂度
|
||||
- 不是所有项目都需要
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 实际调用链路验证
|
||||
|
||||
### **添加调试日志**
|
||||
|
||||
在项目中添加日志验证调用链路:
|
||||
|
||||
```java
|
||||
// 方式1:在工具方法中添加日志
|
||||
@Tool(description = "...")
|
||||
public String getCurrentDateTime() {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
logger.debug("📍 getCurrentDateTime 调用栈:");
|
||||
for (int i = 0; i < Math.min(10, stackTrace.length); i++) {
|
||||
logger.debug(" {} - {}.{}()", i,
|
||||
stackTrace[i].getClassName(),
|
||||
stackTrace[i].getMethodName());
|
||||
}
|
||||
|
||||
String result = LocalDateTime.now()...toString();
|
||||
logger.debug("🕐 getCurrentDateTime 返回: {}", result);
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**预期输出**:
|
||||
```log
|
||||
📍 getCurrentDateTime 调用栈:
|
||||
0 - java.lang.Thread.getStackTrace()
|
||||
1 - org.example.agent.tool.DateTimeTools.getCurrentDateTime()
|
||||
2 - jdk.internal.reflect.NativeMethodAccessorImpl.invoke0()
|
||||
3 - jdk.internal.reflect.NativeMethodAccessorImpl.invoke()
|
||||
4 - jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke()
|
||||
5 - java.lang.reflect.Method.invoke()
|
||||
6 - org.springframework.ai.tool.method.MethodToolCallback.call() ← 确认!
|
||||
7 - com.alibaba.cloud.ai.graph.agent.ReactAgent.executeToolCall()
|
||||
8 - com.alibaba.cloud.ai.graph.agent.ReactAgent.call()
|
||||
```
|
||||
|
||||
**结论**:调用链中**没有 ToolCallingManager**,直接是 `MethodToolCallback`。
|
||||
|
||||
---
|
||||
|
||||
### **方式2:使用 Aspect 拦截**
|
||||
|
||||
```java
|
||||
@Aspect
|
||||
@Component
|
||||
public class ToolCallAspect {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ToolCallAspect.class);
|
||||
|
||||
@Around("@annotation(org.springframework.ai.tool.annotation.Tool)")
|
||||
public Object logToolCall(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
String toolName = joinPoint.getSignature().getName();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
|
||||
logger.info("🔧 [ToolCall] 开始调用: {}, 参数: {}", toolName, Arrays.toString(args));
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
Object result = joinPoint.proceed();
|
||||
long duration = System.currentTimeMillis() - start;
|
||||
|
||||
logger.info("✅ [ToolCall] 完成调用: {}, 耗时: {}ms", toolName, duration);
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("❌ [ToolCall] 调用失败: {}, 错误: {}", toolName, e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 自己实现了 "ToolCallingManager" 的日志记录功能
|
||||
- ✅ 不依赖框架版本
|
||||
- ✅ 可以轻松扩展(权限检查、性能监控)
|
||||
|
||||
---
|
||||
|
||||
## 📊 两种架构的对比
|
||||
|
||||
| 维度 | 直接调用 MethodToolCallback | 通过 ToolCallingManager |
|
||||
|------|---------------------------|------------------------|
|
||||
| **调用链路** | `ReactAgent` → `MethodToolCallback` | `ReactAgent` → `ToolCallingManager` → `MethodToolCallback` |
|
||||
| **性能** | ⭐⭐⭐ 快 | ⭐⭐ 略慢(多一层) |
|
||||
| **复杂度** | ⭐ 简单 | ⭐⭐⭐ 复杂 |
|
||||
| **统一日志** | ❌ 需要在每个工具中实现 | ✅ 集中在 Manager |
|
||||
| **权限控制** | ❌ 需要在每个工具中实现 | ✅ 集中在 Manager |
|
||||
| **性能监控** | ❌ 需要自己实现 | ✅ 集中在 Manager |
|
||||
| **扩展性** | ⭐⭐ 需要修改每个工具 | ⭐⭐⭐ 在 Manager 扩展 |
|
||||
| **适用场景** | 小型项目、简单工具 | 大型项目、企业级应用 |
|
||||
|
||||
---
|
||||
|
||||
## 💡 最佳实践建议
|
||||
|
||||
### **1️⃣ 如果没有 ToolCallingManager,自己实现类似功能** ⭐⭐⭐
|
||||
|
||||
使用 **Spring AOP** 模拟 ToolCallingManager 的功能:
|
||||
|
||||
```java
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ToolCallMonitor {
|
||||
|
||||
private final AtomicLong callCount = new AtomicLong(0);
|
||||
private final Map<String, AtomicLong> toolCallCounts = new ConcurrentHashMap<>();
|
||||
|
||||
@Around("@annotation(tool)")
|
||||
public Object monitorToolCall(ProceedingJoinPoint joinPoint, Tool tool) throws Throwable {
|
||||
String toolName = joinPoint.getSignature().getName();
|
||||
long callId = callCount.incrementAndGet();
|
||||
toolCallCounts.computeIfAbsent(toolName, k -> new AtomicLong(0)).incrementAndGet();
|
||||
|
||||
log.info("🔧 [ToolCall#{}] 开始: {}, 描述: {}", callId, toolName, tool.description());
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
Object result = joinPoint.proceed();
|
||||
long duration = System.currentTimeMillis() - start;
|
||||
|
||||
log.info("✅ [ToolCall#{}] 完成: {}, 耗时: {}ms, 结果长度: {}",
|
||||
callId, toolName, duration,
|
||||
result instanceof String ? ((String) result).length() : "N/A");
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("❌ [ToolCall#{}] 失败: {}, 错误: {}", callId, toolName, e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 60000) // 每分钟输出统计
|
||||
public void printStatistics() {
|
||||
log.info("📊 [ToolCall Statistics] 总调用次数: {}, 各工具调用次数: {}",
|
||||
callCount.get(), toolCallCounts);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**依赖**:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **2️⃣ 使用装饰器模式包装 ToolCallback** ⭐⭐
|
||||
|
||||
如果想在调用层面控制:
|
||||
|
||||
```java
|
||||
public class ManagedToolCallback implements ToolCallback {
|
||||
|
||||
private final ToolCallback delegate; // 原始的 MethodToolCallback
|
||||
private final ToolCallLogger logger;
|
||||
|
||||
public ManagedToolCallback(ToolCallback delegate) {
|
||||
this.delegate = delegate;
|
||||
this.logger = new ToolCallLogger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call(String toolInput, ToolContext context) {
|
||||
String toolName = getToolDefinition().name();
|
||||
|
||||
logger.logBefore(toolName, toolInput);
|
||||
|
||||
try {
|
||||
String result = delegate.call(toolInput, context); // ← 调用原始 MethodToolCallback
|
||||
logger.logAfter(toolName, result);
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.logError(toolName, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToolDefinition getToolDefinition() {
|
||||
return delegate.getToolDefinition();
|
||||
}
|
||||
}
|
||||
|
||||
// 使用
|
||||
ReactAgent.builder()
|
||||
.tools(wrapWithManagement(buildMethodToolsArray())) // ← 包装所有工具
|
||||
.build();
|
||||
|
||||
private ToolCallback[] wrapWithManagement(Object[] methodTools) {
|
||||
// 1. 让 Spring AI 创建 MethodToolCallback
|
||||
// 2. 包装成 ManagedToolCallback
|
||||
// 3. 返回包装后的数组
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **3️⃣ 保持现状,添加必要的日志** ⭐(推荐)
|
||||
|
||||
如果项目规模不大,**保持简单架构**:
|
||||
|
||||
```java
|
||||
// DateTimeTools.java
|
||||
@Tool(description = "...")
|
||||
public String getCurrentDateTime() {
|
||||
logger.debug("🕐 getCurrentDateTime 被调用"); // ← 简单日志
|
||||
String result = LocalDateTime.now()...toString();
|
||||
logger.debug("🕐 getCurrentDateTime 返回: {}", result);
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 简单直接
|
||||
- ✅ 无额外依赖
|
||||
- ✅ 性能最好
|
||||
|
||||
---
|
||||
|
||||
## ✅ 总结
|
||||
|
||||
### **核心答案**
|
||||
|
||||
| 问题 | 答案 |
|
||||
|------|------|
|
||||
| **为什么没有经过 ToolCallingManager?** | 项目使用的 Spring AI 版本采用**简单架构**,直接调用 `MethodToolCallback` |
|
||||
| **MethodToolCallback 是什么?** | 工具调用的**执行器**,通过反射调用 @Tool 方法 |
|
||||
| **ToolCallingManager 是什么?** | 工具调用的**管理器**(某些版本),统一处理日志、权限、监控 |
|
||||
| **两者有什么区别?** | `MethodToolCallback` 是**执行层**,`ToolCallingManager` 是**管理层** |
|
||||
| **是否需要 ToolCallingManager?** | **不一定**,小型项目用 AOP 或简单日志即可 |
|
||||
|
||||
---
|
||||
|
||||
### **推荐方案**
|
||||
|
||||
**短期**(立即实施):
|
||||
1. ✅ 保持现状(`MethodToolCallback` 直接调用)
|
||||
2. ✅ 在工具方法中添加必要的日志(已完成)
|
||||
3. ✅ 使用 debugger 日志记录调用栈(验证架构)
|
||||
|
||||
**中期**(1-2周):
|
||||
1. ⚠️ 添加 Spring AOP 拦截器(模拟 ToolCallingManager)
|
||||
2. ⚠️ 统一日志格式和性能监控
|
||||
|
||||
**长期**(按需):
|
||||
1. 🟢 如果项目规模增大,考虑升级框架版本(如果新版本包含 ToolCallingManager)
|
||||
2. 🟢 或者自己实现装饰器模式的统一管理
|
||||
|
||||
---
|
||||
|
||||
**最终建议**:**不需要担心没有 ToolCallingManager**,这是**正常的架构**,项目当前规模下**直接调用 MethodToolCallback 已经足够**。
|
||||
Reference in New Issue
Block a user