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:
zhuyongxin
2026-06-23 14:14:51 +08:00
parent caef477cec
commit 60be51f4a5
25 changed files with 339 additions and 606 deletions
+659
View File
@@ -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 已经足够**。