上个月帮一个做工业设备的团队收拾他们那套本地知识库,起因特别朴素:售前把三百多页的产品手册、两百多条历史工单和一堆内部报价规则直接丢进公有云对话框里问,被合规那边看到截图,当场叫停。他们的诉求其实很典型——既要大模型那种"问一句话就能翻出答案"的体验,又不能把资料传到任何外部服务上去。折腾了两周,最后落地的就是 Ollama + Open WebUI + AnythingLLM 这套组合:Ollama 在本地跑模型,AnythingLLM 负责把文档切成片、向量化、检索,Open WebUI 给不熟悉命令行的同事当一个统一入口。
这套组合在技术圈被讲了很多遍,但大部分教程停留在"跑起来了"这一层,真正让人头疼的问题全在后面:模型答非所问、文档明明上传了却检索不到、同事第一次登录就报连接超时、跑了一周发现向量库和模型版本对不上要全部重建。这篇就把我踩过的这些坑顺着链路捋一遍,从职责划分、硬件选型、容器部署、文档入库一直讲到效果调优和内网加固。如果你正准备给自己或团队搭一套不外传的知识库,或者已经搭了一半卡在效果上,下面这些内容应该能省你不少时间。
1. 三个组件各管一段:先把职责边界划清楚
大多数教程上来就贴 docker run,结果新手跑完一脸懵:我到底该在哪个界面里传文档?为什么 Open WebUI 里也能传文件,AnythingLLM 里也能传文件,两个都传一遍吗?这个困惑的根源是没搞清楚三个组件在数据流里分别站在哪个位置。先把这张地图画清楚,后面的所有配置决策都会变得有据可依。
1.1 Ollama 在链路里到底承担什么
Ollama 的角色非常单纯:它是一个本地模型运行时,对外暴露一套兼容的 HTTP 接口,负责加载权重、管理显存、执行推理。它不懂文档、不懂切分、不懂向量,你给它一段提示词,它还你一段补全,仅此而已。把它理解成"本地版的模型 API 网关"最准确——它同时也管模型的下载、版本标签、卸载和并发调度。
这个定位带来一个很重要的实践结论:Ollama 是整个链路里唯一吃显存的组件,也是唯一需要认真做资源隔离的组件。AnythingLLM 的检索、切分、向量化(如果不走 Ollama 的 embedding 接口)几乎不占显存,Open WebUI 更是个纯前端加一层转发。所以硬件预算应该几乎全部倾斜给 Ollama 所在的机器,其他两个组件塞在同一台机器的普通内存里就行。
我见过有人在三台机器上分别部署三个组件,结果 Ollama 那台的显卡闲着,另外两台反而因为跨机调用产生了额外的网络延迟和排错成本。除非你有明确的隔离要求,单机 Docker Compose 编排是最省心的方案,容器之间走一个自定义 bridge 网络,用服务名互访,比记 IP 靠谱得多。
另外要提前想清楚的是 Ollama 的模型存储路径。默认它在用户目录下的.ollama里,容器化部署时如果不做卷映射,容器一删模型全没,几十个 G 重新拉一遍能耗掉半天。我一般的做法是单独挂一块盘到./ollama,然后通过OLLAMA_MODELS指过去,这样迁移机器的时候整个目录打包带走即可。
1.2 AnythingLLM 不是"聊天壳子",它管的是文档到答案的那段管道
很多人第一次打开 AnythingLLM 会觉得它就是个套壳聊天界面,这个印象会让你低估它的价值,也会让你在调优时找错方向。它真正干的事情是 RAG 的全流程:文档解析、文本切分、调用 embedding 模型生成向量、写入向量库、收到问题后做相似度检索、把检索到的片段拼进提示词再交给模型。这条管道上任何一个环节出问题,表现出来都是同一句话——"它答得不对"。
AnythingLLM 里有个概念叫工作区(Workspace),这是它设计上最值得称道的地方。每个工作区有自己的文档集合、自己的系统提示词、自己的对话历史。你可以给"产品手册"建一个工作区,给"售后工单"建一个,给"内部制度"再建一个,互不干扰。企业场景下这一点非常关键,因为把报价规则和公开产品手册混在一个向量空间里检索,很容易让模型把不该说的内部价格说出来。
还有一个容易被忽略的开关:文档上传后有两种状态,一种是"已嵌入"(Move to Workspace),一种是"仅存储"(Save and Embed 之外的归档状态)。只有嵌入过的文档才会参与检索。我见过不止一个人把文件拖进去就直接开问,然后抱怨检索不到,实际上是文件还躺在存储区没进向量库。这个交互设计确实有点反直觉,第一次用务必确认文档列表里那个向量图标是点亮的。
1.3 Open WebUI 的定位:管理台还是第二套前端
Open WebUI 的定位需要根据你的团队情况来定,这也是我认为最容易被教程带偏的一点。它自带一套相当完整的对话界面、模型切换、多用户管理、对话历史,功能上其实和 AnythingLLM 的聊天界面有重叠。如果两个都开着让同事用,必然出现"我在 A 界面问的和 B 界面答的不一样"这种混乱。
我的建议是二选一,让另一个退回到它最擅长的位置。有两种典型组合:
| 组合方式 | 前端入口 | 适用场景 | 代价 |
|---|---|---|---|
| Open WebUI 做主入口 | 同事只用 3000 端口 | 需要多用户、模型自由切换、对话归档 | AnythingLLM 只当后台知识库,通过 API 调用 |
| AnythingLLM 做主入口 | 同事只用 3001 端口 | 以文档问答为绝对核心,不需要切模型 | 放弃 Open WebUI 的多用户和模型管理能力 |
| 两个都开但分人群 | 技术用 Open WebUI,业务用 AnythingLLM | 团队里既有折腾模型的也有只用文档的 | 运维两套,权限要分别配 |
我个人更倾向第一种。Open WebUI 的多用户体系、登录鉴权、对话分享做得更成熟,适合直接推给非技术同事;AnythingLLM 退居后台,通过它提供的接口被调用,或者干脆只让知识库维护人员进去管文档。这样前端只有一个入口,培训成本最低,出了问题排查路径也唯一。
顺带说一句,Open WebUI 自己也有文档上传和 RAG 功能,但它对切分参数、向量库的控制粒度比 AnythingLLM 粗不少。如果你的文档结构简单、量不大,用 Open WebUI 内置的就够了;一旦涉及几百份异构文档、需要精细控制切分和检索阈值,还是把 RAG 这层交给 AnythingLLM 更稳。
2. 硬件与模型选型:显存、量化、上下文三者互相拉扯
选型这件事没有标准答案,但有一个必须遵守的原则:先算显存,再定模型,最后调上下文。顺序反了就会陷入"模型跑起来了但一提问就 OOM"的死循环。下面这些数字是我在不同机器上反复试出来的经验值,可以当估算起点。
2.1 显存估算的实际算法
权重占用很好算:参数量乘以每个参数的字节数。常见的 4-bit 量化(Q4_K_M)实际约 4.5 bit 每参数,折算下来是 0.56 字节左右,再加一点点额外开销。所以 7B 模型大约 4~5 GB,14B 大约 9~10 GB,32B 大约 19~21 GB。这只是权重。
真正让显存爆掉的是 KV Cache,它随上下文长度线性增长。粗略的经验公式是:每 1K 上下文,7B 模型约占 0.12~0.15 GB,14B 约占 0.25 GB,32B 约占 0.5 GB。也就是说,一个 14B 模型开 8K 上下文,权重 10 GB 加上 KV Cache 约 2 GB,实际要准备 13 GB 左右才安全。
把这个账算到具体配置上:
| 显卡显存 | 可流畅运行的模型 | 建议上下文 | 备注 |
|---|---|---|---|
| 8 GB | 7B Q4 | 4K | 只够轻量问答,文档一多就吃力 |
| 12 GB | 7B Q5 / 14B Q4(紧) | 4K~8K | 14B 需要限制并发 |
| 16 GB | 14B Q4 | 8K | 个人知识库的甜点配置 |
| 24 GB | 14B Q5 / 32B Q4(紧) | 8K~16K | 32B 效果明显更好但余量小 |
| 48 GB 及以上 | 32B Q5 / 70B Q4 | 16K~32K | 企业多用户场景 |
没有独立显卡也别急着放弃。7B Q4 在普通桌面 CPU 上跑,大概 3~6 token/s,问一句话等十几秒,作为个人查阅工具是可以接受的。但要注意 AnythingLLM 做检索时会先调 embedding 模型,如果 embedding 也在同一台 CPU 上跑,首次入库几百份文档可能要等很久,建议放在夜间批量处理。
2.2 中文知识库场景下的模型梯队
知识库问答对模型的要求和聊天不一样,它更看重三件事:指令遵循(能不能老老实实只根据给定材料回答)、中文长文本理解、以及拒答能力(材料里没有就说没有,而不是编)。参数量在这三件事上的边际收益非常明显,我的实测感受是 7B 到 14B 的提升远大于 14B 到 32B,但从 7B 直接跳到 32B 又是质变。
企业场景里我一般推荐 14B 级别的中文优化模型作为主力,32B 作为效果上限的备选。7B 更适合做"文档摘要""字段抽取"这类结构化任务,放到开放式问答上容易自信地胡说。还有一个常被忽略的点是模型的上下文窗口和 Ollama 实际分配的不一定一致——有些模型标称 128K,但 Ollama 默认只给 2K 或 4K 的num_ctx,你必须显式在 Modelfile 或请求参数里调大,否则 AnythingLLM 塞进去的几个检索片段会被静默截断,模型只看到最后一段,答案自然驴唇不对马嘴。
这一点我踩过实实在在的坑:某次排查一个"回答总是漏掉关键条款"的问题,查了两天检索日志都正常,最后发现是num_ctx设的 2048,而 AnythingLLM 每次塞进去的上下文有 3000 多 token,前面检索到的内容全被截掉了。改完参数立刻正常。
2.3 embedding 模型选错,后面全白搭
embedding 模型是整条管道里最不起眼但影响最大的一个环节。它负责把文本变成向量,向量质量直接决定检索能不能找对片段。生成模型再强,检索环节捞回来的是无关内容,答案也不可能对。
中文场景下我优先选多语言能力强的 embedding 模型,维度一般在 768 到 1024 之间。维度高不代表好,它只影响存储和计算开销,效果还是看训练语料。选型时有个铁律:同一个知识库里,入库和查询必须用同一个 embedding 模型。我见过有人入库时用一个模型,后来换了模型只改了查询配置,结果向量空间对不上,检索出来的东西完全是随机的。
换 embedding 模型的代价是必须重建整个向量库,没有捷径。所以在正式往里灌文档之前,先用二三十份代表性文档做个小规模验证,确认检索效果满意了再全量导入。这个前置动作能省掉后面几个小时的重复劳动。
另外 embedding 模型对硬件要求很低,几百 MB 的模型在 CPU 上跑就够快,没必要和生成模型抢显存。可以在 AnythingLLM 里把 embedding 指向一个独立的 Ollama 实例或者直接用内置的本地 embedding,把显存全留给生成模型。
3. 部署落地:Docker 网络与端口是最先翻车的地方
三个组件单独跑都不难,难在让它们互相看见。90% 的"连接失败"报错都跟容器网络、监听地址、端口映射这三件事有关。下面这份 Compose 编排是我用了很多次的基础版本,可以直接拿去改。
3.1 Ollama 的监听地址与跨容器访问
Ollama 默认只监听127.0.0.1:11434,这个默认值在裸机上是安全考虑,但在容器里就是灾难——容器内的 127.0.0.1 只指向容器自己,别的容器根本连不进来。必须显式改成0.0.0.0。
services: ollama: image: ollama/ollama:latest container_name: ollama ports: - "11434:11434" volumes: - ./ollama:/root/.ollama environment: - OLLAMA_HOST=0.0.0.0 - OLLAMA_KEEP_ALIVE=30m - OLLAMA_NUM_PARALLEL=2 - OLLAMA_MAX_LOADED_MODELS=1 restart: unless-stopped networks: - kb-net几个参数值得单独解释。OLLAMA_KEEP_ALIVE控制模型在空闲多久后从显存卸载,默认只有 5 分钟,意味着同事每隔几分钟问一次,模型就要重新加载一遍,第一次响应会慢到十几秒。设成 30 分钟或更长,体验会好很多,代价是显存一直被占着。OLLAMA_NUM_PARALLEL是并发请求数,设太大会让每个请求分到的上下文变小,反而降低单次回答质量;两个人以内用就设 1 或 2,团队用再往上加。OLLAMA_MAX_LOADED_MODELS在多模型切换场景下可以设 2,但要注意显存够不够同时装下两个。
还有个隐蔽的坑:如果你的宿主机本身已经装了 Ollama 并占用了 11434 端口,容器映射会直接失败。部署前先用ss -lntp | grep 11434确认一下端口空闲。
3.2 AnythingLLM 的环境变量与工作区创建
AnythingLLM 的容器化部署有两个关键点,一是存储目录必须挂出来,二是模型配置可以在环境变量里预置,也可以在首次启动的引导界面里填。我的经验是:环境变量只配最基础的连接信息,模型和工作区相关的配置全部在界面里做。原因是环境变量的变量名在不同版本之间改过好几次,照着旧教程抄很容易配了个不生效的参数还查不出来。
anythingllm: image: mintplexlabs/anythingllm:latest container_name: anythingllm ports: - "3001:3001" volumes: - ./anythingllm:/app/server/storage environment: - STORAGE_DIR=/app/server/storage - LLM_PROVIDER=ollama - OLLAMA_BASE_PATH=http://ollama:11434 - EMBEDDING_ENGINE=ollama cap_add: - SYS_ADMIN restart: unless-stopped networks: - kb-net注意OLLAMA_BASE_PATH这里填的是http://ollama:11434,用的是 Compose 里的服务名而不是 localhost。这就是自定义网络的价值——容器之间可以直接用服务名解析。如果你没用 Compose 而是单独docker run,那就得用--network指定同一个网络,或者退而求其次用宿主机的内网 IP。
cap_add: SYS_ADMIN这一行是为了让容器内的文档解析组件能正常工作,部分镜像版本在做 PDF 转文本时需要它。不加的话可能出现上传成功但解析出空内容的诡异现象。
启动后进 3001 端口,第一件事是在设置里确认 LLM 和 Embedding 两个 provider 都选到了 Ollama,并且点一下测试连接。测试通过再去创建你的第一个工作区。工作区名字建议带业务含义,比如"产品手册-2024",后续文档一多你会感谢自己。
3.3 Open WebUI 接入 Ollama 的两种方式
Open WebUI 接 Ollama 有两个路径,区别在于它是不是和 Ollama 在同一个容器网络里。
open-webui: image: ghcr.io/open-webui/open-webui:main container_name: open-webui ports: - "3000:8080" volumes: - ./open-webui:/app/backend/data environment: - OLLAMA_BASE_URL=http://ollama:11434 - WEBUI_AUTH=true - DEFAULT_MODELS=qwen2.5:14b-instruct-q4_K_M restart: unless-stopped networks: - kb-net这里有一个版本差异必须提醒:较早的版本用单数的OLLAMA_BASE_URL,新版本改成了复数的OLLAMA_BASE_URLS,多个地址之间用分号分隔。变量名用错了不会报错,只会表现为模型列表空空如也。判断方法很简单,进界面后看设置里的连接页面,如果一个模型都列不出来,先去核对容器日志里读到的环境变量名。
WEBUI_AUTH=true是默认值,但我要强调一下别关它。这是在公网或者公司内网暴露服务时最基本的一道门槛,关掉意味着任何能访问到 3000 端口的人都能直接使用你的模型和对话历史。
还有一点,Open WebUI 的数据卷./open-webui里存着用户账号、对话记录、配置,这个目录的备份优先级和向量库一样高。我一般把这两个目录一起放进定时快照任务里。
4. 文档入库:切分策略决定检索质量的上限
前面三步都是把管道搭起来,从这一步开始才真正决定这套东西好不好用。RAG 领域有句话我认为非常准确:检索质量的上限由切分决定,生成质量的上限由模型决定。检索没找对,模型再强也是巧妇难为无米之炊。
4.1 解析层:PDF、扫描件、表格各自的处理方式
文档解析是所有环节里最容易被低估的一步。很多人的第一反应是"PDF 拖进去不就行了",但 PDF 本质上是排版格式而不是文本格式,解析出来的东西质量差别巨大。
- 电子版 PDF(可直接选中文字):解析通常没问题,但要注意多栏排版的文档,解析器可能把左右两栏的文字交错拼接,读起来是断句的。这类文档入库前最好转成纯文本或 Markdown 检查一遍。
- 扫描版 PDF(图片型):解析出来是空的或者全是乱码,必须走 OCR。这是最常见的翻车点,判断方法是拿解析结果搜一个你确定文档里有的词,搜不到就是没解析出来。
- Word 和 Markdown:解析质量最好,结构化信息保留得完整,如果原始资料有这两种格式优先用它们。
- Excel 表格:这一类的处理最麻烦,因为表格的行列关系在纯文本里会丢失。我的做法是把每一行拼成一句自然语言再入库,比如把"型号、功率、价格"三列拼成"型号 X 的功率是 Y,价格是 Z",检索命中率会高很多。
- 图片和图片型表格:除非用多模态模型,否则建议人工转写或干脆不入库。
解析质量不达标,后面调多少参数都救不回来。我通常会在入库前抽 5 份文档,看一眼 AnythingLLM 里显示的解析文本,确认没有大面积乱码和错位再批量导入。
4.2 切分粒度与重叠的取舍
切分参数里最重要的是块大小(chunk size)和重叠(overlap)。默认值通常偏小,对中文文档不一定合适。
块太小的后果是语义被切断,比如一个条款的条件和结论被分到两个块里,检索到条件那半句,模型看到的是半截话,答案就会缺一半。块太大的后果是检索精度下降,一个块里塞了三四个不同主题,向量被平均掉了,跟任何一个具体问题的相似度都不高。
我的经验值是这样:中文技术文档、手册、制度类,块大小在 800 到 1200 字符之间比较合适,重叠设 100 到 200 字符,保证跨块边界的句子不至于完全丢失上下文。FAQ、工单这种每条本身就短的材料,块可以更小,甚至按问答对切。合同、法规这种强结构文档,最好按条款编号切,让每个块天然对齐一个语义单元。
切完之后有个快速验证方法:随便提三个你心里有答案的问题,看检索出来的片段里是不是包含答案所在的那一段。如果片段总是差一点,就是切分粒度不对,调参数重来。这个验证花不了五分钟,比后面反复怀疑模型有用得多。
4.3 向量库选型与重建成本
AnythingLLM 内置支持好几种向量库,默认用轻量的嵌入式方案,个人和小团队场景完全够用,零额外运维。企业场景如果要处理几十万级片段,可以考虑换成独立的向量数据库服务,但在那之前先想清楚:你真的需要吗?我见过文档总量不到两千份就上重型向量库的,纯属给自己增加运维负担。
比选型更重要的是建立"向量库可重建"的意识。向量库是从原始文档派生的产物,不是主数据。任何时候都应该保证:原始文档有一份完整归档,配置参数(切分大小、embedding 模型、向量库类型)有记录,那么向量库随时可以删掉重建。我在实际项目里会把这些参数写在一个 README 里放在文档目录旁边,换人的时候能直接接上。
5. 效果调优:检索不到与答非所问的排查链路
这套东西跑起来之后,最难的不是部署,是同事跑来说"它答得不对"时你怎么办。这句话背后至少对应四种完全不同的故障,不分开排查就只能瞎调参数。我一般按下面这条链路往下走。
5.1 先分清是"没检索到"还是"检索到了不会答"
第一步永远是看检索片段,而不是看答案。AnythingLLM 的回复里可以展开查看引用了哪些文档片段,这一步能直接把问题二分:
| 现象 | 判断 | 方向 |
|---|---|---|
| 引用的片段里没有答案 | 检索问题 | 切分、embedding、TopK、相似度阈值 |
| 引用的片段里有答案但回答错了 | 生成问题 | 提示词、模型能力、上下文长度 |
| 引用为空或极少 | 入库问题 | 文档没嵌入、解析失败、向量库为空 |
| 引用正确但答"不知道" | 提示词问题 | 系统提示词过于保守或过于严格 |
这个二分动作能省掉大量时间。我曾经花了半天调提示词,最后发现是那份关键文档压根没嵌入成功,因为它的文件格式解析器不支持。
5.2 相似度阈值与 TopK 的实测调整
TopK 指的是每次检索返回多少个片段。设太小,答案分散在多个片段里就会漏;设太大,无关片段混进来会干扰模型,也更容易触发上下文截断。默认值一般在 4 左右,我通常调到 5 到 8 之间做对比测试。
相似度阈值是另一道闸门,低于阈值的结果会被直接丢弃。阈值设太高,容易一个都不返回,模型只能说"资料里没有";设太低,什么垃圾片段都往里塞。这个参数没有通用值,因为不同 embedding 模型的分数分布完全不同,必须在自己数据上实测。方法是拿十个你确定知识库里有答案的问题,观察命中片段的分数分布,把阈值定在明显相关和明显无关之间那个位置。
这里有个实操技巧:用具体名词提问来校准,用抽象概念提问来验证。因为具体名词的检索最容易命中,抽象概念最难命中,两种都能过才说明参数合理。
5.3 提示词模板与引用溯源
生成环节最常见的病是"幻觉"——材料里没有,模型自己编。解决办法主要靠系统提示词约束,核心是三句话:只根据提供的参考资料回答;参考资料中没有的内容明确说不知道;回答时标注引用的文档名称。
AnythingLLM 的系统提示词是可以在工作区级别自定义的,我一般会加上一条"如果参考资料之间相互矛盾,把矛盾点列出来而不是自己选一个"。这条在处理多版本手册共存的知识库时特别有用,能避免模型拿旧版参数回答新版问题。
引用溯源这个功能强烈建议开着。它不只是给用户看个出处,更重要的是给你排查问题提供了抓手——用户说答错了,你点开引用就知道是检索错了还是模型读错了,责任划分清清楚楚。没有引用的话,所有问题都会变成"我觉得它答得不对"这种无法复现的反馈。
6. 企业场景的权限、审计与内网加固
个人用和团队用是两码事。个人场景下能跑就行,团队场景下要考虑谁能看哪些文档、出了问题怎么追溯、服务怎么保证不中断。这一节讲的是那套标题里"安全可靠"四个字真正的落点。
6.1 多用户与工作区隔离
AnythingLLM 支持多用户,但它的权限模型相对简单,主要是按角色分管理员和普通用户。真正做隔离要靠工作区:把不同部门、不同密级的资料分到不同工作区,然后只给对应的人开权限。
这里有个现实限制要提前说明:这套组合的工作区隔离是应用层的,不是存储层的。也就是说,所有向量数据都在同一个数据库文件里,管理员账号能看到全部。如果你的场景要求"连管理员也不能看某个部门的资料",那这套组合做不到,需要上更重的方案,比如按部门拆成多套独立部署,各自独立的存储卷和端口。
对大多数中小企业来说,应用层隔离已经够用了。关键是把"管理员账号只给一两个人"这条规矩立住,别图省事所有人共用一个账号,那样审计日志等于没有。
6.2 反向代理下的鉴权与会话
把服务直接暴露在内网 IP 加端口上,时间长了必然出现"谁都能访问"的局面,因为链接一旦在群里传开就收不回来了。正式一点的做法是在前面加一层反向代理,统一做 TLS、访问控制和日志。
代理层配置有几个坑:
- 长连接超时:模型推理可能耗时几十秒,默认 60 秒的代理超时会让请求提前断开,表现为"问了没反应,刷新后历史里也没有"。把读超时调到 300 秒以上。
- 流式输出被缓冲:如果代理开了响应缓冲,打字机效果会消失,用户盯着空白页面等半天然后一次性弹出全部内容。需要关闭代理对响应的缓冲。
- 上传大小限制:批量导入文档时单次请求体积可能很大,默认的 1MB 限制会直接 413。按需调大。
- 登录态与会话:如果代理做了统一认证,要确认转发时保留了必要的头部信息,否则会出现登录后反复跳回登录页的情况。
我个人偏向的做法是:只在公司内网开放,通过内网 DNS 给一个易记的域名,前面挂代理做 TLS,然后用代理的访问日志作为最基础的使用审计。不追求复杂的单点登录集成,够用就好。
6.3 备份与模型/向量库版本管理
这套系统里有三份东西需要备份,重要程度排序是:原始文档、应用数据目录、模型权重。
原始文档是主数据,必须完整归档,我一般用同步盘或者版本库管理。应用数据目录里装着账号、配置、对话历史和向量库,直接打包备份即可,但要注意备份时容器最好处于停止状态或者用支持一致性的快照,避免拷到写了一半的数据库文件。模型权重其实不用备份,重新拉一遍就行,记下用了哪个模型和哪个量化标签更重要。
版本管理这块我要多说一句:把"用了哪个模型、哪个 embedding、什么切分参数"当成配置项记录下来。升级模型是很常见的需求,但升级之后如果效果变差,你需要能快速回退。如果没记录清楚,回退都无从下手。我的做法是在文档目录里放一个config.md,每次改参数就记一行,带上日期,简单但极其有效。
另外提醒一下,升级 AnythingLLM 镜像版本前先备份存储目录,也留意一下 changelog 里有没有涉及向量库结构变更的说明。这类升级偶尔会触发向量库迁移,虽然大多数时候是自动的,但万一失败你得有备份可以回。
7. 几个反直觉的实测结论
写完上面这些,还有几条经验是没法归到具体章节里、但我觉得比配置参数更值钱的,单独拎出来说一下。
**第一,模型不是越大越好,先解决检索再谈模型。**我见过太多人花几天时间折腾怎么在有限显存里塞进更大的模型,结果检索出来的片段就是错的,换什么模型都答不对。资源有限的时候,优先级应该是:先把切分和 embedding 调好,再上更大的生成模型。前者的投入产出比远高于后者。
**第二,知识库不是一次性工程,是需要持续养护的。**手册会改版、制度会更新、工单会累积。我建议给知识库定个节奏,比如每月检查一次失效文档、把过期版本移出工作区。放任不管的结果是向量库里新旧版本共存,模型经常拿旧参数回答新问题,而用户完全不知道。
**第三,给非技术同事准备一份"怎么问"的说明,比调十个参数有用。**这套系统的表现高度依赖提问方式。带具体名词、具体型号、具体条款编号的问题,检索命中率远高于"这个产品怎么样"这种模糊表述。我一般会在界面首页放一段简短的提问示例,效果立竿见影。
**第四,留一个人能看懂整条链路。**这不是技术问题而是组织问题。这套东西的每个环节都能独立出故障,如果团队里没人能说清"文档从上传到变成答案经过了哪几步",那每次出问题都只能重启试试。哪怕只是画一张图贴在文档里,标清楚每一步用的是什么组件什么参数,维护成本都会降一大截。
最后分享一个小技巧:部署完成之后,先别急着灌全量文档。挑二十份最有代表性的、你最熟悉内容的文档先入库,然后自己出二十道题,涵盖"能直接找到答案""需要跨文档综合""文档里根本没有答案"三种类型,把准确率记下来作为基线。这份基线在你后面调任何参数、换任何模型的时候都是唯一的判断依据。没有基线的话,所有调优都是凭感觉,改好改坏自己也不知道。