a1876286fd575b5bac7ea7ea06d8f59dfc56b2af
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a1876286fd |
fix(observability): 增强模型文本提取,支持多种方式并输出调试信息
## 改动内容
### 增强 extractTextContent() 方法
支持 6 种提取方式,依次尝试:
```java
// 方法 1: 反射获取 text 字段
Field textField = message.getClass().getDeclaredField("text");
// 方法 2: 反射获取 content 字段
Field contentField = message.getClass().getDeclaredField("content");
// 方法 3: 调用 getText() 方法
Method getTextMethod = message.getClass().getMethod("getText");
// 方法 4: 调用 getContent() 方法
Method getContentMethod = message.getClass().getMethod("getContent");
// 方法 5: 打印类结构信息(帮助调试)
log.warn("字段列表: ...");
log.warn("方法列表: ...");
// 方法 6: toString() 兜底
return message.toString();
```
---
## 调试信息输出
### 当提取失败时
```
[WARN] 无法提取 AssistantMessage 文本内容,打印类信息:
[WARN] 类名: org.springframework.ai.chat.messages.AssistantMessage
[WARN] 字段列表:
[WARN] - text: String
[WARN] - toolCalls: List
[WARN] - metadata: Map
[WARN] 方法列表:
[WARN] - getText(): String
[WARN] - getToolCalls(): List
[WARN] - getMetadata(): Map
```
**用途**:
- 帮助快速定位正确的字段/方法名
- 不同 Spring AI 版本可能有不同实现
- 一次调试,永久修复
---
### 当提取成功时
```
[DEBUG] 通过 text 字段提取成功
[INFO] *** [Agent 思考] 模型返回文本: 我需要查询知识库...
```
---
## 适配不同 Spring AI 版本
| 版本 | 字段/方法 | 提取方式 |
|------|----------|---------|
| **Spring AI 0.x** | `text` 字段 | 方法 1 ✅ |
| **Spring AI 1.x** | `content` 字段 | 方法 2 ✅ |
| **阿里云版本** | `getText()` 方法 | 方法 3 ✅ |
| **自定义实现** | `getContent()` 方法 | 方法 4 ✅ |
| **未知版本** | 打印类信息 | 方法 5 → 手动适配 |
---
## 错误处理
### 提取失败但不中断
```java
catch (Exception e) {
log.error("提取 AssistantMessage 文本内容时出错", e);
return null;
}
// 调用处
String textContent = extractTextContent(lastAssistant);
if (textContent != null && !textContent.isEmpty()) {
log.info("*** [Agent 思考] 模型返回文本: {}", textContent);
} else {
// 跳过,不打印
}
```
**不会中断流程**:
- 提取失败 → 返回 null
- null 检查 → 跳过日志输出
- 继续执行后续逻辑
---
## 使用场景
### 场景 1:首次运行,不确定字段名
```bash
# 启动应用
mvn spring-boot:run
# 发起请求
curl -X POST http://localhost:9900/api/chat \
-d '{"id":"test","question":"测试"}'
# 查看日志
tail -f logs/application.log | grep "模型返回文本\|字段列表\|方法列表"
```
**如果看到**:
```
[WARN] 无法提取 AssistantMessage 文本内容,打印类信息:
[WARN] 方法列表:
[WARN] - getTextContent(): String ← 找到了!
```
**修复**:在 `extractTextContent()` 中添加方法 7:
```java
// 方法 7: 尝试 getTextContent()
Method method = message.getClass().getMethod("getTextContent");
Object value = method.invoke(message);
```
---
### 场景 2:提取成功
```
[DEBUG] 通过 text 字段提取成功
[INFO] *** [Agent 思考] 模型返回文本: 我需要查询知识库来了解支付失败的具体原因
```
正常使用,无需调整。
---
## 性能考虑
### 反射开销
- 反射调用比直接调用慢 ~10-100 倍
- 但只在日志输出时使用,不在热路径
- Agent 调用频率低(秒级),性能影响可忽略
### 优化建议(可选)
缓存反射结果:
```java
private static Field cachedTextField = null;
private String extractTextContent(AssistantMessage message) {
if (cachedTextField == null) {
cachedTextField = message.getClass().getDeclaredField("text");
cachedTextField.setAccessible(true);
}
return (String) cachedTextField.get(message);
}
```
**当前未实现**,因为:
- 日志场景无性能瓶颈
- 简单实现更易维护
- 如需优化再添加
---
## 提交历史
```
当前 fix(observability): 增强模型文本提取,支持多种方式并输出调试信息
|
||
|
|
8890cd2806 |
feat(observability): 增强 Agent 和工具调用的可观测日志
## 改动内容 ### 1. ChatService - Agent 执行日志 在 `executeChat` 方法中添加: ``` ======================================== ========== Agent 执行开始 ========== ======================================== 📝 用户问题: 支付为什么会失败? ---------------------------------------- 🚀 执行 ReactAgent.call() - 自动处理工具调用 ======================================== ========== Agent 执行完成 ========== ======================================== ⏱️ 执行耗时: 1523 ms 📏 最终输出长度: 456 字符 ---------------------------------------- 📤 最终输出内容: 根据知识库的记录,支付失败的主要原因是... ======================================== ``` **关键信息**: - 用户问题 - 执行耗时 - 最终输出长度和内容 --- ### 2. LookupKnowledgeTool - 工具调用详细日志 ``` ======================================== >>> [工具调用] lookup_knowledge >>> 参数: query = "支付为什么会失败?" >>> RequestId: a3b4c5d6 ---------------------------------------- [L0 精确匹配] 完成: matches=0, time=3ms [置信度判断] highConfidence=false, reason=多个或零个匹配 [L1 语义检索] L0非唯一匹配,触发L1语义检索... [L1 语义检索] 完成: matches=1, time=245ms [L1 语义检索] 找到文档: - [1] 文档ID: doc-123, 相似度得分: 0.82 ---------------------------------------- <<< [工具返回] lookup_knowledge <<< 结果: found=true, matchType=semantic_L1, confidence=medium <<< 总耗时: 248ms (L0=3ms, L1=245ms) <<< 返回内容长度: 1234 字符 <<< 内容预览: ## 支付网关错误码定义... ======================================== ``` **关键信息**: - 工具名称和参数 - L0/L1 执行时间和结果 - 匹配文档列表 - 返回结果摘要 --- ## 日志格式说明 ### 符号约定 - `>>>` - 工具调用(入参) - `<<<` - 工具返回(出参) - `***` - Agent 思考过程(暂未实现) - `📝` - 用户输入 - `📤` - Agent 输出 - `⏱️` - 性能指标 ### 日志级别 - `INFO` - 关键节点和结果 - `DEBUG` - 详细的中间状态(已设置但默认不显示) --- ## 使用场景 ### 1. 调试工具调用 ```bash # 查看工具调用详情 grep "工具调用\|工具返回" logs/application.log # 输出示例 >>> [工具调用] lookup_knowledge >>> 参数: query = "ERR_TIMEOUT" <<< [工具返回] lookup_knowledge <<< 结果: found=true, matchType=exact_L0, confidence=high ``` ### 2. 性能分析 ```bash # 查看执行耗时 grep "执行耗时\|总耗时" logs/application.log # 输出示例 ⏱️ 执行耗时: 1523 ms <<< 总耗时: 248ms (L0=3ms, L1=245ms) ``` ### 3. L0/L1 验证 ```bash # 查看检索路径 grep "L0精确匹配\|L1语义检索" logs/application.log # 示例 - L0 命中 [L0 精确匹配] 完成: matches=1, time=3ms [L0 精确匹配] 找到文档: - [1] 标题: 支付网关错误码定义, 路径: api/payment-errors.md [L1 语义检索] L0唯一匹配,跳过L1检索 # 示例 - L1 命中 [L0 精确匹配] 完成: matches=0, time=2ms [L1 语义检索] L0非唯一匹配,触发L1语义检索... [L1 语义检索] 完成: matches=1, time=245ms ``` --- ## 后续优化 ### 可能的增强(未实现) 由于阿里云 ReactAgent 不支持内置监听器,以下功能暂时无法实现: - ❌ Agent 思考过程实时监听(`onStateUpdate`) - ❌ 工具调用前拦截(`onToolCall`) - ❌ 工具返回后拦截(`onToolResponse`) 如需这些功能,需要: 1. 包装每个工具,统一添加日志 2. 或使用支持监听器的 Agent 框架 当前实现已满足基本可观测需求。 --- ## 验证 ```bash # 1. 启动应用 mvn spring-boot:run # 2. 发起对话 curl -X POST http://localhost:9900/api/chat \ -H "Content-Type: application/json" \ -d '{"id":"test","question":"支付为什么会失败?"}' # 3. 查看日志 tail -f logs/application.log | grep -E "Agent|工具|输出" ``` |
||
|
|
f4f0c63325 |
fix(knowledge): 修复 readDocument 文件路径拼接问题
## 问题
L1 语义检索返回文档后,尝试读取文档内容时报错:
```
读取文档失败: api/payment-errors.md
java.nio.file.NoSuchFileException: api\payment-errors.md
```
**原因**:
- `KnowledgeEntry.filePath` 存储的是相对路径(如 `api/payment-errors.md`)
- `readDocument()` 方法直接使用相对路径读取,未拼接 `knowledge_base` 前缀
- 导致找不到文件
## 修复内容
### 1. 添加 knowledge.base-path 配置
```java
@Value("${knowledge.base-path:knowledge_base}")
private String knowledgeBasePath;
```
**默认值**:`knowledge_base`(当前工作目录下)
### 2. 修复 readDocument 方法
```java
public String readDocument(String filePath, int maxChars) {
// 拼接完整路径:knowledge_base + 相对路径
Path fullPath = Paths.get(knowledgeBasePath, filePath);
String content = Files.readString(fullPath);
// ...
}
```
**修复前**:
```
读取: api/payment-errors.md
实际路径: <当前目录>/api/payment-errors.md ❌
```
**修复后**:
```
读取: api/payment-errors.md
实际路径: knowledge_base/api/payment-errors.md ✅
```
## 数据流
```
L1 语义检索
↓
返回 KnowledgeEntry
filePath = "api/payment-errors.md"
↓
readDocument("api/payment-errors.md", 2000)
↓
拼接路径: Paths.get("knowledge_base", "api/payment-errors.md")
↓
完整路径: "knowledge_base/api/payment-errors.md"
↓
Files.readString(fullPath)
↓
返回文档内容 ✅
```
## 配置
在 `application.yml` 中可以自定义路径:
```yaml
knowledge:
base-path: ./knowledge_base # 默认值
```
或者绝对路径:
```yaml
knowledge:
base-path: /data/knowledge_base
```
## 验证
```bash
# 1. 启动应用
mvn spring-boot:run
# 2. 提问触发 L1
你:支付为什么会失败?
# 3. Agent 应该:
# - lookup_knowledge("支付为什么会失败")
# - L0 失败 → L1 语义检索
# - 找到 api/payment-errors.md
# - 读取文件内容成功 ✅
# - 返回文档内容给用户
```
## 相关代码路径
- `KnowledgeIndexService.readDocument()` - 文件读取
- `KnowledgeEntry.filePath` - 存储相对路径
- `LookupKnowledgeTool` - 调用 readDocument
|
||
|
|
c3a232540a |
refactor(phase1): 完成包名重构 (org.example → com.superbiz.agent)
Task 4.1: 包名统一重构 - 重命名 41 个 Java 文件的包名 - 更新所有 import 语句 - 恢复枚举类(FaultCategory、DiagnosisStatus、SourceType) - 更新测试类的 import 重构范围: - domain/entity: 3 个实体类 - domain/model: 2 个数据类 - domain/enums: 3 个枚举类 - repository: 3 个接口 - service/session: 2 个类(接口 + 实现) - config: 9 个配置类 - controller: 2 个控制器 - agent/tool: 4 个工具类 - client: 1 个客户端 - Main.java: 主类 验证结果: - 编译成功,无错误 - 所有测试通过 (27/27) - ApiDocumentRepositoryTest: 7/7 ✅ - CaseLibraryRepositoryTest: 6/6 ✅ - DiagnosisRecordRepositoryTest: 6/6 ✅ - RedisSessionManagerTest: 8/8 ✅ Progress: 21/33 tasks completed (64%) |