- skill: 素材库分块定义(00~11)、05_极致事件知识库 4 篇、08_对话风格总纲、mcn-video-prompt 提示词质量门禁与外部语料检索流程 - workbench: mcn-work-shop 新增 cli-backend.js(本地 CLI 接入)、工作台视觉交互规范;移除旧 UI 规范 - docs: 根目录极致事件/素材卡/审计方案报告与热门短视频清单入库 - memory: 补 09-14~09-29 日志与自动化任务记忆 - chore: .gitignore 排除 tools/、.tmp-chrome-*/、_k_test.cjs
10 KiB
极致事件卡片 · 文档型容器可行性实测 V1.0
日期:2026-09-23 | 环境:本机 WeKnora 0.8.0(
localhost:31607,容器WeKnora-app) 起因:用户追问「为什么不用文档型知识库呢?不分块可以吗」 方法:全部为真实实测,非推演。实验 1 走零写入的POST /chunker/preview;实验 2 建临时库(跑完DELETE,有 HTTP 200 回执),未触碰正式库MCN极致事件素材库与mcnvideoprompt。 脚本与原始输出:tools/weknora-fit/7_doc_noblock.py、8_doc_vs_faq.py及out/7_doc_noblock.txt、out/8_doc_vs_faq.txt
〇、结论先行(三句话)
- "不分块"能做到 —— 但只有一条路:一卡一文档 + chunk_size ≥ 单卡长度。实测 11 篇文档全部 = 1 块,字段零丢失。
- 分块不是选 FAQ 的理由。即使把卡片做得完整无损,文档型的口语问法召回仍低于 FAQ(阈值 0.5 时 9/11 vs 11/11)。
- 真正的分水岭在**「一个问题有几个语义入口」**:FAQ 能给一个答案挂上「标准问 + N 条相似问」多条问法;文档型只有整卡正文一条。结论:分块问题可绕,问法杠杆绕不过。
一、分块问题:能解决,但前提很硬
1.1 机制(源码依据)
| 事实 | 出处 |
|---|---|
| 文本 ≤ chunkSize 时直接整段返回,完全不切 | internal/infrastructure/chunker/splitter.go:225-227(splitBySeparators 提前 return) |
| 分隔符按优先级递归切:先按第一个切,切出的片段仍超长才用下一个 | splitter.go:214-216(注释明示 parity with Python splitter) |
absoluteMaxSize = 7500 硬上限:任何配置下,单块超 7500 字强制切开 |
splitter.go:400、splitter.go:412-427、splitter.go:499 |
| 无"不分块 / 单块"开关 | ChunkingConfig 结构仅含 chunk_size / chunk_overlap / separators / strategy / token_limit / …(internal/types/knowledgebase.go:254-292) |
1.2 实测结果(3 张卡共 2793 字 / 11 张卡共 8605 字)
| # | 配置 | 输入 | 输出 | 判定 |
|---|---|---|---|---|
| ① | 单卡 + chunk_size 1200 | 1150 字 | 1 块,10/10 字段齐 | ✅ 不切 |
| ② | 单卡 + chunk_size 100000 | 1150 字 | 1 块,10/10 字段齐 | ✅ 绝对上限对单卡无影响 |
| ③ | 整篇 11 卡 + 1200 + 卡片标题 ### 提为第一优先级 |
8605 字 | 10 块 = 6 完整 + 3 块多卡混装 + 2 块半卡 | ❌ |
| ④ | 整篇 11 卡 + chunk_size 100000 | 8605 字 | 2 块(7298 + 1307),块 1 硬塞 9 张卡 | ❌ 7500 上限被证实 |
| ⑤ | 整篇 11 卡 + 1200 + 普通分隔符(对照组) | 8605 字 | 与 ③ 逐块完全一致 | — |
三条硬结论:
- "按卡片标题切"这个思路无效 —— ③ 与 ⑤ 输出逐字相同,说明把
###提到第一优先级没有改变任何边界。卡片内\n\n密度远高于卡间,递归切分照样在卡内下刀。 - "多卡一文档"必碎 —— 8605 字 > 7500 硬上限,必然被切;且切点是字符位置,不是卡片边界 → 块内混装、块间劈卡。
- "一卡一文档"可做到零切割 —— ①② 证明:单卡 1150 字 < chunk_size 即整段返回。
1.3 代价:容器语义被反过来用
要让文档型不切碎,必须把"一篇文档"降到"一张卡"的粒度。这实际上是在用手工方式模拟"一条目 = 一分块"——正是 FAQ 型的原生行为。
二、检索问题:这才是真正的分水岭
前提:文档型走 ①的姿势(一卡一文档),实测 11 篇全部 = 1 块([4] 分块核对 输出 11 行「块数=1」)。即排除分块干扰后再比检索。
2.1 同口径三组探针(A 标准问 / B 相似问 / C 未登录口语问法,各 11 条)
文档型容器(/hybrid-search,vector + keyword 双开,一卡一文档):
| 组 | 0.7 | 0.6 | 0.5 | 0.45 | 0.4 | 0.3 |
|---|---|---|---|---|---|---|
| A 标准问 | 11/11 | 8/11 | 7/11 | 7/11 | 7/11 | 7/11 |
| B 相似问 | 9/11 | 10/11 | 10/11 | 10/11 | 10/11 | 10/11 |
| C 新问法 | 7/11 | 8/11 | 9/11 | 8/11 | 8/11 | 8/11 |
FAQ 型容器(/faq/search,纯向量)— 基线数据来自 out/6_threshold.txt:
| 组 | 0.7 | 0.5 |
|---|---|---|
| A 标准问 | 11/11 | 11/11 |
| B 相似问 | 8/11 | 11/11 |
| C 新问法 | 1/11 | 11/11 |
判定口径:top1 是否为期望卡。文档型判
knowledge_title,FAQ 判返回条目的答案首行事件名。
2.2 三个可读出来的事实
事实 1:文档型没有"可用工作点"。 A 组在 0.7 最好(11/11),C 组在 0.5 最好(9/11)——两者互斥。文档型升阈值救标准问、降阈值救口语问,不存在单点让三组同时达标。FAQ 则在 0.5 单点即三组全 11/11。
事实 2:文档型的召回"卡上限",降阈值不解决。 B 组 0.7→0.3 是 9→10→10→10→10→10;C 组是 7→8→9→8→8→8。到某个上限就不再涨 —— 说明瓶颈不是阈值(召回不足),而是召回不到(语义入口不够)。
事实 3:文档型排序不稳,且降阈值会反噬。 A 组从 0.7 的 11/11 掉到 0.5 的 7/11,是降阈值引入误召回、把正确答案挤出 top1。典型如 C 组「找一条家常菜做到能开店水准的素材」→ 全阈值 ✗(错配到别的菜);B 组「逞强之后立刻被打脸」→ 全阈值 ✗。
2.3 差异的机制(为什么 FAQ 更准)
| FAQ 型 | 文档型 | |
|---|---|---|
| 入索引的文本 | 标准问 + 相似问(question_index_mode=combined) |
整卡正文(600–978 字) |
| 语义单元 | 短问句(一问一答) | 整卡平均语义(含画面内容/镜头语言/复用场景…) |
| 匹配方式 | 问 ↔ 问 | 问 ↔ 整卡 |
文档型把"画面内容 + 镜头语言 + 画面风格 + 复用场景"等大量非关键信息一并向量化,需求句的权重被稀释。举实测原例:
- 探针:「找一条家常菜做到能开店水准的素材」
- 文档型 → 全阈值都错配(辣鸡爪卡片正文里"家常菜/开店水准"这类抽象需求占比太小)
- FAQ → 命中「蒜香辣鸡爪」score 0.6902,靠的正是相似问「有没有难度高、有技术卡点的下饭菜素材」
这就是"问法杠杆":FAQ 允许你为同一个答案挂上多条真实检索口径(含反例边界),而这些问法自己就成了索引。
三、文档型并非全无优势(如实说明)
| 能力 | FAQ 型 | 文档型 |
|---|---|---|
| 条目级多标签 | ❌ 只有 tag_name 单值 |
✅ tag_ids 数组 |
| 文件夹树 | ❌ | ✅ 有 |
| 草稿 / 发布 状态机 | ❌ | ✅ draft/publish |
| 条目正文可编辑(人工改卡面) | ❌ 需重导 | ✅ PUT /knowledge/manual/:id |
| 一次批量导入 | ✅ faq/entries 数组(含 dry_run 预校验) |
❌ 逐篇创建 |
| 一问多入口(口语问法召回) | ✅ 11/11 | ❌ 9/11 |
若将来卡片的标签改成多值一级分类、或需要人工直接在库里改卡面,文档型的这四项是真实优势,届时可重估。
四、落地与维护差异(工程视角)
| 项 | FAQ 型 | 文档型(一卡一文档) |
|---|---|---|
| 灌 11 张卡 | 1 次 API(POST .../faq/entries,11 条一次提交) |
11 次 API(逐篇 POST .../knowledge/manual) |
| 预校验 | ✅ dry_run=true(实测 3/3 通过) |
❌ 无 |
| 索引进度 | 单任务轮询(/faq/import/progress/{task}) |
逐篇轮询(实测 11 篇约 125s) |
| 改一张卡 | 整条 upsert(幂等删旧保新) | 改文档 + 重新索引 |
| 检索接口 | /faq/search(MCP 无此工具) |
/hybrid-search(MCP 有) |
注意最后一行是文档型的唯一独有便利:MCP 通道能直接调
/hybrid-search,而 FAQ 只能走 HTTP。但 MCP 的hybrid_search同样没有tag_ids,标签圈定两边都得上 HTTP。
五、未做的对照(明确声明)
- 「把相似问塞进文档正文开头」的对照组 —— 理论上能让文档型模拟 FAQ 的多入口,但那本身就是"在文档型里手写 FAQ 格式",反而印证 FAQ 结构有效。未做。
- 文档型纯向量(关 keyword)对照组 —— 本轮文档型是 vector+keyword 双开(比 FAQ 的单通道更宽),结果仍低于 FAQ,说明瓶颈不在通道。未做反向对照。
- rerank 组 —— 当前实例未在检索路径启用 reranker(模型已配
bge-reranker-v2-m3但未挂到检索)。这是两边都还有的提升空间,会同时抬高 FAQ 与文档型。
六、最终答复口径
可以不分块,但要改成"一卡一文档"才能做到;而且即便做到了,检索也不如 FAQ。 换容器不是为了"能不能存进去",而是为了一张卡能被多少种问法找到。
决策建议:维持 FAQ 型容器(已上线的 MCN极致事件素材库),把工程力气投在补相似问的真实口径上——本轮实测证明,相似问就是这个库唯一的召回杠杆(DisableKeywordsMatch: true,纯向量)。
附:证据索引
| 结论 | 证据位置 |
|---|---|
| 文本 ≤ chunkSize 不切 | splitter.go:225-227 |
| 分隔符按优先级递归 | splitter.go:214-216 |
| 7500 绝对上限 | splitter.go:400 / 412-427 / 499 |
| 无"不分块"开关 | internal/types/knowledgebase.go:254-292 |
| 单卡不切、多卡一文档必碎 | out/7_doc_noblock.txt(①②③④⑤) |
| 一卡一文档 = 11 篇各 1 块 | out/8_doc_vs_faq.txt [4] 分块核对 |
| 文档型三组命中率 | out/8_doc_vs_faq.txt [5][6] |
| FAQ 基线命中率 | out/6_threshold.txt [一][二] |
| manual 知识路由 | internal/router/routes_knowledge.go:75(/knowledge-bases/:id/knowledge/manual) |
| 文档型检索路由 | routes_knowledge.go:232(/knowledge-bases/:id/hybrid-search) |
| 请求参数结构 | internal/types/search.go(SearchParams) |