本文面向已具备基础API调用经验的开发者,系统介绍LangChain在国内环境下的落地方式与LCEL核心语法。
梳理LangChain的核心优势及0.2至1.x的版本演进与包结构,说明为何在工程场景中值得引入这一框架;重点讲解DeepSeek、通义千问等国产模型的OpenAI兼容接入方案,涵盖依赖安装、环境变量配置与ChatOpenAI工厂封装。在组件层面,文章拆解Prompt、Model、Output Parser、Chain四大核心模块的职责与协作关系,并深入LCEL管道符|的链式编程语法,分析其在可读性、复用性与流式扩展方面的工程化优势。
实战部分给出最简LangChain项目目录结构,并演示如何基于LCEL搭建结构化多轮问答链,结合JsonOutputParser与RunnableWithMessageHistory,实现带会话记忆、可解析JSON输出的完整链路。
一、为什么选LangChain?
直接调用OpenAI兼容API能跑通问答,但一进入工程场景,多步编排、结构化输出、会话记忆、工具调用就会迅速写出大量胶水代码。LangChain的价值在于把LLM应用里反复出现的模式抽象成可组合、可测试、可观测的组件。
1.1 核心优势
LangChain首先强调组件化 :模型、Prompt、解析器、检索器各自独立,某一层换实现(比如从DeepSeek切到千问)不必重写整条链路,也方便对单层做单元测试。
其次,LCEL编排 用|管道把步骤串成声明式链条,读起来像数据流,而不是层层嵌套的函数调用。在此基础上,并行分支、流式输出都更容易扩展。
第三,所有环节统一实现Runnable协议 。invoke、stream、batch接口一致------监控埋点、回调、LangSmith追踪可以挂在链的任意节点,线上排错成本更低。
此外,LangChain在生态集成 上有深厚的OpenAI兼容接口、主流向量库、Agent框架都有现成适配,避免每个项目重复造轮子。配合可选的可观测性工具如LangSmith,还能做链路回放与评测回归。
1.2 版本与包结构
LangChain在0.2之后完成包拆分 ,1.x进一步统一Agent与模型初始化方式。理解分层有助于正确安装依赖,避免装了langchain却缺langchain-openai的常见问题。
最底层是 langchain-core ,提供Runnable、LCEL、Prompt、OutputParser等基础抽象,其他包都依赖它。
对接OpenAI及兼容接口 (DeepSeek、千问等)需单独安装 langchain-openai 。向量库、文档加载器等社区集成在 langchain-community 中,按需引入即可。
日常开发常用的 langchain 包则提供高层API,例如init_chat_model和Agent相关能力。若要做有状态Agent或持久化会话,1.x更推荐 langgraph,它是LangChain生态里负责带记忆的多步决策的方向。 如下图所示为官方提供的依赖关系:

1.x 典型变化:
- LCEL成为默认组合方式 ,旧式
LLMChain类逐步边缘化; init_chat_model("provider:model")统一多厂商模型入口;- Agent与记忆 更推荐LangGraph持久化,但LCEL+
RunnableWithMessageHistory仍是理解多轮对话的简洁路径。
二、中国环境适配DeepSeek与通义千问
国内开发常遇到两类问题,官方SDK面向OpenAI、Anthropic设计。国内模型提供OpenAI兼容REST ,但base_url、模型名、Key环境变量各不相同。LangChain通过ChatOpenAI+自定义base_url统一接入。
2.1 接口对照
国内两大常用模型均兼容OpenAI Chat Completions协议,接入LangChain时只需对齐下表中的base_url、模型名与密钥变量:
| 厂商 | 兼容 Base URL | 常用模型 | 环境变量 |
|---|---|---|---|
| DeepSeek | https://api.deepseek.com |
deepseek-chat |
DEEPSEEK_API_KEY |
| 通义千问 | https://dashscope.aliyuncs.com/compatible-mode/v1 |
qwen-turbo、qwen-plus |
DASHSCOPE_API_KEY 或 OPENAI_API_KEY |
因此在LangChain中统一使用langchain_openai.ChatOpenAI,切换厂商时改这三个字段即可,无需单独引入各厂商SDK。
2.2 依赖安装
在项目目录执行:
bash
pip install langchain langchain-core langchain-openai python-dotenv pydantic
或使用requirements.txt锁定最低版本:
text
langchain>=0.3.0
langchain-core>=0.3.0
langchain-openai>=0.2.0
python-dotenv>=1.0.0
pydantic>=2.0.0

2.3 环境变量配置
将密钥写入.env:
bash
# 方案A:DeepSeek
DEEPSEEK_API_KEY=sk-xxx
LLM_MODEL=deepseek-chat
# 方案B:通义千问
DASHSCOPE_API_KEY=sk-xxx
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
LLM_MODEL=qwen-turbo
加载配置的代码逻辑,优先DeepSeek Key,否则回退千问。DeepSeek使用独立base_url,避免与其他.env中的OPENAI_BASE_URL冲突。
python
import os
from dataclasses import dataclass
from dotenv import load_dotenv
load_dotenv()
@dataclass(frozen=True)
class Settings:
provider: str
api_key: str
base_url: str
model: str
def load_settings() -> Settings:
if key := os.getenv("DEEPSEEK_API_KEY"):
return Settings(
provider="deepseek",
api_key=key,
base_url=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
model=os.getenv("LLM_MODEL", "deepseek-chat"),
)
if key := os.getenv("DASHSCOPE_API_KEY") or os.getenv("OPENAI_API_KEY"):
return Settings(
provider="qwen",
api_key=key,
base_url=os.getenv(
"DASHSCOPE_BASE_URL",
"https://dashscope.aliyuncs.com/compatible-mode/v1",
),
model=os.getenv("LLM_MODEL", "qwen-turbo"),
)
raise RuntimeError("未配置 DEEPSEEK_API_KEY 或 DASHSCOPE_API_KEY")
借助dataclass 定义了不可变的配置实体类Settings,统一封装大模型调用所需的服务商标识、密钥、接口地址与模型名称四类核心参数,同时通过python-dotenv加载本地.env环境变量,实现配置与代码逻辑解耦,方便多环境灵活切换参数。
load_settings函数按优先级读取环境变量,优先加载DeepSeek相关密钥与接口配置,无配置则降级适配通义千问兼容模式。自动填充接口与模型默认值兜底,未检测到任一有效密钥时直接抛出运行时异常,确保项目启动前必校验大模型调用凭证,避免运行时接口请求失败。
2.4 构建ChatModel
无论DeepSeek还是千问,工厂函数形式一致:
python
from langchain_openai import ChatOpenAI
def build_chat_model(settings: Settings) -> ChatOpenAI:
return ChatOpenAI(
model=settings.model,
api_key=settings.api_key,
base_url=settings.base_url,
temperature=0.3,
)
也可使用LangChain 1.x统一入口需已安装对应集成包:
python
from langchain.chat_models import init_chat_model
model = init_chat_model(
model="deepseek-chat",
model_provider="openai",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
)
如下图所示加载Deepseek:

三、四大核心组件解析
LCEL链路由四类组件串联而成。掌握它们等于掌握LangChain的积木块。

四者分工可概括为:
| 组件 | 典型类 | 职责 |
|---|---|---|
| Prompt | ChatPromptTemplate |
把变量填进system/human消息模板 |
| Model | ChatOpenAI |
调用大模型,返回AIMessage |
| Output Parser | StrOutputParser/JsonOutputParser |
将模型输出转为str或dict |
| Chain(LCEL) | Runnable管道 | 声明式组合上述步骤,形成可复用单元 |
数据沿表格从左向右流动。Prompt接收dict输入并格式化为消息,Model生成回复,Parser提取最终结果。|把四步封装为一条可invoke/stream的链。
3.1 Prompt消息模板
ChatPromptTemplate支持多轮占位符 MessagesPlaceholder,用于插入历史对话:
python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "你是中文助教,简洁回答。"),
MessagesPlaceholder(variable_name="history"), # 历史消息槽位
("human", "{question}"),
])
段落说明from_messages接受元组列表,角色可以是system、human、ai;{question}为运行时传入的占位符;history由多轮组件自动注入。
3.2 Model聊天模型
Model层实现LangChain的BaseChatModel接口。传入Prompt格式化后的ChatPromptValue,返回带content的AI消息。国内模型通过base_url指向兼容端点即可。
3.3 Output Parser输出解析
StrOutputParser:提取消息文本,用于普通问答;JsonOutputParser:配合Pydantic Schema,强制JSON结构,便于前端或下游服务消费。
python
from pydantic import BaseModel, Field
from langchain_core.output_parsers import JsonOutputParser
class StructuredAnswer(BaseModel):
answer: str = Field(description="直接回答")
key_points: list[str] = Field(description="要点列表")
confidence: str = Field(description="high/medium/low")
follow_up: str = Field(description="建议追问")
parser = JsonOutputParser(pydantic_object=StructuredAnswer)
format_instructions = parser.get_format_instructions() # 注入Prompt
3.4 Chain LCEL管道
LCEL(LangChain Expression Language)用管道符 | 连接Runnable,数据从左向右流动:
python
chain = prompt | llm | StrOutputParser()
result = chain.invoke({"question": "LCEL是什么?"})
等价于parser.invoke(llm.invoke(prompt.invoke({"question": "..."}))),但写法更直观,且原生支持 .stream()、.batch()。
四、LCEL核心语法与工程化优势
4.1 最小可运行链
下面三行构成LangChain世界的Hello World:
python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "你是简洁的中文助教。"),
("human", "{question}"),
])
chain = prompt | build_chat_model() | StrOutputParser()
answer = chain.invoke({"question": "用一句话解释 LCEL 是什么?"})
print(answer)
invoke接收dict,键名与Prompt占位符一致。链的每一步输出类型自动成为下一步输入StrOutputParser将AIMessage转为纯字符串。
4.2 Runnable协议要点
LCEL链上的每个节点都实现Runnable 接口。日常最常用的是 invoke(input) ,同步执行一次完整调用;需要打字机效果时用 stream(input) ,逐块推送输出;批量处理多条输入则调用 batch([inputs]) 。在异步服务(FastAPI、 asyncio)中,对应使用 ainvoke 和 astream ,避免阻塞事件循环。
这些能力由链整体继承,无需为流式或批处理单独写包装代码。例如流式输出只需:
python
for chunk in chain.stream({"question": "介绍 LangChain"}):
print(chunk, end="", flush=True)
下面的表格是Runnable协议的五大标准方法:
| 方法 | 作用 | 使用场景 |
|---|---|---|
invoke() |
同步单次执行,一次性返回完整结果 | 普通问答、后台接口、简单查询 |
stream() |
流式逐块输出,打字机效果 | 前端聊天框、实时回复、文案生成 |
batch() |
批量并发执行多条请求 | 批量文案处理、批量问答、数据批量抽取 |
ainvoke() |
异步单次调用(不阻塞) | FastAPI 后端、异步接口、协程服务 |
astream() |
异步流式输出 | 高并发流式对话、WebSocket 实时推送 |
4.3 工程化优势
与手写胶水代码相比,LCEL在可读性 上优势明显。管道从左到右即数据流向,不必在多层函数嵌套里追踪变量。复用 方面,Prompt、Model、Parser任意一层可单独替换或抽出测试,改动面小。流式输出和批处理由Runnable内置,省去自行实现iterator或线程池的麻烦。出问题时可以对链中某一段单独 .invoke 断点调试,快速定位是Prompt模板、模型响应还是Parser解析出了错。
需要插入预处理(日志、鉴权、格式转换)时,用| RunnableLambda即可,不必把业务逻辑和模型调用耦在同一段函数里。
4.4 插入自定义步骤
用RunnableLambda在链中增加纯Python逻辑,下面的是加一个时间戳:
python
from langchain_core.runnables import RunnableLambda
def add_timestamp(inputs: dict) -> dict:
from datetime import datetime
return {**inputs, "ts": datetime.now().isoformat()}
chain = RunnableLambda(add_timestamp) | prompt | llm | StrOutputParser()
更常用的是添加日志:
python
def log_input(data): print("入参日志:", data)
return data chain = RunnableLambda(log_input) | prompt | llm | parser
五、实战最简LangChain项目结构
推荐的最小目录布局如下:
text
my_langchain_app/
├── .env # API Key
├── requirements.txt
├── config.py # 环境变量Settings
├── models.py # build_chat_model()
├── chains/
│ └── structured_qa.py # LCEL多轮链
└── main.py # 入口

main.py负责串联验证,先跑最简链,再跑结构化多轮链。
python
def run_minimal_chain():
prompt = ChatPromptTemplate.from_messages([
("system", "你是简洁的中文助教。"),
("human", "{question}"),
])
chain = prompt | build_chat_model() | StrOutputParser()
answer = chain.invoke({"question": "用一句话解释 LCEL 是什么?"})
print(answer)
def main():
settings = load_settings()
print(f"当前模型:{settings.model}({settings.provider})")
run_minimal_chain()
run_structured_multi_turn()
最简链输出结果,如下图所示:

六、实战,基于LCEL的结构化多轮问答链
多轮对话需要在链外维护会话历史 ,并将JSON结构化输出。核心思路:
- 用
MessagesPlaceholder("history")预留历史槽位; - 用
JsonOutputParser+Pydantic约束输出字段; - 用
RunnableWithMessageHistory按session_id读写历史。
说明LangChain 1.x中
RunnableWithMessageHistory标记为deprecated,生产环境更推荐LangGraph持久化。
6.1 定义结构化 Schema
python
class StructuredAnswer(BaseModel):
answer: str = Field(description="对用户问题的直接回答")
key_points: list[str] = Field(description="2-4 条要点")
confidence: str = Field(description="high / medium / low")
follow_up: str = Field(description="建议继续追问的问题")
Pydantic字段的description会进入Parser的format instructions,引导模型按schema填JSON。
6.2 组装Prompt、Parser、Model
python
parser = JsonOutputParser(pydantic_object=StructuredAnswer)
prompt = ChatPromptTemplate.from_messages([
("system", SYSTEM_PROMPT + "\n\n{format_instructions}"),
MessagesPlaceholder(variable_name="history"),
("human", "{question}"),
]).partial(format_instructions=parser.get_format_instructions())
chain = prompt | build_chat_model() | parser
.partial()预填format_instructions,避免每次invoke重复传入。
6.3 挂载会话历史
python
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
_store: dict[str, InMemoryChatMessageHistory] = {}
def get_history(session_id: str) -> InMemoryChatMessageHistory:
if session_id not in _store:
_store[session_id] = InMemoryChatMessageHistory()
return _store[session_id]
multi_turn_chain = RunnableWithMessageHistory(
chain,
get_history,
input_messages_key="question",
history_messages_key="history",
)
调用时传入session_id,同一ID下历史自动累积:
python
config = {"configurable": {"session_id": "user-001"}}
r1 = multi_turn_chain.invoke(
{"question": "LCEL 的核心语法符号是什么?"},
config=config,
)
r2 = multi_turn_chain.invoke(
{"question": "它和传统 Chain 类相比有什么优势?"},
config=config,
)
r3 = multi_turn_chain.invoke(
{"question": "请结合上一题,给一个最简单的代码示例。"},
config=config,
)
第三轮能引用前两轮上下文,体现多轮连贯性 。 如下图所示,进行多轮: 
6.4 结构输出示例
json
{
"answer": "LCEL 使用管道符 | 将 Prompt、Model、Parser 串联成 Runnable 链。",
"key_points": [
"声明式组合,替代深层嵌套调用",
"统一 invoke/stream/batch 接口",
"组件可独立替换与测试"
],
"confidence": "high",
"follow_up": "如何用 LCEL 实现流式输出?"
}
七、常见问题排查
401 invalid_api_key :通常是Key填错、过期,或.env未被load_dotenv()加载。先在厂商控制台确认密钥有效,再打印os.getenv("DEEPSEEK_API_KEY")等变量是否为空。
DeepSeek请求误打到千问URL :多份.env并存时,OPENAI_BASE_URL可能覆盖DeepSeek默认地址。建议DeepSeek使用独立的DEEPSEEK_BASE_URL=https://api.deepseek.com,与千问配置分开。
JSON解析失败 :模型有时在JSON外包裹说明文字。可降低temperature,在system中强调只输出JSON;必要时先用StrOutputParser拿到原文,再用正则或二次解析兜底。
ModuleNotFoundError: langchain_openai :只装了langchain而未装集成包。执行pip install langchain-openai即可。
八、小结
选用LangChain的核心收益,在于组件化+LCEL+Runnable协议 三者配合,把LLM应用里高频出现的编排模式标准化,显著减少胶水代码。在国内环境下,DeepSeek与通义千问均可通过ChatOpenAI加兼容base_url接入,配置成本与OpenAI基本一致。
一条完整的LCEL链由Prompt\Model\OutputParser 串联而成,|管道负责声明式组合,并天然支持流式、批处理与异步调用。多轮结构化问答则在上述链路上叠加MessagesPlaceholder会话槽位、JsonOutputParser约束输出格式,以及按session_id维护的历史记录。