在这里插入图片描述


课程导读 & 学习目标

在前三课中,我们完成了LangChain开发环境的搭建、核心设计思想的学习,并掌握了通过API调用云端大模型的方法。但云端调用始终存在几个痛点:数据隐私风险(你的对话内容会上传到第三方服务器)、网络依赖(断网或高峰期时服务不可用)、以及成本累积(即使是便宜的模型,大规模调用也是一笔不小的开支)。

那么,有没有一种方法能够完全摆脱对云端的依赖,在自己的电脑上运行大模型?答案是肯定的——Ollama + LangChain 本地私有化部署就是你的最佳选择。正因如此,在第3课讲授云端模型接入之后,现在正是引入本地部署方案的最佳时机。

🛡️ 数据安全第一:与第3课的云端大模型接入方式不同,本课的本地私有化部署方案最大优势在于——整个对话过程的全部数据都保留在你的本地计算机,绝不会上传到任何服务器。这一特性被称为私有化部署(Private Deployment)本地优先(Local-first),对于处理敏感信息的企业和个人开发者来说,这一点甚至可以决定整个技术方案的选择。

本课将带你完整走完这条“数据不出本地”的技术路径。学完之后,你将拥有一个完全离线、数据安全、零成本的大模型应用开发环境。

学完本节课,你将达到以下目标:

  1. 掌握Ollama的安装与基本使用:在Windows/Mac/Linux本地部署大模型。
  2. 理解本地私有化部署的核心原理:Ollama的架构、模型仓库、推理API。
  3. 学会使用LangChain的Ollama集成:ChatOllama、OllamaLLM、OllamaEmbeddings调用本地模型。
  4. 完成三个渐进实战:基础调用、带记忆的聊天机器人、本地RAG问答系统。
  5. 掌握本地模型的选择与优化:不同规模模型的硬件要求和参数配置。
  6. 应对常见部署问题:GPU加速、端口冲突、模型管理。

本课之后,你将不再受限于云端API的额度、延迟和隐私问题,真正拥有“自己的大模型”。

🧩 知识的自然衔接:本课与第3课“云端大模型接入”形成完美的“云端+本地”双轨方案。你可以根据不同场景灵活选择——数据敏感场景用Ollama本地部署,追求最强推理能力时用云端GPT/Qwen,两者还能通过工厂模式无缝切换。

前置知识与环境准备

1.1 硬件要求与模型选择

在开始之前,请先评估你的硬件配置。Ollama对硬件的要求弹性和适应性很强,从树莓派到服务器都能运行。如果硬件条件有限,选择小参数模型或纯CPU模式依然可以体验。

根据你的硬件条件,以下是Ollama运行不同规模模型的推荐配置(以4-bit量化模型为基准):

模型规模 推荐场景 最低内存(RAM) 推荐显存(VRAM) 参考GPU 典型模型
1B-3B 入门体验、函数调用 4GB 无需/4GB 集成显卡 qwen:1.8b, phi-3:3.8b
7B-8B 个人日常助手、代码补全 8GB 6-8GB RTX 3060 llama3.2:7b, qwen2.5:7b
13B-14B 复杂对话、中等推理 16GB 12-16GB RTX 4070 llama3.1:13b
32B-34B 专业研究、高质量输出 32GB 20-24GB RTX 4090 deepseek-r1:32b
70B+ 企业级高精度推理 64GB+ 48GB+ A100/H100 llama3.3:70b

💡 量化模型的威力:通过4-bit量化技术,7B模型的实际存储空间约4.7GB,运行内存需求降低到8GB左右。这就是为什么普通办公本也能跑本地大模型的原因。
💡 纯CPU模式:即使没有独立显卡,你依然可以运行Ollama——所有计算都交给CPU。如果不追求速度,这完全可行。比如3B模型在普通笔记本上纯CPU模式每次回复约需10-20秒。

模型选择建议

  • 硬件受限者(8GB内存,无独显) :选择 qwen:1.8bphi3:3.8b-mini-4k-instruct
  • 普通办公本(16GB内存,可选集成显卡) :选择 qwen2.5:7bllama3.2:7b。7B模型需要约16GB内存,若关闭后台其他大型应用后可流畅运行
  • 游戏本/工作站(16GB+内存,RTX显卡) :选择 deepseek-r1:14bqwen2.5:14b
  • 追求最佳中文能力:推荐 qwen2.5:7b/14b(通义千问本地版,中文优化极佳)
  • 通用英文能力最佳:推荐 llama3.2:7b/13b(Meta出品,英文理解力强)或 deepseek-r1 系列

1.2 安装Ollama

Ollama支持Windows、macOS和Linux三大操作系统,真正实现了跨平台兼容。以下是各系统的安装方法:

Windows安装
  1. 访问官网 https://ollama.com/download,点击“Download for Windows”下载OllamaSetup.exe安装文件。
  2. 运行安装程序,按提示完成安装(可自定义安装路径)。
  3. 安装完成后,Ollama会自动启动后台服务。
  4. 打开命令提示符(CMD)或PowerShell,输入ollama --version验证安装是否成功。
macOS安装
  1. 访问官网下载Ollama.dmg安装包。
  2. 双击安装包,将Ollama应用拖入Applications文件夹。
  3. 首次运行时,按住Ctrl点击Ollama图标选择“打开”,然后按提示确认启动。
Linux安装

使用以下一键安装脚本:

curl -fsSL https://ollama.com/install.sh | sh

安装完成后,Ollama会自动注册为systemd服务。如果服务未自动启动,请执行:

systemctl --user start ollama
验证安装

打开终端(Windows使用CMD或PowerShell),运行:

ollama --version

如果显示版本号(如 0.22.0),说明安装成功。

1.3 下载本地模型

Ollama的核心命令之一是ollama pull,用于从Ollama模型库下载模型到本地。模型默认存储在~/.ollama/models目录下(Windows为C:\Users\<用户名>\.ollama\models)。

根据你的硬件配置,选择以下命令之一下载模型:

# 入门级(4-8GB内存,纯CPU可运行)
ollama pull qwen:1.8b
ollama pull phi:3.8b-mini-4k-instruct

# 主流配置(8-16GB内存,推荐大多数人)
ollama pull qwen2.5:7b
ollama pull llama3.2:7b

# 高性能配置(16-32GB内存,需要独显)
ollama pull deepseek-r1:14b
ollama pull qwen2.5:14b

💡 模型标签(Tag)速查指南

  • 版本标签:7b13b70b 表示参数数量,参数越多能力越强,但硬件要求也更高
  • 量化标签:q4_0q4_K_Mq8_0 表示不同的量化等级。q4_0q8_0 节省约50%显存,但会有轻微的质量损失
  • 如果未指定标签,Ollama会默认下载“latest”版本,通常指向最新、参数量最小的推荐版本

下载过程中的进度条会显示文件大小和下载速度,如文件为4.7GB的7B模型,等待完成后即可使用。

1.4 测试模型运行

下载完成后,用以下命令测试模型是否正常运行:

# 直接运行模型(进入交互式对话)
ollama run qwen2.5:7b

# 或单次生成(非交互模式)
ollama run qwen2.5:7b "请用一句话介绍什么是大语言模型"

如果模型成功回复,说明你的Ollama环境已经准备就绪。

1.5 安装Python依赖

# 激活虚拟环境(沿用第1课创建的langchain_course)
source venv/bin/activate  # Mac/Linux
# venv\Scripts\activate   # Windows

# 安装LangChain Ollama集成包
pip install -U langchain-ollama

langchain-ollama是LangChain官方提供的Ollama集成包,通过它可以在LangChain中调用本地Ollama服务启动的大模型。

验证安装
# 测试能否导入ChatOllama
from langchain_ollama import ChatOllama
print("✓ langchain-ollama 安装成功!")

核心概念深度拆拆

2.1 Ollama是什么?—— 类比“Git for LLM”

如果用一个类比来理解Ollama:Ollama之于大模型,相当于Docker之于容器,或者npm之于Node.js包。它提供了三个核心功能:

  • 模型仓库:ollama.com/library 上托管了数百个开源模型,用ollama pull即可下载。
  • 模型管理ollama list查看已安装模型,ollama rm删除模型,ollama cp复制模型。
  • 推理服务ollama serve启动本地API服务器(默认端口11434),对外提供RESTful API。
Ollama命令 作用 类比
ollama pull <model> 下载模型到本地 git clone / docker pull
ollama run <model> 交互式运行模型 python 打开交互式Python环境
ollama list 列出已安装的模型 ls / docker images
ollama serve 启动API服务 docker run 启动容器服务

2.2 Ollama与LangChain的集成架构

理解Ollama与LangChain的协作关系有两个层次:基础部署调用框架工作流编排

基础部署调用——Ollama在后台启动一个HTTP服务(默认监听localhost:11434),LangChain通过langchain-ollama包中的客户端连接到这个服务发送请求。

渲染错误: Mermaid 渲染失败: Lexical error on line 2. Unrecognized text. ...art LR subgraph “你的笔记本/服务器” ----------------------^

核心流程

  1. 启动Ollama服务(ollama serve),它会加载指定的模型到内存/显存。
  2. LangChain调用ChatOllama.invoke(),将HumanMessage转为JSON格式。
  3. 通过HTTP POST发送到http://localhost:11434/api/chat
  4. Ollama执行模型推理,将结果封装后返回。
  5. LangChain将响应解析为AIMessage对象返回。

2.3 LangChain中Ollama的三类组件

LangChain为Ollama提供了三个层次的集成组件,覆盖了不同的使用场景:

组件 导入路径 用途
ChatOllama from langchain_ollama import ChatOllama 聊天模型最常用),支持多轮对话、系统提示
OllamaLLM from langchain_ollama import OllamaLLM 文本补全模型,适合简单的生成任务
OllamaEmbeddings from langchain_ollama import OllamaEmbeddings 文本向量化,用于RAG的知识库构建

这三者都继承了LangChain的Runnable接口,意味着它们可以无缝融入你之前学过的LCEL链中——这正是我们第2课中学到的LangChain核心设计思想在实际中的应用。

📌 ChatOllama核心参数全解析(基于LangChain官方文档):

  • model(必填):你下载的Ollama模型名称,如 “qwen2.5:7b”
  • temperature:采样温度,范围0.0~1.0,值越低输出越确定性,越高越随机
  • num_predict:最大生成token数,控制输出长度
  • keep_alive:模型保持在内存中的时间,默认5分钟。设为“-1”保持常驻,适合频繁调用
  • validate_model_on_init:初始化时验证模型是否已在Ollama中本地存在,强烈建议设为True
  • reasoning:控制支持推理能力的模型(如DeepSeek-R1)是否启用思维链输出模式
  • base_url:Ollama API地址,默认http://localhost:11434

⚠️ 特别注意validate_model_on_init=True是一个非常实用的参数——它会在创建ChatOllama对象时立即检查模型是否已在你的Ollama中下载,如果不存在会立即报错,而不是等到真正调用时才报“模型未找到”的错误,能极大缩短调试时间。

2.4 私有化部署的核心价值

本地私有化部署相对于云端API调用,有四个关键优势:

  • 数据隐私:所有数据都留在你的电脑上,绝不外传——对于企业级应用和工作保密属性的项目来说,这一点甚至超过了模型能力成为首要关注点。外部接口可能会将你的提示词用于模型训练,而本地部署完全杜绝了这种风险。
  • 零网络依赖:无论是飞机上、电梯里还是网络故障时,模型都能正常工作。
  • 无成本累积:只要你的电费,没有按token计费的压力。不限次、不限量调用。
  • 完全控制权:可以调整推理参数、修改模型配置、甚至用自己的数据微调模型。

有了Ollama,你可以在自己的电脑上免费、离线地运行原本需要付费调用API的大模型,而且数据安全——所有计算都在本地完成,绝不联网。

底层运行原理剖析

3.1 Ollama服务生命周期

Ollama的核心是一个常驻后台的HTTP服务器。当你安装Ollama后,它会自动注册为系统服务并开机自启。

以下是Ollama服务从启动到响应的完整过程:

  1. 服务启动:运行ollama serve命令或在后台开机自启。Ollama会在localhost:11434上监听HTTP请求。
  2. 模型懒加载:当第一次收到请求时,Ollama才会将模型权重加载到内存(有GPU则加载到显存)。这个过程称为“懒加载”,视模型大小和硬件速度通常需要5-30秒。
  3. 推理计算:收到请求后进行矩阵运算生成每个token,逐字返回。纯CPU模式下,7B模型每次回复需约3-20秒;有GPU加速时,速度可提升3-5倍。
  4. 连接管理:如果HTTP客户端主动关闭连接,Ollama会停止生成。请求结束后,模型会按keep_alive参数决定何时从内存中卸载(默认5分钟),防止内存泄漏。

3.2 LangChain调用Ollama的时序图

为了让你对整个调用链路有更立体、直观的理解,我们把文字描述转换成一段身临其境的旅程:

步骤 你的代码 LangChain Ollama服务 你的模型
1 llm = ChatOllama(model=“qwen2.5”) - - -
2 response = llm.invoke(messages) - - -
3 - 验证validate_model_on_init时检查模型是否存在 - -
4 - 将LangChain消息转换为Ollama格式{“messages”: […]} - -
5 - HTTP POST到/api/chat - -
6 - - 接收JSON,转发给模型加载器 -
7 - - - 执行模型推理
8 - - 逐token返回stream -
9 - 接收chunk,触发回调 - -
10 - 组装完整AIMessage返回 - -
11 代码得到最终回答response.content - - -

3.3 两种调用风格的底层差异

ChatOllama vs OllamaLLM:这两种接口对应Ollama API的两个不同端点。

  • ChatOllama:对应 /api/chat 端点,专为多轮对话设计,支持角色标记(system/user/assistant),消息历史管理,符合OpenAI Chat API规范。
  • OllamaLLM:对应 /api/generate 端点,仅文本生成,无消息结构,适合单轮问答但无法维护会话上下文。

日常开发中你应该默认使用ChatOllama,因为它更符合现代聊天应用的需求。

核心API/组件源码解读

4.1 消息转换的核心机制

langchain-ollama内部将LangChain的BaseMessage转换为Ollama API可识别的格式:

# 简化版转换逻辑(源码理解)
def _convert_messages_to_ollama_messages(messages: List[BaseMessage]):
    ollama_messages = []
    for msg in messages:
        if isinstance(msg, SystemMessage):
            ollama_messages.append({"role": "system", "content": msg.content})
        elif isinstance(msg, HumanMessage):
            ollama_messages.append({"role": "user", "content": msg.content})
        elif isinstance(msg, AIMessage):
            ollama_messages.append({"role": "assistant", "content": msg.content})
    return ollama_messages

通过_chat_params()方法将消息与温度、num_predict等参数合并,然后发送请求。

4.2 响应解析与元数据映射

Ollama返回的响应是一个流式字典,LangChain将其转换为标准化的AIMessage对象:

{
  "message": {"role": "assistant", "content": "生成的文本内容"},
  "done": true,
  "done_reason": "stop",
  "total_duration": 411875125,
  "load_duration": 1898166,
  "prompt_eval_count": 32,
  "prompt_eval_duration": 128125000,
  "eval_count": 71,
  "eval_duration": 2656556000
}

这些元数据被映射到AIMessage.response_metadata字段,可以用于成本估算、性能分析和调优判断。

手把手项目实战教学

前方高能——现在是时候让你亲眼见证自己电脑上跑起来的“私人AI管家”了。通过以下三个由简到难的实战,你将逐步打通LangChain + Ollama本地调用链路:

  1. 实战一:基础调用 —— 让本地模型说出第一句话。
  2. 实战二:带记忆的对话机器人 —— 让模型能记住你之前说了什么。
  3. 实战三:本地RAG问答(备用) —— 从你自己的文档中检索并回答。

实战一:基础调用 — 让本地模型说出第一句话

这是零门槛的第一步——我们将完成一次简单的问答。

在开始前,请先确认Ollama服务已在后台运行。启动方法:打开终端执行ollama serve,或者如果Ollama已开机自启(任务栏有Ollama图标),则无需手动启动。如果你的Ollama尚未启动,请用以下命令启动:

ollama serve

打开新终端窗口,保持Ollama服务在后台运行。以下所有Python代码都在另一个终端窗口中执行。

文件01_basic_ollama.py

"""
LangChain 第4课 - 实战一:Ollama本地模型基础调用
"""

from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage

# 1. 初始化 ChatOllama(使用已下载的模型)
llm = ChatOllama(
    model="qwen2.5:7b",           # 你在 ollama pull 时下载的模型名
    temperature=0.7,               # 控制创造性,值越低输出越确定
    num_predict=512,               # 最大生成token数,控制输出长度
    validate_model_on_init=True,   # 初始化时验证模型是否已下载,强烈建议开启
    keep_alive="5m"                # 模型在内存中保持5分钟,保持默认即可
    # reason="True"                # 仅对支持思考模式的模型(如 deepseek-r1)开启
)

# 2. 构建消息
messages = [HumanMessage(content="请用一句话介绍什么是Ollama")]

# 3. 调用模型(同步)
print("[调用开始] 等待模型返回...\n")
response = llm.invoke(messages)

# 4. 输出结果
print("【模型回答】", response.content)
print("\n【元数据】")
print(f"  模型名称: {response.response_metadata.get('model')}")
print(f"  评估token数: {response.response_metadata.get('eval_count')}")
print(f"  加载耗时(纳秒): {response.response_metadata.get('load_duration')}")
print(f"  总耗时(纳秒): {response.response_metadata.get('total_duration')}")

运行命令:

python 01_basic_ollama.py

🔥 注意首次调用时间:第一次使用ollama run/pull以外的LangChain方式调用时,Ollama会把模型加载到内存,这个加载过程可能耗时5-30秒(取决于硬盘速度和模型大小)。第二次及之后的调用会快很多。

实战二:带记忆的对话机器人

现在,升级体验——让模型记住你之前说过的话。LangChain的RunnableWithMessageHistory在这里扮演了一个“记忆管家”的角色:它把每次对话都存进档案馆(我们代码中的session_storage字典),然后在发送下一轮对话前自动往前翻翻对话记录,添加上让AI知道自己刚才说过什么的上下文。通过这种机制,你可以实现多轮对话,无需每次都将历史手动拼接到消息中。

文件02_memory_chat.py

"""
LangChain 第4课 - 实战二:带记忆的多轮对话机器人
使用 RunnableWithMessageHistory 实现会话历史存储
"""

from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

# ---------- 初始化本地模型 ----------
llm = ChatOllama(
    model="qwen2.5:7b",
    temperature=0.7,
    validate_model_on_init=True
)

# ---------- 构造带历史占位符的提示模板 ----------
# 占位符 {history} 会被 RunnableWithMessageHistory 自动填充历史消息
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个友好、乐于助人的AI助手。记住用户告诉你的个人信息。"),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{input}")
])

# 构建基础链(无记忆的原始链)
chain = prompt | llm

# ---------- 会话存储 ----------
store = {}

def get_session_history(session_id: str):
    """根据session_id获取该会话的历史记录"""
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

# 包装成带记忆的链
chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="input",       # 输入中哪个字段是当前用户消息
    history_messages_key="history"    # 模板中历史占位符的变量名
)

# ---------- 开始多轮对话 ----------
config = {"configurable": {"session_id": "user-001"}}

print("=" * 50)
print("欢迎!我会记住我们的对话内容。")
print("=" * 50)

# 第一轮:告诉模型我的名字
resp1 = chain_with_history.invoke(
    {"input": "嗨!我叫小明,是一个Python开发者"},
    config=config
)
print(f"\n用户: 嗨!我叫小明,是一个Python开发者")
print(f"AI: {resp1.content}\n")

# 第二轮:测试模型是否还记得我的名字
resp2 = chain_with_history.invoke(
    {"input": "你还记得我叫什么名字吗?我是做什么的?"},
    config=config
)
print(f"用户: 你还记得我叫什么名字吗?我是做什么的?")
print(f"AI: {resp2.content}\n")

# 第三轮:继续追问,测试上下文连贯性
resp3 = chain_with_history.invoke(
    {"input": "那你能给我推荐一个适合Python初学者的项目吗?"},
    config=config
)
print(f"用户: 那你能给我推荐一个适合Python初学者的项目吗?")
print(f"AI: {resp3.content}\n")

print("对话结束。模型的记忆将在会话期间持续有效。")

运行命令:

python 02_memory_chat.py

预期现象:在第二轮对话中,模型能正确回忆出你在第一轮告诉它的名字和职业。RunnableWithMessageHistory会自动将之前的对话记录添加到MessagesPlaceholder的位置,让模型在回答时“看到”历史。这会使用到第2课中学到的Runnable接口和会话记忆管理机制。

实战三:本地RAG问答系统(信息检索增强生成)

至此,你已经能进行本地对话了。但大模型再强大,它知识库的更新时间默认都停留在训练数据截止的那一天。为了让本地AI能回答你自己文档中的专属内容(比如公司内部资料、你的技术博客集),我们需要给它配备一个外挂“知识书柜”——这就是RAG。

本地RAG问答系统”是指:不向云端发送你的专有文档,而是在检索端就切断数据流动”,完全在本地完成“文档向量化→相似度匹配→上下文增强→大模型生成”的全流程。因此,它是一个纯本地、数据绝对隐私的高级AI应用。

我们来构建的流程是三段式:

  1. 文档加载与切分:读取你的Markdown文件,切分成便于检索的“碎片”。
  2. 本地向量化存储:用Ollama提供的OllamaEmbeddings组件,把每一段文字转成向量,存入向量数据库Chroma。
  3. 本地问答:根据你的问题,通过向量搜索找到最相关的文档片段,连同问题一并提交给本地ChatOllama,生成最终答案。

文件03_local_rag.py

"""
LangChain 第4课 - 实战三:本地 RAG 问答系统(完全不联网)
基于 Ollama 本地模型 + Chroma 向量数据库
"""

import os
from langchain_ollama import ChatOllama, OllamaEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import Chroma

# ========== 1. 加载本地文档 ==========
# 这里以当前目录下的 README.md 为例,你可以换成自己的 .txt/.md 文档
file_path = "README.md"
if not os.path.exists(file_path):
    print(f"⚠️ 当前目录没有找到 {file_path},使用示例文本代替")
    with open("sample.txt", "w", encoding="utf-8") as f:
        f.write("LangChain 是一个用于构建大语言模型应用的框架。"
                "它支持链式调用、记忆管理、提示模板等核心功能。"
                "Ollama 可以在本地运行开源大模型。")
    file_path = "sample.txt"

loader = TextLoader(file_path, encoding="utf-8")
documents = loader.load()
print(f"✓ 加载文档:{len(documents)} 段")

# ========== 2. 文档切片 ==========
# 将长文档切割成适合检索的小段落(chunk)
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,      # 每段最大字符数
    chunk_overlap=50,    # 段落之间的重叠字符,保持上下文连贯
    separators=["\n\n", "\n", "。", "!", "?", " ", ""]
)
chunks = text_splitter.split_documents(documents)
print(f"✓ 文档拆分为 {len(chunks)} 个文本片段")

# ========== 3. 使用Ollama本地向量化模型 ==========
# 选用 nomic-embed-text 模型(专门用于向量化,体积小、免费、本地运行)
# 如果尚未下载,请先执行:ollama pull nomic-embed-text
embeddings = OllamaEmbeddings(
    model="nomic-embed-text",
    validate_model_on_init=True
)

# ========== 4. 构建向量数据库(存储在 ./chroma_db 目录) ==========
vector_store = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"   # 持久化存储,下次运行不必重新向量化
)
print(f"✓ 向量数据库已创建,共 {vector_store._collection.count()} 条向量")

# ========== 5. 定义检索器(Retriever) ==========
retriever = vector_store.as_retriever(
    search_kwargs={"k": 3}   # 每次检索返回最相似的3个文档片段
)

# ========== 6. 构建 RAG 提示模板 ==========
# 包含两个占位符:{context}(检索到的文档片段)和 {question}(用户问题)
rag_prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个专业知识助手。根据以下文档内容回答问题。"
               "如果你不知道答案,请直接说不知道,不要编造。\n\n"
               "文档内容:\n{context}"),
    ("human", "{question}")
])

# ========== 7. 初始化本地大模型 ==========
llm = ChatOllama(
    model="qwen2.5:7b",
    temperature=0.2,          # RAG需要精确性,温度设置更低
    validate_model_on_init=True,
    num_predict=1024
)

# ========== 8. RAG 问答函数 ==========
def ask_question(question: str):
    """当面问题,从向量库召回相关文档,交给大模型回答"""
    # 步骤1:检索
    docs = retriever.invoke(question)
    context = "\n\n".join([doc.page_content for doc in docs])
    
    print("\n" + "=" * 60)
    print(f"📌 你的问题:{question}")
    print("-" * 60)
    print(f"📖 检索到的相关文档片段(共 {len(docs)} 段):")
    for idx, doc in enumerate(docs):
        print(f"  [{idx+1}] {doc.page_content[:150]}...")
    
    # 步骤2:生成答案
    response = llm.invoke(rag_prompt.format_messages(context=context, question=question))
    
    print("-" * 60)
    print(f"🤖 AI 回答:{response.content}")
    print("=" * 60)
    
    return response.content

# ========== 9. 演示问答 ==========
if __name__ == "__main__":
    print("\n✨ 本地 RAG 系统已启动!✨")
    print("提示:向量数据库已持久化,再次运行将直接使用现有索引,无需重新构建。\n")
    
    # 测试问题
    ask_question("LangChain 的核心功能有哪些?")
    ask_question("Ollama 可以用来做什么?")
    
    # 你可以自由补充你的真实文档目录,并使用带 for 循环的交互式问答

运行命令:

# 如果你还没有 nomic-embed-text 向量模型,先执行这一步(仅需一次,文件约274MB)
ollama pull nomic-embed-text

# 然后运行 RAG 代码
python 03_local_rag.py

预期效果:代码会自动读取README.md或自动生成的示例文件。当用户发问时,系统先从向量库检索最相关的3段文档,再把这些文档片段连同问题交给本地大模型应答。在这个过程中,你的文档和所有向量数据都只存在于你的计算机中,没有任何一行文本被发送到云端。

关键组件说明

  • OllamaEmbeddings:用nomic-embed-text模型将文本转为向量。这个模型是一个专用嵌入模型,专为检索任务设计、体积小且开源。
  • Chroma:轻量级本地向量数据库,用于存储和检索文档的Embedding向量。
  • RecursiveCharacterTextSplitter:智能切分器,尽可能在段落或句子的边界切分文档,保持语义完整。
  • 检索+生成的完整链路正是**RAG(Retrieval-Augmented Generation)**的核心思想。

💡 实战三涉及多个新增概念,在RAG专栏课(第12-18讲)中会专门彻底讲解。当前你可以把它理解为“本地知识库”的神器,解决了纯对话模型无法引用本地专有数据的问题。

完整可运行Python代码(带逐行详细注释)

将三个实战中最实用的部分整合为一个完整的“本地大模型助手”脚本:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
LangChain 第4课:Ollama + LangChain 本地私有化部署完整演示
包含:基础调用、多轮记忆对话、本地 RAG 问答
"""

import os
from langchain_ollama import ChatOllama, OllamaEmbeddings
from langchain_core.messages import HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import Chroma

# ============================================================
# 第一部分:基础调用
# ============================================================
def demo_basic():
    """演示最基本的一次性问答"""
    print("\n" + "=" * 60)
    print("【演示一】本地模型基础调用")
    print("=" * 60)
    
    llm = ChatOllama(
        model="qwen2.5:7b",
        temperature=0.7,
        validate_model_on_init=True  # 初始化时验证模型是否存在
    )
    
    messages = [HumanMessage(content="请用一句话介绍什么是LangChain")]
    response = llm.invoke(messages)
    
    print(f"🤖 模型回答:{response.content}")
    print(f"📊 Token使用:输入 {response.response_metadata.get('prompt_eval_count', 0)},"
          f"输出 {response.response_metadata.get('eval_count', 0)}")

# ============================================================
# 第二部分:多轮记忆对话(含会话持久化)
# ============================================================
def demo_memory_chat():
    """演示带会话记忆的多轮对话"""
    print("\n" + "=" * 60)
    print("【演示二】多轮记忆对话")
    print("=" * 60)
    
    llm = ChatOllama(
        model="qwen2.5:7b",
        temperature=0.7,
        validate_model_on_init=True
    )
    
    # 带历史消息占位符的模板
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个友好的AI助手,记住用户告诉你的信息。"),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{input}")
    ])
    
    chain = prompt | llm
    
    # 会话存储(实际生产环境可用 Redis 或 SQLite)
    store = {}
    
    def get_session_history(session_id: str):
        if session_id not in store:
            store[session_id] = ChatMessageHistory()
        return store[session_id]
    
    chain_with_history = RunnableWithMessageHistory(
        chain,
        get_session_history,
        input_messages_key="input",
        history_messages_key="history"
    )
    
    config = {"configurable": {"session_id": "demo_user"}}
    
    # 模拟两轮对话
    resp1 = chain_with_history.invoke({"input": "我叫小明,我喜欢编程"}, config)
    print(f"用户:我叫小明,我喜欢编程")
    print(f"AI:{resp1.content}\n")
    
    resp2 = chain_with_history.invoke({"input": "我叫什么名字?我喜欢什么?"}, config)
    print(f"用户:我叫什么名字?我喜欢什么?")
    print(f"AI:{resp2.content}")

# ============================================================
# 第三部分:本地 RAG 问答
# ============================================================
def demo_local_rag():
    """演示本地 RAG:不依赖外部 API 的向量检索 + 生成"""
    print("\n" + "=" * 60)
    print("【演示三】本地 RAG 问答(完全不联网)")
    print("=" * 60)
    
    # 确保有示例文档
    sample_content = (f"LangChain 是一个用于构建大语言模型应用的开源框架。\n"
                      f"它的核心组件包括:\n"
                      f"1. Model I/O:封装模型调用、提示模板和输出解析。\n"
                      f"2. Retrieval:从向量数据库检索相关文档。\n"
                      f"3. Chains:将多个组件串联成工作流。\n"
                      f"4. Agents:让 LLM 自主选择工具并执行决策。\n"
                      f"Ollama 可以本地运行 Llama 3.3、Qwen 2.5、DeepSeek-R1 等大模型。\n")
    
    with open("rag_sample.txt", "w", encoding="utf-8") as f:
        f.write(sample_content)
    
    loader = TextLoader("rag_sample.txt", encoding="utf-8")
    documents = loader.load()
    
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=30)
    chunks = text_splitter.split_documents(documents)
    print(f"✓ 文档切分完成,共 {len(chunks)} 个片段")
    
    # 使用 Ollama 的专用嵌入模型
    embeddings = OllamaEmbeddings(
        model="nomic-embed-text",
        validate_model_on_init=True
    )
    
    vector_store = Chroma.from_documents(
        documents=chunks,
        embedding=embeddings,
        persist_directory="./demo_rag_db"
    )
    
    retriever = vector_store.as_retriever(search_kwargs={"k": 2})
    
    rag_prompt = ChatPromptTemplate.from_messages([
        ("system", "根据以下文档回答问题:\n{context}"),
        ("human", "{question}")
    ])
    
    llm = ChatOllama(
        model="qwen2.5:7b",
        temperature=0.2,
        validate_model_on_init=True
    )
    
    question = "LangChain 的核心组件有哪些?"
    docs = retriever.invoke(question)
    context = "\n".join([doc.page_content for doc in docs])
    
    response = llm.invoke(rag_prompt.format_messages(context=context, question=question))
    print(f"📌 问题:{question}")
    print(f"🤖 回答:{response.content}")

# ============================================================
# 主入口
# ============================================================
def main():
    print("\n" + "=" * 60)
    print("LangChain 第4课:Ollama + LangChain 本地私有化部署完整演示")
    print("=" * 60)
    
    demo_basic()
    demo_memory_chat()
    demo_local_rag()
    
    # 清理临时文件
    for f in ["rag_sample.txt", "demo_rag_db"]:
        if os.path.exists(f):
            import shutil
            if os.path.isdir(f):
                shutil.rmtree(f)
            else:
                os.remove(f)

if __name__ == "__main__":
    main()

环境依赖安装命令

# 1. 激活虚拟环境
source venv/bin/activate  # Mac/Linux
# venv\Scripts\activate   # Windows

# 2. 安装 langchain-ollama 核心包
pip install -U langchain-ollama

# 3. 如果进行 RAG 实战,还需要安装以下依赖
pip install chromadb langchain-community

# 4. 完整安装命令(直接复制)
pip install -U langchain-ollama chromadb langchain-community

# 5. 确认 Ollama 服务已运行
ollama serve  # 如果提示 Address already in use,说明服务已运行,无需重复启动

常见报错坑点与避坑方案

坑1:requests.exceptions.ConnectionError: Connection refused

现象:代码报错 Connection refused

原因:Ollama 服务未启动,或默认端口 11434 被其他程序占用。Connection refused 表示没有任何进程在监听该端口。

避坑方案

  • 确认 Ollama 服务已运行:ps aux | grep ollama(Mac/Linux)或任务管理器(Windows)
  • 若未运行,执行 ollama serve 启动服务
  • 若端口被占用,释放 11434 端口:关闭 Docker 容器或 kill 残余进程;详见 taskkill /f /im ollama.exe(Win)或 killall ollama(Mac/Linux)

坑2:OllamaModelNotFoundError: model "xxx" not found

现象:初始化 ChatOllama(model=“xxx”) 或调用 .invoke() 时报错。

原因:指定的模型未通过 ollama pull 下载到本地。如果没有开启 validate_model_on_init=True,LangChain 直到真正调用 .invoke() 时才会触发模型下载缺失的报错,会浪费大量调试时间。

避坑方案

  • 运行 ollama list 查看已下载的模型列表
  • 若缺失,执行 ollama pull <模型名>,例如 ollama pull qwen2.5:7b
  • ChatOllama 中强烈建议开启 validate_model_on_init=True——这样模型名称写错时,创建对象就能立刻发现
  • 模型名称要精确匹配 Ollama 标识符(如 qwen2.5:7b 而非 qwen2.5

坑3:ModuleNotFoundError: No module named ‘langchain_ollama’

原因:未安装 langchain-ollama 包。

避坑方案:执行 pip install -U langchain-ollama 安装。

坑4:模型推理速度极慢

现象:每次回复需要 20 秒以上。

可能原因

  • 使用纯 CPU 模式(无独立显卡或显卡驱动/ CUDA 未启用),7B 模型每次回复需 10-30 秒
  • 硬盘较慢(尤其是机械硬盘 HDD),模型首次加载耗时较长
  • 内存不足导致系统使用 Swap,加重延迟
  • 选择了参数过大的模型(如 13B/70B),超过了硬件承载能力
  • temperature 值导致生成时间延长

避坑方案

  • 检查 Ollama 是否正在使用 GPU:运行 ollama ps。GPU 加速下大模型推理速度可提升 3-5 倍
  • 若使用 NVIDIA GPU,确保已安装最新显卡驱动(531+),且 CUDA 环境正确
  • 换用更小的模型,如 qwen2.5:3bphi:3.8bllama3.2:3b
  • 使用 4-bit 量化版本(默认拉取的就是量化模型,显著降低显存)
  • 增加系统内存:纯 CPU 模式下,7B 量化模型至少需要 8GB 内存,推荐 16GB

坑5:并发调用时报错 “too many open files”

原因keep_alive 时间过长(默认 5 分钟),导致多个模型实例同时占据内存资源,超出硬件承载上限。

避坑方案

  • 缩短 keep_alive 值:keep_alive=“10s”keep_alive=“1m”,及时释放模型
  • 使用单例模式:多个 LangChain 调用复用同一个 ChatOllama 对象实例
  • 关闭后台不需要的模型进程:ollama stop <model_name>

坑6:RAG 实战中 ollama pull nomic-embed-text 失败

现象:拉取向量模型时卷无法下载,或者下载中途卡死。

原因

  • 网络问题(国外镜像站访问缓慢)
  • 没有正确的 embedding 模型权限

避坑方案

  • 尝试多次 ollama pull nomic-embed-text,或不同时间段重试
  • 考虑使用其他嵌入模型:例如 ollama pull llama3.2 也可做嵌入,但非专用嵌入模型效果较差;推荐 nomic-embed-text(专用、通用、小体积)
  • 离线导入:若无法直接下载,去 Hugging Face 等网站手动下载 GGUF 版本,再用 Modelfile 创建自定义嵌入模型

本节核心知识点总结

📌 Ollama 是什么:一个开源的本地大模型运行工具,通过简洁的命令行接口让普通人也能在自己的设备上运行 Llama、Qwen、DeepSeek 等数百个主流开源模型。

📌 Ollama 核心命令速查

  • ollama run <model>:调启动模型交互对话
  • ollama pull <model>:下载模型到本地
  • ollama list:查看已下载模型列表
  • ollama serve:启动 API 服务(默认端口 11434)
  • ollama rm <model>:删除模型释放空间

📌 LangChain 的 Ollama 集成组件

  • ChatOllama:聊天模型,支持多轮对话、流式输出、工具调用(最常用)
  • OllamaLLM:文本补全模型,适合单轮生成任务
  • OllamaEmbeddings:文本嵌入/向量化,用于 RAG 的知识库构建

📌 本地私有化的核心优势

  • 数据隐私:所有数据留存本地,不发送给第三方,断绝数据泄露风险
  • 零成本:无需按 token 付费,无限次使用
  • 零网络依赖:完全离线运行
  • 完全控制:可修改模型参数、系统提示词,甚至微调模型

📌 硬件要求与配置

  • 入门级(1B-3B 模型):8GB 内存即可运行,纯 CPU 也勉强可用
  • 主流配置(7B-8B 模型):16GB 内存 + 推荐 8GB VRAM(流畅体验)
  • 纯 CPU 模式也能跑 7B 模型,一次生成 10-30 秒

课后练习题

选择题

1. 以下哪个命令可以下载 Qwen2.5 7B 模型到本地?
A. ollama run qwen2.5:7b
B. ollama list qwen2.5:7b
C. ollama pull qwen2.5:7b
D. ollama serve qwen2.5:7b

2. 在 LangChain 中使用 ChatOllama 调用本地模型时,模型不存在会报什么错误?(假设未开启 validate_model_on_init=True
A. ConnectionRefusedError
B. OllamaModelNotFoundError
C. ImportError
D. ValueError

3. 以下关于本地私有化部署的说法,正确的是?
A. 本地部署的模型必须始终联网才能工作
B. 本地部署的模型完全离线且不依赖网络
C. 本地部署不需要硬件资源
D. 只有 OpenAI 模型才能做本地部署

4. Ollama 默认的 API 监听端口是?
A. 8000
B. 5000
C. 11434
D. 8080

5. 以下哪一个是 LangChain 中专用于向量化的 Ollama 组件?
A. ChatOllama
B. OllamaLLM
C. OllamaEmbeddings
D. OllamaTool

答案及解析:

  1. Collama pull 用于下载模型,ollama run 用于运行模型对话。ollama list 列出已安装模型,ollama serve 启动服务。

  2. B。LangChain 找不到模型时会抛出 OllamaModelNotFoundError 或类似异常(取决于版本),提示指定的模型未在本地找到。强烈建议开启 validate_model_on_init=True 以便在创建对象时就能立即发现此错误。

  3. B。本地部署的核心优势是完全离线独立运行。A 和 C 显然错误,D 错误——很多开源模型(Qwen、Llama、DeepSeek)都支持本地部署。

  4. C。Ollama 默认监听端口 11434

  5. COllamaEmbeddings 用于生成文本的嵌入向量,是 RAG 检索的基础组件。

简答题

6. 请简述 LangChain 中 ChatOllamaOllamaLLM 的区别,以及各自适用场景。

参考答案
区别

  • ChatOllama 是聊天模型,对应 Ollama 的 /api/chat 端点。基于消息列表(带有 role 角色,如 system、user、assistant),天然支持多轮对话,适合构建聊天机器人应用。
  • OllamaLLM 是原始的文本补全模型,对应 /api/generate 端点。直接接收字符串,返回生成的续写文本,属于“无状态”的单轮问答,无法有效维护会话上下文。

适用场景

  • ChatOllama 适用于打造现代智能助手、对话系统、客服机器人等任何需要上下文概念的应用。
  • OllamaLLM 更适用于纯文本生成任务,如代码自动补全、摘要生成等简单场景。

7. 列举三种在使用 LangChain + Ollama 时可能遇到的性能瓶颈,并提出优化建议。

参考答案
瓶颈1:纯 CPU 推理慢,7B 模型在纯 CPU 下可能耗时 10 秒以上。
优化建议:配置 NVIDIA GPU(需驱动 531+)以启用 CUDA 加速,推理速度可提升 3-5 倍。

瓶颈2:模型加载时间长(5-30 秒),尤其是 HDD 硬盘。
优化建议:换用 SSD 存储模型文件,或是使用 keep_alive=“-1” 保持模型常驻内存(适合频繁调用场景)。

瓶颈3:RAM/VRAM 不足导致系统卡顿或模型无法运行
优化建议:选择更小的量化模型(如 3B 模型 qwen2.5:3b),或使用 4-bit 量化(q4_0)。

实践题

8. 编写代码实现一个“本地私人助手”,要求:支持多轮对话(记住用户说的话),并且当用户问“刚才我对你说了什么?”时,能够准确复述之前对话中透露的关键信息。

参考答案

from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

# 初始化模型
llm = ChatOllama(
    model="qwen2.5:7b",
    temperature=0.7,
    validate_model_on_init=True
)

# 定义带历史的提示模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个私人助手,需要记住用户的个人信息。"
               "如果用户问你'我刚才对你说了什么'或类似问题,"
               "请从我们的对话历史中提取信息并准确回答。"),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{input}")
])

chain = prompt | llm

# 会话存储
store = {}

def get_history(session_id):
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

chain_with_history = RunnableWithMessageHistory(
    chain,
    get_history,
    input_messages_key="input",
    history_messages_key="history"
)

config = {"configurable": {"session_id": "test_user"}}

# 模拟对话
chain_with_history.invoke({"input": "我叫王小明,今年28岁,是一名医生"}, config)
chain_with_history.invoke({"input": "我住在上海市浦东新区"}, config)
response = chain_with_history.invoke({"input": "我刚才对你说了什么?"}, config)

print(response.content)
# 期望输出:你叫王小明,28岁,是一名医生,住在上海市浦东新区。

解析:本练习考察综合应用 RunnableWithMessageHistory 实现有状态会话的能力。当用户调用“我刚才对你说了什么?”时,模型会从历史消息中提取相关信息组织回答,而不是从零生成——这是大模型对上下文理解的体现。


🔗《30节课 LangChain 从入门到精通》系列课程导航

去订阅

🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐