Files
SuperBizAgent-java/mvp/engineering/harness/Harness 整体架构学习笔记-从装配到入口到记忆到知识库写入.md
T
zhuyongxin 473bb5d004 docs(mvp): update RAG docs for py-rag extraction
- rewrite RAG architecture doc for py-rag service contract mapping, ingest and rebuild ops
- refresh observability/trace doc for post-L0 single-attempt semantics
- add py-rag chain exploration note; mark superseded Milvus/L0 notes with status banners
- close ISS-017 (superseded by L0 sinking); mark knowledge_domain table orphaned
- refresh architecture/mvp/engineering indexes
2026-09-30 17:03:50 +08:00

229 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Harness 整体架构学习笔记:从装配到入口到记忆到知识库写入
> **现状说明(2026-09-29)**:RAG 模块抽离后,本文「知识库写入链路」章节描述的
> `KnowledgeIndexService` / Milvus 写入路径已删除(入库下沉 py-rag `documents:ingest`);
> 其余装配/入口/记忆章节仍现行。当前架构见
> [../../architecture/RAG知识检索架构.md](../../architecture/RAG知识检索架构.md)。
**更新日期**:2026-08-04
**主题**:整体架构五块补充(了解层面)——配置装配中心 / HTTP 入口层 / 会话系统与记忆体系 / 知识库写入链路
**配套**:九域主线笔记(core/retry/progress/tool/guard/release/agent/audit/contract 全 ✅)
## 1. 配置装配中心(整体怎么搭起来)
### 1.1 装配全景(Bean 拓扑)
```mermaid
flowchart LR
C["ChatHarnessProperties<br/>配置集中(yml)"] --> K["DiagnosisHarnessCore<br/>总闸门:超时/预算/重试/收敛"]
K --> B["ToolBoundary<br/>工具底座:canonical/门禁/投影"]
B --> A1["RagToolAdapter"]
B --> A2["QueryLogsToolAdapter"]
B --> A3["MysqlToolAdapter<br/>(可选装配)"]
A1 --> E["HarnessEvidenceTools<br/>组装注册"]
A2 --> E
A3 --> E
K --> G["GuardModelCall<br/>(守卫/修复/路由共用底座)"]
G --> SG["SemanticGuard"]
G --> ER["EvidenceRepair"]
G --> R["IntentRouter"]
E --> F["DiagnosisAgentFactory"]
F --> UC["DiagnosisAgentUseCase"]
UC --> D["DiagnosisChatExecutor"]
R --> D
R --> S["SystemChatExecutor"]
R --> KQ["KnowledgeQueryExecutor"]
D --> A["ChatApplicationUseCase<br/>应用入口"]
S --> A
KQ --> A
```
### 1.2 三层组织(话术版)
```text
① 总闸门(core):能花多少钱/跑多久/怎么重试/何时停——配置集中,改一处全局生效
② 工具底座:所有工具统一留痕(canonical)/拦截(ToolBoundary)/裁剪(投影)——行为整齐划一
③ 具体工具:RAG/Logs/MySQL 按需装配(没配数据源不装死工具)→ 组装注册给 Agent
串联:先判意图(路由)→ 走对应分支 → 全程在总闸门管辖下
一句话:边界集中、执行统一、工具可插拔
```
### 1.3 关键设计点
| 设计 | 为什么 |
|---|---|
| 单一装配入口(HarnessChatConfiguration) | 读 Bean 签名 = 读架构拓扑 |
| 一切围绕 core | 所有链路共享同一套门禁(超时/预算/重试/收敛) |
| 配置属性集中(@EnableConfigurationProperties) | 一处改全局生效,不会有的环节漏管 |
| 工具可选装配(mysqlEnabled 判断) | 没配置不装死工具;ObjectProvider 可选后端 |
| 守卫/修复/路由共用 GuardModelCall | LLM judge 模式:同一轻量模型底座 |
| Redis 存 canonical | 跨实例共享 + TTL 过期 |
| 线程池 AbortPolicy | 队列满直接拒绝(fail fast) |
## 2. HTTP 入口层(薄协议适配)
### 2.1 请求流时序
```text
POST /api/chat {Id, Question}
→ 校验 → new SseEmitter + ChatSseSession(= ChatApplicationObserver)
→ chatWorkerExecutor.execute(...) ← 异步:HTTP 线程不跑模型
→ 立即返回 200 + TEXT_EVENT_STREAM
→ worker 线程执行编排,经 session 推事件
→ 队列满 → 503(RejectedExecutionException)
```
### 2.2 SSE 状态机 + 五类事件
```text
状态机:NEW → OPEN → TERMINAL(收尾)/ DISCONNECTED(断连)
每个方法 requireState 校验顺序——防乱序推送
事件协议:
metadata → {session_id, run_id}(首推)
status → 编排进度(ROUTING / DIAGNOSIS_RUNNING / SAFETY_VALIDATING…)
content → 最终内容(content_type + payload)
failure → 失败码 + 消息
done → 终态(SUCCESS/FALLBACK/FAILED)★ CANCELLED 对外不可见
```
### 2.3 断连取消链路(贯穿到 Harness)
```text
客户端断开 → emitter.onTimeout/onError/onCompletion → session.disconnect()
→ 状态 DISCONNECTED → runControl.cancelClientDisconnect()
→ Harness 取消机制接管(checkActive / 拦截器 / 线程池 cancel)
onStarted 时若已断连:直接取消——不白跑
```
### 2.4 失败两层出口
```text
SSE 通道:ChatApplicationException → session.fail(failure + done(FAILED))
其他 RuntimeException → INTERNAL_FAILURE 通用信息(不泄露细节)
REST 通道:GlobalExceptionHandler → 404(SessionNotFound)/ 400(参数/文档/文件超限)/ 500(兜底)
→ 编排异常走 SSE failure,REST 异常走 HTTP 状态码——都不暴露内部细节
```
## 3. 会话系统与记忆体系(术语精确校准)
### 3.1 会话存储:不存历史,存「可重放的发布结果」
```text
ChatSession(chat_session 表):只存元数据(status/messagePairCount/时间戳)
——「message history is not persisted here」
DiagnosisSession:诊断快照(query/answer/selfEvaluation/feedback)
真正的历史:DiagnosisRun(每次运行一行)+ PublishedResult(JSON 落库)
```
### 3.2 PreviousTurn 注入链路(短期记忆)
```text
ChatApplicationUseCase 开头读 findPreviousTurn(sessionId)
→ 查最近 SUCCESS+DIAGNOSIS+publishedResult 非空的 Run
→ 反序列化 PublishedResult → PublishedResultPolicy 生成【有界】摘要
(limitations 10 条×500 字 / 源文档 10 个 / 字段限长——有界在生成时)
→ 传 executePath → DiagnosisChatExecutor
→ new DiagnosisAgentInput(query, previous_turn)
→ 序列化成输入 JSON → agent.call(inputJson) → 模型从输入读到
设计三决策(话术版):
诊断短流程 → 只取上一轮(更早记忆靠多轮逐层传递)
非 SUCCESS 误导 → SUCCESS 才准入(且非 SUCCESS 轮次根本没写 PublishedResult)
token 爆炸 → 有界摘要(PreviousTurnLimits)
补充:可回放——模型看有界摘要,审计看全量 JSON(两层分离)
```
### 3.3 记忆术语校准(重要认知)
```text
判定标准:记忆 = 会被【注入 prompt/上下文】的东西(不是存了什么)
PreviousTurn → 注入输入 JSON(user message)→ ✅ 短期记忆(当前会话)
lookup_knowledge → 工具调用动态获取(tool result)→ ❌ 记忆,是检索增强(RAG)
知识库 → 检索源,规模太大无法全量注入 → 归检索侧(工具检索是正确形态)
案例库 → 有结构有写入、缺检索注入 → 长期记忆的【候选原料】
运行档案 → 元数据层永不注入 → 审计数据
项目真实情况:只有短期记忆(PreviousTurn)+ 检索增强(RAG),【没有】长期记忆层
```
### 3.4 长期记忆设计路径(如果要做)
```text
筛选标准:规模可控 + 跨会话价值 + 可注入形态
案例库最符合:root_cause+solution 结构化摘要、注入 top 2-3 条、相似故障复用解法
PreviousTurn 扩展:最近 N 轮结论摘要(短期 → 中期记忆)
Feedback 偏好:用户偏好摘要
知识库不符合:全量太大 → 保持工具检索
案例 → skill 提炼(项目已实现):
6 个 SKILL.md(diagnose-mysql-connection-pool 等)
结构:Workflow / Required Evidence / Stop Conditions / Report Rules / Eval Anchor
注入:ClasspathSkillRegistry → SystemPromptTemplate → system prompt(程序性长期记忆)
情景记忆(案例)→ 程序记忆(skill)→ 常驻注入 ✅ 长期记忆的正确形态
现有缺口:单技能激活(只放行 1 个)/ 静态加载(无按 query 自动匹配)/ skill 与案例库断开
```
## 4. 知识库写入链路(RAG 写半边)
```text
上传(/api/documents/upload)
→ TextExtractorService(文本提取)
→ DocumentChunkService.chunkDocument(分块)★
→ VectorEmbeddingService(dense embedding)
→ VectorIndexService.indexDocumentChunks(写 Milvus)★
→ 检索侧(lookup_knowledge)读同一份索引
```
### 关键设计
```text
① 分块:按章节分块(非定长硬切)+ 相邻 chunk 保留 overlap(减轻边界断裂)
双条件限制:maxSize(字符)+ maxTokens(token)
② hybrid 写入:dense(应用侧 embedding → vector 字段)
+ BM25(buildSearchText → search_text 字段)
★ 关键:dense embedding 输入 与 BM25 search_text 【同源】——
同一个「增强文本」既喂 embedding 又写 BM25 字段
→ 两路召回看到完全一致的文档内容,混合检索才公平
③ 弃用:legacy MilvusServiceClient(旧 SDK)/ Spring AI VectorStore#add(无 hybrid schema)
唯一后端:MilvusHybridKnowledgeStore(Milvus SDK v2)
```
## 5. 易错点
| 易错 | 正确 |
|---|---|
| Controller 做业务编排 | 薄适配层:校验+开 SSE+异步+写回,编排在 Application |
| SSE 顺序不重要 | requireState 状态机校验——防乱序推送 |
| 取消对外可见 | Done 拒绝 CANCELLED——客户端只看到 SUCCESS/FALLBACK/FAILED |
| 知识库 = 长期记忆 | 是检索源(工具动态获取);记忆 = prompt 注入——项目无长期记忆层 |
| 案例 = 长期记忆 | 是候选原料——缺检索注入;skill 才是程序性长期记忆(已实现) |
| 工具预算在工具层 | maxToolCalls/收敛参数都在 core(总闸门) |
| 分块定长硬切 | 按章节 + overlap + 双条件限制 |
## 6. 面试话术(30 秒)
### 6.1 整体架构怎么组织
> "整个系统从下往上三层:最底层一套全局规则(超时/预算/重试/收敛,配置集中改一处全局生效);中间一层工具共用的底座(调用留痕、统一拦截、结果裁剪);上层按需装配具体工具(知识库/日志/数据库,配了才装)。最后串成应用入口——先判意图再走分支,全程在总闸门管辖下。一句话:边界集中、执行统一、工具可插拔。"
### 6.2 记忆体系
> "记忆的判定标准是会不会被注入上下文:PreviousTurn 是短期记忆(注入输入 JSON,上一轮 SUCCESS 的有界摘要);知识库是检索增强不是记忆(工具动态获取);项目没有长期记忆层——skill(案例提炼的诊断方法)注入 system prompt 是程序性长期记忆的正确形态;案例库是候选原料,缺检索注入。"
## 7. 代码位置索引
| 块 | 文件 |
|---|---|
| 装配中心 | `config/HarnessChatConfiguration.java`(+ `config/ChatHarnessProperties.java`) |
| HTTP 入口 | `controller/ChatController.java` + `controller/sse/ChatSseSession.java` / `ChatSseEvent.java` / `SseEmitterChatSink.java` |
| 异常映射 | `exception/GlobalExceptionHandler.java` |
| 会话存储 | `harness/application/persistence/JpaChatRunStore.java` / `PreviousTurnLimits.java` / `PublishedResultPolicy.java` |
| 记忆注入 | `harness/application/ChatApplicationUseCase.java` + `harness/application/executor/DiagnosisChatExecutor.java` + `harness/agent/DiagnosisAgentUseCase.java` |
| skill 机制 | `config/SkillConfig.java` + `src/main/resources/skills/*/SKILL.md` |
| 知识库写入 | `service/DocumentChunkService.java` / `VectorIndexService.java` / `VectorEmbeddingService.java` / `KnowledgeBaseInitService.java` + `controller/DocumentController.java` / `KnowledgeBaseController.java` |