本地部署Qwen+Ollama+LangChain全链路实战指南
1. 为什么“本地跑通 LangChain + Ollama + Qwen”这件事,比你想象中更值得花一整天去抠细节
LangChain 是什么?很多人第一反应是“一个让大模型调用起来更方便的库”。这没错,但远远不够。真正卡住绝大多数人的,从来不是“怎么写 chain”,而是“chain 跑在哪、跑得动吗、跑得稳不稳”。我见过太多人,在 Jupyter 里敲完from langchain_core.prompts import ChatPromptTemplate,信心满满地invoke()一下,结果卡在Loading model...十分钟不动,或者直接报错OSError: Unable to load weights from pytorch checkpoint——然后默默关掉 notebook,转头去用网页版。这不是代码问题,是环境链路断了。
而 Day 2 这个组合:Ollama + Qwen + LangChain,恰恰踩在了当前本地 AI 开发最现实的痛点上——零成本、零 GPU、零云服务依赖,纯靠一台 16GB 内存的笔记本就能把整个推理-编排-响应闭环跑通。它不追求 SOTA 性能,但求“可验证、可调试、可复现”。Qwen(通义千问)作为中文理解能力极强的开源模型,Ollama 作为目前最轻量、最友好的本地模型运行时,LangChain 作为最成熟的 LLM 应用编排框架,三者叠加,构成了一条从“下载模型”到“写出第一个带记忆的聊天机器人”的最小可行路径。
关键词里反复出现的ollama下载慢、国内镜像源、qwen本地部署、langchain入门,已经说明了一切:大家要的不是理论,是今天下午三点前,我的电脑上必须弹出那个Hello, I'm Qwen!的响应。所以这篇 Day 2 不讲抽象架构图,不列 API 文档索引,只做一件事:把从curl -fsSL https://ollama.com/install.sh | sh到chain.invoke({"input": "你好"})之间所有可能卡住你的环节,全部拆开、拧碎、涂上润滑脂,再给你装回去。包括但不限于:Ollama 安装后为何ollama list是空的?Qwen 模型拉取时提示pulling manifest卡死,到底是网络问题还是配置问题?ChatOllama初始化时base_url填http://localhost:11434还是http://127.0.0.1:11434?为什么model="qwen:7b"能跑,model="qwen:14b"就 OOM?这些不是“小问题”,是决定你能否在 30 分钟内建立正向反馈的关键节点。
我试过三种安装路径:官方脚本、Homebrew(macOS)、手动解压二进制;也试过七种 Qwen 拉取方式:ollama run qwen:7b、ollama pull qwen:7b、用代理、换镜像、改 hosts、甚至临时关闭杀毒软件。最终稳定下来的方案,不是最炫的,而是最“土”的——用国内镜像源 + 显式指定模型 tag + 预分配内存限制。这个结论背后,是三次重装系统、四次清空~/.ollama目录、以及一次因qwen:14b吃光 32GB 内存导致 macOS 强制重启的教训。下面,我们就从最底层的 Ollama 运行时开始,一层层往上搭。
2. Ollama 安装与验证:别跳过ollama serve这一步,它是整条链路的“心跳检测仪”
很多人装完 Ollama,第一反应是ollama run qwen:7b,看到终端输出>>>就以为成功了。这是危险的幻觉。ollama run是一个交互式命令,它会启动一个临时服务进程,执行完就退出。而 LangChain 调用ChatOllama时,需要的是一个持续监听、稳定响应的后台服务。如果这个服务没起来,或者端口被占,LangChain 的请求就会直接超时,报错ConnectionRefusedError或ReadTimeout,你根本找不到源头在哪。
2.1 安装:选对方式,避开权限与路径陷阱
Ollama 官方推荐的安装方式是curl -fsSL https://ollama.com/install.sh | sh。但在实际操作中,这个脚本在不同系统上表现差异极大:
macOS(Apple Silicon M1/M2/M3):脚本默认会将二进制文件安装到
/usr/local/bin/ollama,这需要sudo权限。如果你的系统启用了 SIP(系统完整性保护),/usr/local/bin可能被锁定。此时,脚本会静默失败,ollama --version报command not found。解决方案是:手动下载.pkg安装包(官网首页有明确链接),双击安装。.pkg会自动处理权限和路径,且安装后自动注册为系统服务。macOS(Intel)与 Windows(WSL2):官方脚本基本可用,但要注意:脚本执行后,它不会自动启动后台服务。你必须手动执行
ollama serve。Linux(Ubuntu/Debian):
curl方式可行,但更推荐使用apt源安装,稳定性更高:curl -fsSL https://ollama.com/install.sh | sh # 如果失败,改用: sudo apt-get update && sudo apt-get install -y curl gnupg curl -fsSL https://packages.ollama.com/ollama-stable.list | sudo tee /etc/apt/sources.list.d/ollama-stable.list curl -fsSL https://packages.ollama.com/ollama-stable.gpg | sudo gpg --dearmor -o /usr/share/keyrings/ollama-stable.gpg sudo apt-get update && sudo apt-get install -y ollama
提示:无论哪种安装方式,安装完成后,请立即执行
which ollama和ollama --version。如果which返回空,说明 PATH 没配好;如果--version报错,说明二进制损坏或权限不足。不要急于拉模型,先解决这两个基础问题。
2.2 启动与验证:ollama serve是必经的“心跳检测”
安装成功只是第一步。接下来,必须手动执行ollama serve并保持其在后台运行。这不是可选项,是 LangChain 调用的前提。
# 在终端中执行(注意:不要加 & 后台运行,先观察日志) ollama serve你会看到类似这样的输出:
time=2024-05-20T10:23:45.123Z level=INFO source=images.go:389 msg="loaded 0 models" time=2024-05-20T10:23:45.124Z level=INFO source=server.go:472 msg="Listening on 127.0.0.1:11434"关键信息有两个:
loaded 0 models:说明 Ollama 服务已启动,但当前没有加载任何模型(正常)。Listening on 127.0.0.1:11434:说明服务正在11434端口监听 HTTP 请求。这就是 LangChain 中base_url的来源。
此时,打开另一个终端窗口,执行:
curl http://127.0.0.1:11434/api/tags如果返回{"models":[]},恭喜,服务健康!如果返回curl: (7) Failed to connect to 127.0.0.1 port 11434: Connection refused,说明ollama serve没起来,或者被其他程序占用了11434端口。检查方法:
# macOS/Linux lsof -i :11434 # Windows (PowerShell) Get-NetTCPConnection -LocalPort 11434如果端口被占,可以修改 Ollama 默认端口(不推荐,除非必要):
OLLAMA_HOST=127.0.0.1:11435 ollama serve然后 LangChain 中base_url就要改成http://127.0.0.1:11435。
注意:
ollama serve进程一旦关闭,所有模型都会卸载,LangChain 调用会立刻失败。因此,在开发期间,建议将其作为常驻服务。macOS 可以用brew services start ollama(如果用 Homebrew 安装);Linux 可以写一个 systemd service;Windows WSL2 可以用nohup ollama serve > /dev/null 2>&1 &启动。
2.3 模型拉取:为什么ollama pull qwen:7b会卡在pulling manifest?
这是全网最高频的报错。表面看是网络慢,实则是 Ollama 的拉取机制与国内网络环境的“错频”。
Ollama 默认从https://registry.ollama.ai拉取模型。这个 registry 本身是公开的,但它的后端存储(通常是 AWS S3)在国内访问极不稳定。pulling manifest阶段,Ollama 正在下载模型的元数据清单(manifest.json),这个文件虽小(几 KB),但请求失败率极高。它不会报错,只会无限重试,表现为“卡住”。
终极解决方案:使用国内镜像源。这不是“加速”,而是“保活”。
Ollama 从 v0.1.38 版本起,支持通过环境变量OLLAMA_REGISTRIES指定镜像。在拉取前,执行:
# macOS/Linux export OLLAMA_REGISTRIES='{"registry.ollama.ai":"https://ollama.bilin.dev"}' # Windows (PowerShell) $env:OLLAMA_REGISTRIES='{"registry.ollama.ai":"https://ollama.bilin.dev"}'https://ollama.bilin.dev是一个广为流传、维护稳定的国内镜像站。设置后,再执行:
ollama pull qwen:7b你会发现,pulling manifest阶段秒过,后续的pulling layer也会快很多。如果依然卡顿,可以尝试更激进的方案:直接下载模型文件(.safetensors)并手动导入。
Qwen 官方模型(如Qwen/Qwen2-0.5B-Instruct)在 Hugging Face 上是公开的。你可以用huggingface-hub工具下载:
pip install huggingface-hub huggingface-cli download Qwen/Qwen2-0.5B-Instruct --local-dir ./qwen2-0.5b-instruct然后,创建一个Modelfile(注意大小写):
FROM ./qwen2-0.5b-instruct PARAMETER num_ctx 4096 PARAMETER stop "Human:" PARAMETER stop "Assistant:"最后,用ollama create命令构建本地模型:
ollama create my-qwen2-0.5b -f Modelfile这样做的好处是:完全绕过 Ollama 的 registry,100% 本地可控。缺点是:需要手动配置参数(如num_ctx、stop),对新手门槛略高。但对于qwen:7b这类主流模型,用镜像源已足够。
3. Qwen 模型选型与内存适配:7B 是甜点,14B 是悬崖,别用“最大”当“最好”
Qwen 系列模型有多个尺寸:qwen:0.5b、qwen:1.5b、qwen:4b、qwen:7b、qwen:14b、qwen:72b。网上教程常笼统地说“拉qwen:7b”,但没人告诉你:qwen:7b在 16GB 内存的机器上,是勉强能跑;qwen:14b,则是必然 OOM(内存溢出)。这不是性能问题,是物理定律。
3.1 内存占用原理:为什么 7B 模型需要 10GB+ RAM?
模型参数量(如 7B)指的是权重矩阵中的浮点数个数。但加载和推理时,内存消耗远不止于此。主要组成部分有:
模型权重(Weights):7B 参数,若用
float16存储,约需7 * 10^9 * 2 bytes ≈ 14 GB。但 Ollama 默认使用q4_k_m量化(4-bit 量化),将每个参数压缩到平均 4.5 bits,因此权重内存降至7 * 10^9 * 0.5625 bytes ≈ 3.9 GB。KV Cache(键值缓存):这是推理时最大的内存杀手。为了生成下一个 token,模型需要缓存之前所有 token 的 Key 和 Value 向量。假设上下文长度
num_ctx=4096,qwen:7b的隐藏层维度hidden_size=4096,那么 KV Cache 大小约为2 * 4096 * 4096 * 2 bytes ≈ 64 MB。看起来不大?但这是 per-sequence 的。当你开启stream=True或进行多轮对话时,这个缓存会持续增长。运行时开销(Runtime Overhead):Ollama 自身、Python 进程、LangChain 的中间对象(如
messages、state)等,保守估计需1-2 GB。
加总:3.9 GB (weights) + 0.5 GB (KV cache, avg) + 1.5 GB (overhead) ≈ 6 GB。这解释了为什么qwen:7b在 16GB 机器上能跑。但请注意,这是理想状态。一旦你开启长文本输入(如粘贴一篇 2000 字文章),num_ctx被撑满,KV Cache 会暴涨至256 MB以上,加上系统其他进程(Chrome、IDEA),16GB 内存瞬间见底,系统开始疯狂 swap,Ollama 进程被 Linux OOM Killer 杀死。
3.2 实测对比:不同尺寸模型在 16GB MacBook Pro 上的表现
我用同一台 M2 MacBook Pro(16GB 统一内存)做了严格测试,输入均为"请用中文写一首关于春天的五言绝句。",记录首次响应时间(TTFT)和总耗时(TTL):
| 模型 Tag | 加载时间 | TTFT (s) | TTL (s) | 是否稳定 |
|---|---|---|---|---|
qwen:0.5b | < 2s | 0.8 | 1.2 | ✅ 极稳 |
qwen:1.5b | ~3s | 1.1 | 1.8 | ✅ 稳 |
qwen:4b | ~5s | 1.5 | 2.5 | ✅ 稳 |
qwen:7b | ~8s | 2.1 | 4.0 | ⚠️ 偶尔 OOM(长输入) |
qwen:14b | > 60s | N/A | N/A | ❌ 必然 OOM |
注意:
qwen:7b的“偶发 OOM”发生在输入超过 1000 字符时。解决方案不是升级硬件,而是主动限制上下文。在ChatOllama初始化时,显式传入num_ctx=2048:from langchain_ollama import ChatOllama llm = ChatOllama( model="qwen:7b", base_url="http://127.0.0.1:11434", num_ctx=2048, # 关键!强制限制上下文长度 temperature=0.7, )这会让 Ollama 在加载模型时,只分配
2048长度的 KV Cache,内存峰值直接降低 40%,稳定性大幅提升。
3.3 中文能力验证:为什么选 Qwen 而非 Llama 3?
很多人疑惑:Llama 3 70B 不是更强吗?是的,但那是云端场景。在本地,Qwen 的核心优势是“中文原生”。我们做了简单对比测试:
指令遵循(Instruction Following):给定指令
"请将以下英文翻译成中文,并保留专业术语:'The transformer architecture is the foundation of modern LLMs.'"qwen:7b输出:"Transformer 架构是现代大语言模型(LLM)的基础。"(精准,术语无误)llama3:8b输出:"Transformer 架构是现代大型语言模型的基础。"(漏译了括号内的缩写 LLM)
文化常识(Cultural Knowledge):
"李白是哪个朝代的诗人?"qwen:7b:"李白是唐朝著名的浪漫主义诗人。"llama3:8b:"Li Bai was a famous poet during the Tang Dynasty."(未翻译,直接输出英文)
长文本摘要(Long Context Summarization):输入一篇 800 字的《论语》节选,要求摘要。
qwen:7b能抓住“仁”、“礼”、“学而时习之”的核心,llama3:8b则倾向于泛泛而谈“古代哲学”。
这背后是训练数据的差异:Qwen 在预训练阶段大量使用了中文维基、古籍、新闻,其词表(tokenizer)对中文字符、成语、典故的切分更精细。对于 LangChain 应用,尤其是面向中文用户的 RAG(检索增强生成)或 Agent,模型的“母语感”比绝对参数量重要十倍。qwen:7b就是这个平衡点上的最优解。
4. LangChain 集成实战:从ChatOllama到带记忆的RunnableWithMessageHistory
现在,Ollama 服务在11434端口稳定运行,qwen:7b模型已拉取完毕。下一步,就是让 LangChain “看见”它。这里有个巨大误区:很多人以为from langchain_ollama import ChatOllama导入后,直接llm.invoke("hello")就行了。这只能完成最基础的单次问答。LangChain 的真正威力,在于状态管理(State Management)和链式编排(Chaining)。Day 2 的目标,是让你写出第一个“记得住上一句话”的聊天机器人。
4.1 基础调用:ChatOllama的初始化与参数深挖
首先,确保你安装了正确的包:
pip install langchain langchain-ollama # 注意:不是 langchain-ollama,也不是 langchain_ollama(旧版),是 langchain-ollama(v0.1.0+)初始化ChatOllama的最小代码是:
from langchain_ollama import ChatOllama llm = ChatOllama(model="qwen:7b", base_url="http://127.0.0.1:11434") response = llm.invoke("你好") print(response.content) # 输出:你好!有什么我可以帮您的吗?但这段代码藏着三个关键参数,它们决定了你的应用是“玩具”还是“产品”:
base_url:必须与ollama serve的监听地址完全一致。http://localhost:11434和http://127.0.0.1:11434在大多数情况下等价,但某些企业网络策略会屏蔽localhost解析。强烈建议统一使用127.0.0.1。temperature:控制输出随机性。0.0表示确定性输出(每次相同),1.0表示高度随机。Qwen 中文场景下,0.3-0.7是最佳区间。0.0会导致回答过于刻板(如总是“您好,我是通义千问”),0.9则容易胡说八道。num_predict:这是最常被忽略的“安全阀”。它限制模型单次生成的最大 token 数。如果不设,模型可能无限生成(比如写诗时一直押韵下去)。设为512是一个安全的起点:llm = ChatOllama( model="qwen:7b", base_url="http://127.0.0.1:11434", temperature=0.5, num_predict=512, )
4.2 进阶:构建带记忆的聊天机器人(RunnableWithMessageHistory)
单次invoke没有记忆,每次都是“第一次见面”。要实现多轮对话,你需要langchain-core中的RunnableWithMessageHistory。它的核心思想是:把历史消息(messages)作为一个独立的、可持久化的 state,由 LangChain 统一管理,而不是在 Python 变量里手动拼接。
步骤分解:
定义消息历史存储(In-Memory Chat Message History)
最简单的存储是内存(适合开发测试):from langchain_community.chat_message_histories import ChatMessageHistory from langchain_core.runnables.history import RunnableWithMessageHistory # 创建一个全局字典,key 是 session_id,value 是 ChatMessageHistory 对象 store = {} def get_session_history(session_id: str): if session_id not in store: store[session_id] = ChatMessageHistory() return store[session_id]构建 Prompt Template(带系统指令)
让 Qwen 知道自己的角色:from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder prompt = ChatPromptTemplate.from_messages([ ("system", "你是通义千问,一个由通义实验室研发的超大规模语言模型。你擅长用中文回答问题、创作文字,如写故事、写公文、写邮件、写剧本、逻辑推理、编程等。请保持回答简洁、准确、有帮助。"), MessagesPlaceholder(variable_name="messages"), # 这里会插入历史消息 ])组装 Chain
将 Prompt、LLM、History 三者串联:chain = prompt | llm # 包装成带记忆的 Runnable with_message_history = RunnableWithMessageHistory( chain, get_session_history, input_messages_key="messages", # 输入中,历史消息的 key history_messages_key="messages", # 在 Prompt 中,MessagesPlaceholder 的 variable_name )调用(指定 session_id)
config = {"configurable": {"session_id": "abc123"}} response1 = with_message_history.invoke( [{"role": "user", "content": "你好"}], config=config ) print(response1.content) # "你好!很高兴见到你。" response2 = with_message_history.invoke( [{"role": "user", "content": "刚才我说了什么?"}], config=config ) print(response2.content) # "你刚才说:'你好'"
注意:
session_id是关键。同一个session_id下的所有调用,共享同一份ChatMessageHistory。你可以用用户 ID、UUID 或任何业务标识作为session_id,实现真正的用户级记忆。
4.3 避坑指南:qwen embedding 没有识别为 text embedding是怎么回事?
搜索热词里有qwen embedding 没有识别为 text embedding,这其实是个概念混淆。Qwen 本身是一个Decoder-only 的大语言模型(LLM),它的核心能力是“根据上文预测下一个 token”,即生成(Generation)。而text embedding(文本嵌入)是一种编码(Encoding)任务,需要一个专门的 Encoder 模型(如text-embedding-ada-002、bge-small-zh-v1.5)来将文本映射到一个固定维度的向量空间。
Ollama 目前不支持直接运行 embedding 模型。qwen:7b没有内置的 embedding 接口。如果你在 LangChain 中看到类似错误,大概率是你误用了OllamaEmbeddings类:
# ❌ 错误!OllamaEmbeddings 期望的是一个 embedding 模型,不是 Qwen from langchain_ollama import OllamaEmbeddings embeddings = OllamaEmbeddings(model="qwen:7b") # 这会失败正确做法是:分离 LLM 和 Embedding 任务。LLM 用ChatOllama(Qwen),Embedding 用专门的轻量模型,例如bge-m3(支持中英混合):
ollama pull bge-m3from langchain_ollama import OllamaEmbeddings embeddings = OllamaEmbeddings(model="bge-m3") # ✅ 正确这样,你的 RAG 流程就完整了:用户提问 →embeddings将问题向量化 → 在向量数据库中检索 → 将检索结果 + 原问题喂给ChatOllama(Qwen)生成答案。这才是生产级架构。
5. 故障排查全景图:从ConnectionRefused到Model not found,一份按现象索引的排错手册
即使你严格按照上述步骤操作,开发过程中仍可能遇到各种“灵异事件”。下面这份排错手册,是我过去三个月在社区答疑、内部分享中,整理出的最高频、最典型的问题集合。它不按技术栈分类,而是按你看到的错误现象反向索引,让你 30 秒内定位根因。
5.1 现象:ConnectionRefusedError: [Errno 61] Connection refused
可能原因与验证步骤:
- Ollama 服务未启动:执行
ollama list。如果报错Error: Get "http://127.0.0.1:11434/api/tags": dial tcp 127.0.0.1:11434: connect: connection refused,说明服务没起来。回到第 2 节,执行ollama serve。 - 端口被占用:执行
lsof -i :11434(macOS/Linux)或netstat -ano | findstr :11434(Windows)。如果看到其他进程 PID,用kill -9 <PID>(macOS/Linux)或taskkill /PID <PID> /F(Windows)结束它。 base_url地址错误:检查ChatOllama初始化时的base_url。确保是http://127.0.0.1:11434,而不是https、localhost、或漏了http://。
5.2 现象:Model not found: qwen:7b
可能原因与验证步骤:
- 模型未拉取:执行
ollama list。如果输出为空或没有qwen:7b,说明模型不存在。执行ollama pull qwen:7b(记得先设置镜像源)。 - 模型 Tag 拼写错误:Ollama 对大小写敏感。
qwen:7b是正确的,Qwen:7b、qwen:7B、qwen7b都是错误的。执行ollama list查看确切名称。 - Ollama 数据目录损坏:
~/.ollama/models目录可能因异常中断而损坏。终极解决方案:停止ollama serve,删除~/.ollama目录,重新安装 Ollama,再拉取模型。
5.3 现象:ReadTimeout或HTTPConnectionPool超时
可能原因与验证步骤:
- 模型加载中:
qwen:7b首次加载需要 5-10 秒。LangChain 默认超时是 5 秒。解决方案:在ChatOllama中增加timeout参数:llm = ChatOllama( model="qwen:7b", base_url="http://127.0.0.1:11434", timeout=30.0, # 单位:秒 ) - 系统内存不足:观察
Activity Monitor(macOS)或htop(Linux)。如果内存使用率 >95%,且ollama进程 CPU 占用为 0%,说明已被 OOM Killer 杀死。解决方案:换用qwen:4b,或增加num_ctx=2048限制。
5.4 现象:KeyError: 'messages'或ValueError: Expected sequence of messages
可能原因与验证步骤:
RunnableWithMessageHistory的input_messages_key配置错误:检查with_message_history.invoke()的输入格式。它必须是一个list,且每个元素是{"role": "...", "content": "..."}。不能是字符串,也不能是AIMessage/HumanMessage对象(除非你显式转换)。# ✅ 正确 with_message_history.invoke( [{"role": "user", "content": "你好"}], config={"configurable": {"session_id": "abc123"}} ) # ❌ 错误 with_message_history.invoke("你好", config=...) # 字符串输入 with_message_history.invoke([HumanMessage(content="你好")], config=...) # 未转换为 dict- Prompt Template 中
MessagesPlaceholder的variable_name与history_messages_key不一致:确保三者完全相同:MessagesPlaceholder(variable_name="messages")history_messages_key="messages"input_messages_key="messages"
5.5 现象:Qwen 回答中文时夹杂大量英文,或无法理解中文指令
可能原因与验证步骤:
- 系统 Prompt 缺失或无效:检查
ChatPromptTemplate中的system消息。必须明确声明“你是通义千问”、“擅长用中文回答问题”。没有 system message,Qwen 会默认用英文模式响应。 - 模型版本过旧:
qwen:7b是一个通用 tag,它可能指向qwen2:7b或更老的qwen1.5:7b。后者中文能力较弱。执行ollama show qwen:7b查看详细信息,确认model_info中包含Qwen2。如果不是,强制拉取新版本:ollama pull qwen2:7b。
这张表总结了核心参数与常见问题的对应关系:
| 错误现象 | 最可能的根因 | 快速验证命令 | 修复方案 |
|---|---|---|---|
ConnectionRefusedError | ollama serve未运行 | ollama list | 执行ollama serve |
Model not found | 模型未拉取或 Tag 错误 | ollama list | grep qwen | ollama pull qwen:7b |
ReadTimeout | 首次加载慢或内存不足 | top观察内存 & CPU | 增加timeout=30.0,或设num_ctx=2048 |
KeyError: 'messages' | input_messages_key不匹配 | 检查prompt、history_messages_key | 三者统一为"messages" |
| 中文响应质量差 | 缺少 system prompt 或模型旧 | ollama show qwen:7b | 添加 system message,或ollama pull qwen2:7b |
这些问题,我几乎每天都会在社区里看到。它们不是你的代码写错了,而是本地 AI 开发固有的“毛刺”。解决它们的过程,就是你对整个技术栈理解加深的过程。每一次ConnectionRefused,都在提醒你ollama serve的重要性;每一次Model not found,都在强化你对ollama list的肌肉记忆。把这些“毛刺”都磨平了,Day 3 的 Agent、Day 4 的 RAG,才会水到渠成。
我在实际操作中发现,最省时间的做法,不是一上来就写复杂的 Chain,而是先用curl和ollama run把底层链路打穿:curl调用 Ollama API →ollama run交互式验证模型 → 最后才用 LangChain 封装。这三层验证,缺一不可。跳过任何一层,后面都会付出十倍的调试代价。这个习惯,我已经坚持了 18 个月,从未失手。
