ollama+langchain+rag

接入与开发

讲清用 Ollama 配 LangChain 搭本地 RAG 的工程落地:检索与生成怎么拆、超时并发怎么设、跑偏了怎么查。

本地跑 RAG 最容易卡在「模型能跑、问答不能用」:拉模型顺利,接上 LangChain 之后要么检索结果不对,要么生成慢得像卡死,要么报错看不出是哪一段出的问题。这篇不从概念讲起,只按工程顺序过一遍:链路怎么拆成检索和生成两段、每段怎么单独验、超时和并发怎么设、日志该记哪些字段、效果怎么小成本评测。

本地跑 RAG 最常见的失败不是模型拉不下来,而是「模型能跑、问答不能用」。拉模型、跑个 hello world 都挺顺,一接上 LangChain,要么检索出来的片段跟问题不搭边,要么生成慢得像卡死,要么报一堆错但看不出是哪一段出的问题。

这篇不讲概念,按工程顺序过一遍:链路怎么拆成检索和生成两段、每段怎么单独验、超时并发怎么设、日志该记哪些字段、效果怎么用最小成本评测。凡是涉及具体延迟或费用,一律到站内页面看当时的数字,别照抄别人的环境。

先把链路拆成两段:检索一段,生成一段

拆分的标准很简单:检索段不调生成模型也能独立跑完,打印出命中的片段和相似度;生成段塞死一段文本也能出答案。如果做不到,说明两段还缠在一起,后面任何问题都定位不了。

检索段
吃一个问题,输出若干候选文档片段和相似度,全程只碰嵌入模型和向量库
生成段
吃问题和候选片段,输出答案,只碰生成模型,不关心片段从哪来
为什么不塞改写和重排
第一版加进去,出问题时你分不清是检索差还是改写把问题改歪了
  • 生成和嵌入尽量别用同一个模型,嵌入模型单独选,可以对照站内 /embeddings 的清单再定
  • LangChain 那一侧只留两个职责清晰的函数,中间不要塞改写、压缩、重排,第一版先不加
  • 两段各自写一个可单独执行的入口,方便你只跑检索或只跑生成
  • 先跑通「问题→片段→答案」这一条最短路径,再考虑加别的环节

建库和查询前,先量一条基线

本地模型的首字延迟和云端差很多,拿云端经验估本地,十有八九会误判成「卡死了」。先用固定的一小批问题,量出本机的模型加载时间、单次生成耗时、单次嵌入耗时,把这三个数记下来当基线。

要量的项怎么量量出来干嘛
模型加载耗时第一次请求单独计时判断要不要让模型常驻
单次生成耗时同一个问题跑两遍看波动大不大,波动大是资源在打架
单次嵌入耗时批量嵌入一小段文本估算建库总时间,决定要不要预建
端到端耗时检索加生成一起计作为后续任何改动的对照点

同一批问题连续跑两遍,如果耗时波动很大,先别怀疑链路,多半是显存或并发在打架。基线不稳的话,后面调参数全是白调。

超时、并发与降级:把默认值全部改掉

默认超时基本是按云端接口设的,放到本地会出两种极端:要么生成还没开始就被掐断,要么检索卡住整个请求挂在那里。嵌入和生成要分别设超时,生成段的超时通常要宽得多,因为本地模型吐字慢。

  1. 先设成串行跑通并发数设成一,确认单条请求稳定返回,再考虑加路数
  2. 一路一路往上加每次加一路,跑到开始排队或内存报错的位置退回来,那里就是这台机器的上限
  3. 给生成设独立超时超时后走降级逻辑,而不是让调用方一直等
  4. 把降级写清楚生成超时就退回「只给检索片段」,检索为空就明确告诉调用方没查到,别让模型硬编答案

判断标准:如果加并发后检索耗时开始跟着涨,说明资源被生成任务抢了,这时候要么降并发,要么把嵌入和生成错峰跑。这套判断在单机部署下基本都成立,但如果你把向量库放到另一台机器,检索和生成的资源就不再互相抢,并发可以分开设。

日志记哪几个字段,出问题按什么顺序查

出问题时最怕的就是「报错了但不知道哪一段」。每次请求至少落下这几个字段,基本能一眼判断是检索挂了还是生成挂了。

  • 问题原文,保留原样不要截断
  • 命中片段数,为零就是检索没结果
  • 每段的相似度,看是不是普遍偏低
  • 检索耗时和生成耗时,分开记
  • 实际使用的模型名,换过模型能对得上
  • 请求是否走了降级逻辑,方便统计
现象优先怀疑先查什么
答案跟问题无关检索段打印命中的片段和相似度,看片段本身对不对
检索为空切分或向量库确认向量是否入库、维度是否一致
生成极慢资源或并发看是不是并发开太高,或模型没常驻
报维度错误入库环节检查嵌入模型换了之后有没有重建库

排查顺序固定成一条线:先确认 Ollama 服务本身响应正常,再确认嵌入是否返回了向量,再看检索候选是否为空,最后才怀疑生成模型。跳着查容易在生成那一层白折腾半天。

给自己留一条能反复跑的小评测

没法量化的改动等于没改。准备一批带标准片段的问题,脚本只比对「命中的文档有没有包含标准片段」,不评答案文采,先看检索准不准。这一步成本很低,但能把「改好了还是改坏了」从感觉变成事实。

  • 问题要覆盖你真实场景里的问法,别只挑好答的
  • 故意放几条查不到的问题,看链路会不会老实说没查到
  • 改动切分方式、换嵌入模型、调 top-k 之后重跑同一批
  • 记录每次改动的命中率变化,作为下一版的对照
  • 评测脚本和检索代码分开,避免互相影响

这条评测循环在个人项目里够用,但如果你的文档经常变、问题分布也在变,固定的一批用例会慢慢失真,那时候需要定期补新问题。这一点不适用于文档几乎不变的一次性场景,那种情况跑一次就够。

下一步把基线量清楚再动手调参数,站内工具页能直接测本机延迟,顺便核对嵌入模型的选型清单,省得反复推倒重来。

常见问题

本地用 Ollama 跑 RAG,是不是模型越大效果越好?

不一定。生成模型大一点通常回答更顺,但检索效果主要由切分方式和嵌入模型决定,跟生成模型大小关系不大。本机显存有限时,优先把嵌入模型选对、把切分调合理,比换更大的生成模型有效得多。什么时候不适用:如果你的问题本身就是需要长推理的复杂问答,那生成模型的能力才变成瓶颈。

检索出来的片段看着不相关,先查哪里?

按这个顺序:先看切分出来的片段是不是太碎或太长,再看嵌入模型和文档语言是否一致,再看 top-k 是不是设得太小只召回了几条。这三步能覆盖大部分情况。什么时候不适用:如果文档本身是扫描件或表格,问题出在进料阶段,得先把文本清洗干净再谈检索。

本地跑时不时卡住没反应,怎么定位?

先分开计时:单跑一次嵌入看耗时,单跑一次生成看耗时,哪个异常就是哪一段。本地常见原因是显存不够触发换页,或者并发开多了在排队。先把并发降到 1 跑通,再逐步加上去看从第几路开始变慢。什么时候不适用:如果嵌入和生成分开跑都正常、合起来才卡,那是资源争抢,得考虑错峰或分开部署。

本文依据各平台公开文档与官方公告整理,只做中文归纳;具体做法、参数与限制以官方文档为准。