Files

180 lines
14 KiB
Markdown
Raw Permalink Normal View History

# 极致事件卡片 × WeKnora 适配性评估 V1.0
> 日期:2026-09-23 | 评估对象:`极致事件卡片`(V1.3 口径,10 固定字段 + 所属库专属 N)能否存放在 WeKnora 的知识库模式下
> 方法:**本机 WeKnora 0.8.0 实例实测**(含 5 轮真实调用,非纯推演)+ 源码取证(file:line)
> 性质:**评估与方案,未改动任何技能文件,未改动你已有的 `mcnvideoprompt` 知识库**(实验全部用临时库,跑完即删)
---
## 一、结论先行
**适合,但必须换容器:这类卡片要的是「卡片库」,不是「文档库」。**
| 判断 | 内容 |
|:--:|------|
| ✅ **适合** | 卡片天然是「可被检索调用的素材单元」——WeKnora 的 标签 + 检索信道 + 生成式问法 三件套正好对上「定库 → 圈范围 → 召回」这个链路 |
| ❌ **不适合** | **不要走文档分块那条路**。实测同一批卡,512 字粒度下**一张卡被切成 4 块、字段组打散、重叠导致字段重复、块还跨越两张卡**(详见第二节矩阵 A/B) |
| ⭐ **最优容器** | **FAQ 型知识库**:一条目 = 一分块,不走文档分块器 → 一张卡一个原子单元,字段不打散(实测答案长度 691 / 1149 与原文完全一致) |
| ⚠️ **前提** | FAQ 型库有 **3 个默认坑**:索引管线默认关闭(会导致"入库成功但永远检不到"的静默失败)、必须绑 embedding 模型、dry_run 是异步任务。三个都已实测并给出规避参数(第五节) |
| 🔒 **边界** | **git 仍是唯一权威源**,WeKnora 只做派生的检索层;**规则类文本(S1-S11 流程 / 35 项清单 / 映射表 / 评估标准)不要进**(第七节) |
一句话:**素材进检索层,规则留在文件里;卡片走 FAQ 容器,不走文档分块。**
---
## 二、实测矩阵(核心证据)
同一批 3 张真实卡(691 / 952 / 1150 字,取自 `极致事件提取_多类别样本_20260922.md`),同一台 WeKnora,只换容器与参数:
| # | 路线 | 切成几块 | 字段完整性 | 卡片原子性 | 检索 |
|:--:|------|:--:|------|------|------|
| **A** | **你当前 `mcnvideoprompt` 的配置**<br>512 / overlap 80 / auto / 父子 4096-384 | **9 块** | ✗ 卡被拆成「内容层 4 字段 + 3 字段 + 呈现层 4 字段」三片 | ✗ 一卡跨 4 块 | 需靠父子回补+重排,易半卡 |
| **B** | heading 策略 512/80 | **9 块** | ✗ 与 A 逐块完全相同 | ✗ 同上 | 同上 |
| **C** | 结构化档 1200 / overlap 0 / legacy | 4 块 | △ 字段基本齐全,但仍有跨块 | ✗ 一块含 2 张卡;卡 2 仍被切开 | 一块多卡 |
| **D** | 长档 2000 / overlap 0 | 2 块 | ✓ 每块 10/10 字段 | ✗ 一块含 2 张卡 | 一块多卡 |
| **E** | ⭐ **FAQ 容器**(一条目 = 一分块) | **2 条目 = 2 单元** | ✓ 10/10 字段整张保留 | ✓ **答案长度 691 / 1149,与卡片原文一致** | ✓ 标准问命中 score **0.8219**;通用 hybrid-search 亦可命中 |
**A 的翻车细节(值得记住)**:
- 691 字的卡被切成 → `[31 字:只剩卡头 + 元信息行]` + `[4 字段]` + `[3 字段]` + `[4 字段]`
- **overlap 80 导致同一字段出现在两块**(`事件标签` 同时出现在第 2、3 块)→ 检索可能返回"同一张卡的两片"
- **第 4 块里既装着上一张卡的尾部字段、又装着下一张卡的卡头** → 跨卡污染
- **不可预测**:1150 字的卡反而整块未切(1231 字单块,超过 chunk_size)
> 复现脚本与原始输出:`tools/weknora-fit/1_docchunk_fit.py` → `out/1_docchunk.txt`(A/B/C/D);`4_pipeline_verify.py` → `out/4_pipeline.txt`(E)
---
## 三、逐项对齐:卡片契约 ↔ WeKnora 数据模型
### 3.1 对得上的(可直接支撑)
| 卡片侧需求 | WeKnora 对应能力 | 证据 |
|------|------|------|
| 定库:主标签决定落哪个库 | **`KnowledgeTag`**:KB 内**同名唯一**、可排序、可着色;多对多关系表 | `internal/types/tag.go:12-88` |
| 筛条:按标签圈定检索范围 | **`SearchParams.TagIDs` / `ScopeTagIDs`**;`DisableRecallThresholds` 保证圈定范围内不被阈值抹掉 | `internal/types/search.go:20-55, 232-246` |
| 主标签优先 / 辅助标签次级 | FAQ 检索的 **两级标签优先** `first_priority_tag_ids` / `second_priority_tag_ids` | `internal/types/faq.go:376-386`、`service/knowledge_faq.go:949-960` |
| 一条卡的「多种检索说法」 | FAQ 的 **标准问 + 相似问 + 反例问**;且**反例不参与索引**(只做精度控制) | `service/knowledge_faq.go:1633-1647` |
| 卡片正文保真(原话不概括) | FAQ 的 **`Content`(返回给模型的卡面)与 `IndexContent`(向量化内容)分离** | `service/knowledge_faq.go:1633-1647 / 1890-1901` |
| 一卡一单元、字段不打散 | FAQ 条目**不走文档分块器**,`Content: buildFAQChunkContent(meta, mode)` 逐条构建 | `service/knowledge_faq.go:189、192` |
| 治理:批量改名/换标签/停用 | **按 ID 或按 Tag 批量改字段**(含 `ExcludeIDs`)、导出→编辑→重新导入、append/replace 合并 | `internal/types/faq.go:389-410`、`router/routes_knowledge.go:151-180` |
| 入库门禁(10 字段硬规则) | **`dry_run` 批量预校验**:实测 3/3 通过、返回 `success_count / failed_count / 部分失败明细` | `internal/types/faq.go:322-330、367-375`;实测见 `out/2_faq.txt` |
| 召回增强(不必手写全部问法) | 文档侧**每 chunk 自动生成 3 个问题并独立索引**(该库已开启) | 实例配置 `question_generation_config` |
### 3.2 对不上的(要么绕、要么放弃)
| 卡片侧需求 | 现实 | 处置 |
|------|------|------|
| **多字段联合硬过滤**(如"铺垫量级=短 **且** 极致类型=反转") | **做不到**。`CustomMetadata`(JSON)**不参与检索过滤**,只在结果里作为 `KnowledgeCustomMetadata` 暴露给模型 | 压成 Tag(把枚举值做成标签)→ 只能两级;或靠 rerank 排序 |
| 10 字段顺序 + N 的结构化契约 | WeKnora 无"字段"概念,只有条目/分块/标签/元数据 | 契约由**卡面文本自身**承载(写入时校验,读出不解析) |
| 沿革句 / diff / 双向同步 | 没有 git 语义;改动是"重新解析"的异步任务 | **git 保源,WeKnora 保检索**(单向下沉) |
| 12 维闭集、禁自造词 | **`auto_tag_config` 会 LLM 自动打 ≤3 个标签**,会破坏闭集 | **必须保持 `enabled=false`**(你的实例当前已是 false,属既有隐性正确配置) |
---
## 四、卡片为什么"天生适合 FAQ 容器"(语义再对齐一次)
FAQ 容器常被理解为"用户提问 → 客服答复",但把字段摊开看,它和卡片其实是同构的:
| FAQ 条目字段 | 映射到卡片 | 说明 |
|------|------|------|
| `standard_question` | 这张卡的**主检索意图**(一句话问法) | 如「找一个高温把人打脸的反差事件」 |
| `similar_questions` | 这张卡的**其他问法** | 就是 S7 在布位时可能说出的不同措辞 |
| `negative_questions` | **不该命中这张卡**的边界(不入索引) | 天然承载"伪极致 / 无效召回"的边界控制 |
| `answers[0]` | ⭐ **整张卡正文(10 字段 + N)** | 实测 1149 字整张放入无损 |
| `tag_name` | **主标签**(定库依据) | 对应 12 维取值 |
| `is_recommended` | 是否进推荐位 | 可对应"爆点级/常规级" |
**唯一需要声明的语义错位**:FAQ 有"直接回答"通道(`FAQPriorityEnabled` / `FAQDirectAnswerThreshold`),若素材库与问答库混用,素材卡会被当成"直接答案"抛给用户。→ **素材库必须独立成库**(你已建的 `mcnvideoprompt` 正好空着,但它是 `document` 型,需另建 `faq` 型的素材库)。
---
## 五、落地配方(已实测可复现)
### 5.1 三个默认坑(全部实测踩到)
| # | 坑 | 现象 | 规避 |
|:--:|------|------|------|
| **P1** ⭐ | **FAQ 型库默认 `vector=false / keyword=false`** | 入库 100% 成功、条目/分块/标签都在,但**检索永远返回空**(`success:true, data:[]`)—— 静默失败,不报错 | 建库时显式传 `indexing_strategy.{vector_enabled,keyword_enabled} = true` |
| **P2** | **建库/建条目不绑 embedding 模型** | 导入任务卡在 `processing / 0%`,`progress` 接口的 `error` 字段**为空**,只能翻容器日志看到 `failed to get embedding model: model ID cannot be empty` | 建库时传 `embedding_model_id`(用 `03f0cf45-…` bge-m3) |
| **P3** | **`dry_run` 是异步任务,会占用导入通道** | dry_run 之后立刻发起正式导入 → `400 该知识库已有导入任务正在进行中` | dry_run 后轮询其 task 至 `completed`,再发正式导入(实测各约 2 秒) |
> P1 的根因日志:`knowledgebase_search.go:195 [HybridSearch] | No retrievable indexing pipelines across 1 KBs` —— FAQ 检索复用 HybridSearch,管线为空就直接短路。
### 5.2 终验通过的一组参数(实测结果见 `out/4_pipeline.txt`)
```
POST /api/v1/knowledge-bases
{
"name": "<账号>-极致事件库",
"type": "faq", ← FAQ 型容器
"embedding_model_id": "03f0cf45-38dd-4330-aed9-38e394e66160", ← P2 必须
"faq_config": { "index_mode": "question_answer", "question_index_mode": "combined" },
"indexing_strategy": { "vector_enabled": true, "keyword_enabled": true,
"graph_enabled": false, "wiki_enabled": false } ← P1 必须
}
```
- 标签:`POST /knowledge-bases/{id}/tags`,把 **12 维取值**建成标签(闭集由人工维护,禁 auto_tag)
- 入库:`POST /knowledge-bases/{id}/faq/entries`,`{entries:[…], mode:"append", dry_run:true}` → 轮询 `GET /faq/import/progress/{task_id}` → 再正式导入
- 检索:`POST /knowledge-bases/{id}/faq/search`,可带 `first_priority_tag_ids`
**终验实测**:2 条卡 → 导入 2/2 → 排查式检索命中 1 条 `score=0.8219`,返回**答案长度 691 = 整张卡**。
### 5.3 已知待调项(如实记录)
| # | 观察 | 待办 |
|:--:|------|------|
| 1 | **用"语义改写问法"检索命中 0**(「我需要一个反差感强的事件,人物被高温天气打脸的」→ 0;而标准问原句 → 0.82) | 相似问需覆盖**真实检索口径**(S7 实际会怎么问),或降 `vector_threshold`、或改走通用 `/hybrid-search`(实测该通道对 FAQ chunk 可命中) |
| 2 | 按标签优先检索那一次命中 0 | 需在问法匹配成立的前提下复测,标签优先级是否生效尚未独立验证 |
| 3 | 当前实例只有 `document` 型的 `mcnvideoprompt`(0 条目),无 FAQ 型库 | 落地需新建 FAQ 型素材库 |
| 4 | **MCP 通道拿不到标签能力** | MCP 暴露 40+ 工具,但**无 FAQ / Tag 工具**,`hybrid_search` 参数也没有 `tag_ids` → 走 MCP 无法"按标签圈定 + 建卡",必须走 HTTP API 或扩展 MCP |
---
## 六、推荐的落地形态(三档)
| 档 | 做法 | 适用 |
|:--:|------|------|
| **A(推荐)** | **FAQ 型库 + 一卡一条目 + 12 维做成标签 + 索引管线显式开启**;标准问写主检索意图、相似问写 3 种真实问法、答案放整张卡原文、反例问放"不该命中"的边界 | 要让 S7 在创作时"按需召回素材" |
| **B(保守)** | `manual` 型条目(markdown 正文 + `FolderPath`=所属库 + `TagIDs`=主标签),chunk_size 提到 **≥1200 / overlap 0** | 想要原文保真 + 可 diff,能接受"一块多卡" |
| **C(不推荐)** | 把整份实测报告/清单文件直接入库 | 实测 A/B 路线:字段打散、跨卡污染、overlap 重复 |
> 注:B 档的 `manual` 型也支持 `TagIDs`、`FolderPath`、标题/描述,且有条目级版本(`ManualKnowledgeMetadata`,`types/knowledge.go:288-300`)—— 是"要版本感"时的次优解。
---
## 七、边界:什么**不**该进 WeKnora
| 内容 | 为什么不进 |
|------|------|
| **规则类文本**:S1-S11 流程、35 项检查清单、`01_极致类型-素材标签映射表`、`03_极致事件评估标准` | RAG 是"检索回来给模型看",召回率不是 100%(`vector_threshold` 默认 0.5 / `keyword_threshold` 0.3 会直接滤掉)。**规则必须 100% 生效 → 只能走技能文件的硬闸门**,进检索层等于把硬约束降级成软建议 |
| **沿革句 / 变更日志** | 无 git 语义,且本来就是给人和 git 看的 |
| **闭集词表本身**(12 维定义) | 词表要能被机械校验(禁自造词),进 RAG 就变成"参考建议"。**词表留文件,取值做成标签** |
一句话判据:**要"被翻出来看"的进 WeKnora;要"必须遵守"的留文件。**
---
## 八、待你决策
| # | 事项 | 我的建议 |
|:--:|------|------|
| 1 | 是否落地 WeKnora 作为卡片检索层 | 建议**试跑**:新建 FAQ 型素材库,先灌 12 张存量卡(`mode=append`,实测 3 秒内完成) |
| 2 | 素材库与 `mcnvideoprompt` 的关系 | **独立成库**(FAQ 型专库);`mcnvideoprompt` 保持 document 型不动 |
| 3 | 落地前是否先修 §5.3-1 的语义问法召回 | 建议先补"真实检索问法"到相似问,再灌量 |
| 4 | 是否扩 MCP(补 FAQ / Tag 工具) | 若不扩,S7 只能走 HTTP API —— 需确认技能侧是否允许调用本机 HTTP |
---
## 附:证据索引
| 类别 | 位置 |
|------|------|
| 实测脚本 | `tools/weknora-fit/1_docchunk_fit.py`(A/B/C/D)・`2_faq_container.py`・`3_search_diag.py`・`4_pipeline_verify.py`(E) |
| 原始输出 | `tools/weknora-fit/out/*.txt` |
| WeKnora 源码 | `\\wsl.localhost\Ubuntu-24.04\home\maidou\weknora\`(0.8.0) |
| 分块规格 | `docs/CHUNKING.md`(512/80 默认;结构化记录建议 overlap 0) |
| 实例 | `localhost:31607`;容器 `WeKnora-app / docreader / postgres / redis / frontend` 均已运行 3 天 |
| 模型 | `glm-5.3-flash`(KnowledgeQA) ・ `BAAI/bge-m3`(Embedding) ・ `BAAI/bge-reranker-v2-m3`(Rerank) |
**未改动**:本评估未修改任何技能文件;未向 `mcnvideoprompt` 写入任何数据;4 个临时实验库已全部删除(删除均有 HTTP 200 回执)。