zhuyongxin
da18fdf4e1
docs(harness): add agent domain learning note and mark all nine domains complete
...
- Add agent note: factory assembly, dual interceptors (model budget/token audit,
tool five gates), use case loop shell, controlled stop recovery, dual-view projection
- Mark agent domain complete in roadmap (all nine domains done)
2026-08-10 10:12:04 +08:00
zhuyongxin
074d1aa5a9
docs(harness): add MySQL sandbox learning note and mark tool domain complete
...
- Add MySQL sandbox note: three defense layers (semantic/connection/output),
fail-closed validation, allowlist, parameterization, cancellation, redaction
- Mark tool domain complete in roadmap; next: guard
2026-08-06 17:46:53 +08:00
zhuyongxin
f26d395650
docs(harness): annotate RAG backend core classes and add retrieval learning note
...
- Annotate LookupKnowledgeTool, KnowledgeEvidencePostProcessor, RrfFusion, KnowledgeDocumentRetriever
- Add RAG retrieval learning note: L0 navigation, multi-recall + RRF, qualityScore,
degradation, contract semantics, validation (audit + offline eval), discussion insights
2026-08-05 18:37:50 +08:00
zhuyongxin
ff0752a16c
docs(harness): annotate tool domain classes and add tool chain learning notes
...
- Annotate 43 tool domain classes (contract/projection/boundary/store/adapter/mysql)
- Add tool registration and execution chain learning note
- Add tool call chain runtime journey note (model decision to observation)
2026-08-04 18:37:17 +08:00
zhuyongxin
7844bcea40
docs(harness): annotate progress core classes and add code-level learning notes
...
- Annotate DiagnosisProgressTracker, HarnessToolInterceptor, DiagnosisProgressProjector,
DiagnosisReleaseUseCase, ToolBoundary, ToolBoundaryResult, CanonicalToolInvocation
- Add progress code learning note: interceptor gates, tracker state machine,
canonical lifecycle, execution gate, projection/release pipeline
- Update learning roadmap: progress marked as deeply learned, next is tool domain
2026-08-03 18:37:23 +08:00
zhuyongxin
5b2fb985d9
docs(harness): annotate entry orchestration, enums, and exception recovery paths
2026-07-30 19:08:03 +08:00
zhuyongxin
b39a625e5b
docs(mvp): add remaining harness audit guides and engineering index
2026-07-30 19:04:59 +08:00
zhuyongxin
e9f1c48d34
docs(harness): add loop inner/outer exception handling guide
2026-07-30 18:55:43 +08:00
zhuyongxin
3a7eee8af4
docs(mvp): add harness design and progressive guides
2026-07-29 19:04:44 +08:00
zhuyongxin
584639fa2a
docs(mvp): move engineering notes under mvp/engineering
...
Relocate RAG and diagnosis decision/E2E writeups from docs/ root into
mvp/engineering so architecture, issues, and engineering narrative stay
together. Update indexes and cross-links; leave docs/learning as legacy.
2026-07-29 10:49:45 +08:00
zhuyongxin
bdac35567c
docs(issues): add ISS-017 L0 filter fallback hardening
...
Track L0 hard-filter + unfiltered retry brittleness. Keep current
behavior; prefer A+C later and defer soft-constraint redesign (D).
2026-07-29 10:48:47 +08:00
zhuyongxin
7ae9707a3b
feat(harness,rag): dual LLM audit fields, run conclusion, and hybrid quality
...
Persist provider reasoning and assistant text separately on agent_reasoning_audit
(DeepSeekAssistantMessage path), extract diagnosis_run.conclusion, enrich RAG
tool audit (step_id/query/qualityScore), gate empty mysql tools, drop devtools,
and align MVP docs after live E2E verification.
2026-07-28 19:43:13 +08:00
zhuyongxin
2f40536248
docs(mvp): document dense+BM25 hybrid knowledge retrieval
...
Add current RAG architecture covering MilvusClientV2 hybrid search,
chunk evidence identity, rebuild ops, and update the MVP architecture
index and system overview links.
2026-07-28 09:56:08 +08:00
zhuyongxin
729cd3544a
chore(rag): remove obsolete PowerShell rebuild script
2026-07-28 09:50:15 +08:00
zhuyongxin
1e532fb851
chore(rag): rebuild script in Python and use biz collection
...
Switch default knowledge collection back to biz (drop+recreate on rebuild),
replace PowerShell rebuild runner with Python, skip README.md imports, and
merge duplicate rag keys in application.yml.
2026-07-28 09:49:43 +08:00
zhuyongxin
38f781b157
feat(harness): complete protocol repair stop and archive ISS-016
...
Add repairable INVALID_PROGRESS_PROTOCOL observations, independent
PROGRESS_PROTOCOL_VIOLATED saturation, and controlled release paths.
Archive the OpenSpec change after syncing main specs and devflow.
2026-07-27 19:10:07 +08:00
zhuyongxin
5c369f3b6c
feat(rag): add hybrid knowledge rebuild API and script
...
Add confirm-gated rebuild-hybrid endpoint that drops biz_hybrid, clears
api_document and L0, then force-imports knowledge_base markdown into the
dense+BM25 store. Include PowerShell runner and ops README.
2026-07-27 18:55:00 +08:00
zhuyongxin
f035538531
feat(rag): dense+BM25 hybrid on MilvusClientV2, drop SDK path
...
Replace legacy MilvusServiceClient knowledge search/write with a single
MilvusClientV2 hybrid store (BM25 function + dense ANN + RRFRanker).
Use collection biz_hybrid and require knowledge reindex.
2026-07-27 18:49:20 +08:00
zhuyongxin
376ad0c241
feat(rag): hybrid multi-path search with RRF fusion
...
Add configurable hybrid mode on KnowledgeSearchPort that fuses dense
unfiltered, dense filtered, and lexical ranks via RRF while preserving
dense-compatible threshold scores. Archives Delivery 2 OpenSpec change.
2026-07-27 18:33:31 +08:00
zhuyongxin
ac1f831903
feat(rag): chunk evidence identity, dedup, and search port
...
Preserve same-document multi-chunk evidence with evidenceKey identity,
per-document caps, retrieve-k/return-n split, and a dense KnowledgeSearchPort.
Archives Delivery 1 OpenSpec change as the foundation for hybrid retrieval.
2026-07-27 18:26:15 +08:00
zhuyongxin
99d4f6f216
docs(rag): add comments on knowledge retrieval pipeline
...
Document the lookup_knowledge flow from L0 hints through L1 retrieval,
post-processing, packing, and Agent projection so the boundaries and
current limitations are easier to follow.
2026-07-27 16:26:06 +08:00
zhuyongxin
de5a5b09d9
docs(interview): refresh materials for single-agent harness narrative
...
Archive pre-refactor interview notes and add current deep-dives on
architecture evolution, issue-derived stories, and evidence gates.
2026-07-24 18:14:49 +08:00
zhuyongxin
e47f2dead0
docs(mvp): align architecture and audit table design
2026-07-23 20:16:29 +08:00
zhuyongxin
49180abccf
docs(issue): archive legacy issues and add iss-015
2026-07-23 19:54:01 +08:00
zhuyongxin
529f4ff43b
docs(demo): add successful diagnosis request
2026-07-23 19:06:37 +08:00
zhuyongxin
75fa154a0a
chore(agent): add shared skills and GitNexus guidance
2026-07-23 19:05:22 +08:00
zhuyongxin
8300435a63
docs(issue): add iss-014 handoff
2026-07-23 18:03:14 +08:00
zhuyongxin
e20249c5d9
feat(harness): improve trace fallback and reasoning audit
2026-07-23 17:52:01 +08:00
zhuyongxin
8fbc443f76
docs(issue): close iss-014 stage status
2026-07-22 18:08:29 +08:00
zhuyongxin
8ee7cc0b70
refactor(harness): remove legacy agent architecture
2026-07-22 18:02:01 +08:00
zhuyongxin
bc36248cd8
feat(chat): cut over to single SSE endpoint
2026-07-22 10:01:12 +08:00
zhuyongxin
f8809cb7dd
feat(harness): add chat application use case
2026-07-22 00:57:39 +08:00
zhuyongxin
ee0949d464
feat(harness): add evidence and semantic guards
2026-07-21 23:50:51 +08:00
zhuyongxin
2362665519
feat(harness): add single diagnosis react agent
2026-07-21 22:29:49 +08:00
zhuyongxin
85029d96a7
feat(harness): add readonly mysql tool
2026-07-21 21:24:42 +08:00
zhuyongxin
3e602781d6
feat(harness): add rag and log projections
2026-07-21 20:12:18 +08:00
zhuyongxin
0dbdd7d8d3
feat(harness): add canonical tool invocation boundary
2026-07-21 19:36:25 +08:00
zhuyongxin
6b74990f86
feat(harness): add run context and retry core
2026-07-21 18:36:19 +08:00
zhuyongxin
4274f3350b
feat(harness): freeze aci tool contracts
2026-07-21 18:01:18 +08:00
zhuyongxin
58c39107c5
refactor(harness): freeze single-agent contracts
2026-07-21 17:33:25 +08:00
zhuyongxin
30d3296043
docs(mvp): archive session run trace issue
2026-07-11 16:46:20 +08:00
zhuyongxin
3578709896
docs(openspec): archive session run isolation
2026-07-10 22:52:39 +08:00
zhuyongxin
f9df94377b
feat(trace): finish run-aware demo verification
2026-07-10 21:37:43 +08:00
zhuyongxin
78c1477198
feat(trace): isolate aiops runs
2026-07-10 20:57:46 +08:00
zhuyongxin
d928a1968a
feat(trace): bind feedback to runs
2026-07-10 20:29:56 +08:00
zhuyongxin
027aed1eeb
feat(trace): add run-scoped trace reads
2026-07-10 20:07:23 +08:00
zhuyongxin
26d5529280
feat(trace): isolate chat runs
2026-07-10 19:02:04 +08:00
zhuyongxin
6fdbd34bab
docs(openspec): tighten run isolation contract
2026-07-10 17:56:51 +08:00
zhuyongxin
52bf0302c6
feat(trace): add session run isolation schema
2026-07-10 17:47:56 +08:00
zhuyongxin
841437fa06
docs(mvp): organize mvp documentation
2026-07-09 13:40:08 +08:00
zhuyongxin
9c9a0024d4
feat(demo): add interview quality audit
2026-07-09 11:18:49 +08:00
zhuyongxin
a6c2d4459c
docs(openspec): propose interview demo quality audit
2026-07-09 10:34:33 +08:00
zhuyongxin
da45fa3fb0
docs(devflow): sort index by date
2026-07-09 10:24:35 +08:00
zhuyongxin
a6afbfaa9d
chore: add editorconfig
2026-07-07 21:08:51 +08:00
zhuyongxin
0ee27eb523
feat(trace): improve session workbench review
2026-07-07 19:06:18 +08:00
zhuyongxin
3b62a8940c
chore(openspec): archive executor evidence output contract
2026-07-07 19:04:24 +08:00
zhuyongxin
04eb50e2b4
feat(agent): add executor evidence output contract
2026-07-07 19:02:02 +08:00
zhuyongxin
7f2e47ca38
docs(mvp): record executor evidence loop design
2026-07-07 18:51:47 +08:00
zhuyongxin
64adb998cf
chore(rag): add eval knowledge base mirror
2026-07-06 21:48:21 +08:00
zhuyongxin
ed7efc58b7
feat(rag): close eval pipeline with live snapshots
2026-07-06 21:39:27 +08:00
zhuyongxin
cf3333d607
feat(rag): modularize knowledge retrieval pipeline
2026-07-06 17:06:05 +08:00
zhuyongxin
a375daead7
fix: clean up aiops mojibake text
2026-07-06 11:38:01 +08:00
zhuyongxin
a5a0e0c6be
Merge remote-tracking branch 'origin/refactor/mvp1.0' into refactor/mvp1.0
2026-07-06 10:54:45 +08:00
zhuyongxin
9e8e20b3b5
chore: update agent instructions
2026-07-06 10:47:54 +08:00
zhuyongxin
37083fc92a
chore: add agent skills
2026-07-06 10:18:10 +08:00
zhuyongxin
246c99b954
add query sql script
2026-07-04 20:14:57 +08:00
zhuyongxin
f01866c1a2
refactor: use sequential agent for chat workflow
2026-07-03 18:03:06 +08:00
zhuyongxin
6919092b83
feat: archive mvp demo trace acceptance
2026-07-03 16:25:00 +08:00
zhuyongxin
5b827fe90e
fix: avoid low-confidence supervisor retry by default
2026-07-03 15:32:09 +08:00
zhuyongxin
b0f288ae36
use supervisor agent for complex chat
2026-07-03 14:10:55 +08:00
zhuyongxin
1ff7f09d25
fix chat session traces and document paths
2026-07-03 13:53:16 +08:00
zhuyongxin
fd89d84fc0
docs: add MVP review issue
2026-07-03 11:21:24 +08:00
zhuyongxin
9050487307
feat: add chat verifier agent
2026-07-03 10:54:33 +08:00
zhuyongxin
4f5316d473
chore(cleanup): 清理临时文件和已归档标记
...
- .gitignore 添加 *.stackdump 和 NUL 规则
- 移除 bash.exe.stackdump 跟踪
- 移除已归档的 .archive-ready 旧标记
2026-07-01 18:32:12 +08:00
zhuyongxin
2a7164288f
chore(docs): 补充 ISS-001 架构设计文档到 mvp
...
- 新增 mvp/architecture/session-dedup-knowledge-map.md
- 更新 mvp/README.md 文档导航
- ISS-001 issue 关联架构文档
2026-07-01 18:28:19 +08:00
zhuyongxin
a1c896ebda
chore(docs): 归档 ISS-001 session-dedup-knowledge-map + ISS-002 mvp 文档
...
- 移动 session-dedup-knowledge-map OpenSpec 到 archive 目录
- 提交 ISS-001 遗留的 devflow 档案文件
- 更新 ISS-002 状态为已修复
- 新增 mvp/architecture/action-memory-relevance.md 设计文档
- 更新 mvp/README.md 文档导航
- 更新 devflow/index.md OpenSpec 链接指向 archive
2026-07-01 18:27:04 +08:00
zhuyongxin
e438df4355
feat(knowledge): Executor 行动记忆 + 归一化质量等级解决 ISS-002 重复检索
...
- RetrievedDocTracker 升级为域级+文档级双层记录(Map<sessionId, Map<domain, Set<filePath>>>)
- LookupKnowledgeTool 新增 Min-Max 归一化层(BGE-M3 L2 距离→[0,1] similarity)
- 三等级 relevanceLevel:PRECISE / HIGHLY_RELEVANT / REFERENCE + completenessHint 兜底信号
- LookupResult 新增 relevanceLevel、completenessHint、retrievedDomainsThisSession
- Executor prompt 重写:4 条检索约束 + 合法出口不查全不追责,重复检索才惩罚
- 入库可观测性:V010 迁移 + retrieval_details JSON 扩展
- 归档 executor-action-memory-relevance change
2026-07-01 18:24:41 +08:00
zhuyongxin
e4f37cb9e6
fix(knowledge): 修复循环依赖 + 归档 session-dedup-knowledge-map + 记录 ISS-002
...
- KnowledgeIndexService: 域级生成从 @PostConstruct 移到 @EventListener(ApplicationReadyEvent),解决 KnowledgeIndexService ↔ KnowledgeDomainService 循环依赖
- devflow 归档: evidence.md + acceptance.md(含运行验证结果)
- devflow/index.md: session-dedup-knowledge-map 状态改为 archived
- openspec .archive-ready 标记
- mvp/issues/ISS-002: Executor 无约束重复调用 lookup_knowledge
2026-07-01 14:26:59 +08:00
zhuyongxin
354ffc1947
feat(feedback): 补提交 feedback 相关源码(漏提交的新建文件)
2026-07-01 10:57:49 +08:00
zhuyongxin
bb44140901
feat(knowledge): 会话级去重 + 知识域地图注入 Planner 解决 ISS-001 重复检索
...
- RetrievedDocTracker: sessionId → Set<filePath> 会话级去重,LookupKnowledgeTool Step 5 过滤已检索文档
- KnowledgeDomainService: 域级聚合,LLM 生成 when_to_retrieve,构建 knowledge map YAML
- DocumentFieldEnricher: 上传时 LLM 补全 covers + whenToRetrieve(含同域文档排除上下文)
- KnowledgeDomain entity + V009 迁移: 域级元数据持久化,避免重启重复 LLM 调用
- ChatService: 注入 knowledge map 到 Planner prompt,会话结束时清理去重状态
- KnowledgeIndexService: 手写 JSON 解析替换为 Jackson ObjectMapper,启动时补建缺失域记录
- chat-planner-prompt: 新增知识库检索规则(按域 when_to_retrieve 判断,每域最多一次检索)
- doc-field-enricher-prompt / domain-summary-prompt: 外部化 LLM 提示词
2026-07-01 10:47:46 +08:00
zhuyongxin
2a796da490
feat(feedback): 置信度评分与用户反馈机制 & 归档 confidence-feedback change
2026-06-30 18:01:06 +08:00
zhuyongxin
3ffa5cc366
Merge branch 'emdash/afraid-geese-carry-h5718' into refactor/mvp1.0
...
# Conflicts:
# devflow/index.md
2026-06-26 17:36:59 +08:00
zhuyongxin
e3f20b1f06
chore: 归档 session-storage change
...
- 创建 devflow 项目档案(brief/evidence/decisions/acceptance)
- 更新 devflow/index.md 索引
- 移动 OpenSpec 到 archive
2026-06-26 17:33:56 +08:00
zhuyongxin
9b52afce07
docs: 合并 .docs/mvp 到根 mvp 目录并更新文档
...
- 删除 .docs/mvp 目录,内容合并到根目录 mvp/
- 更新 session-storage-design.md 实现变更记录
- 修复引用路径
2026-06-26 17:29:27 +08:00
zhuyongxin
a3abe3f7a2
refactor(session): 清理代码 & RunnableConfig 传 sessionId
...
- AgentLoggingHook 改为从 config.metadata 读取 sessionId(线程安全)
- 移除 AgentLoggingHook 调试用的 metadata 日志
- TokenTrackingChatModel 日志降为 debug
- SessionContextHolder 移除未使用的 setAgentName/getAgentName
- ChatService 清理无用 import
- 修复 stream 路径下 ThreadLocal NPE
2026-06-26 17:28:30 +08:00
zhuyongxin
0d9cce75f9
feat(session): 会话存储体系实现 & Chat多Agent路由
...
- 新增诊断会话(diagnosis_session/agent_step/tool_invocation)三表
- AgentLoggingHook 持久化 agent_step,记录决策链和耗时
- LookupKnowledgeTool 写入 tool_invocation,记录L0/L1检索质量
- TokenTrackingChatModel 捕获真实token用量
- Chat接口支持意图路由:简单问题单Agent,复杂问题多Agent(Planner+Executor)
- Prompt外置到 src/main/resources/prompts/
- 删除旧 diagnosis_record 表及相关文件
- 新增SessionContextHolder(ThreadLocal传递sessionId)
- QuestionComplexity 复杂度判断工具
- 测试覆盖三张新表的Repository
2026-06-26 16:22:05 +08:00
zhuyongxin
a74ccea5be
feat(knowledge): breadcrumb分块上下文 & LookupKnowledgeTool日志优化
...
- DocumentChunk新增breadcrumb字段,分块时构建完整标题层级路径
- DocumentChunkService splitByHeadings维护标题层级栈算法
- VectorIndexService 将breadcrumb写入Milvus metadata
- LookupKnowledgeTool日志替换为结构化摘要,替代原始MD预览
- L0返回策略:唯一匹配用正文摘要,多匹配+L1有结果仅元数据(不读文件)
- 新增buildCompactSummary / buildMetadataOnlySummary方法
- 安装frontend-design skill
- 创建mvp/文档目录(架构设计+会话存储方案)
- 更新测试适配新逻辑
2026-06-26 13:56:10 +08:00
zhuyongxin
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): 增强模型文本提取,支持多种方式并输出调试信息
934d8ee feat(observability): 在 Hook 中输出模型返回的文本内容
7c8758d refactor(observability): 简化 ChatService 日志,避免与 Hook 重复
```
2026-06-25 17:41:37 +08:00
zhuyongxin
934d8eee29
feat(observability): 在 Hook 中输出模型返回的文本内容
...
## 改动内容
### 增强 AgentLoggingHook.afterModel()
在模型调用完成后,提取并输出模型返回的文本内容:
```java
@Override
public AgentCommand afterModel(List<Message> messages, RunnableConfig config) {
// 查找最后一条 AssistantMessage
AssistantMessage lastAssistant = ...;
// 提取文本内容
String textContent = extractTextContent(lastAssistant);
log.info("*** [Agent 思考] 模型返回文本: {}", textContent);
// 检查工具调用
if (hasToolCalls) {
log.info("*** [Agent 思考] 模型决定调用 N 个工具");
} else {
log.info("*** [Agent 思考] 这是最终答案");
}
}
```
---
### extractTextContent() 实现
通过反射提取 AssistantMessage 的文本内容:
```java
private String extractTextContent(AssistantMessage message) {
try {
// 尝试获取 text 或 content 字段
Field textField = message.getClass().getDeclaredField("text");
textField.setAccessible(true);
Object value = textField.get(message);
return value != null ? value.toString() : null;
} catch (NoSuchFieldException e) {
// 尝试 content 字段
try {
Field contentField = message.getClass().getDeclaredField("content");
// ...
} catch (NoSuchFieldException ex) {
// 字段不存在,返回 null
}
}
}
```
**为什么用反射?**
- Spring AI 的 `AssistantMessage` 没有公开的 `getText()` 或 `getContent()` 方法
- 不同版本可能使用 `text` 或 `content` 字段
- 反射可以兼容不同实现
---
## 日志输出示例
### 第 1 轮:模型决定调用工具
```
========================================
*** [Agent 思考] 第 1 轮思考完成
*** [Agent 思考] 模型返回文本: 我需要查询知识库来了解支付失败的原因
*** [Agent 思考] 模型决定调用 1 个工具:
- 工具: lookup_knowledge, 参数: {"query":"支付失败原因"}
*** [Agent 思考] 等待工具执行结果...
========================================
```
---
### 第 2 轮:模型返回最终答案
```
========================================
*** [Agent 思考] 第 2 轮思考完成
*** [Agent 思考] 模型返回文本: 根据知识库的记录,支付失败的主要原因包括:
1. ERR_TIMEOUT - 支付网关响应超时,通常是网络问题或第三方服务不稳定
2. ERR_INVALID_SIGNATURE - 签名验证失败,检查密钥配置
3. ERR_INSUFFICIENT_BALANCE - 账户余额不足
... (已截断,总长度: 1234)
*** [Agent 思考] 模型决定不调用工具
*** [Agent 思考] 这是最终答案,准备返回给用户
========================================
```
---
## 关键观测点
| 轮次 | 模型输出内容 | 决策 |
|------|-------------|------|
| **第 1 轮** | 模型的推理过程(通常很短) | 决定调用工具 |
| **第 2 轮** | 模型的最终答案(完整回复) | 不调用工具 |
---
## 内容截断策略
- 长度 ≤ 500:完整输出
- 长度 > 500:截断前 500 字符,显示总长度
```
模型返回文本: 根据知识库的记录,支付失败的主要原因包括...
(前 500 字符)
... (已截断,总长度: 1234)
```
---
## 异常处理
如果反射失败(字段不存在或访问被拒绝):
```java
catch (Exception e) {
log.debug("无法提取 AssistantMessage 文本内容: {}", e.getMessage());
return null;
}
```
日志输出:
```
*** [Agent 思考] 模型返回文本: (无法提取)
```
不会中断程序,只是跳过文本输出。
---
## 完整的思考流程日志
```
📝 用户问题: 支付为什么会失败?
*** [Agent 思考] 第 1 轮思考开始
*** [Agent 思考] 准备调用模型...
*** [Agent 思考] 第 1 轮思考完成
*** [Agent 思考] 模型返回文本: 我需要查询知识库
*** [Agent 思考] 模型决定调用 1 个工具:
- 工具: lookup_knowledge, 参数: {"query":"支付失败"}
>>> [工具调用] lookup_knowledge
<<< [工具返回] lookup_knowledge
<<< 结果: found=true
*** [Agent 思考] 第 2 轮思考开始
*** [Agent 思考] 准备调用模型...
*** [Agent 思考] 第 2 轮思考完成
*** [Agent 思考] 模型返回文本: 根据知识库的记录,支付失败...
*** [Agent 思考] 模型决定不调用工具
*** [Agent 思考] 这是最终答案,准备返回给用户
⏱️ 总耗时: 1523 ms
📏 输出长度: 456 字符
```
---
## 提交历史
```
当前 feat(observability): 在 Hook 中输出模型返回的文本内容
7c8758d refactor(observability): 简化 ChatService 日志,避免与 Hook 重复
b3ea6e2 feat(observability): 添加 Agent 思考过程日志 Hook
```
2026-06-25 17:27:48 +08:00
zhuyongxin
7c8758d7fa
refactor(observability): 简化 ChatService 日志,避免与 Hook 重复
...
## 改动内容
### 修改前:重复的日志
```java
// ChatService.executeChat()
logger.info("========== Agent 执行开始 ==========");
logger.info("📝 用户问题: {}", question);
logger.info("🚀 执行 ReactAgent.call() - 自动处理工具调用");
// ... Agent 执行 ...
logger.info("========== Agent 执行完成 ==========");
logger.info("⏱️ 执行耗时: {} ms", duration);
logger.info("📤 最终输出内容:");
// 打印完整答案
```
**问题**:
- 与 `AgentLoggingHook` 的日志重复
- 日志过于冗长
- Hook 已经覆盖了 Agent 的思考过程
---
### 修改后:精简的日志
```java
// ChatService.executeChat() - 只保留最外层框架
logger.info("========================================");
logger.info("📝 用户问题: {}", question);
// ... Agent 执行(Hook 负责内部日志)...
logger.info("⏱️ 总耗时: {} ms", duration);
logger.info("📏 输出长度: {} 字符", answer.length());
logger.info("========================================");
```
**优势**:
- 职责清晰:ChatService 只记录最外层信息
- 避免重复:思考过程由 Hook 负责
- 更简洁:减少噪音日志
---
## 日志分层
| 层级 | 负责类 | 职责 |
|------|--------|------|
| **外层框架** | `ChatService` | 用户问题、总耗时、输出长度 |
| **思考过程** | `AgentLoggingHook` | 每轮思考、模型决策、消息流转 |
| **工具执行** | `LookupKnowledgeTool` | L0/L1 检索、工具参数/返回 |
---
## 日志输出对比
### 修改前(重复冗长)
```
========================================
========== Agent 执行开始 ========== ← ChatService
========================================
📝 用户问题: 支付为什么会失败? ← ChatService
----------------------------------------
🚀 执行 ReactAgent.call() ← ChatService
*** [Agent 思考] 第 1 轮思考开始 ← Hook
...
*** [Agent 思考] 第 1 轮思考完成 ← Hook
>>> [工具调用] lookup_knowledge ← Tool
...
<<< [工具返回] lookup_knowledge ← Tool
*** [Agent 思考] 第 2 轮思考开始 ← Hook
...
*** [Agent 思考] 第 2 轮思考完成 ← Hook
========================================
========== Agent 执行完成 ========== ← ChatService (重复)
========================================
⏱️ 执行耗时: 1523 ms ← ChatService
📤 最终输出内容: ← ChatService
根据知识库的记录...(完整答案) ← ChatService (太长)
========================================
```
---
### 修改后(清晰简洁)
```
========================================
📝 用户问题: 支付为什么会失败? ← ChatService (简洁)
*** [Agent 思考] 第 1 轮思考开始 ← Hook
...
*** [Agent 思考] 第 1 轮思考完成 ← Hook
>>> [工具调用] lookup_knowledge ← Tool
...
<<< [工具返回] lookup_knowledge ← Tool
*** [Agent 思考] 第 2 轮思考开始 ← Hook
...
*** [Agent 思考] 第 2 轮思考完成 ← Hook
*** [Agent 思考] 这是最终答案 ← Hook (已说明)
⏱️ 总耗时: 1523 ms ← ChatService (简洁)
📏 输出长度: 456 字符 ← ChatService (摘要)
========================================
```
**改进**:
- ✅ 去掉重复的"开始/完成"标记
- ✅ 不再打印完整答案(通过 HTTP 响应已返回)
- ✅ Hook 已说明"这是最终答案"
- ✅ 日志更紧凑,信噪比更高
---
## 设计原则
### 1. 单一职责
- **ChatService**:顶层编排,只记录执行框架
- **Hook**:Agent 内部状态,记录思考过程
- **Tool**:工具执行细节,记录检索过程
### 2. 避免重复
- 不在多处打印相同信息
- Hook 已说明"最终答案",ChatService 不再重复
### 3. 信息密度
- 关键信息:保留(用户问题、耗时、长度)
- 冗余信息:删除(重复标题、完整答案)
---
## 查看日志
```bash
# 完整日志
tail -f logs/application.log
# 只看框架
tail -f logs/application.log | grep "📝 \|⏱️ \|📏 "
# 只看思考过程
tail -f logs/application.log | grep "Agent 思考"
# 只看工具调用
tail -f logs/application.log | grep "工具调用\|工具返回"
```
---
## 提交历史
```
当前 refactor(observability): 简化 ChatService 日志,避免与 Hook 重复
b3ea6e2 feat(observability): 添加 Agent 思考过程日志 Hook
8890cd2 feat(observability): 增强 Agent 和工具调用的可观测日志
```
2026-06-25 17:24:59 +08:00
zhuyongxin
b3ea6e202d
feat(observability): 添加 Agent 思考过程日志 Hook
...
## 改动内容
### 1. 创建 AgentLoggingHook
基于 Spring AI Alibaba 的 `MessagesModelHook` 实现:
```java
@HookPositions({HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL})
public class AgentLoggingHook extends MessagesModelHook {
// 在模型调用前
public AgentCommand beforeModel(List<Message> messages, RunnableConfig config)
// 在模型调用后
public AgentCommand afterModel(List<Message> messages, RunnableConfig config)
}
```
---
### 2. 集成到 ReactAgent
在 `ChatService.createReactAgent()` 中添加 Hook:
```java
ReactAgent.builder()
.name("intelligent_assistant")
.model(chatModel)
.hooks(new AgentLoggingHook()) // ✅ 添加日志 Hook
.build();
```
---
## 日志输出示例
### 完整的 Agent 思考流程
```
========================================
========== Agent 执行开始 ==========
========================================
📝 用户问题: 支付为什么会失败?
----------------------------------------
🚀 执行 ReactAgent.call() - 自动处理工具调用
========================================
*** [Agent 思考] 第 1 轮思考开始
*** [Agent 思考] 当前消息数量: 2
*** [Agent 思考] 最近 2 条消息:
[1] 角色: User(用户), 类型: UserMessage
[2] 角色: User(用户), 类型: UserMessage
*** [Agent 思考] 准备调用模型...
========================================
========================================
*** [Agent 思考] 第 1 轮思考完成
*** [Agent 思考] 模型输出: <AssistantMessage>
*** [Agent 思考] 模型决定调用 1 个工具:
- 工具: lookup_knowledge, 参数: {"query":"支付失败原因"}
*** [Agent 思考] 等待工具执行结果...
========================================
========================================
>>> [工具调用] lookup_knowledge
>>> 参数: query = "支付失败原因"
>>> RequestId: a3b4c5d6
----------------------------------------
[L0 精确匹配] 完成: matches=0, time=2ms
[L1 语义检索] L0非唯一匹配,触发L1语义检索...
[L1 语义检索] 完成: matches=1, time=245ms
<<< [工具返回] lookup_knowledge
<<< 结果: found=true, matchType=semantic_L1, confidence=medium
========================================
========================================
*** [Agent 思考] 第 2 轮思考开始
*** [Agent 思考] 当前消息数量: 4
*** [Agent 思考] 最近 3 条消息:
[1] 角色: User(用户), 类型: UserMessage
[2] 角色: Assistant(模型), 类型: AssistantMessage
[3] 角色: Tool(工具返回), 类型: ToolResponseMessage
*** [Agent 思考] 准备调用模型...
========================================
========================================
*** [Agent 思考] 第 2 轮思考完成
*** [Agent 思考] 模型输出: <AssistantMessage>
*** [Agent 思考] 模型决定不调用工具
*** [Agent 思考] 这是最终答案,准备返回给用户
========================================
========================================
========== Agent 执行完成 ==========
========================================
⏱️ 执行耗时: 1523 ms
📏 最终输出长度: 456 字符
📤 最终输出内容:
根据知识库的记录,支付失败的主要原因包括...
========================================
```
---
## 核心观测点
| 阶段 | 日志标识 | 信息 |
|------|---------|------|
| **Agent 开始** | `Agent 执行开始` | 用户问题 |
| **思考开始** | `第 N 轮思考开始` | 消息数量、最近消息 |
| **思考完成** | `第 N 轮思考完成` | 模型决策(调用工具 or 返回答案) |
| **工具调用** | `工具调用 lookup_knowledge` | 工具名称、参数 |
| **工具返回** | `工具返回 lookup_knowledge` | 结果摘要、耗时 |
| **Agent 完成** | `Agent 执行完成` | 总耗时、最终输出 |
---
## Hook 机制说明
### MessagesModelHook
- **触发时机**:
- `BEFORE_MODEL`:模型调用前
- `AFTER_MODEL`:模型调用后
- **消息流转**:
```
用户问题
↓
[第1轮] beforeModel → 模型决定调用工具 → afterModel
↓
工具执行(lookup_knowledge)
↓
[第2轮] beforeModel → 模型生成最终答案 → afterModel
↓
返回给用户
```
- **轮次统计**:
- 每次调用模型计为一轮
- 通常需要 2 轮:第 1 轮调用工具,第 2 轮生成答案
---
## 技术细节
### 1. 为什么不用 ModelHook?
`ModelHook` 需要处理 `OverAllState`,更复杂。`MessagesModelHook` 直接操作消息列表,更简单。
### 2. 为什么跳过消息内容?
Spring AI 的 `Message` 接口没有统一的 `getContent()` 方法,不同实现类有不同的访问方式。工具调用的详细内容已在工具层日志体现。
### 3. 消息类型识别
```java
UserMessage → "User(用户)"
AssistantMessage → "Assistant(模型)"
ToolResponseMessage → "Tool(工具返回)"
```
---
## 验证方法
```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
# 4. 过滤关键日志
tail -f logs/application.log | grep -E "Agent|思考|工具|输出"
```
---
## 提交历史
```
当前 feat(observability): 添加 Agent 思考过程日志 Hook
8890cd2 feat(observability): 增强 Agent 和工具调用的可观测日志
f4f0c63 fix(knowledge): 修复 readDocument 文件路径拼接问题
```
2026-06-25 17:02:13 +08:00
zhuyongxin
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|工具|输出"
```
2026-06-25 16:16:46 +08:00
zhuyongxin
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
2026-06-25 15:56:49 +08:00
zhuyongxin
3fd2e103d2
add doc
2026-06-25 15:13:49 +08:00
zhuyongxin
92ab8d27ee
feat(doc-management): 添加文档管理前端页面
...
- 新增 documents.html 文档管理页面
- 文档列表展示(支持筛选和分页)
- 文档上传功能(带元信息表单)
- 文档详情查看(右侧滑出面板)
- 文档删除功能
- 状态统计卡片(待处理/处理中/已索引/失败)
- 新增 documents.css 和 documents.js
- 纯静态页面实现,无需额外框架
- 与现有 index.html 保持一致的设计风格
- 修复列表滚动问题(覆盖 body overflow 设置)
- 修复时间字段显示 NaN 问题(增加 Invalid Date 检查)
- 在 index.html 侧边栏添加文档管理入口
- 归档项目文档到 devflow 和 openspec
- devflow/projects/2026-06-25-doc-management-ui/
- openspec/changes/doc-management-ui/
- 更新 devflow/index.md
2026-06-25 15:05:48 +08:00
zhuyongxin
f02a1389c8
refactor(knowledge): KnowledgeIndexService 从数据库加载索引
...
## 改动内容
### 修改前:从文件系统扫描
```java
@Value("${knowledge.base-path}")
private String knowledgeBasePath;
@Autowired
private FrontmatterParser frontmatterParser;
@PostConstruct
public void loadIndex() {
// 1. 扫描 knowledge_base 目录
// 2. 读取每个 .md 文件
// 3. 解析 frontmatter
// 4. 构建内存索引
}
```
**问题**:
- 依赖文件系统,无法利用数据库已有数据
- 启动时需要重新扫描和解析所有文件
- 文件和数据库可能不一致
---
### 修改后:从数据库加载
```java
@Autowired
private ApiDocumentRepository apiDocumentRepository;
@PostConstruct
public void loadIndex() {
// 1. 从数据库读取所有文档
List<ApiDocument> documents = apiDocumentRepository.findAll();
// 2. 解析 metadata JSON
// 3. 构建内存索引
}
```
**优势**:
- ✅ 数据源统一:数据库是唯一真实数据源
- ✅ 启动更快:无需重新扫描文件和解析 frontmatter
- ✅ 数据一致:L0 索引与数据库完全同步
- ✅ 支持动态更新:初始化接口更新数据库后,L0 索引也会更新
---
## 核心方法
### 1. parseDocumentToEntry
从 `ApiDocument` 转换为 `KnowledgeEntry`:
```java
private KnowledgeEntry parseDocumentToEntry(ApiDocument doc) {
String metadata = doc.getMetadata();
String title = extractJsonValue(metadata, "title");
String summary = extractJsonValue(metadata, "summary");
String category = extractJsonValue(metadata, "category");
List<String> keywords = extractJsonArray(metadata, "keywords");
return KnowledgeEntry.builder()
.filePath(doc.getFilePath())
.title(title)
.keywords(keywords)
.summary(summary)
.category(category)
.build();
}
```
### 2. 简单的 JSON 解析
```java
private String extractJsonValue(String json, String key) {
// 提取 "key":"value" 格式
}
private List<String> extractJsonArray(String json, String key) {
// 提取 "key":["v1","v2"] 格式
}
```
**注意**:使用简单的字符串解析,避免引入 JSON 库依赖
---
## 数据流
```
应用启动
↓
KnowledgeIndexService.loadIndex()
↓
apiDocumentRepository.findAll()
↓
读取所有 api_document 记录
↓
解析每条记录的 metadata JSON
↓
构建 KnowledgeEntry
↓
加入内存索引(CopyOnWriteArrayList)
↓
L0 索引就绪
```
---
## 启动日志
```
[INFO] 开始从数据库加载知识库索引
[INFO] 知识库索引加载完成,共 6 个文档
```
---
## 与初始化接口的配合
### 流程 1:首次启动(数据库为空)
```
1. 应用启动
2. KnowledgeIndexService.loadIndex() → 0 个文档
3. 调用 POST /api/knowledge/init
4. 写入数据库 + 调用 knowledgeIndexService.addToIndex()
5. L0 索引更新为 6 个文档
```
### 流程 2:重启应用(数据库有数据)
```
1. 应用启动
2. KnowledgeIndexService.loadIndex() → 从数据库加载 6 个文档
3. L0 索引已就绪,无需再调用初始化接口
```
---
## 兼容性
### metadata JSON 示例
```json
{
"title": "支付网关错误码定义",
"summary": "记录了支付网关所有核心错误码的含义及排查方向",
"category": "api",
"keywords": ["ERR_TIMEOUT","超时","支付网关"]
}
```
### 字段映射
| metadata | KnowledgeEntry | 说明 |
|----------|---------------|------|
| title | title | 文档标题 |
| summary | summary | 文档摘要 |
| category | category | 文档分类 |
| keywords | keywords | 关键词列表 |
---
## 删除的代码
- ❌ `@Value("${knowledge.base-path}")`:不再需要文件路径配置
- ❌ `FrontmatterParser` 依赖:不再扫描文件
- ❌ `indexFile()` 方法:不再读取文件
- ❌ `extractCategoryFromPath()` 方法:从 metadata 获取
---
## 验证
```bash
# 1. 清空数据库
# DELETE FROM api_document;
# 2. 启动应用
mvn spring-boot:run
# 3. 查看日志
# [INFO] 知识库索引加载完成,共 0 个文档
# 4. 初始化
curl -X POST http://localhost:9900/api/knowledge/init
# 5. 重启应用
# [INFO] 知识库索引加载完成,共 6 个文档
```
2026-06-25 15:05:29 +08:00
zhuyongxin
91931363d4
refactor(knowledge): 重构 FaultCategory 枚举为文档分类
...
## 改动内容
### 1. 重构 FaultCategory 枚举
**修改前**:故障类别枚举
```java
EXTERNAL_API("外部接口调用失败"),
INTERNAL_ERROR("系统内部错误"),
DATABASE("数据库问题"),
...
```
**修改后**:文档分类枚举
```java
API("API 接口文档"),
INFRASTRUCTURE("基础设施文档"),
DOMAIN("领域业务文档"),
TROUBLESHOOTING("故障排查文档"),
GENERAL("通用文档");
```
### 2. 新增 fromString 映射方法
```java
public static FaultCategory fromString(String category) {
switch (category.toLowerCase()) {
case "api": return API;
case "infrastructure": return INFRASTRUCTURE;
case "domain": return DOMAIN;
case "troubleshooting": return TROUBLESHOOTING;
default: return GENERAL;
}
}
```
### 3. 更新所有引用
- `ApiDocument`: 默认值 EXTERNAL_API → GENERAL
- `DocumentManagementService`: 默认值 EXTERNAL_API → GENERAL
- `KnowledgeBaseInitService`: 使用 FaultCategory.fromString() 映射
### 4. 字段映射关系
| Frontmatter | 数据库字段 | 枚举值 | 说明 |
|-------------|-----------|--------|------|
| `category: "api"` | `fault_category` | API | API 接口文档 |
| `category: "infrastructure"` | `fault_category` | INFRASTRUCTURE | 基础设施文档 |
| `category: "domain"` | `fault_category` | DOMAIN | 领域业务文档 |
| `category: "troubleshooting"` | `fault_category` | TROUBLESHOOTING | 故障排查文档 |
| `category: "xxx"` | `fault_category` | GENERAL | 默认/其他 |
## 数据库影响
**不需要修改数据库结构**:
- `fault_category` 字段仍然是 VARCHAR(32)
- 只是存储的值从 `EXTERNAL_API` 变为 `API`, `INFRASTRUCTURE` 等
**已存在的数据**:
- 旧数据中的 `EXTERNAL_API` 仍可以正常读取(枚举向后兼容)
- 新导入的文档会使用新的枚举值
## 验证
```bash
# 1. 重新初始化
curl -X POST http://localhost:9900/api/knowledge/init?force=true
# 2. 查询统计
curl http://localhost:9900/api/knowledge/stats
# 3. 响应
{
"categories": {
"API": 1,
"INFRASTRUCTURE": 3,
"DOMAIN": 1,
"TROUBLESHOOTING": 1
}
}
```
## 数据库查询
```sql
SELECT fault_category, COUNT(*)
FROM api_document
GROUP BY fault_category;
-- 结果
API | 1
INFRASTRUCTURE | 3
DOMAIN | 1
TROUBLESHOOTING | 1
```
2026-06-25 14:23:57 +08:00
zhuyongxin
4e3502a51b
fix(knowledge): 将 category 存储到 fault_source 字段
...
## 问题
数据库表 api_document 没有独立的 category 字段,导致 frontmatter 的 category 信息无法正确存储。
### 表结构分析
```sql
CREATE TABLE api_document (
fault_category VARCHAR(32) DEFAULT 'EXTERNAL_API', -- 固定枚举,不合适存储自定义分类
fault_source VARCHAR(128), -- 可以存储自定义分类
...
)
```
## 解决方案
使用 `fault_source` 字段存储 frontmatter 的 category:
```java
// 保存时
document.setFaultSource(category); // api, infrastructure, domain, troubleshooting
// 统计时
Map<String, Long> categoryCount = apiDocumentRepository.findAll().stream()
.collect(Collectors.groupingBy(
doc -> doc.getFaultSource() != null ? doc.getFaultSource() : "general",
Collectors.counting()
));
```
## 字段映射关系
| Frontmatter | 数据库字段 | 示例值 |
|-------------|-----------|--------|
| `title` | `api_name` | "支付网关错误码定义" |
| `category` | `fault_source` | "api" / "infrastructure" |
| `keywords` | `metadata` (JSON) | ["ERR_TIMEOUT","超时"] |
| `summary` | `metadata` (JSON) | "记录了..." |
## 优势
1. **充分利用现有字段**:fault_source (VARCHAR 128) 足够存储分类
2. **避免枚举限制**:不受 FaultCategory 枚举约束
3. **查询方便**:直接通过 fault_source 字段查询和统计
4. **向后兼容**:metadata 中仍保留完整的 frontmatter 信息
## 验证
```bash
# 初始化
curl -X POST http://localhost:9900/api/knowledge/init
# 查询统计
curl http://localhost:9900/api/knowledge/stats
# 响应
{
"categories": {
"api": 1,
"infrastructure": 3,
"domain": 1,
"troubleshooting": 1
}
}
```
## 数据库查询
```sql
-- 按分类统计
SELECT fault_source, COUNT(*)
FROM api_document
GROUP BY fault_source;
-- 结果
api | 1
infrastructure | 3
domain | 1
troubleshooting | 1
```
2026-06-25 14:08:50 +08:00
zhuyongxin
b01f133efb
fix(knowledge): 修复状态字段和 indexed_at 时间戳设置
...
## 问题
1. **状态字段不正确**:
- 保存到数据库时直接设置 status="INDEXED"
- 实际上此时还未索引到 Milvus
- 应该先设置为 "PENDING",索引成功后更新为 "INDEXED"
2. **indexed_at 时间戳过早**:
- 在保存数据库时就设置了 indexed_at
- 应该在 Milvus 索引成功后才设置
3. **fault_category 字段说明**:
- fault_category 是枚举类型(EXTERNAL_API, DATABASE, CACHE 等)
- frontmatter 的 category 是自定义分类(api, infrastructure, domain 等)
- 两者不匹配,保持 fault_category 默认值
- 真实的分类信息保存在 metadata JSON 中
## 修复内容
### 1. 状态流转正确
```java
// 保存到数据库时
document.setStatus("PENDING"); // 初始状态
// Milvus 索引成功后
document.setStatus("INDEXED");
document.setChunkCount(chunks.size());
document.setIndexedAt(LocalDateTime.now()); // 此时才设置时间戳
// Milvus 索引失败后
document.setStatus("FAILED");
document.setErrorMessage(e.getMessage());
```
### 2. metadata 结构说明
```json
{
"title": "支付网关错误码定义",
"summary": "记录了支付网关所有核心错误码的含义及排查方向",
"category": "api", // 自定义分类,不是 fault_category
"keywords": ["ERR_TIMEOUT","超时","支付网关"]
}
```
### 3. 数据库字段含义
- `fault_category`:固定枚举(EXTERNAL_API, DATABASE 等),保持默认值
- `metadata.category`:frontmatter 自定义分类(api, infrastructure, domain 等)
- `status`:索引状态(PENDING → INDEXED / FAILED)
- `indexed_at`:索引完成时间(索引成功后设置)
## 验证
```bash
# 1. 启动应用(Milvus 可以不启动)
mvn spring-boot:run
# 2. 初始化
curl -X POST http://localhost:9900/api/knowledge/init
# 3. 检查数据库
# - Milvus 未启动:status = "FAILED", indexed_at = NULL
# - Milvus 已启动:status = "INDEXED", indexed_at = 实际时间
# - fault_category:始终为 "EXTERNAL_API"(默认值)
# - metadata:包含真实的 category 信息
```
2026-06-25 14:04:10 +08:00
zhuyongxin
3ed48e38cd
feat(knowledge): 完整实现知识库初始化 - 包含 Milvus 向量索引
...
## 核心改动
在上一版本基础上,补充完整的 Milvus (L1) 向量索引功能。
### 新增依赖注入
```java
@Autowired
private DocumentChunkService documentChunkService;
@Autowired
private VectorIndexService vectorIndexService;
@Autowired
private VectorEmbeddingService vectorEmbeddingService;
```
### 完整的数据流
```
knowledge_base/*.md
↓ 1. 扫描 & 解析 frontmatter
↓ 2. 保存到 MySQL (api_document)
↓ 3. 提取正文 & 文档分块
↓ 4. 生成向量并索引到 Milvus
↓ 5. 加入 L0 内存索引
完成 (L0 + L1 双层索引)
```
### 关键代码
```java
// 1. 提取正文(去除 frontmatter)
String body = extractBody(content);
// 2. 文档分块
List<DocumentChunk> chunks = documentChunkService.chunkDocument(body, relativePath);
// 3. 上传到 Milvus
vectorIndexService.indexDocumentChunks(document.getDocId(), chunks, category);
// 4. 更新状态
document.setStatus("INDEXED");
document.setChunkCount(chunks.size());
```
### 错误处理
- Milvus 索引失败时:
- 更新文档状态为 FAILED
- 记录错误信息到 error_message 字段
- 继续处理下一个文档(不中断整个流程)
### 响应示例
```json
{
"success": true,
"scanned": 6,
"inserted": 6,
"failed": 0,
"details": {
"api/payment-errors.md": "导入成功(L0+L1)"
}
}
```
### 数据库字段
新增:
- `chunk_count`:分块数量
- `error_message`:错误信息(失败时)
## 验证步骤
```bash
# 1. 启动应用(确保 Milvus 已运行)
mvn spring-boot:run
# 2. 初始化知识库
curl -X POST http://localhost:9900/api/knowledge/init
# 3. 验证结果
# - MySQL: 检查 api_document 表
# - Milvus: 检查 knowledge_base_collection
# - L0: 日志显示"知识库索引加载完成,共 6 个文档"
# 4. 测试 L1 语义检索
# lookup_knowledge("支付为什么会失败")
# 应该返回 semantic_L1 结果
```
## 文档更新
- 更新使用文档,删除"暂未实现 L1"的说明
- 添加 Milvus 数据结构说明
- 添加 Milvus 相关错误处理
2026-06-25 11:00:10 +08:00