前两篇我们完成了本地部署环境的搭建,把服务骨架也跑通了。但说实话,那个阶段的系统只能算"有壳没瓤"——模型没接、知识库是空的,你问它什么,它都只能返回一句预设好的话。到了系列第三篇,终于要进入真正决定这套本地AI知识库问答系统有没有用的环节:配置模型和知识库。
这篇的内容,简单说就是两件事:让模型真正"能说话",让资料真正"能被检索到"。如果你跟着系列走到了这一步,看完这篇就能凑成一条完整可用的问答链路;如果你是半路搜到这篇的,也没关系,我会把配置的前因后果讲清楚,你理解了再回头补环境搭建那两篇,会顺畅很多。
1. 配置前先想清楚:整条链路里,模型和知识库各负责什么
1.1 一条请求的完整数据流
先别急着敲命令。动手配置之前,我建议你花五分钟把整条链路在脑子里过一遍。一个用户输入问题,到你看到答案,中间其实经历了四个环节:先是系统把问题转成向量,到向量库里做相似度检索,找出和问题最相关的几个文档片段;然后系统把这些片段和原始问题拼成一段提示词;再把提示词交给本地大模型;最后模型基于这段提示词生成回答返回给用户。
从这个数据流能很清楚地看到,配置工作天然分成两块。一块是模型端,负责的是最后一个"生成回答"的环节;另一块是知识库端,负责的是前面"文档处理、向量化、存储、检索"的环节。两者在最终问答时会汇合,但配置时完全可以拆开独立做。
这也是我踩过不少坑之后总结出来的经验:别指望一次性把所有配置都做完再测。正确做法是先把模型端单独配好、单独测通,再把知识库端配好、单独测通,最后才把两端拼进同一条问答链路里。分开配置最大的好处是,出了问题你能立刻判断是哪一边的锅,而不是对着一个报错提示无从下手。
1.2 模型配置与知识库配置为什么必须分开做
有人可能会问:既然最终是一个系统,为什么非要把模型配置和知识库配置分成两个独立的部分?这还真不是图省事,而是这套架构本身的设计逻辑决定的。
模型配置和知识库配置的职责完全不同。模型端关心的是"用哪个模型文件、怎么启动服务、回答时的温度参数是多少、上下文窗口开多大";知识库端关心的是"文档用什么方式加载、切成多长的片段、用什么嵌入模型转成向量、存在哪个向量库里、检索时召回多少条"。这两者之间唯一的关联点,就是嵌入模型和对话模型都要跑在同一个本地推理服务上,但它们在逻辑上是独立的。
这种独立性带来一个很实用的好处:可以各自替换。你发现 7B 模型回答质量不够,可以换成 14B 模型,知识库不用重建;你发现某类文档检索效果差,重新切分、重新灌库,模型也不用动。如果一开始就把两端强耦合在一起,后面任何一处的调整都会牵一发而动全身,整个系统会变得非常难维护。
2. 模型端配置:选模型、起服务、调参数
2.1 模型选型的三把尺子:硬件、量化和中文能力
模型端配置的第一步不是写代码,而是选模型。本地知识库问答场景和单纯聊天不一样,它要求模型有足够强的指令理解能力,能老老实实基于给定的资料片段作答,而不是天马行空地发挥。所以我选模型时主要用三把尺子来衡量。
第一把尺子是硬件。本地部署不是云端调用,你能跑多大的模型,完全取决于机器配置。以我的实测经验,16GB 内存起步算比较舒服的配置,如果是独立显卡且有 8GB 以上显存,体验会更好。在这个前提下,7B 量级的模型是性价比最高的选择,再往上到 14B,效果当然更好,但对硬件的要求是成倍增长的。
第二把尺子是量化等级。同样是 7B 模型,不同量化等级的体积和效果差很多。我常年用的是 Q4_K_M,这个等级在体积、速度和精度之间平衡得最好,正常运行的时候显存占用大概 5GB 左右。Q8 精度更高,但体积大不少,速度也会慢;Q2 不推荐,质量掉得厉害,省下来的那点资源不值得。
第三把尺子是中文能力。既然你的知识库主要是中文资料,那就要选在中文语料上训练充分的模型。我本地常备的是 qwen 系列,7B 指令版配合 Q4_K_M 量化,用在知识库问答里很稳。你也可以根据自己手里的资料语言情况做调整,原则就是:资料用什么语言,模型就要擅长什么语言。
2.2 用 ollama 拉模型并启动本地服务
确定模型之后,下一步是把它拉下来并启动服务。我用的是 ollama 这个开源推理服务工具,配置过程非常直接,就两个动作。
第一个动作是拉模型,把模型权重下载到本地:
ollama pull qwen2.5:7b-instruct-q4_K_M第二个动作是启动模型服务:
ollama run qwen2.5:7b-instruct-q4_K_Mollama 默认监听本地 11434 端口。服务启动后,你在浏览器或 curl 里访问http://localhost:11434就能验证服务是否正常。这一步的验证很重要,因为后续所有代码都是通过这个地址和模型通信的。
这里有一个很多新手容易忽略的点:ollama 默认只监听本机回环地址。如果你的问答服务部署在同一台机器上,那没问题;但如果你把问答服务跑在另一台机器上,或者打算从局域网其他设备调用,就必须在启动时设置OLLAMA_HOST=0.0.0.0,否则外部请求根本连不上这个服务。我在帮别人排查问题时,遇到过好几次这种情况:代码里的 base_url 明明写对了,模型服务也确实在跑,但请求就是超时,最后发现是网络监听范围没放开。
2.3 生成参数:影响回答质量的几个旋钮
模型服务起来之后,还要在接入代码时设置几个关键参数。这可不是随便填的,每个参数对回答质量都有直接影响。
最关键的参数是 temperature,也就是温度。它控制模型输出时的随机性。知识库问答场景下,我强烈建议把温度调到 0.2 到 0.3 之间。原因很简单:这时候你要的是模型"从资料里找答案",不是让它自由创作。温度调高了,模型就容易在资料之外脑补内容,知识库问答最忌讳的就是这个。
第二个参数是 top_p,控制采样的概率累计范围,一般保持在 0.9 左右就好。第三个是 num_predict,也就是最大生成 token 数,这个要看你的答案预期长度,我一般设 2048 左右。第四个是 num_ctx,上下文窗口大小,这个特别重要,它决定模型能"看到"多长的输入。知识库问答时,输入里既有提示词模板、又有召回的多个片段、还有问题本身,窗口太小的话,内容一多就被截断。
给一个我在代码里接入本地模型时的配置示例,用的是 LangChain 框架:
from langchain_community.llms import Ollama llm = Ollama( model="qwen2.5:7b-instruct-q4_K_M", base_url="http://localhost:11434", temperature=0.2, top_p=0.9, num_predict=2048, num_ctx=8192, )配完之后,先写两三行代码直接问它一个纯对话性质的问题,确认模型能正常返回,再做后面的知识库配置。模型端的独立测试到此就完成了。
3. 知识库配置:从原始文档到向量库的完整处理链
3.1 文档加载:PDF、Word、Markdown 先过一遍
模型端通了,接下来是重头戏——知识库配置。这一步是把你的原始文档变成模型能检索的东西,整个处理链是:加载文档、切成片段、转成向量、存入向量库。
第一步是文档加载。很多人觉得这有什么好说的,不就是把文件读进来吗?等你真正处理过一批真实的资料就知道坑有多深了。不同的文档格式要用的加载器完全不同,PDF、Word、Markdown、纯文本各有各的加载方式。我平时用得最多的是 PDF 和 Markdown:
from langchain_community.document_loaders import PyPDFLoader, TextLoader, UnstructuredMarkdownLoader loader = PyPDFLoader("docs/产品手册.pdf") docs = loader.load()加载完成后,别急着往下走,先打印一下文档数量和前几页的内容。这一步是为了确认三件事:能读进来、没有乱码、页眉页脚没有混进正文。我曾经处理过一份扫描版 PDF,加载完每条记录都是空文本,后来才发现这类文件必须先做 OCR,直接硬读是读不出来的。
3.2 文本切分:chunk_size 与 overlap 的平衡术
文档加载进来之后,第二个要做的操作是切分。这一步几乎直接决定知识库问答的效果上限,因为检索的基本单位就是切分后的片段。
切分有几个参数要理解清楚。chunk_size 是每个片段的长度上限,chunk_overlap 是相邻片段之间重叠的长度。我实测下来,中文资料用 chunk_size=500、chunk_overlap=50 是一个很稳的起点。为什么不把片段切得更大?因为片段太大,一个问题就会匹配到大量无关内容,模型消化不了;为什么又不能太小?因为片段太小,语义不完整,检索时容易漏掉关键信息。500 字左右,单段语义基本完整,检索粒度也合适。
overlap 的值,直觉上可能觉得重叠是浪费,但实际上非常重要。它是在防止一个语义完整的句子恰好被从中间切开,导致前半段和后半段各自都残缺。有了重叠,即使切在了句子中间,相邻片段里也还有上下文信息。
还有一个实操技巧:对于有明确章节标题的文档,别直接按固定长度切。先按标题把整个文档切成小节,再在每个小节内部按长度切分。这样能保证一个片段尽量落在同一个主题内,检索时的语义相关性会高很多。我用这个办法处理过产品手册类文档,问答准确率比直接盲目切分高出一大截。
切分代码示例:
from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", ".", "!", "?", " ", ""], ) chunks = splitter.split_documents(docs)注意 separators 里一定要加中文标点符号,否则切分器会按照默认的英文标点来切,长段落里的中文句子就没法被正确切断,导致某些 chunk 异常地大。
3.3 嵌入模型:向量化的关键一步
片段切好了,接下来要把这些文字变成向量。这一步用的模型叫嵌入模型,它做的事是把一段文本转换成一串数字,让意思相近的文本在向量空间里距离更近。检索时,用户的提问也会被转换成向量,然后去向量库里找距离最近的片段。
这里必须强调一个新手经常混淆的概念:嵌入模型和对话模型是两回事。对话模型是负责生成回答的那个,前面配的 qwen 就是;嵌入模型负责把文本向量化,它们连用途都不一样。嵌入模型可以另外选,我本地用的是 bge-m3,中文效果相当不错。
代码示例:
from langchain_community.embeddings import OllamaEmbeddings embeddings = OllamaEmbeddings(model="bge-m3", base_url="http://localhost:11434")嵌入模型有一个属性要特别记住:它的输出维度是固定的。bge-m3 这类模型的向量维度是 1024 维。这个维度直接决定了向量库里一条记录的字段结构,所以一开始选定嵌入模型之后就不要随便换,否则后面向量库会报维度不匹配的错误,这点我在后面的排查章节还会细说。
3.4 向量库初始化与入库验证
向量化能力准备好之后,就可以初始化向量库,把切好的片段正式存进去了。我用的是 Chroma,一个轻量级的本地向量数据库,对个人项目和中小型团队来说非常够用。
from langchain_community.vectorstores import Chroma vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./chroma_db", )persist_directory 是向量库的持久化目录,存的是本地文件。这个目录建议固定下来,而且尽量用相对路径,和代码放在同一个工程目录下,方便以后整体迁移。等你跑完这一段,去目录里看一眼,里面已经有大量索引文件生成,就说明入库成功了。
入库之后,别急着去拼问答链路,先做一个快速验证:从原始文档里挑一句比较有特征的话,转换成向量,再对整个向量库做一次相似度检索,看看能不能返回到相关的片段。这一步验证的是"文档确实可被检索",是知识库配置完成的标志。如果这一步都检不出来,那后面做出来的问答一定是答非所问。
4. 检索与问答链路组装:模型和知识库正式汇合
4.1 检索参数:召回数量与相似度过滤
知识库配好之后,开始组装最后的问答链路。第一步是配置检索器,也就是决定"用户提问后,从知识库里捞出哪些片段"。
检索器最核心的参数是召回数量 k,也就是一次检索返回多少个片段。k 值设得太小,比如 1 或 2,召回的信息不全,模型可能找不到答案;k 值设得太大,比如 10 或 20,大量无关片段混进来,模型容易被干扰,回答质量反而下降。我习惯从 k=5 起步,看实际回答效果再上下调整,这个值不是固定的,和你的文档切分粒度、资料类型都有关系。
除了 k 值,还可以加相似度阈值过滤。Chroma 在检索时会给每个片段算一个相似度分数,低于某个阈值的片段会被直接丢弃。这个小功能很实用,因为有些用户问题可能跟知识库里的内容关系不大,如果没有阈值过滤,检索器也会硬凑出几个片段返回,模型就只好基于一堆不相关的资料强行作答。加了阈值之后,检索不到相关内容时系统会直接告诉用户"资料中未提及",这比硬答要诚实得多。
4.2 提示词模板:把"基于资料回答"写进约束
检索器配好之后,还有一个非常影响回答质量的环节:提示词模板。很多人觉得提示词模板随便写写就行,大错特错。同一个模型,用不同的提示词模板,回答质量的差别能让你怀疑模型是不是被换掉了。
我的知识库问答模板长期是这样的:
请根据以下资料回答问题。如果资料中没有相关信息,请直接说明"资料中未提及",不要编造。 资料: {context} 问题:{question}这个模板的核心约束有两条。第一条是"必须根据资料回答",把模型的注意力限定在召回的片段范围内;第二条是"资料中没有就直说",给模型一个明确的兜底选项。没有这两条约束,模型特别容易在资料信息不完整的时候脑补一个看似合理、实际错误的答案。知识库问答的底线就是不撒谎,模板的作用就是把这条底线写死。
4.3 链路组装与一次完整实测
检索器和提示词模板都准备好了,就可以把两端拼进同一条问答链路了。我用 LangChain 的 RetrievalQA 来组装,它会自动完成"检索片段 + 拼接提示词 + 调用模型生成回答"这个完整流程:
from langchain.chains import RetrievalQA qa = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", retriever=retriever, return_source_documents=True, ) result = qa.invoke("这个产品的退货政策是什么?")组装完,找几个你预先知道答案的问题做实测。实测的时候一定要开 return_source_documents,这样结果里会带上模型回答时实际参考了哪些片段。这个信息是调试的命根子。
怎么看结果?如果返回的 source 里根本没有包含正确答案的片段,那就是检索环节的问题,去调切分参数和 k 值;如果 source 里有相关资料,但模型回答得不对,那就是生成环节的问题,去调提示词模板和生成参数。这个区分方式我用了无数次,每次都能快速定位问题方向。整套链路调通一次之后,后面换知识库、换模型都有了稳定的操作路径。
5. 配置过程中我踩过的坑:问题现象与排查方法
5.1 模型加载失败,提示 model not found
这是配置模型端时最常见的问题。现象很直接,服务报错 model not found。我第一次遇到时也很懵,明明刚 pull 过模型。后来发现,问题几乎都出在模型名称上:ollama 的模型名是分"名称"和"标签"两段的,qwen2.5:7b-instruct-q4_K_M和qwen2.5是两个概念,代码里写的必须和ollama list里显示的完整标签完全一致。
排查方法很简单,终端里跑一下ollama list看看当前有哪些模型,复制完整的名称标签填进代码。另外一个容易忽略的点是,ollama 服务如果在你修改配置之后没有重启,也会出现类似"模型拉下来了但服务不认识"的怪现象,改完配置记得重启一下服务。
5.2 向量维度不一致,报 dimension mismatch
这个坑我在配置知识库时踩得很深,也帮别人排查过很多次。现象是:系统跑了一段时间,你换了个嵌入模型,再重新灌知识库,向量库直接报维度不匹配的错误。
原因其实很简单:Chroma 的持久化目录里,之前已经用旧嵌入模型建好了集合,集合里每条向量的维度是固定的。你换了新嵌入模型,新模型生成的向量维度如果和旧的不一样(比如从 768 维换成 1024 维),再往同一个集合里写入就必然报错。
解决方法是把持久化目录里的旧数据清掉,删掉 chroma_db 目录,然后重新执行入库脚本。这也解释了为什么我前面强调嵌入模型一旦选定就不要轻易换——换模型的成本不只是下载一个权重文件,还要重建整个向量库。
5.3 问答质量差,先分清是检索问题还是生成问题
问答系统配置完,最常遇到的困惑是:"系统能跑,但回答质量很差,不知道是哪里出了问题。"针对这个问题,我整理了一张速查表,基本覆盖了最常见的几类症状:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 答非所问 | 检索到大量无关片段 | 减小 k 值、加相似度阈值、检查切分粒度 |
| 回答明显在编造 | 提示词缺少"资料中没有就直说"的约束 | 加强提示词模板的约束 |
| 回答不完整或截断 | 上下文窗口不够用 | 增大 num_ctx、减少召回片段数量 |
| 同一问题每次回答差异大 | 温度参数过高 | 把 temperature 调到 0.2 以下 |
| 检索结果混乱、主题跳来跳去 | 切分时没有按章节边界 | 先按标题切分,再按长度二次切分 |
这张表是我实际调试时一步一步总结出来的。你可以把它当作排查手册,遇到问题时先对照症状缩小范围,再用前面说的"看 source"方法做最终定位。
5.4 几个容易忽略的配置细节
最后分享几个配置过程中容易被忽略的细节,都是我用真金白银换来的经验。
第一个是关于中文切分的。我前面提过,切分器的 separators 列表里一定要加中文标点。没加之前,我的 PDF 文档切出来的片段很多超出了预设的 chunk_size,后来检查才发现是因为切分器默认用英文标点切分,中文长句完全切不动。
第二个是嵌入模型的首次运行耗时。第一次跑入库脚本时,系统要下载嵌入模型的权重文件,这个过程可能要好几分钟,界面看起来像是卡死了。别急着终止进程,观察一下日志和网络流量,等下载完成之后入库就会快起来。
第三个是测试问题的选择。实测问答质量时,别只用那些你自己写得非常清楚的文档内容来测,要选一些隐藏在长文档深处、需要综合多个片段才能回答的问题。这样的测试才能真正暴露检索和切分的问题。只用简单问题测试,表面上全绿,一到真实使用就露馅。
第四个是向量库目录的备份习惯。chroma_db 目录一旦建立,建议在调整知识库之前先复制一份备份。内容多的时候重新灌库很耗时,有备份在手,改坏了还能快速回滚,这个习惯帮我省下过不少时间。
我个人在实际配置中最深的一点体会是:这套系统能不能用好,七分在知识库处理,三分在模型选择。模型各家各型号差异确实有,但只要你选的是主流中文本地模型,基本都够用;真正的差距,几乎全在文档怎么切、检索怎么配、提示词怎么约束上。配置过程中不要急于求快,每次只改一个变量、对比一次效果,慢慢就能摸清你手里这批资料的最优参数组合。等这一套配置流程被你跑熟了,后续换知识、换场景,无非都是今天这套方法的重复应用。