# 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 & push，commit 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 反馈（已实现）。
