LangChain 解决的不是「怎么调一次模型」,而是「怎么把一堆 LLM 相关步骤组合成可维护的应用」。裸 SDK 能让你
client.chat.completions.create(...)调通一次;但当你要在调用前套 Prompt 模板、调用后解析结构化输出、中间插入向量检索、再挂一个工具调用循环时,代码会迅速退化成胶水。LangChain 的答案是:让每个步骤都实现同一个接口,组合就变成拼接。
一、为什么需要它:从胶水代码到可组合链
一个真实的 LLM 应用很少只调一次模型。典型的 RAG 问答链长这样:用户问题 → 向量检索相关文档 → 把文档塞进 Prompt 模板 → 调 ChatModel → 解析输出。如果用裸 SDK,这五步是五段彼此不兼容的代码;任何一步想换实现(换模型、换向量库、加缓存),都要改胶水。
| 维度 | 裸 SDK 手写 | LangChain |
|---|---|---|
| 组合方式 | 函数嵌套 / 手动传参,调用顺序写死 | prompt | model | parser 管道拼接 |
| 换模型 | 改 SDK 调用代码 + 重写参数 | 换一个集成包的 ChatModel 实例即可 |
| 流式/批处理 | 每个步骤单独适配 stream / 并发 | 整条链自带 stream / batch / ainvoke |
| 检索/工具/记忆 | 各自手写、互不复用 | 都是 Runnable,可直接插入链中 |
| 调试可观测 | 自己埋 print / 日志 | 统一 Callbacks + LangSmith 追踪 |
二、架构全景:四层包结构
从 0.1 版本起,LangChain 把单一巨包拆成了职责清晰的多个包。理解这张分层图,你就知道任何一个类该 import 自哪里——这是新手最大的困惑来源。
- langchain-core:最底层,只定义抽象接口与基类——
Runnable、BaseChatModel、BasePromptTemplate、BaseOutputParser、消息类型(HumanMessage/AIMessage/SystemMessage)。无任何第三方重依赖,所有其他包都依赖它。 - 集成包(langchain-openai / langchain-anthropic / langchain-google-genai 等):每个第三方厂商一个独立包,提供该厂商的具体实现,如
langchain_openai.ChatOpenAI。版本独立、按需安装,避免一个巨包拖着上百个 SDK。 - langchain:编排层,提供链、Agent、检索策略等高层组件,如
create_retrieval_chain、create_react_agent。它依赖 core 与集成包,把抽象拼成可用模式。 - langchain-community:社区贡献的大量集成(各种 DocumentLoader、VectorStore、Tool),更新快但稳定性参差,按需取用。
- langgraph(进阶):用图(StateGraph)表达带循环与分支的复杂 Agent 流程,是当前官方推荐的 Agent 构建方式,后续 Agent 章会展开。
三、贯穿全局的主线:Runnable
Runnable 是整个框架的统一接口。只要一个对象实现了 Runnable,它就一定有 invoke / stream / batch 这三个方法(以及对应的异步版 ainvoke / astream / abatch)。ChatModel 是 Runnable、Prompt 模板是 Runnable、OutputParser 是 Runnable、用 | 拼出来的整条链还是 Runnable。这就是为什么组合如此自然——管道两端的类型天然契合。
# 安装核心包 + OpenAI 集成包(包名用横线)
pip install -U langchain langchain-openai
# 配置 API key(macOS / Linux);Windows 用 set / $env:
export OPENAI_API_KEY="sk-..."
# 最小 hello world:直接把 ChatModel 当成一个 Runnable 来 invoke
from langchain_openai import ChatOpenAI
# ChatOpenAI 本身就是一个 Runnable
# model 参数指定具体模型,temperature 控制随机性(0 = 最确定)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# invoke 是 Runnable 的统一入口:传入即可,返回一个 AIMessage 对象
response = llm.invoke("用一句话解释 LangChain 是什么")
print(type(response)) # <class 'langchain_core.messages.ai.AIMessage'>
print(response.content) # 真正的文本回答在 .content 字段里
# 同一个 Runnable 接口,自带 stream / batch / 异步——无需改写调用代码
# 1) 流式输出:逐块拿到 token(聊天界面打字机效果就靠它)
for chunk in llm.stream("写一句关于秋天的话"):
print(chunk.content, end="", flush=True)
print()
# 2) 批处理:一次并发跑多条输入,返回结果列表(顺序与输入一致)
results = llm.batch(["翻译成英文:你好", "翻译成英文:再见"])
for r in results:
print(r.content)
# 3) 异步:在 async 函数里用 ainvoke / astream(接口名只多个 a 前缀)
# import asyncio
# async def main():
# resp = await llm.ainvoke("async 调用示例")
# print(resp.content)
# asyncio.run(main())
四、本 Wiki 路线图:每章在全景图的哪一块
- 安装与环境配置 → 把包结构落到本地:装哪些包、API key 怎么配、跑通第一个 invoke。
- 核心概念 Runnable 与 LCEL → 主线放大:
|组合、RunnablePassthrough、RunnableParallel怎么搭并行与透传。 - Prompt / ChatModel / OutputParser 三件套 → 全景图最常用的一条基础链,把自由文本变成结构化输出。
- RAG 全链路 → 在链前面接上检索:DocumentLoader → TextSplitter → Embeddings → VectorStore → Retriever。
- Tools 与 Agent → 让模型自己决定调用哪个工具、循环决策(create_react_agent / langgraph)。
- Memory 与流式/异步/批处理 → 给链加上对话历史,并把 Runnable 的 stream/batch/异步能力用到生产场景。
- 回调调试与 LangServe 部署 → Callbacks 观测整条链,最后把链发布成 HTTP 服务。
✓推荐做法
- 先建立「一切皆 Runnable」的心智,再去看任何官方示例代码
- 按需安装集成包(langchain-openai 等),而不是想当然地只装一个 langchain
- 区分 invoke(要完整结果)/ stream(要打字机体验)/ batch(要批量并发)三种调用形态
✗不推荐
- 不要把 LangChain 当成「调模型的语法糖」——它的价值在组合,单次调用裸 SDK 也行
- 不要在新项目里抄老教程的
from langchain import ...顶层导入,包早已拆分 - 不要忽略 .content——invoke 返回的是 AIMessage 对象,不是裸字符串
⚠常见误区
- 包名横线(langchain-openai)vs import 下划线(langchain_openai)写反导致 ModuleNotFoundError
- 误以为只有 ChatModel 能 stream,其实整条 LCEL 链都能 stream
- 把 langchain-community 当成稳定核心来重度依赖,实际它更新快、质量参差
能用一句话说出 Runnable 是什么、能正确从对应包 import ChatOpenAI、并跑通 invoke 拿到 .content,即算掌握本章。
框架的价值不在于它能调用模型,而在于它让你把调用、检索、工具、记忆当成同一种东西来拼装。
— 本章核心论点