LangChain 系列 · (一):为什么不直接调用API
🎯 适合人群:有 Python 基础、了解大语言模型基本概念、想系统学习 LangChain 的工程师
⏱️ 阅读时间:约 20 分钟
💬 本文从"直接调 API 有什么问题"切入,介绍 LangChain 的核心定位与基础抽象,并手把手构建第一个可运行的 Chain
一、直接调 API 有什么问题?
在接触 LangChain 之前,大多数工程师构建 LLM 应用的方式是这样的:
# ❌ 直接调用 OpenAI API 的典型写法
import openai
client = openai.OpenAI()
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "你是一个专业的翻译助手。"},
{"role": "user", "content": f"请将以下文本翻译成英文:{text}"}
]
)
result = response.choices[0].message.content
这段代码能跑,但随着需求增长,问题逐渐暴露:
问题一:提示词散落在代码各处
提示词以字符串形式硬编码,当同一个模板在多处复用时,修改一处就需要全局搜索替换。
问题二:模型切换成本高
如果需要从 OpenAI 切换到 Anthropic 或本地模型,意味着要修改所有调用点的 API 结构、参数名称和响应解析逻辑。
问题三:组合逻辑难以维护
实际应用往往需要多步调用:先检索文档,再构建 prompt,再调用模型,再解析输出。用原生 API 实现时,这些步骤散落在函数调用链中,可读性和可测试性都很差。
问题四:常用能力需要重复实现
流式输出、重试策略、并行调用、输出解析——这些能力每个项目都需要,但都得自己实现一遍。
LangChain 正是为了解决这些问题而设计的。
二、LangChain 是什么
LangChain 是一个用于构建 LLM 驱动应用的开源框架,核心思想是将 LLM 应用的各个组成部分抽象为可组合的模块,通过统一接口进行拼装。
与直接调用 API 相比,LangChain 提供了以下核心价值:
| 维度 | 直接调 API | 使用 LangChain |
|---|---|---|
| 模型切换 | 修改所有调用点 | 替换一行模型初始化 |
| 提示词管理 | 字符串硬编码 | 类型化 Template 对象 |
| 组合多步逻辑 | 手写函数调用链 | LCEL 管道操作符 | |
| 流式输出 | 手动处理 SSE | .stream() 内置支持 |
| 可观测性 | 自行实现日志 | LangSmith 集成 |
📝 LangChain 的版本演进较快。本系列基于 LangChain 0.3.x,使用 LCEL(LangChain Expression Language)作为主要编程范式,避免使用已废弃的
LLMChain、ConversationChain等旧式 API。
三、环境搭建
3.1 安装依赖
pip install langchain langchain-openai python-dotenv
| 包名 | 用途 |
|---|---|
langchain |
核心框架,提供抽象接口和工具 |
langchain-openai |
OpenAI 模型集成(ChatOpenAI、OpenAIEmbeddings) |
python-dotenv |
从 .env 文件加载环境变量 |
💡 LangChain 采用拆包设计:
langchain只包含核心抽象,具体模型集成在独立的langchain-openai、langchain-anthropic、langchain-ollama等包中。这样做的好处是按需安装,不引入不必要的依赖。
3.2 配置 API Key
在项目根目录创建 .env 文件:
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx
在代码入口处加载:
from dotenv import load_dotenv
load_dotenv() # 加载 .env 文件中的环境变量
⚠️ 切勿将 API Key 硬编码在代码中或提交到版本控制系统。
.env文件应加入.gitignore。
四、核心抽象:LangChain 的"积木体系"
LangChain 将 LLM 应用拆解为几类标准化组件,就像乐高积木——每块形状统一,可以自由拼装。
4.1 Messages:消息类型体系
langchain_core.messages 模块提供了以下消息类型:
| 类 | 用途 |
|---|---|
HumanMessage |
表示用户发送的消息 |
AIMessage |
表示模型返回的消息,包含 content 和可选的 tool_calls |
SystemMessage |
表示系统级指令,设定模型的角色和行为边界 |
ToolMessage |
表示工具调用的返回结果,需关联 tool_call_id |
AIMessageChunk / HumanMessageChunk |
流式输出时的消息片段,可拼接为完整的消息对象 |
FunctionMessage |
兼容旧版 Function Calling 的消息类型,已废弃,推荐用 ToolMessage |
常用的三种如下:
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
# 用户发出的消息
human_msg = HumanMessage(content="LangChain 是什么框架?")
# 模型返回的消息
ai_msg = AIMessage(content="LangChain 是一个用于构建 LLM 应用的框架...")
# 系统级别的指令
system_msg = SystemMessage(content="你是一个专业的技术文档助手。")
这套消息体系屏蔽了不同模型提供商的格式差异——OpenAI 的 {"role": "user", "content": "..."} 和 Anthropic 的消息格式都被统一抽象为这三种类型。
4.2 Chat Models:统一的模型接口
所有 Chat Model 都继承自同一个基类,暴露相同的方法:
from langchain_openai import ChatOpenAI
# 初始化模型
model = ChatOpenAI(
model="gpt-4o-mini",
temperature=0.7, # 生成随机性,0 为确定性输出
max_tokens=1024, # 最大输出 token 数
)
# invoke:同步调用,返回 AIMessage
response = model.invoke([HumanMessage(content="用一句话解释什么是向量数据库")])
print(response.content) # 直接访问文本内容
💡 切换到其他模型只需替换初始化行:
# 切换到 Anthropic Claude from langchain_anthropic import ChatAnthropic model = ChatAnthropic(model="claude-3-5-sonnet-20241022") # 切换到本地 Ollama 模型 from langchain_ollama import ChatOllama model = ChatOllama(model="llama3.2")之后的所有代码无需修改。
4.3 PromptTemplate:将提示词结构化
langchain_core.prompts 模块提供了以下模板类:
| 类 | 用途 |
|---|---|
PromptTemplate |
单轮文本模板,适用于纯文本输入的 LLM(非 Chat Model) |
ChatPromptTemplate |
多轮对话模板,由多条消息组成,适用于 Chat Model,最常用 |
HumanMessagePromptTemplate |
ChatPromptTemplate 中用于定义用户消息的子模板 |
AIMessagePromptTemplate |
ChatPromptTemplate 中用于定义 AI 消息的子模板,常用于 Few-shot 示例 |
SystemMessagePromptTemplate |
ChatPromptTemplate 中用于定义系统消息的子模板 |
MessagesPlaceholder |
在 ChatPromptTemplate 中插入动态消息列表,常用于注入对话历史 |
FewShotPromptTemplate |
包含 examples 的单轮提示模板,适用于 LLM |
FewShotChatMessagePromptTemplate |
包含 examples 的多轮对话模板,适用于 Chat Model |
PipelinePromptTemplate |
将多个 PromptTemplate 组合为一个,支持模块化提示词管理 |
PromptTemplate 的本质是带有占位符的字符串模板,类似 Python 的 str.format(),但增加了类型校验和可复用性:
from langchain_core.prompts import ChatPromptTemplate
# 定义模板:{target_language} 和 {text} 是占位符
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业翻译,擅长将文本翻译成{target_language}。"),
("human", "请翻译以下内容:\n\n{text}")
])
# 渲染模板(生成具体的消息列表)
messages = prompt.invoke({
"target_language": "英文",
"text": "大语言模型正在改变软件开发的方式。"
})
print(messages)
# ChatPromptValue containing:
# [SystemMessage("你是一个专业翻译,擅长将文本翻译成英文。"),
# HumanMessage("请翻译以下内容:\n\n大语言模型正在改变软件开发的方式。")]
4.4 Output Parsers:解析模型输出
langchain_core.output_parsers 及相关模块提供了以下解析器:
| 类 | 用途 |
|---|---|
StrOutputParser |
提取 AIMessage.content,返回纯字符串,最常用 |
JsonOutputParser |
将模型输出解析为 Python dict,可结合 Pydantic 指定 schema |
PydanticOutputParser |
将模型输出解析为 Pydantic BaseModel 对象,带类型校验 |
CommaSeparatedListOutputParser |
将逗号分隔的输出解析为 Python list |
NumberedListOutputParser |
将编号列表输出解析为 Python list |
XMLOutputParser |
将 XML 格式的输出解析为嵌套字典 |
DatetimeOutputParser |
将输出解析为 Python datetime 对象 |
EnumOutputParser |
将输出解析为指定的枚举类型 |
OutputFixingParser |
包装其他 parser,当解析失败时自动调用模型修正格式 |
模型默认返回 AIMessage 对象,Output Parser 负责将其转换为所需的数据类型:
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
# StrOutputParser:提取 AIMessage.content,返回纯字符串
parser = StrOutputParser()
text = parser.invoke(AIMessage(content="这是模型输出的文本"))
print(type(text)) # <class 'str'>
# JsonOutputParser:将模型输出解析为 Python 字典
json_parser = JsonOutputParser()
4.5 Chain:用 | 连接所有组件
LCEL(LangChain Expression Language) 使用管道操作符 | 将各组件串联,构成一个 Chain:
# Chain = Prompt | Model | Parser
chain = prompt | model | parser
这行代码的含义是:
prompt接收输入字典,渲染出消息列表model接收消息列表,调用 LLM 返回AIMessageparser接收AIMessage,提取.content返回字符串
🤔 这里的
|不是位运算符,而是 LangChain 通过__or__方法重载的管道操作符,语义上等同于 Unix shell 的|——将左侧的输出作为右侧的输入。LCEL 的详细机制将在第二篇深入讲解。
五、第一个完整示例
将上述概念组合,构建一个多语言翻译助手:
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
load_dotenv()
# 1. 定义 Prompt 模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业翻译助手,擅长将文本准确翻译成{target_language},保持原文语气和风格。"),
("human", "请翻译以下内容:\n\n{text}")
])
# 2. 初始化模型
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 3. 定义输出解析器
parser = StrOutputParser()
# 4. 组合成 Chain
translation_chain = prompt | model | parser
# 5. 调用
result = translation_chain.invoke({
"target_language": "英文",
"text": "大语言模型正在深刻改变软件工程的边界,从代码补全到自主 Agent,每一层抽象都在重新定义。"
})
print(result)
# Large language models are profoundly reshaping the boundaries of software engineering,
# from code completion to autonomous agents, redefining every layer of abstraction.
5.1 批量处理
Chain 内置 batch 方法,支持并发处理多个输入:
inputs = [
{"target_language": "英文", "text": "人工智能的发展日新月异。"},
{"target_language": "日文", "text": "向量数据库是 RAG 系统的核心组件。"},
{"target_language": "法文", "text": "大模型推理成本正在快速下降。"},
]
# 并发执行,max_concurrency 控制并发数
results = translation_chain.batch(inputs, config={"max_concurrency": 3})
for r in results:
print(r)
5.2 流式输出
对于需要实时展示生成过程的场景,使用 stream 方法:
# stream 返回一个生成器,逐 token 输出
for chunk in translation_chain.stream({
"target_language": "英文",
"text": "这是一段需要实时翻译并流式输出的文本。"
}):
print(chunk, end="", flush=True)
print()
六、常见坑与最佳实践
坑一:直接打印 Chain 输出得到 AIMessage 对象
# ❌ 忘记加 Output Parser
chain = prompt | model
result = chain.invoke({...})
print(result)
# content='翻译结果' additional_kwargs={} response_metadata={...}
# ✅ 加上 StrOutputParser
chain = prompt | model | StrOutputParser()
result = chain.invoke({...})
print(result)
# 翻译结果
坑二:使用已废弃的旧式 API
# ❌ 旧式写法(LangChain 0.1.x 时代,现已废弃)
from langchain.chains import LLMChain
chain = LLMChain(llm=model, prompt=prompt)
result = chain.run(text="...")
# ✅ 新式 LCEL 写法
chain = prompt | model | StrOutputParser()
result = chain.invoke({"text": "..."})
⚠️
LLMChain、ConversationChain等旧式 Chain 类在 LangChain 0.2.x 已标记为废弃,0.3.x 起推荐完全迁移到 LCEL。
坑三:temperature=0 与 temperature=1 混用
# 翻译、信息提取、代码生成等需要确定性输出的任务
model = ChatOpenAI(model="gpt-4o-mini", temperature=0) # ✅ 确定性
# 创意写作、头脑风暴等需要多样性的任务
model = ChatOpenAI(model="gpt-4o-mini", temperature=0.9) # ✅ 多样性
# ❌ 全部用默认值(0.7),不根据任务类型调整
坑四:API Key 未正确加载
# ❌ 忘记调用 load_dotenv(),导致 OPENAI_API_KEY 为 None
from langchain_openai import ChatOpenAI
model = ChatOpenAI() # AuthenticationError
# ✅ 在程序入口处加载
from dotenv import load_dotenv
load_dotenv() # 必须在初始化模型之前调用
from langchain_openai import ChatOpenAI
model = ChatOpenAI()
七、总结
| 概念 | 类比 | 核心作用 |
|---|---|---|
HumanMessage / SystemMessage |
对话角色 | 统一消息格式,屏蔽不同模型的接口差异 |
ChatOpenAI |
模型插槽 | 统一调用接口,一行代码切换模型提供商 |
ChatPromptTemplate |
可复用模板 | 结构化提示词,支持动态变量注入 |
StrOutputParser |
输出适配器 | 将 AIMessage 转为所需数据类型 |
LCEL 管道 | |
流水线 | 将各组件串联为完整的处理链 |
🎯 LangChain 的核心价值不在于"能做什么",而在于"如何让 LLM 应用的各个部分可组合、可替换、可测试"。掌握这套抽象体系,是构建生产级 LLM 应用的基础。
参考资料
下期预告
本篇通过 prompt | model | parser 初次展示了 LCEL 的管道写法,但这背后的机制远不止如此。
第二篇《LCEL:用管道的方式写 AI 逻辑》 将深入 Runnable 接口,讲解 invoke、stream、batch、ainvoke 四种调用模式,以及并行执行、条件分支、错误重试等高级用法——这些是构建生产级 Chain 的必备技能。
更多推荐


所有评论(0)