Wiki 能力
上传一堆散乱的文档之后,你常常还是不知道「这批资料里到底有什么」。Wiki 就是为这件事准备的:文档入库后,Yuheng 用大模型从原文里抽出人物、产品、概念等条目,为每个条目生成一篇带出处的 Markdown 页面,页面之间互相链接,形成一个可以像维基百科一样浏览的知识站点。
它和普通问答的区别在于:问答是「你问我答」,Wiki 是「先替你把知识整理好」。资料越多、越零散,Wiki 的价值越明显。而且这些页面不只给人看——问答管线检索时会把 Wiki 页面作为一类来源并加权(见下文「与问答管线的关系」);模型写错的地方你可以直接改,每次改动都留版本、可回滚(见「人工编辑与版本历史」)。
怎么开启
- 创建知识库时选上 Wiki:在知识库设置的「基本信息」→「索引策略」里勾选「Wiki 知识库」(可以与「RAG 检索」「知识图谱」同时开启,至少保留一种)。知识库里已经有内容时索引策略被锁定(「知识库已有内容,索引策略暂不支持调整。如需变更,请先清空知识库」),所以要在上传文档之前决定是否开启。
- 调整 Wiki 设置(勾选后出现在同一页):
- 「提取粒度」:聚焦 / 标准(默认)/ 详尽,见下文「抽取粒度」;
- 「Wiki 内容生成要求」:控制摘要、页面和首页的表达重点,引用、合并与防幻觉规则由系统固定维护;
- 「Wiki 提取重点」:说明应重点识别的领域实体和概念。 另外在「模型配置」里可以单独指定「Wiki 合成模型」,不设置时回退使用知识库的摘要模型。修改这些设置后,已有内容要重新解析才会受影响。
- 上传文档,等待生成。Wiki 生成是异步的,文档多时会持续一段时间,知识库面包屑上有「索引中」提示。
- 浏览:进入知识库的 Wiki 页签浏览页面,图谱页签看条目之间的链接关系。页面可以直接编辑、新建、移动到文件夹,每次改动都留版本、可回滚(见「人工编辑与版本历史」)。
生成过程要调大模型,成本与文档量成正比。Wiki 页面也可以通过 MCP 工具 wiki_search / wiki_read_page / wiki_index_view 读取(见 MCP 集成)。
页面模型与层级
页面类型(PageType)
internal/types/wiki_page.go 定义了 6 种页面类型:
| 类型 | 说明 |
|---|---|
summary | 单篇源文档的摘要页(slug 形如 summary/<knowledge-uuid>) |
entity | 实体页(人、组织、产品、技术等) |
concept | 概念/主题页 |
index | wiki 级索引页(元数据) |
synthesis | 综合分析页,入库管道不自动生成(保留类型,供人工整理时选用) |
comparison | 对比页,入库管道不自动生成(保留类型,供人工整理时选用) |
页面状态(WikiPageStatus):draft / published(默认)/ archived。
目录树(Folder Hierarchy)
migration 000061_wiki_page_hierarchy.up.sql 引入独立的 wiki_folders 表(邻接表模型):
WikiFolder以ParentID(空串代表根)+ 物化Path(/连接的名称链)组织树;空文件夹可独立存在,用户可以先搭好骨架;WikiPage.FolderID是页面归属的唯一事实来源(FK →wiki_folders.id,空串表示 wiki 根);- 页面上的
CategoryPath/WikiPath/Depth/SortOrder是从 folder 链派生的缓存投影; - 目录最深 3 级(常量
WikiCategoryMaxDepth = 3),CleanWikiCategoryPath()会规范化全角分隔符(/、|→/)并剔除类型标签。
关键字段
Slug:页面在 KB 内的唯一标识(见下节);SourceRefs:来源引用,格式"<knowledge_id>|<doc_title>";ChunkRefs:分块级证据引用;InLinks/OutLinks:wiki-link 反向/正向链接,维护图结构,GET /graph可查询全局或 ego 视图;Aliases:别名(用于搜索与去重合并后的旧名指向);Version:版本号。
生成流程
Wiki 生成由文档摄入(knowledge ingest)触发,经 Redis 任务队列异步执行。任务类型定义在 internal/types/task.go:
TypeWikiIngest = "wiki:ingest"
TypeWikiFinalize = "wiki:finalize"整个管道分四个阶段(Map-Reduce 结构):
| 阶段 | 任务 | 做什么 | LLM 提示词(internal/application/service/wikiprompts/prompts_wiki.go) |
|---|---|---|---|
| Pass 0:候选抽取 | wiki:ingest | 从文档抽取候选 slug 骨架(entities + concepts 的 JSON) | WikiCandidateSlugPrompt |
| Pass 1..N:分块引文 | wiki:ingest | 逐 chunk 为候选 slug 标注引用,输出 { citations: {"slug": ["c001", ...]}, new_slugs: [...] };长前缀复用 prefix caching | WikiChunkCitationPrompt |
| Reduce:页面合并 | wiki:ingest | 按 slug 增量更新或合并页面,输出 SUMMARY: ... + Markdown 正文;严格接地、禁止幻觉、去重、禁止自链接 | WikiPageModifySystemPrompt + WikiPageModifyUserPrompt |
| Finalize:收尾 | wiki:finalize | 重建索引页、清理死链、补交叉链接、目录修剪——纯 SQL/图算法,不调用 LLM | — |
辅助提示词:
WikiTaxonomyPlanPrompt:为同一批次的所有实体/概念统一规划目录路径(最多 2 级、优先复用已有文件夹),保证目录树连贯;WikiDeduplicationPrompt:判断新抽取项是否与既有页面同指一物,核心原则是 "related ≠ same"(相关不等于相同),返回{ merges: { "entity/new": "entity/existing" } }。
抽取粒度
WikiConfig(存于 knowledge_bases.wiki_config JSONB 列)中的 WikiExtractionGranularity 控制抽取密度:
| 粒度 | 行为 |
|---|---|
focused | 仅 3-7 个主要主题 |
standard(默认) | 主题 + 被实质性讨论(一段/多条/2-3 句以上)的实体概念 |
exhaustive | 穷举所有命名事物与公认概念 |
并发与批处理
WikiConfig 相关参数(internal/types/wiki_page.go):
| 参数 | 默认 | 说明 |
|---|---|---|
IngestBatchSize | 5 | 单批认领的待处理文档数 |
IngestMapParallel | 10 | Map 阶段(每文档抽取+引文)errgroup 并发数 |
IngestReduceParallel | 10 | Reduce 阶段(每 slug 写页面)并发数 |
IngestMaxInflight | 4 | 同一 KB 最大并发批次(保证跨 KB 公平) |
生成流程图
Slug 机制
格式:
<type>/<name>,如entity/acme-corp、concept/rag、summary/<knowledge-uuid>;小写、连字符分隔,非拉丁文名做罗马化/拼音;唯一性:数据库唯一索引(
000037_wiki_and_indexing.up.sql):sqlCREATE UNIQUE INDEX idx_kb_slug ON wiki_pages (knowledge_base_id, slug) WHERE deleted_at IS NULL即 slug 在单个知识库内唯一,跨 KB 可以重复;
稳定性:文档更新重新抽取时,提示词强制模型复用旧 slug——
If an entity or concept from the previous extraction still exists in the current document, reuse its exact slug from the previous list. Do NOT generate a new slug for the same thing.
只有新出现的事物才生成新 slug,消失的项不再输出;
Slug Handle(句柄代理):ingest 的 LLM 调用中,高熵的真实 slug(尤其含 UUID 的
summary/...)会被替换为短句柄(ref-1、ref-2),模型输出[[ref-1|title]]后由后端还原为真实 slug,避免模型抄错 UUID(internal/application/service/wiki_slug_handles.go);引用作用:页面正文中的 wiki 引用以
[[slug|title]]形式互相链接,InLinks/OutLinks依 slug 维护页面图;更新页面修改 slug 时会自动更新所有反向链接。
发布与访问
所有 Wiki 路由挂在 /api/v1/knowledgebase/:kb_id/wiki 之下(internal/router/routes_knowledge.go 的 RegisterWikiPageRoutes),没有免登录的公开访问模式,读写均受 RBAC 与 KB 访问控制约束:
读接口(Viewer + KBAccess)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /pages | 页面列表 |
| GET | /pages/*slug | 按 slug 取单页 |
| GET | /folders | 目录树 |
| GET | /index | 索引页 |
| GET | /graph | 链接图(全局概览 / ego 模式) |
| GET | /stats | 统计 |
| GET | /search?q=... | 搜索 |
| GET | /lint | 质量检查结果 |
| GET | /revisions/*slug | 版本历史列表;带 ?version=N 取该版本全文 |
KBAccess 要求知识库属于调用者的工作区(别的工作区的知识库返回 404)。
写接口(OwnedWikiKBOrAdmin + KBAccess)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST / PUT / DELETE | /pages、/pages/*slug | 创建 / 更新 / 删除页面 |
| POST / PUT / DELETE | /folders、/folders/:folder_id | 目录管理 |
| PUT | /move-page | 移动页面到目录 |
| POST | /rebuild-links | 重建链接图 |
| POST | /auto-fix | 触发自动修复 |
| POST | /revert | 回滚到指定版本(body:{slug, version}) |
写权限按 KB 归属判定:贡献者只要拥有该 KB 即可管理其 wiki,否则 403。API Key 场景下读接口需要 retrieve 能力,写接口需要 ingest 能力(或全量权限)。
前端由 WikiBrowser.vue 提供浏览界面;文档解析期间 wikiStatusRefresh.ts 轮询 parse_status(pending / processing / finalizing),解析完成而摘要仍在生成时继续轮询。目录树的展开状态由 wikiDirectoryState.ts 单独维护:新建文件夹或刷新数据后,expandWikiDirectoryPath() 会把当前路径上的各级目录标记为「用户已展开」,避免刷新时整棵树塌回默认折叠状态。
人工编辑与版本历史
Wiki 页面是 LLM 生成的,难免有需要人工订正的地方。页面因此支持直接编辑,并保留完整版本历史(migration 000075)。
每个版本记的是谁改的
wiki_pages 上有两个溯源字段,last_edit_source 标记当前版本的作者类型:
edit_source | 含义 |
|---|---|
pipeline | Wiki 生成管道写的(空串也按 pipeline 处理) |
user | 人工在编辑器里改的 |
revert | 回滚产生的版本 |
配套的 last_editor_id 记录操作者(后台管道写入时为空)。界面上据此区分「这段是模型写的还是人改的」。
快照与回滚
- 每次页面被覆盖前,旧版本先整份快照进
wiki_page_revisions(标题、正文、摘要、页面类型、状态、别名,加上该版本的作者与时间);当前版本只存在于wiki_pages里。(page_id, version)上的唯一索引配合ON CONFLICT DO NOTHING,让「先快照再更新」这条写路径在重试下保持幂等; GET /revisions/*slug列历史版本(倒序,不含正文,附当前版本号);带?version=N则取该版本全文,用于 diff;POST /revert传{slug, version}回滚。回滚不是把版本号退回去,而是以目标版本的内容产生一个新版本,edit_source记为revert,因此回滚本身也可被回滚。回滚到当前版本会返回 400(通常意味着前端拿的是过期的历史列表)。
历史保留策略
无节制留快照会被管道刷爆,因此采用两级上限(internal/types/wiki_page.go):
- 软上限 50 版:只清理「可裁剪」的快照,即
pipeline写的和历史遗留的空来源; - 硬上限 200 版:不分作者一律裁剪,保证纯人工维护的页面存储也有界。
这样设计的用意是:一个热点页面被管道反复重写时,不会把用户真正在意的人工编辑挤出历史。
与问答管线的关系
Wiki 页面是问答管线的一类检索来源:rerank 之后 wiki_boost.go 会对 wiki_page 类型的 chunk 加权(×1.3),让 LLM 预综合的 Wiki 页面优先于原始分块进入上下文;被引用的 Wiki 内容同样出现在引用面板中。
质量检查:GET /lint(wiki_lint.go)按需计算问题报告,类型包括孤立页面(orphan_page)、死链(broken_link)、过期引用(stale_ref)、缺少交叉引用(missing_cross_ref)、内容过少(empty_content)、重复 slug(duplicate_slug);POST /auto-fix 自动修复其中可自动修复的项(死链改为纯文本、内容过少的页面归档)。界面上的入口是 Wiki 页签的「Wiki 检查」:打开时运行一次检查,列出健康分和问题,点问题可跳到对应页面;知识库编辑者可以一键修复可自动修复的项,修复后报告会重新计算。报告不做存储,每次都遍历全部页面,所以只在打开时运行,不随页面浏览刷新。
操作历史(知识库活动流)
Wiki 曾经维护一份独立的操作日志(wiki_log_entries 表 + GET /wiki/log 接口 + WikiBrowser 里的日志页签)。这份 feed 与知识库活动流内容重叠,已在 migration 000077_remove_wiki_log 中整体移除:表被 DROP,历史遗留的 page_type = 'log' 页面一并删除,log 不再是合法页面类型。
现在知识库活动流是唯一的操作历史入口:
- ingest 批次结束时,
wiki_ingest_batch.go汇总本批各类动作数量,调用service.RecordWikiContentActivity()写一条wiki.content_changed活动; - 人工在 WikiBrowser 中创建/编辑/删除页面时,
internal/handler/wiki_page.go同样把manual_create/manual_edit/manual_delete投影到活动流; - 活动记录落在审计日志体系(
kb_activity.go→AuditLogService),可在「知识库 → 设置 → 活动」查看,保留策略与其它审计日志一致(见可观测性与审计); - 写入是 best-effort:活动记录失败不会让 wiki 编辑本身失败。
升级注意:如果外部集成还在调用 GET /api/v1/knowledgebase/:kb_id/wiki/log,需要改用知识库活动流接口 GET /api/v1/knowledge-bases/:id/activity。
失败恢复
internal/container/recover_pending_wiki_tasks.go 在服务启动时闭合无 Redis 模式(进程内 SyncTaskExecutor)或 Redis 入队中断留下的缺口:
- 扫描持久化的
task_pending_ops表中scope = knowledge_base且task_type ∈ {wiki:ingest, wiki:finalize}的待处理组合; - 清理已删除 KB 的残留行(fail-closed);
- 对每个活跃 KB 重新入队触发任务:
wiki:ingest不带 TaskID(允许多批并发),wiki:finalize使用"wiki-finalize-" + KB_ID去重(同一 KB 只保留一个 finalize)。重复入队无害——ingest 认领互不相交的行,finalize 在 lane 内合并。
实现参考
想读源码时按下表定位(路径相对仓库根目录):
| 层 | 文件 |
|---|---|
| 数据结构 | internal/types/wiki_page.go |
| HTTP Handler | internal/handler/wiki_page.go |
| 生成管道 | internal/application/service/wiki_ingest.go、wiki_ingest_batch.go、wiki_ingest_cite.go、wiki_ingest_dedup.go、wiki_ingest_taxonomy.go |
| 页面服务 | internal/application/service/wiki_page.go、wiki_linkify.go、wiki_lint.go、wiki_slug_handles.go |
| 问答加权 | internal/application/service/chat_pipeline/wiki_boost.go |
| 活动流 | internal/application/service/kb_activity.go |
| LLM 提示词 | internal/application/service/wikiprompts/prompts_wiki.go |
| 失败恢复 | internal/container/recover_pending_wiki_tasks.go |
| 路由 | internal/router/routes_knowledge.go(行为测试见 internal/router/router_wiki_test.go) |
| 数据库迁移 | migrations/versioned/000037_wiki_and_indexing.up.sql、000061_wiki_page_hierarchy.up.sql、000075_wiki_page_revisions.up.sql、000077_remove_wiki_log.up.sql |
| 前端 | frontend/src/views/knowledge/wiki/WikiBrowser.vue、WikiRevisionDrawer.vue、wikiDirectoryState.ts、frontend/src/views/knowledge/wikiStatusRefresh.ts、frontend/src/api/wiki/、frontend/src/utils/wikiRevisionDiff.ts |