Files

198 lines
12 KiB
Markdown
Raw Permalink 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.
# Phase 3 开发日志
> 本文件记录 Phase 3(知识库基础设施)各 Task 的已完成事实。
> 续接快照(待办/决策)见单独文件,不混入本 log。
---
## Task 1 — embedding 最小链路验证 ✅(2026-05-26 完成)
### 目标
用最小代价验证 Phase 3 的命脉:文本能否转成正确维度的向量、整条「解析→embedding→入库→语义检索」链路能否跑通。不碰 schema、不碰前端、不碰 docx。
### 关键结论(地基事实,后续 Task 依赖)
- **embedding 服务确定为 MiniMax embo-01**。前期查证已排除 DeepSeek(官方无 embedding 接口)。
- **embo-01 输出维度 = 1536**,与 `001_init.sql``knowledge_embeddings.embedding vector(1536)` 完全匹配,无需改维度、无需改宪法。
- **embo-01 真实接口结构**(实测确认,非凭印象):
- 请求:`{"model":"embo-01", "texts":[...], "type":"db"|"query"}` —— 注意是 `texts` 数组,不是 OpenAI 的 `input`
- 返回:向量在 `vectors` 字段(`data["vectors"][0]`),不是 OpenAI 的 `data[0].embedding`
- 存文档用 `type="db"`,查询用 `type="query"`,不可混。
- 调用需 `Authorization: Bearer {API_KEY}` + `GroupId` 两个凭证(MiniMax 特有 GroupId)。
- **检索方式**:使用 pgvector 数据库原生余弦距离算子 `<=>`,不在 Python 侧计算。
- **凭证管理**API Key + GroupId 存于 `backend/.env``MINIMAX_EMBED_API_KEY` / `MINIMAX_GROUP_ID`),代码从 .env 读取,未进任何代码、未进 git.env 已在 .gitignore)。
- 注意:embedding 接口地址 `https://api.minimax.chat/v1/embeddings`,与 Cline 写代码所用的 M2.7 chat 接口是两套独立配置,互不影响。
### 新增/改动文件(已 commit & pushcommit 38873ac
- `backend/requirements.txt`+pgvector==0.2.5
- `backend/app/core/config.py`+引导凭证字段)
- `backend/app/models/knowledge.py`(新增,embedding 字段用 pgvector.Vector
- `backend/app/services/embedding_service.py`(新增,embo-01 调用封装)
- `backend/app/services/knowledge_service.py`(新增,写库 + SQL 向量检索)
- `backend/scripts/test_embo01_api.py`(探路脚本)
- `backend/scripts/verify_embedding.py`(全链路验证脚本)
- `backend/sample_md/`(空目录,测试文件已清)
### 收尾
- 验证用测试数据已全部清除:knowledge_items = 0 行,knowledge_embeddings = 0 行,sample_md/ 空。
- 知识库当前为干净空库,等待真实笔记导入。
### 顺手核实到的现状(未处理,留待办)
- episodes 表当前仅 7 行。
- Phase 2 的「批量导入」前端有入口但从未真实导入过收视数据(制片人确认:是流程上没提醒导入,加上收视表里有测试数据不会删)。属 Phase 2 遗留债,记入 backlog,另行处理,不并入 Phase 3。
---
## Task 2 — 知识库管理后台(最小可用版)✅(2026-05-26 完成)
### 目标
做一条端到端跑通的链路:上传 .md 笔记 → 解析 yaml frontmatter → 入库(含语义向量)→ 列表展示 → 删除。不改 schema、不碰认证、不引向量索引以外的新依赖。
### 新增/改动文件(4 次独立 commit)
| 文件 | 动作 | 说明 |
|------|------|------|
| `backend/requirements.txt` | 改 | +python-frontmatter==1.1.0 |
| `backend/app/services/knowledge_service.py` | 扩展 | +parse_md_file() + delete_item() + list_items() + get_distinct_sources();复用 EmbeddingService |
| `backend/app/api/knowledge.py` | 新增 | 4 个接口:POST /upload、GET /items、DELETE /items/{id}、GET /sources |
| `backend/app/main.py` | 改 | +app.include_router(knowledge_router) |
| `frontend/src/services/knowledgeService.js` | 新增 | 封装 4 个 API 调用 |
| `frontend/src/pages/KnowledgeBase/KnowledgeBase.jsx` | 重写 | 上传区(Dragger) + 筛选栏(Select) + 列表(Table) + 删除(Popconfirm) |
### yaml frontmatter 解析映射(按真实样本,写死)
```python
# 类型 → source_type(硬映射,不猜测)
杂志文章/军报 military_report
节目文稿 manuscript
报题单 baoti
# 标题
名称 标题 title
# 作者
作者 编导 author
# 出处详情(存 JSONB 的 source_detail
期刊 + 期号 拼接"航空知识 2026年第1期"
# 播出日期
容错"待补充"等非日期文本 null
# 权重
原样存 JSONB不展示不排序Phase 4 排序用
# 双链 [[...]]
_extract_double_brackets() 原样存入 JSONB 预留给 Phase 4
```
### 关键设计决策
1. **source_detail 存 JSONB 不 ALTER**Task Brief 说"不要新增表字段"`tags` JSONB 列已用于存权重,故 source_detail 也放进来,不 ALTER TABLE。查询时从 `tags->>'source_detail'` 解压。
2. **来源筛选动态从 DB 取**`get_distinct_sources()` 查库里所有 `tags->>'source_detail'` DISTINCT 值,下拉选项不写死,新增一本杂志自动出现。
3. **embedding 复用**:直接调用 `EmbeddingService.embed_single(content_md, embed_type="db")`1536 维向量已验证,不重写。
4. **删除 CASCADE**:数据库层已有 `ON DELETE CASCADE`Python 侧只删 `KnowledgeItem` 即可。
### 测试结果(已验证)
| 验收项 | 结果 |
|--------|------|
| 两篇 md 均成功入库 | ✅ id=27(光辉之路)、id=28(超级战舰) |
| 杂志篇显示作者"钱峰",无播出时间 | ✅ author=钱峰, publish_date=null |
| 节目篇显示编导"左鑫",播出日期"待补充"不显示 | ✅ author=左鑫, publish_date=null |
| source_detail 正确存下("航空知识 2026年第1期" | ✅ tags 中可见 |
| 来源筛选下拉可据此筛 | ✅ /api/knowledge/sources 返回 `航空知识 2026年第1期` |
| knowledge_embeddings 有对应行(向量已生成) | ✅ 两行,19207/19174 字符(1536 维) |
| 删除有二次确认,确认后条目+向量一并消失 | ✅ 删除 id=27 验证,embeddings 级联消失 |
### commit 历史
- `779429a` feat: 添加 python-frontmatter 依赖用于解析 md yaml frontmatter
- `7b6ae24` feat: 知识库管理后台上传/列表/删除API,含frontmatter解析
- `7155a18` feat: 知识库管理前端页面,含上传/列表/筛选/删除
### 潜在风险
1. **windows CRLF 警告**:每次 commit 都有 CRLF→LF 警告,不影响功能但难看,属 git 全局配置问题(core.autocrlf),下次提醒用户设一下。
2. **"待补充"播出日期**:节目文稿的播出日期"待补充"正确存为 null,列表"播出/发表时间"格无值时不显示(render: val || null)。
3. **删除后 sources 下拉残留**:删除后 `get_distinct_sources()` 重新查会去掉已删条目的 source_detail,正常。
4. **embo-01 调用失败时整条上传失败**:当前是 try-except 包装,失败时返回 errors 数组,不污染已入库数据(因为单条原子),但前端需要展示 errors 反馈(已实现)。
---
## Task 3 — 知识库树形视图(路线B 按来源分组)✅(2026-05-27 完成)
> 本段为追加内容,接续 phase3_log 的 Task 1 / Task 2 之后。
> Task 3 实际分三轮(Task 3 主体 + 3.1 修复 + 3.2 调整)完成并验收。
### 目标
在 Task 2 的扁平列表外,包一层"可展开/收起的树形导航",按"来源"两层分组,
点树节点 → 右侧列表联动过滤。保留原有上传/筛选/删除。不改 schema。
### 路线选择(已定)
- **走路线B:按来源分组**(制片人拍板)。理由:现有字段(source_type + source_detail)即可建树,零返工。
- 路线A(按主题分组:涉及装备/技术等)的原料其实已在库(related_entities / tags JSONB),
将来转 A 路大概率无需补字段重传,只改分组层 + 映射表。
- **解耦红线**:交互层(KnowledgeTree.jsx)与数据分组层(useKnowledgeGrouping.js + 后端 get_grouped_items)分离,
转 A 路只改分组层一处,树组件不动。
### 三轮各自做了什么
**Task 3(树主体)**
- 两层树:来源大类 → 二级(出处)。"全部"为根节点;空大类规则、_none_ 不泄漏。
- 交互:展开/收起、全部展开/全部收起、节点高亮、右侧联动。
**Task 3.1(修复)**
- 修出处筛选下拉为空(根因:前端从未调用 listSources,用 items.length 误判;改为调 /sources 动态填充)。
- 修页面宽度跳动(右侧容器加 minWidth:0 约束)。
- 表格列宽规划;上传区收成一行按钮;隐藏"入库时间"列(仅前端隐藏,DB 字段保留)。
**Task 3.2(调整)**
- 大类固定排序:节目文稿 → 杂志文章 → 报题单 → 其他。
- 节目文稿按作者(author)归堆二级(如"左鑫");映射表 SECONDARY_GROUP_FIELD 落地。
- 左右两列对齐(曾尝试 flex→Grid 重构,连续改坏,最终回退 flex 止损,保留功能改进)。
- 空大类(0条)不显示。
### 关键设计决策
1. **二级分组维度按大类不同**manuscript→authormilitary_report→source_detail,其余 None。
做成**可配置映射表**(前端 useKnowledgeGrouping.js + 后端 knowledge_service.py 同名映射),改一处即可调整。
⚠️ 此设计使"第二层维度"在不同大类下含义不同,是制片人知情拍板的临时方案;
将来宜把"按编导看稿"迁为独立筛选(见快照 Backlog#3)。
2. **点击筛选为前端本地计算**displayedItems 基于已拉取全量 items + 选中节点实时算,不发网络请求,稳定。
3. **过滤按大类用对应字段**manuscript 比 authormilitary_report 比 source_detailTask 3.2 最终修对的关键)。
4. **空大类不显示根因**:初始化时 type_groups 预建所有大类空列表,致 continue 失效;改为动态收集真实出现的 source_type。
### 改动文件
| 文件 | 动作 |
|------|------|
| `backend/app/services/knowledge_service.py` | 新增 get_grouped_items()SOURCE_TYPE_ORDERSECONDARY_GROUP_FIELD;空大类动态收集 |
| `backend/app/api/knowledge.py` | 新增 GET /api/knowledge/grouped |
| `frontend/src/services/knowledgeService.js` | 新增 getGroupedItems() |
| `frontend/src/hooks/useKnowledgeGrouping.js` | 新增(数据分组层,解耦核心) |
| `frontend/src/components/KnowledgeTree/KnowledgeTree.jsx` | 新增(树交互组件) |
| `frontend/src/components/KnowledgeTree/KnowledgeTree.css` | 新增 |
| `frontend/src/pages/KnowledgeBase/KnowledgeBase.jsx` | 重构左右分栏;按 type 分支过滤;上传区收小;隐藏入库时间列 |
### 验收结果(制片人真实点页面逐条验,已通过)
| 验收项 | 结果 |
|--------|------|
| 树两层、大类固定顺序(节目文稿在前) | ✅ |
| 节目文稿 → 左鑫 → 点击右侧出现超级战舰 | ✅ |
| 杂志文章 → 航空知识2026年第1期 → 点击右侧出现对应2篇 | ✅ |
| 点"全部"/各节点联动正确 | ✅ |
| 全部展开/全部收起、宽度不跳、上传按钮收小 | ✅ |
| 空大类(报题单/其他 0条)不显示 | ✅ |
| 原有上传/筛选/删除无回归 | ✅ |
### commit 历史
- 树主体与修复多次提交,最终修复 commit `3409d48`(前序含 `769a048` 等)。
### 教训(重要,下次接手避雷)
1. **Cline 自检多次虚标 ✅**:完工报告打勾项实测多坏。纪律:不信自检,制片人真实点页面验;禁止用会误报的检查方式(如跨多行正则)。
2. **key 两端对齐反复踩坑**:树节点 key 格式改动时生成端与过滤端未同步,致点节点筛不出。改 key 属高风险,改后必实测联动。
3. **布局别轻易换实现方式**:flex→Grid 整体重构连续改坏,靠"回退 flex + 保留功能"止损。已验证好用的布局不为对齐而重构。
4. **排查 > 盲改**:Cline 报告自相矛盾或与页面不符时,让制片人直接发源码文件给顾问看,一眼定位胜过反复试错。
5. **见好就收**:像素级打磨边际收益递减,记 backlog 改天连同视觉规范统一弄。
### 潜在风险 / 遗留
1. 界面像素级未到位:上传按钮框与筛选栏框上下精确对齐未做到;整体配色/字号属"能接受"非"精致",记 backlog。
2. 200+ 笔记尚未批量录入:需先试 10 篇验证解析,且宜等 pdf 存储方案定后再全量(见快照 Backlog#1#2)。
3. Phase 3 内"语义搜索"尚未做(向量已就位,随时可开),是下一刀候选。