总原则:确定性外壳包围概率核心
模型只负责必须依赖语义判断的候选工作;程序接管可验证的不变量,评测系统负责判断语义结果是否达到发布门槛。
| 交给 LLM | 交给程序 |
|---|---|
| 提出实体、概念、主张与矛盾候选 | 路径合法性、格式解析、日期、来源规范 |
| 决定新建还是补充哪个领域页 | Index 更新、Log 追加、聚合页保护 |
| 生成语义合并草案 | 版本检查、结构化 diff、事务发布、历史与缓存提交 |
| 生成研究主题与答案 | 检索、排名融合、Token 预算、引用结构 |
这种边界减少模型“看起来成功”的空间:文本很漂亮不代表文件协议完整,页面写出来也不代表索引、缓存和血缘一致;工程状态一致也不代表内容真实,因此还需要独立语义评测。
源码阅读地图
src/App.tsx启动与项目水合总入口;理解配置和后台任务如何恢复。src/components/layout/app-layout.tsx三栏产品骨架与视图切换。src/stores/wiki-store.ts项目、LLM、搜索、Embedding、多模态、图谱 UI 等领域状态定义。src/lib/ingest.ts最关键 TypeScript:知识写入协议、提交顺序与部分失败语义。src/lib/source-lifecycle.ts来源的创建、变化与删除如何传播。src-tauri/src/commands/fs.rs文件系统与多格式解析主入口。src-tauri/src/commands/search.rs共享混合检索核心。src-tauri/src/agent/runtime.rs工具规划循环、事件、引用、取消和最终生成。src-tauri/src/lib.rs所有原生模块的装配根。mcp-server/src/index.ts外部 Agent 接入面的工具定义。先读项目模板和数据目录,再读 ingest;之后读 search/graph;最后进入 Agent runtime。直接从最大文件 runtime.rs 开始,容易只看到复杂度而看不到领域模型。
状态与持久化边界
内容资产
raw/ 与 wiki/ 是用户资产,可由 Obsidian、Git 和文本工具直接使用。
项目运行状态
.llm-wiki/ 保存聊天、审核、摄入缓存、队列、文件历史、图片描述缓存和 Skill。
全局偏好
Tauri Store 的 app-state.json 保存最近项目、Provider、代理、主题、语言和服务设置。
可见生成物
agent-workspace/ 明确不隐藏,便于用户发现、预览和带走 Agent 生成的文件。
聊天保存有竞态保护:项目切换与异步水合同时发生时,会检查当前项目身份并合并期间新产生的消息,避免旧加载覆盖新会话。
文件写入与历史
- 原子写:提供
write_file_atomic,先写临时文件再替换目标,降低崩溃留下半文件的概率。 - 页面历史:写入前把旧版本保存到
.llm-wiki/page-history/,记录时间、作者与工具,可恢复。 - 内容清洗:生成文件统一规范 frontmatter、sources 和日期。
- 受管页面:Index 与 Overview 不接受模型直接输出,避免大 Wiki 全量重写和并发覆盖。
- 解析警告:非致命异常进入活动面板和持久日志,不隐藏在 DevTools。
- 刷新与失效:写入后统一刷新文件树、递增 dataVersion,让图谱缓存与 UI 同步。
write_file_atomic 保护的是单个目标文件,页面历史提供恢复材料;一次摄入涉及多个页面、Index、Log、缓存与向量,目前没有共同事务提交。不要把“单文件原子”写成“知识库原子”。
删除比创建更难
来源删除不是简单 unlink。系统必须同时维护知识血缘:
- 删除原始文件、解析缓存、解析器标记与摄入缓存。
- 扫描 Wiki 页的
sourcesfrontmatter,移除被删来源。 - 仍有其他来源支撑的共享页面保留并改写 sources。
- 只由被删来源支撑的页面级联删除,并清理它的媒体目录与向量。
- 从 Index、正文 wikilink 和 related 数组中移除悬空引用。
- 向 Log 追加删除原因、删除页数和保留页数。
知识页是多来源共同派生物,删除本质上是“带引用计数的数据血缘回收”。这也是持久 Wiki 相比纯查询索引多出的真实成本。
多模型兼容不是换一个 Base URL
Provider 层要处理 OpenAI Chat Completions、Anthropic Messages、Azure 特殊部署、Reasoning 参数、流式事件、错误 Envelope、代理和自定义 Header。
- Chat 与 Ingest 可以路由到不同模型,项目还能覆盖全局配置。
- Provider Preset 将默认模型与用户 override 合并,版本升级时默认值可以修正而不覆盖显式选择。
- 前端网络请求通过 Tauri Rust HTTP 插件发出,绕开第三方 API 的浏览器 CORS 限制。
- Reasoning-only 响应、
<think>流和普通正文分开检测。 - Embedding、图片描述、结构化摄入与聊天使用各自适合的 Token 和推理配置。
跨平台与坏输入
| 问题 | 实现应对 |
|---|---|
| Windows 路径分隔符 | TypeScript/Rust 边界统一成正斜杠,canonicalize 后再检查作用域。 |
| Linux WebKit/Mesa | Linux 启动前按需设置合成与 DMA-BUF 兼容环境变量。 |
| 解析器 Panic | Release 使用 unwind,Tauri Command 边界 catch_unwind 转成可展示错误。 |
| 端口冲突 | 本地服务重试绑定并公开 starting/running/conflict/error 状态。 |
| 超大文件 | 预处理缓存、长文分块、读取与 API Body 上限、渐进列表渲染。 |
| 隐藏文件与秘密 | 目录遍历跳过 .git、.cache、内部状态和敏感配置来源。 |
测试策略
仓库当前有 143 个 TypeScript 测试文件(主应用 140、MCP Server 3)和约 400 个 Rust 测试标记,测试不只覆盖纯函数。
Mock 单元测试
协议解析、路径、缓存、Token、Provider、搜索、图谱、删除和队列状态机。
Property Tests
使用 fast-check 验证路径、语言检测、审核变换等在大量随机输入下保持不变量。
Scenario Tests
测试助手把 ingest、search、lint、enrich 等组织成可复用真实内容场景。
Real LLM Tests
*.real-llm.test.ts 独立运行、不并行,验证 Mock 无法覆盖的模型行为。
测试脚本将 mocks 与 real-llm 分开,使日常 CI 可稳定快速运行,同时保留对 Provider 真实行为的验收层。MCP Server 也有自己的构建和测试。
现有测试很好地覆盖协议、路径、状态机和兼容性,但固定提交中没有发现可复现的检索评测集,也没有系统展示摘要忠实度、主张支持率、合并信息保留率或端到端故障注入结果。代码测试与知识质量评测必须分别建设。
当前工程风险与可改进点
| 风险 | 现状判断 | 建议 |
|---|---|---|
| 核心文件过大 | ingest.ts、agent/runtime.rs、agent/tools.rs、api_server.rs 都已达数千行。 | 按状态机阶段、协议解析、工具执行器、HTTP handler 拆模块,保持公共不变量集中。 |
| 缓存指纹不完整 | 摄入缓存核心绑定来源内容,没有完整绑定解析器、Schema、Purpose、Prompt、模型与流水线版本。 | 构建统一 derivation fingerprint,并提供精确失效、重建计划与迁移报告。 |
| 跨文件半提交 | 失败会诚实报错并保留已写页面,但读者和重试可观察到部分状态。 | staging + manifest + intent log;索引和向量只消费已提交版本,启动时恢复未完成事务。 |
| 主张级血缘不足 | 来源主要挂在页面 frontmatter,难以证明具体句子、时间和冲突状态。 | 为主张记录来源版本、文本跨度、有效期、置信、审核状态和派生指纹。 |
| 前后端检索双实现 | Rust 是共享主路径,但 TypeScript 仍保留图关联与部分搜索逻辑。 | 明确 canonical implementation,减少算法长期漂移。 |
| Markdown 轻 Schema | 灵活但依赖正则与 YAML 容错,页面规模增大后冲突更难。 | 引入版本化 Schema 校验报告,但保持文件仍可人工编辑。 |
| 文档/行为漂移 | README Rust 版本低于 Cargo 实际要求;README 声称摄入自动更新 Overview,但当前 ingest 路径只明确更新 Index 与 Log。 | 从清单生成环境要求;为 Overview 建立明确更新器或修正文档。 |
| 效果声明不可复现 | README 有检索提升数字,仓库没有对应数据集、脚本和报告。 | 版本化 eval corpus、标注规范、运行清单与结果;所有指标注明基线和置信区间。 |
| 本地服务信任过宽 | 已有 Token/CORS/限流,但回环与共享 Token 仍扩大入口间泄漏半径,query token 还可能进入 URL 记录。 | 拒绝 URL Token,严格 Origin/Host;按客户端、项目和能力签发可撤销短期令牌。 |
| 写时模型成本 | 两阶段、图片描述与审核提高质量,也增加延迟和费用。 | 暴露每来源成本与阶段耗时,允许按项目选择质量档。 |
这些并不否定项目;相反,它们说明产品已经从 Demo 进入“复杂度需要主动治理”的阶段。
如果从零建设,应按什么顺序
- 先写验收问题:选一组真实查询、来源更新、冲突、删除与故障场景,冻结基线和隐私边界。
- 定义知识契约:版本化 Evidence、Claim、View、Schema、Purpose 与派生指纹;明确谁是可重建资产。
- 做最小可信摄入:只支持 Markdown,候选主张绑定文本跨度,结构校验后进入 staging。
- 先实现事务与恢复:manifest、intent log、幂等 operation_id、历史、取消和故障注入要早于批量格式支持。
- 建立来源生命周期:更新、共享支撑、冲突、有效期和级联失效必须在规模化前完成。
- 从词法基线开始检索:先做可解释关键词和引用;只有评测证明有增益才加入向量、RRF 与图。
- 补语义与运行评测:持续测支持率、遗漏、检索相关性、延迟、成本和失败恢复,而非只跑单元测试。
- 最后开放 Agent/API:先完成服务端鉴权、能力隔离、审计和撤回,再把能力交给模型与外部客户端。
- 按故障收益扩格式:每种解析器和多模态路径都必须带契约测试、缓存版本和坏输入样本。
不是三栏 UI,也不是炫目的图谱;而是“放入一篇 Markdown → 生成可定位证据的候选主张 → 第二篇资料能更新或保留冲突 → 任一步崩溃都不暴露半提交 → 删除来源后派生内容准确失效”。
上线前的四级门槛
| 等级 | 必须具备 | 不能用什么冒充 |
|---|---|---|
| L0 · Demo | 单文档可生成、可检索、可人工查看来源。 | 不能把演示成功称为长期可靠。 |
| L1 · Personal | 幂等、历史、恢复、完整缓存指纹、来源更新和删除闭环。 | 不能用单文件原子写冒充整体一致性。 |
| L2 · Trusted | 主张级血缘、固定评测集、发布门槛、冲突与撤回、成本监控。 | 不能用测试数量或主观体验冒充质量。 |
| L3 · Shared / Agentic | 多主体并发、权限隔离、审计、秘密治理、Prompt 注入防护与服务 SLO。 | 不能用隐藏 UI、回环监听或 System Prompt 冒充安全。 |
一个项目可以有意停在 L1,也可以只为单用户服务。重要的是明确承诺等级,不让功能丰富度掩盖可靠性边界。
最终结论
LLM Wiki 展示了一个值得继续验证的方向:LLM 的价值可以不只体现在每次回答,也可以用于维护开放、结构化、可追踪的知识制品。 项目的强项是覆盖了摄入、查询、维护、删除和多入口接入;它的不足也很典型——实现完整度领先于证据粒度、事务语义、可复现评测和安全隔离。
开放资产、Purpose + Schema、确定性外壳和薄适配值得保留;更应补上的,是版本化证据、原子主张、完整派生指纹、可恢复提交、检索与忠实度评测、以及模型之外的最小权限边界。
ingest*、source-lifecycle*、project-mutex*、api_server.rs、agent/* 和构建清单。成熟度门槛、建设顺序与评测要求是本次审计的通用化结论。