☰
DeepSeek Harness:Obsidian 可溯源本地知识库实践
2026/10/23 7:08:23 网站建设 项目流程

1. 先把“Harness”这个词掰开看:它挂的到底是什么

给 Obsidian 接一个 DeepSeek 的对话框,这事儿在社区里早就不新鲜了。真正难的部分从来不是“让它开口”,而是“让它开口的时候,脑子里装的是我自己那几千篇笔记”。我见过太多人折腾一晚上,插件装上了,Key 填进去了,问一句“帮我总结下我的项目笔记”,结果模型一脸茫然地反问你“请问您指的是哪个项目”。这不是模型不行,是链路只通了半截。

所以我更愿意把这次要做的东西叫DeepSeek Harness——借用“挽具、挂载架”这层意思:它不是模型本身,也不是编辑器本身,而是把模型的推理能力、你本地的笔记语料、以及两者之间的检索与拼装逻辑,牢牢扣在一起的那套装置。搭好之后你得到的不是“一个能聊天的笔记软件”,而是一个读过你全部笔记、知道你自己那套术语和黑话、回答时还能指出出处是第几篇的本地 AI 知识库。这件事适合两类人:一类是笔记量已经过了五百篇、开始记不住自己写过什么的;另一类是笔记不算多,但希望把“查资料—理解—归档”这条链路压缩成一步的。前者受益于检索,后者受益于生成,两拨人都能用同一套装置。

需要先声明一点:标题里的 Harness 并不是某个官方标准组件名,市面上也没有一个叫这个名字的现成模块。下面讲的结构,是我按“一个合格的从业者在这种情况下最可能采用的方案”补全出来的,属于工程约定,不是唯一答案。你可以照抄,也可以按自己的习惯换掉其中任何一层,只要层与层之间的接口别乱。

1.1 只装聊天框为什么会翻车

先讲清楚失败的原因,后面所有设计才有依据。纯对话插件的工作方式是:你选中一段文字,或者手动粘贴一段内容,它把这段内容塞进请求里发出去。也就是说,上下文完全由你手动搬运。笔记十条以内时这没问题,笔记上千条时,你根本不知道自己该搬哪一段,于是只能凭记忆去搜关键词,搜完再贴,贴完再问——AI 省下的那点写作时间,全被“找素材”吃掉了,净收益接近零。

更隐蔽的一个坑是术语漂移。你自己的笔记里,某个模块可能被你叫作“调度层”,在另一篇里又叫“路由模块”,第三篇里写成了“分发器”。字面检索一条都对不上,向量检索也未必能把它们聚在一起,因为它们在你脑子里的语义是同一个,但在文本里长得完全不一样。这时候如果没有一个稳定的中间层去做同义归并和上下文扩展,模型只会照着你最后一次输入的说法回答,前面那些笔记等于白写。

1.2 Harness 的四层结构,少一层都别扭

我把整套装置拆成四层,从上到下依次是:

层级职责典型实现缺了会怎样
交互层触发时机、选区、快捷键、结果展示编辑器内命令 + 侧边栏面板每次都要手动复制粘贴,用两次就不想用了
编排层拆问题、决定检索几次、拼装上下文、控制预算一段本地脚本或插件内逻辑简单问题也走全量检索,慢且贵
检索层分块、向量化、关键词匹配、融合排序本地嵌入模型 + 向量表 + 全文索引模型只能靠瞎猜,开始编造
模型层理解与生成DeepSeek 的对话模型接口没有推理能力,只剩关键词搜索

交互层决定你“愿不愿意用”,编排层决定“快不快、贵不贵”,检索层决定“答得准不准”,模型层决定“说得顺不顺”。大部分人只盯着第四层,觉得换个更强的模型就万事大吉,结果折腾一圈发现瓶颈在第一层和第三层——你懒得用,或者它找错了材料。我的建议是:预算和时间优先投在检索层和交互层,模型层够用就行。

1.3 三条路线的取舍:全云端、全本地、混合

路线选择是第一个真正的决策点,选错了后面全是返工。

全云端:笔记切片直接发给模型服务商做嵌入和生成。优点是零配置、效果稳、中文语义质量高;缺点是每问一次都要上传一批原文,对内容敏感的人来说心里不踏实,而且笔记量大的时候嵌入成本会累积。适合笔记内容本身就是要公开的写作素材。

全本地:嵌入模型跑在自己机器上,生成也尽量用小参数模型。优点是数据不出机器;缺点是笔记本上跑 7B 级别的模型,生成质量和速度都明显打折,尤其涉及长上下文归纳时,容易答得又慢又空。

混合:嵌入在本机跑(数据不出门),生成交给远程接口(只要召回后的片段)。这是我最终选的方案,也是我认为在“效果、成本、隐私”三角里最平衡的一点。代价是:召回的内容仍然会离开本机,所以我在下面第 2 章会专门讲怎么用目录隔离把“能出去的”和“不能出去的”分开——这一点比选哪家模型重要得多。

提醒:不管走哪条路线,先把“哪些目录允许参与检索”这件事定死,再动手写代码。反过来做的人,八成会在某次误召回之后推倒重来。

2. 动手前的准备工作:目录、元数据与密钥

很多人上手第一件事是去申请 API Key,我觉得顺序反了。应该先把仓库结构理顺。因为检索质量的上限,在文件被切块之前就已经被目录和元数据决定了。你笔记堆得越乱,后面调参调得越痛苦,而且这种痛苦是调参解决不了的。

2.1 目录约定:给“可检索”划一条硬边界

我用的是一套很朴素的数字前缀目录,不是为了好看,是为了让脚本能靠路径前缀做白名单判断:

/00-Inbox 临时收集,默认不参与检索 /10-Notes 长期笔记,主力检索区 /20-Projects 项目文档,参与检索但权重降低 /30-Reference 外部资料摘录,参与检索 /90-AI AI 输出落地区,只写不读 /99-Attach 附件,跳过 /.ai-index 索引文件,完全排除

关键在于/90-AI这个目录。AI 生成的内容如果混进检索库,下一次检索会把上一次的推测当成事实证据,几轮之后整个库就被自己的回声污染了。这叫索引自噬,是本地知识库最常见也最难发现的质量衰减原因。我的做法是物理隔离:写回只往/90-AI写,检索白名单里永远不含这个路径。脚本里就是一行判断,但这一行能省掉你半年后的一次大清理。

另外建议给每篇笔记的第一个一级标题加上固定的命名习惯,别用“未命名”“新建文档 3”这种。分块器通常靠标题层级切段,标题质量直接决定块的语义完整性。

2.2 frontmatter 字段:让检索能按“意图”过滤

纯文本检索的局限在于它不知道你的意图。问“上季度的复盘结论”和问“某个概念的原始定义”,在文本上可能命中同一批笔记。frontmatter 就是给检索加的一层结构化筛选条件。我固定用这么几个字段:

--- type: note # note / project / reference / log status: active # active / archived / draft topic: [检索, 索引] # 主题标签,2-4 个为宜 updated: 2025-01-01 confidence: high # 我对这条内容的确定程度 ---

status是最有用的一个。draft和archived的笔记在检索时降权或者直接排除,能显著减少“拿半成品当结论”的情况。confidence这个字段稍微反直觉,但实测很有价值:问事实性问题时优先召回high,问探索性问题时把low也放进来,因为那些往往是你还没想清楚、最需要 AI 帮忙理一理的段落。

topic建议控制在 2 到 4 个。我试过给一篇笔记打十几个标签,结果标签本身变成了噪声,检索时反而稀释了相关性。

2.3 密钥与网络边界的处理

API Key 千万不要写进笔记正文,也不要用明文塞进插件配置的可见字段里然后同步到云端仓库。我的做法是:Key 存在系统环境变量里,插件和脚本都读环境变量。Obsidian 的插件配置是明文 JSON,跟着仓库一起同步的话,等于把钥匙贴在门上。

# 存进用户级环境变量,不要写进仓库任何文件 export DS_API_KEY="sk-xxxxxxxx"

如果你在多个设备上用同一个仓库,每台机器单独配一遍,别图省事共享配置文件。同理,.ai-index目录也要加进忽略规则——索引文件里存的是笔记原文的切片,同步出去等于把内容又复制了一份。

注意:本地嵌入服务默认监听127.0.0.1,别图方便改成0.0.0.0。局域网里任何人扫到端口就能读你的笔记向量,虽然向量不直接等于原文,但配上字典攻击基本能还原大意。

3. 核心实现:把 DeepSeek 接进 Obsidian 的三条链路

结构定好了,开始接线。我把它拆成三条互相独立的链路,这样可以分批上线、分批验证,出问题时也能迅速定位是哪一段断了。三条链路分别是:对话直连(最快见效,用来验证 Key 和网络)、本地嵌入与索引(最费时间,决定质量上限)、混合检索与上下文组装(最需要调,决定最终体验)。

3.1 链路一:对话直连,十分钟先跑通

这一步的目的不是好用,是确认地基没问题。DeepSeek 的接口兼容常见的对话补全协议,这意味着社区里绝大多数支持自定义接口地址的 AI 类插件,改两个字段就能用,不需要自己写插件。

社区插件市场里的 AI 插件大致分三类:对话类、向量检索类、模板生成类。对话类那批基本都支持自定义接口,配置项通常是这几个:

接口地址: https://api.deepseek.com 模型名: deepseek-chat 密钥: 从环境变量读取 温度: 0.3 最大输出: 2000

温度我建议先设 0.3。做知识库问答时,创意是负资产,你要的是“照着材料说”,不是“自由发挥”。等链路全通了,再考虑给写作类任务单独开一个高温预设。

验证方法很土但有效:随便选一段笔记,问它“这段里提到了哪几个具体的数字”,看它是不是只从材料里找答案。如果它开始补充材料里没有的数字,说明你的提示词里缺了“只依据给定材料回答”这条约束,先别往下走,把这条补上。

3.2 链路二:本地嵌入与向量索引

这是整套装置里最耗时的部分,也是最值得认真做的部分。思路很直接:把每篇笔记切成块,用本地嵌入模型把每块转成一串数字(向量),存进本地数据库。检索时把问题也转成向量,算相似度,取最像的几块。

嵌入模型我选本机跑的轻量多语模型,通过本地推理服务暴露 HTTP 接口。这一步的好处是:笔记原文永远不出本机,只有最终被选中的那几块会随问题一起发出去。

索引脚本的核心逻辑大概是这样:

# build_index.py 仅示意核心流程,字段可按需增删 import hashlib, json, sqlite3, time from pathlib import Path import requests VAULT = Path("./vault") ALLOW = ("10-Notes", "20-Projects", "30-Reference") DB = VAULT / ".ai-index" / "index.sqlite3" EMBED_API = "http://127.0.0.1:11434/api/embeddings" MODEL = "bge-m3" def chunk_by_heading(text, max_chars=900, overlap=120): """按二级/三级标题切块,超长段落再按长度二次切分""" blocks, buf = [], "" for line in text.splitlines(): if line.startswith("## ") and len(buf) > 200: blocks.append(buf) buf = line + "\n" else: buf += line + "\n" if buf.strip(): blocks.append(buf) out = [] for b in blocks: if len(b) <= max_chars: out.append(b) else: step = max_chars - overlap for i in range(0, len(b), step): out.append(b[i:i + max_chars]) return out def embed(text): r = requests.post(EMBED_API, json={"model": MODEL, "prompt": text}, timeout=60) r.raise_for_status() return r.json()["embedding"] def main(): db = sqlite3.connect(DB) db.execute("""CREATE TABLE IF NOT EXISTS chunks( id TEXT PRIMARY KEY, path TEXT, heading TEXT, body TEXT, vec TEXT, updated REAL)""") seen = set() for sub in ALLOW: for f in (VAULT / sub).rglob("*.md"): text = f.read_text(encoding="utf-8") for i, c in enumerate(chunk_by_heading(text)): uid = hashlib.sha1(f"{f}#{i}".encode()).hexdigest() seen.add(uid) vec = embed(c) db.execute( "INSERT OR REPLACE INTO chunks VALUES(?,?,?,?,?,?)", (uid, str(f), f"chunk-{i}", c, json.dumps(vec), time.time())) db.commit() # 清理已删除文件的残留块,防止索引膨胀 rows = db.execute("SELECT id FROM chunks").fetchall() for (uid,) in rows: if uid not in seen: db.execute("DELETE FROM chunks WHERE id=?", (uid,)) db.commit()

这里有两个细节容易被忽略。第一是增量更新:全量重建在几百篇笔记时还能忍,上千篇就要跑很久,所以脚本里用INSERT OR REPLACE配合固定 id,只重算变化过的文件。第二是残留清理,代码最后那段看着不起眼,但如果不做,删掉的笔记会继续被检索到,你会对着一个“幽灵笔记”抓狂半小时。

块大小我定在 900 字左右、重叠 120 字。为什么是这个数,第 4 章会给出计算过程。

3.3 链路三:混合检索与上下文组装

只用向量检索有个经典缺陷:它擅长“意思相近”,不擅长“字面精确”。你搜一个具体的编号、一个函数名、一个日期,向量可能给你一堆意思相关但答案错误的块。所以我在向量召回之外,再叠一层关键词全文索引,两路结果融合。

本机的 SQLite 自带全文检索能力,中文场景下用 trigram 分词最省事,不需要额外装分词库:

CREATE VIRTUAL TABLE IF NOT EXISTS fts USING fts5( body, path UNINDEXED, tokenize='trigram' );

两路结果的融合我用的是倒数排名融合(RRF),公式很朴素:每路召回里排第 n 的块,贡献1 / (k + n)分,k 取 60,把两路分数加起来重排。这个做法的好处是不需要关心两路分数的量纲差异——余弦相似度是 0 到 1,BM25 是几十上百,硬相加没有意义,而 RRF 只看排名,天然可比。

def rrf(vec_hits, kw_hits, k=60, top_n=8): score = {} for rank, hid in enumerate(vec_hits, 1): score[hid] = score.get(hid, 0) + 1 / (k + rank) for rank, hid in enumerate(kw_hits, 1): score[hid] = score.get(hid, 0) + 1 / (k + rank) return sorted(score.items(), key=lambda x: -x[1])[:top_n]

拿到 top_n 之后,组装提示词的顺序也很讲究。我按“最相关的放最前,次相关的放最后”排列,中间放问题。原因是长上下文里模型对开头和结尾的注意力更集中,把最可能的答案放在两端,命中率更高。每块前面加上文件路径和标题,方便模型在回答里标注来源。

3.4 写回与版本保护

AI 生成的内容怎么落地,是个容易被轻视的环节。直接覆盖原笔记是灾难,追加到原笔记也会污染原文。我固定写到一个带时间戳的新文件里:

/90-AI/2025-01-01-1432-关于检索召回率的讨论.md

文件头自动写入source: [被引用的笔记路径列表]和model: deepseek-chat,把溯源信息固化下来。这样三个月后你翻到这篇,能立刻知道它是哪来的、基于哪些材料,而不是面对一堆不知道谁写的文字。

心得:写回文件我建议默认只读(在文件属性里设为只读,或者用插件加个标记)。人对自己写的东西有修改惯性,对 AI 写的东西有信任惯性,只读能强迫你至少复制一份再改,这个动作本身就是一次审视。

4. 分块与检索参数怎么定:一份能抄的计算过程

参数这东西,抄别人的数值不如理解推导过程。因为你的笔记风格、问答习惯、模型上下文窗口都不一样,直接套用别人的 512 或者 1024 大概率不合适。这章把三个关键参数的计算过程写清楚,你可以照着算一遍自己的。

4.1 分块大小:900 字是怎么来的

分块要同时满足三个约束:语义自足(一块里能读完一个完整意思)、检索精度(块太大,相似度被稀释)、上下文预算(召回几块不能撑爆窗口)。

先看语义自足。我的笔记里,一个小节平均 300 到 600 字,一个完整的论证段落大约 700 到 1000 字。块切到 300 字以下,经常出现“这一段在说某个方案的缺点,但方案是什么在上一块里”,检索命中后模型拿不到前提,只能猜。切到 2000 字以上,一块里混了三四个主题,向量把它们的语义平均掉了,反而不容易命中任何一个。

再看精度。经验规律是:块内主题越单一,向量越“纯”,检索越准。900 字大致对应我笔记里的一个三级标题下的完整内容,主题单一性还可以。

最后看预算。DeepSeek 对话模型的上下文窗口足够大,但我们不需要用满——召回内容越多,噪声越多,模型越容易分心。假设一次召回 8 块,每块 900 字,中文粗略按 1 个汉字约 0.6 到 0.7 个 token 估算,就是 8 × 900 × 0.65 ≈ 4680 token,加上系统提示词、对话历史、输出预留,总共在 8K token 以内,占窗口的比例很低,延迟和成本都可控。

三个约束取交集,900 字(约 600 token)是个舒服的位置。重叠 120 字的理由更简单:中文段落跨块时,最怕的是把一句话从中间劈开。120 字大概是一到两个完整句子的长度,能保证边界处的句子至少有一块是完整的。

4.2 top-k 与重排:8 是个起点,不是终点

top-k 取多少,取决于你的问题类型。我做过一轮粗糙的对比:同一批 30 个问题,k 分别取 4、8、12、20,人工判断答案是否包含正确信息。

top-k命中率(主观评估)平均响应时间主要问题
4明显偏低快正确块经常排在第 5 到 8 位,被截掉
8明显改善可接受偶尔混入弱相关块
12略有提升变慢噪声开始影响归纳类问题
20基本持平慢模型开始抓错重点,答案变啰嗦

结论是 8 是个不错的默认值,但真正提升命中率的不是加大 k,而是加一层重排:先用向量粗略召回 30 块,再用一个小的交叉编码模型或者直接让对话模型给这 30 块打分,取前 8。粗排快、精排准,这是检索系统的标准套路。重排那一步会多一次调用,延迟增加大概几百毫秒,但换来的是命中率明显上升,我觉得这笔交易划算。

如果不想引入重排模型,有个廉价替代方案:让模型自己挑。把 20 块的标题列表(不是全文)先发给模型,让它选出最相关的 6 个标题,再把全文取回来组装。多一次往返,但省掉了重排模型的部署成本。

4.3 阈值与拒答:宁可说不知道

相似度阈值这块,很多人不敢设,怕“本来能答的答不出来”。我的观点相反:知识库场景下,编造的代价远高于拒答。一个说“我在你的笔记里没找到相关内容”的回答,你顶多再问一次;一个编造的回答,你可能直接拿去用了。

具体做法是给融合分数设一条下限,低于这条线就触发拒答分支,返回固定话术并附上“最接近的三篇笔记”作为提示。阈值不要拍脑袋定,拿 20 个你明确知道答案在库里的问题、20 个明确不在库里的问题,跑一遍看分数分布,取两类分布的交界处。我这边跑下来,交界大概在 RRF 分数 0.015 附近,低于这个值基本就是没找到。

这套校准过程花了我一个下午,但它带来的收益是长期的:你敢信它的回答了。这比任何参数调优都值钱。

5. 提示词与输出规范:让每句话都带出处

检索把材料摆上桌了,接下来是让模型“照着材料说话”。这一步的成败几乎全在提示词里,模型本身的能力差异反而没那么关键。我踩过的最大的坑是:早期提示词写得太客气,模型就顺着自己的知识往下聊,聊得挺顺,但一半内容不在我的笔记里。

5.1 系统提示词模板

现在用的版本大概是这样,可以直接拿去改:

你是我的笔记检索助手。规则如下: 1. 只能使用【材料】中的内容回答,不得引入材料之外的知识。 2. 每一条结论后面用 [序号] 标注来源,序号对应材料的编号。 3. 如果材料不足以回答,直接回复"笔记中未找到相关内容", 并列出材料中最接近的三个片段标题。 4. 材料之间如果存在矛盾,明确指出矛盾点并分别标注来源, 不要自行裁决哪一方正确。 5. 用中文回答,语言简洁,不要复述材料原文超过两句。

第 4 条是我后来加的,价值超出预期。笔记库里的内容本来就是不同时间写的,前后观点不一致很正常。早期的提示词没管这个,模型会自己挑一个看起来更合理的说法,把另一半悄悄丢掉。现在它会告诉我“关于这个问题,你在两篇笔记里的判断相反”,这本身就是极有价值的提醒——矛盾往往正是你还没想清楚的地方。

第 5 条是为了控制啰嗦。不加这条,模型倾向于大段引用原文,看着很充实,实际上你读完还得自己提炼一遍。

5.2 引用格式与可点击跳转

光有[1][2]这种编号还不够,你得能点回去看原文。我的做法是让模型输出 Obsidian 的双链格式:[[笔记路径#标题]]。这样回答里的引用直接能点开跳转,从“AI 告诉我”变成“AI 带我去看”。

实现上就是在组装材料时,给每块加一个可识别的头部标记:

【材料 3】path=10-Notes/检索设计.md#分块策略 <正文>

然后在系统提示词里要求“引用时使用材料头部的 path 值,写成双链”。实测模型对格式的遵守程度还不错,偶尔会漏掉#后面的标题部分,但路径基本没错。

提醒:如果你的笔记文件名里有空格或者特殊符号,双链会失效。要么统一改成用连字符连接,要么让脚本在生成引用时做转义。这个坑我踩了两次,两次都是因为新建笔记时随手起了个带空格的名字。

6. 实测中最常见的八类问题与排查路径

下面这些是我和身边几个人在搭这套东西时反复遇到的,按出现频率排序。问题本身不难,难的是现象和原因之间隔着好几层,容易往错的方向查。

6.1 问题速查表

现象最可能的原因排查动作
请求直接返回鉴权失败环境变量没被继承到 GUI 进程从终端启动应用,或用绝对路径读配置
短时间大量报限流批量嵌入时并发过高并发降到 2 到 3,加 200ms 间隔
明明有相关笔记却检索不到该目录不在白名单,或索引未更新检查路径前缀,手动触发增量索引
回答内容对但来源标注错组装材料顺序与编号不一致打印最终提示词,逐条核对编号
回答越来越像自己抄自己AI 输出目录被纳入检索检查白名单,确认/90-AI已排除
中文检索命中率差全文索引未用 trigram 分词重建 FTS 表并指定 tokenizer
索引文件越来越大删除的笔记残留块未清理执行残留清理逻辑
首次响应要等十几秒冷启动 + 全量索引扫描预热嵌入服务,索引加路径索引

6.2 几个反直觉的坑

坑一:不是模型越强答案越准。我一度把生成模型换成参数量更大的版本,结果归纳类问题的答案反而变啰嗦了。原因是大模型更倾向于“把话说圆”,遇到材料里没有的部分,它会用合理的推测补上,而不是老实说没有。后来我换回中等规模,配上更严格的提示词,答案反而更贴材料。约束比能力更重要,这个结论在知识库场景里几乎总是成立。

坑二:检索不到,八成不是阈值问题。遇到“明明有却搜不到”,大部分人的第一反应是调低阈值。但真正的原因往往是分块把关键信息切碎了,或者那篇笔记的status是draft被降权了。我的排查顺序固定是:先看那篇笔记在不在白名单,再看它被切成几块、每块内容是什么,最后才动阈值。前两步能解决八成问题。

坑三:慢的根因经常在文件系统。索引跑得慢,很多人去优化向量计算,其实瓶颈可能是一次性扫描几万个附件文件。排除99-Attach之后,我的索引时间从四十多秒降到七秒。这类问题不难,难在你会默认“慢就是算力不够”。

坑四:中文标点会让关键词检索失效。全角逗号、句号、书名号在分词时如果处理不当,会把一个词条切成碎片。我最早的全文索引连“分块策略”这四个字都搜不到,最后发现是分词器把中文按字节切了。换成 trigram 之后才正常。这类底层细节,文档里通常一笔带过,但能不能用往往就差在这一步。

7. 长期维护:索引更新、成本控制与笔记卫生

搭起来只是开始,真正决定这套东西能不能用上一年的是维护。我见过不少人的知识库在第三个月就荒废了,不是因为不好用,是因为“更新索引”这个动作太麻烦,慢慢地就不做了,索引一过期,检索结果开始错,错几次就不信了,然后就不用了。这是个很典型的死亡螺旋。

7.1 增量索引与触发时机

全量重建必须被淘汰。我的方案是记录每个文件的修改时间和内容哈希,只处理变化过的:

state = json.loads((VAULT / ".ai-index/state.json").read_text()) changed = [] for f in iter_notes(): h = hashlib.sha1(f.read_bytes()).hexdigest() if state.get(str(f)) != h: changed.append(f) state[str(f)] = h

触发时机上,我不建议做成“保存即索引”——写笔记的时候频繁触发嵌入计算,风扇会一直转,体验很差。我改成两个触发点:一是手动命令,写完一批笔记按一次;二是每天固定时间跑一次后台任务。前者保证重要内容及时入库,后者兜底。

增量索引还有一个隐藏收益:它让“索引健康度”变成可观测的。每次跑完打印“新增 N 篇、更新 M 篇、删除 K 篇”,如果某天突然显示“更新 300 篇”,说明你可能批量改了格式,值得看一眼。

7.2 成本与延迟的实际感受

成本这块,嵌入在本机跑,边际成本基本为零,主要开销在生成。按我每天二十来次问答、每次召回八块算,用量很小,一个月下来是很低的量级,具体数字取决于当时的计费标准,不建议按别人说的固定数去估。

延迟更值得关注。一次完整问答的耗时分布大概是:嵌入问题(本地,几十毫秒)、向量检索(几十毫秒)、关键词检索(几十毫秒)、生成(占九成以上)。也就是说,优化检索环节对体感几乎没有帮助,想更快只能减少输出长度或者换更快的模型。这个认知帮我省了不少无用功——我曾经花两天优化向量检索,最后发现总耗时只降了不到百分之三。

7.3 笔记卫生:真正决定上限的东西

最后说点不那么技术、但影响最大的事。这套装置的上限不是由模型决定的,是由你的笔记质量决定的。垃圾进,垃圾出,而且是有理有据的垃圾出,比直接搜还危险,因为它带着一层权威感。

我后来给自己定了几条规矩,执行下来效果明显。第一,每篇笔记开头必须有一句话摘要,这句话同时是检索时的重要信号。第二,术语统一,同一个东西只用一种叫法,其他叫法写进这篇笔记正文里,让向量能关联上。第三,观点和事实分开写,事实部分注明来源,观点部分标confidence。第四,定期清理draft,三个月没动过的草稿要么推进要么删掉,留着只会污染检索。

这几条跟 AI 没什么关系,但做了之后,检索命中率的提升比任何参数调优都大。我个人在实际操作中的体会是:搭 AI 知识库这件事,七分功夫在整理笔记,三分才在接模型。前三次我都是反着来的,先折腾技术,折腾完发现效果一般,然后放弃;第四次先花两周把笔记结构理顺,再接模型,一次就跑通了,而且用到现在没再怎么大改。

如果你也想动手,我建议第一步不是申请接口、不是装插件,而是打开你的笔记库,按第 2 章那套目录和 frontmatter 规范,把你最重要的五十篇笔记先规整一遍。这活儿枯燥,但它决定后面所有环节的天花板。等你把这五十篇跑通、问答准确率让你满意了,再往外扩——扩到五百篇的时候,你会发现几乎没有新的技术问题,全是体力活,这反而是最好的结果。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询