首页/LangChain 指南/认识 LangChain
LangChain 指南 · 第 01 章

认识 LangChain

《LangChain指南》是一本轻量 Python 用法手册:不讲玄学,只讲「这个东西怎么用」。 全书示例围绕一个虚构场景——拾光书屋:一个线上阅读社区,有书籍、书评、阅读笔记和会员。你要给它装一个「AI 店员」

约 27 分钟10,774 字12 个代码示例

《LangChain指南》是一本轻量 Python 用法手册:不讲玄学,只讲「这个东西怎么用」。 全书示例围绕一个虚构场景——拾光书屋:一个线上阅读社区,有书籍、书评、阅读笔记和会员。你要给它装一个「AI 店员」。

本章技术基线

项目 约定
语言 Python 3.10+
包管理 uv(uv add / uv run)
框架 LangChain 1.x(langchain + langchain-ollama 两个包)
模型 本地 Ollama 跑 qwen3:4b(不联网、不花钱)
场景 拾光书屋(书籍 / 书评 / 阅读笔记 / 会员)

学完本章,你能:跑通第一次模型对话、看懂返回值里的每一个字段、让回答一个字一个字蹦出来(流式)、给模型装上缓存让重复提问「秒回」。


1. 环境准备:开工前先摆好工具

作用:本章所有代码都依赖三样东西——Python 项目、LangChain 包、本地 Ollama。先把它们摆好,后面 11 章就不用再折腾环境。

装法:

# 1. 安装 uv(一个极快的 Python 包管理器,已装可跳过)
#    macOS / Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh

# 2. 建项目、装依赖(langchain-core 会作为依赖自动带上,不用单独装)
uv init shiguang-ai
cd shiguang-ai
uv add langchain langchain-ollama

# 3. 安装 Ollama 桌面版(或 brew install ollama),拉模型
ollama pull qwen3:4b

# 4. 确认服务在跑(默认监听 http://localhost:11434)
ollama list

最小示例(全书第一段代码,5 行跑通):

from langchain_ollama import ChatOllama

model = ChatOllama(model="qwen3:4b")

ai = model.invoke("用一句话向新会员介绍「拾光书屋」。")
print(ai.content)

保存后运行:uv run demo/demo_chat.py(demo 文件在配套的 demo/ 目录里)。

常见坑:

  • Python 版本:LangChain 1.x 要求 Python 3.10+。如果你在 3.9 上 pip install,只会装到老的 0.3.x,很多导入路径都不一样,教程会对不上号。
  • ConnectError 报错:httpx.ConnectError ... localhost:11434 说明 Ollama 服务没启动——打开 Ollama App 或跑 ollama serve。
  • 404 model not found:服务在跑但没拉模型,先 ollama pull qwen3:4b。
  • 别用系统自带的 pip 裸装,容易污染全局环境;跟着 uv 走,省心。

2. LangChain 是什么:一块乐高底板

作用:回答「我为什么需要它」。一句话:LangChain 是搭 AI 应用的乐高底板——大模型只是其中一块积木,你还需要工具、记忆、文档检索这些积木;LangChain 把它们做成统一接口,让你「咔哒」一声拼在一起。

对拾光书屋来说,你想要的「AI 店员」其实是好几种能力的组合:

你想要的效果 背后需要的能力
会员能和店员连续聊 多轮对话 + 记忆
店员能查书、算折扣 工具调用
店员答得上来书屋自己的规矩 RAG(检索书屋知识库)
店员自己决定先查库存还是先写推荐语 Agent
店员每句话风格统一 提示词模板

这些能力 LangChain 全都预置了「积木」。没有它,你就得自己写胶水代码:拼接请求、管理上下文、解析输出、重试……有了它,你专注拼装。

顺带澄清一个常见误解:LangChain 不是 Python 专属,它有 JS/TS 版(还挺能打),Go、Rust 版也在长。但 Python 是旗舰:新功能最先落地、集成最全、社区最大。所以这本手册走 Python 线——同样的思路,语言只是外壳。

常见坑:

  • LangChain 不生产模型,也不替你思考。它只是「编排层」,模型行不行,还是看模型本身(qwen3:4b 是个 4B 小模型,别拿它当 GPT-5 用)。
  • 别一上来就研究「架构图」,先跑起来,再回头理解。

3. 生态对比:为什么这本手册选 LangChain

作用:选型不纠结。AI 应用框架不止一家,各有各的地盘:

框架 一句话定位 适合谁
LangChain 全场景积木库:RAG / Agent / 记忆 / 工具全覆盖 想一站式掌握 AI 应用开发的人
LlamaIndex 数据索引与检索专精 只做 RAG、且检索是绝对核心
Haystack 企业级搜索问答管线,带评估体系 偏工程化、要严格评测的团队
AutoGen / CrewAI 等 多 Agent 协作编排 研究「多个 AI 分工合作」

选 LangChain 的三个理由(很实在):

  1. 一张全家桶门票:12 章目录就是它的能力地图——提示词、LCEL、流式、解析器、文档加载、向量库、RAG、记忆、工具、Agent、回调,一路学下去不用换框架。
  2. 生态最厚:Ollama 这种本地引擎开箱即用;FAISS、Chroma 这些向量库无缝对接;出问题搜一下基本都有人踩过坑。
  3. 对学习者友好:命名直白(invoke、stream 这类词看名字就懂),模块能拆开单独玩,适合边拆边学。

常见坑:

  • 别陷入「框架对比内耗」。这三个框架的核心概念高度相通,学会一个,换别的只是换 API 名字。
  • LangChain 不是数据库、不是 Web 框架,也别用它替代你的业务代码——它只管「和模型打交道」这一段。

4. 模块全景:拾光书屋的部门分布图

作用:把全书 12 章映射到 LangChain 的模块上,心里有张地图,走到哪都知道自己在哪。

模块 管什么 拾光书屋的对应物 对应章节
Models(模型) 跟大模型说话的统一接口 你雇的驻店作家 01 / 03
Prompts(提示词) 需求单模板,可复用可插变量 店员话术手册 02
LCEL / Runnable 管道编排 后厨流水线 03 / 04
Output Parsers 把回答变成 Python 对象 回答装订成标准表格 05
Indexes(文档与检索) 知识库的入库与查找 书评库分类上架 06 / 07 / 08
Memory(记忆) 多轮对话的上下文 会员档案卡 09
Tools(工具) 让模型能调用外部函数 查库存的计算器 10
Agents 模型自己决定下一步 值班店经理 11
Callbacks(回调) 观测、日志、调试 店内监控摄像头 12

装法:Python 侧的包是「四层结构」(这一点和 JS 版的三包结构思路一致,只是拆得更细):

包 角色 你要手动装吗
langchain-core 内核抽象:Runnable、消息、缓存等 不用,自动带上
langchain 高层入口:init_chat_model、通用封装 要(uv add langchain)
langchain-community 社区集成大杂烩 按需(本章 SQLite 缓存会用到)
langchain-ollama 等 每个模型供应商一个专属包 要(uv add langchain-ollama)

最小示例(四层结构在代码里的样子):

# 高层入口(langchain 包)
from langchain.chat_models import init_chat_model

# 供应商专属包(langchain-ollama 包)
from langchain_ollama import ChatOllama

# 内核抽象(langchain-core 包,自动安装)
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.caches import InMemoryCache

常见坑:

  • 老教程里 from langchain.chat_models import ChatOllama 这种写法在 1.x 已经移除——供应商类都搬进各自的集成包了。看到报 ImportError,先检查导入路径。
  • langchain-community 里也有一些和集成包同名的类(历史上搬过家),优先用 langchain-xxx 专属包里的版本。
  • langchain-core 你几乎永远不需要显式安装,它是所有包的公共地基。

5. 模型接口(上):ChatOllama,把作家请进店

作用:模型接口是后面 11 章的「发动机」。在其他教程里你常见到 ChatOpenAI(连 OpenAI 云端 API);本手册用 ChatOllama——同样的事,只是作家住在自己家(本地),不要钱。

先补一个背景知识:LangChain 里有两类模型接口——

补全模型 对话模型
类 OllamaLLM(继承 BaseLLM) ChatOllama(继承 BaseChatModel)
输入 纯字符串 消息列表(带角色)
输出 纯字符串 AIMessage 对象
场景 单轮文本任务:改写、摘要 聊天、Agent、结构化输出

如今主流是对话模型(OpenAI 官方都停了补全接口),全书只用 ChatOllama。知道 OllamaLLM 存在即可,遇到了别慌。

装法:uv add langchain-ollama(第 1 节已装)。

最小示例(两种输入写法):

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_ollama import ChatOllama

model = ChatOllama(model="qwen3:4b", temperature=0.7)

# 写法 A:直接丢一个字符串(LangChain 会自动把它包成一条用户消息)
ai = model.invoke("给《夜晚的潜水艇》写一句 30 字以内的推荐语。")

# 写法 B:消息列表,能带上「角色」——店员的人设就是 SystemMessage 放的
messages = [
    SystemMessage(content="你是拾光书屋的资深店员,说话简洁、只谈书。"),
    HumanMessage(content="会员问:有适合通勤时读的短篇集吗?"),
]
ai = model.invoke(messages)

print(ai.content)

看懂返回值:AIMessage 不是字符串,是快递包裹

invoke 返回的不是 str,而是一个 AIMessage 对象。拆包裹:

from langchain_core.messages import AIMessage

ai: AIMessage = model.invoke("用一句话介绍「拾光书屋」。")

ai.content              # 货物:正文本身(多数时候就是 str)
ai.response_metadata    # 面单:模型侧回执,比如 model 名、耗时(字段随供应商变化)
ai.usage_metadata       # 称重单:token 统计 {"input_tokens": …, "output_tokens": …, "total_tokens": …}
ai.id                   # 运单号:本次调用的唯一 ID,形如 "run-xxxx"
ai.tool_calls           # 工具调用记录(第 10 章才用得上,现在是空列表)

为什么非要包一层?因为后面链条(LCEL)、工具调用、日志追踪都需要这些元信息。取正文永远用 ai.content——这是新手最常忘的一步。

常见坑:

  • qwen3 会「思考」:默认模式下,它会把思考过程用特殊标签混在 content 里输出,正文看起来脏脏的。解法:ChatOllama(model="qwen3:4b", reasoning=False) 直接关;或 reasoning=True 让思考单独放进 ai.additional_kwargs["reasoning_content"],正文保持干净。本课 demo 统一用 reasoning=False。
  • ai 是对象不是字符串:print(ai) 会打出一堆结构,print(ai.content) 才是你要的。
  • 消息别自己拼 dict({"role": "user"} 那种 OpenAI 原生格式),用 SystemMessage / HumanMessage / AIMessage,LangChain 的组件都认这些。
  • base_url 默认就是 http://localhost:11434,Ollama 跑在本机时不用写。

6. 模型接口(下):init_chat_model,一行换作家

作用:今天用本地 qwen3:4b 白嫖,明天要上 OpenAI 收费档,你希望只改一个字符串,其余代码一行不动。LangChain 1.x 给的答案就是 init_chat_model——像个「作家中介」:你说要谁,它负责把人带进门(自动 import 对应的包)。

最小示例:

from langchain.chat_models import init_chat_model

# 「供应商:模型名」写在一根字符串里
model = init_chat_model("ollama:qwen3:4b", temperature=0)

# 等价写法:分开传
model = init_chat_model("qwen3:4b", model_provider="ollama")

# 哪天要换作家(先 uv add langchain-openai):
# model = init_chat_model("openai:gpt-4o-mini")

print(model.invoke("用一句话介绍「拾光书屋」。").content)

拿到的 model 就是一个如假包换的 ChatOllama 实例(可以 type(model) 验证),.invoke、.stream、后面章节的 bind_tools 全部照常用。好处是「换供应商」可以做成配置项:

import os

model = init_chat_model(os.getenv("BOOKSTORE_MODEL", "ollama:qwen3:4b"))

那到底用哪种? 简单决策:

场景 选择
学习、demo、单供应商 ChatOllama 直连——IDE 补全友好,参数一目了然
应用要可配置、多环境切换 init_chat_model——供应商写进配置,代码零改动

常见坑:

  • 字符串按第一个冒号切分,所以 ollama:qwen3:4b 会被正确切成「ollama」+「qwen3:4b」,模型名里的冒号不会切错。
  • 没装对应集成包会直接 ImportError,提示你 pip install langchain-xxx——照做即可(用 uv 就是 uv add)。
  • 供应商名写错(比如 ollama 拼成 olama)报的是「不认识的 provider」,不是「拼写错误」,别被带偏。

7. 参数旋钮:temperature 与它的家族

作用:控制作家的「发挥程度」。写书评要中规中矩,写活动文案要天马行空——同一模型,一个参数切换。

最小示例:

from langchain_ollama import ChatOllama

稳重派 = ChatOllama(model="qwen3:4b", temperature=0, reasoning=False)   # 照标准话术办事
发挥派 = ChatOllama(model="qwen3:4b", temperature=0.9, reasoning=False) # 允许自由发挥

print(稳重派.invoke("为「拾光书屋」写一句迎宾语。").content)
print(发挥派.invoke("为「拾光书屋」写一句迎宾语。").content)

常用参数速查(Ollama 侧):

参数 管什么 常用值
temperature 随机度:0 最稳,越高越发散 0(严谨)/ 0.7(日常)/ 1.0+(创意)
top_k / top_p 另外两个采样旋钮,和 temperature 配合 一般不动
num_ctx 上下文窗口(能塞进去多少 token) 长文档时调大,默认只有几千
num_predict 最多生成多少 token 限制废话长度时用
seed 随机种子 想复现同一次输出时设
repeat_penalty 重复惩罚 模型车轱辘话多时调
keep_alive 模型在内存里驻留多久 高频调用设 "5m" 省冷启动
base_url Ollama 服务地址 默认 http://localhost:11434

常见坑:

  • temperature=0 不等于逐字复读——本地推理仍有微小浮动,要可复现请配合 seed。
  • 高 temperature + 缓存 = 白开(见第 9 节的坑 5)。
  • num_ctx 默认不大(2k~4k 量级,随版本而异),长文档会被「悄悄截断」——这是第 06 章要学「文档切割」的原因之一。

8. 流式输出初见:店员边写边递

作用:invoke 是「店员写完一整封信再交给你」;stream 是「边写边撕页递给你」。对聊天界面来说,后者体验完全不是一个档次——用户 1 秒内看到第一个字,而不是干等 20 秒。

最小示例:

from langchain_ollama import ChatOllama

model = ChatOllama(model="qwen3:4b", temperature=0.7, reasoning=False)

pieces: list[str] = []
for chunk in model.stream("为《百年孤独》写一段 60 字左右的书评开头。"):
    print(chunk.content, end="", flush=True)   # 每拿到一片就立刻打印
    pieces.append(chunk.content)
print()  # 换行
print("拼接结果:", "".join(pieces))

for 循环里拿到的 chunk 是 AIMessageChunk——AIMessage 的切片,chunk.content 是增量片段,不是完整回答,要自己拼。

常见坑:

  • 忘了拼接:把最后一个 chunk 当成完整回答(内容只有尾巴)。
  • print(chunk.content) 不加 end="",屏幕上一行一片,乱成一锅粥。
  • 流式不走缓存——即使答案已经缓存过,stream 还是会逐字重新生成(第 9 节专门演示这个坑)。
  • 流式的进阶玩法(异步流、并发、批处理)在第 04 章展开,这里先会「用」就行。

9. 缓存:让第二次提问变成「白嫖」

作用:书屋会员特别爱问重复问题——「营业时间几点」「怎么积分」。让店员每个问题都现场写一遍答案,又慢又费钱(如果调的是云端 API,那就是真金白银)。缓存的思路:前台放一个金句卡片盒,同一个问题第二次直接发卡。

三个好处:快(毫秒级返回)、省(少调模型)、稳(同一问题永远同一答案)。

装法:InMemoryCache 就在 langchain-core 里(已自动安装);SQLiteCache 在 langchain-community 里,用它之前先 uv add langchain-community。

最小示例 A:全局缓存(最常用)——设一次,全店生效:

from langchain_core.caches import InMemoryCache
from langchain_core.globals import set_llm_cache
from langchain_ollama import ChatOllama

set_llm_cache(InMemoryCache())  # 开店时设一次:所有模型调用默认走它

model = ChatOllama(model="qwen3:4b", temperature=0, reasoning=False)
# 第一次:真调用(慢)
model.invoke("用一句话介绍「拾光书屋」。")
# 第二次:命中缓存(毫秒级,返回和第一次完全相同)
model.invoke("用一句话介绍「拾光书屋」。")

最小示例 B:SQLite 持久化——内存缓存关机就没,SQLite 落到磁盘文件,重启进程也还在:

from langchain_community.cache import SQLiteCache

set_llm_cache(SQLiteCache(database_path="shiguang.db"))
# 想清空缓存:删掉 shiguang.db 文件即可

显式缓存(挂在模型实例上):不想全店生效,只想给某一个模型挂缓存?ChatOllama 构造参数里藏着一个 cache,文档里很低调,但很好用:

from langchain_core.caches import InMemoryCache

model_a = ChatOllama(model="qwen3:4b", cache=InMemoryCache())  # 自带私有缓存,不依赖全局
model_b = ChatOllama(model="qwen3:4b", cache=False)            # 强制绕过全局缓存(即使 set_llm_cache 设过)
model_c = ChatOllama(model="qwen3:4b", cache=True)             # 明确要求用全局缓存

三条规则记一下:传缓存实例 = 用这个实例;cache=False = 强制关闭;cache=True = 必须先 set_llm_cache,否则一调用就抛 ValueError。

常见坑:

  1. 缓存 key 是「精确匹配」:key = 提示词的序列化串 + 模型配置串。「营业时间几点?」和「请问营业时间是几点?」是两个不同的 key,不会命中。改一个字、换一个参数(比如 temperature)、调换消息顺序,都会 miss。想让「相似问题也命中」得自己实现语义缓存(用嵌入算相似度),属于进阶玩法。
  2. stream() 不走缓存:缓存的检查发生在 invoke 这类整体调用的入口,而 stream 返回的是逐块迭代器,拿到的是生成过程本身——所以哪怕答案在缓存里躺着,流式仍然会完整生成一遍。(想要「流式 + 缓存」,常见做法是缓存命中后自己把整段答案切成小块模拟推送。)
  3. cache=True 没设全局缓存会直接报错:ValueError: Asked to cache, but no cache found at 'langchain.cache'。看到这个错,要么先 set_llm_cache(...),要么直接传缓存实例。
  4. 开发期的缓存幻觉:缓存开着,你会觉得「模型怎么突然又快又稳」——其实是缓存。调试新提示词前,删 .db 文件或 InMemoryCache().clear()。
  5. 高 temperature + 缓存 = 自欺欺人:同一个 key 永远返回第一次的答案,你以为模型稳定输出,其实只是缓存冻结了某一次的发挥。缓存场景建议 temperature=0。
  6. 一条无害的警告:用 langchain-community 的 SQLiteCache 读缓存时,可能看到 LangChainPendingDeprecationWarning(提示 allowed_objects 默认值未来会收紧)——库自己在做迁移预告,不影响使用,忽略即可。

10. 本章小结

你拿到了什么:

  • ✅ 环境:uv + LangChain 1.x + 本地 Ollama(qwen3:4b)
  • ✅ 认知:LangChain 是乐高底板,Python 是旗舰语言,12 章就是模块地图
  • ✅ 模型接口两种姿势:ChatOllama 直连(学习用)与 init_chat_model(可切换用)
  • ✅ AIMessage 五件套:content / response_metadata / usage_metadata / id / tool_calls
  • ✅ 参数:temperature 家族,qwen3 记得 reasoning=False
  • ✅ 流式:stream() + chunk 拼接
  • ✅ 缓存:set_llm_cache 全局、cache= 实例级、SQLite 持久化,以及五个坑

配套 demo(在本章 demo/ 目录):

文件 演示内容
demo/demo_chat.py invoke 两种写法、AIMessage 结构、流式输出、init_chat_model
demo/demo_cache.py 显式缓存、全局缓存、cache=False 绕过、SQLite 持久化、流式不走缓存

跑法:uv run demo/demo_chat.py、uv run demo/demo_cache.py(先按第 1 节把环境装好)。

下一章:02 · 提示词模板与消息