LangChain 系列 · (一):为什么不直接调用API
🎯 适合人群:有 Python 基础、了解大语言模型基本概念、想系统学习 LangChain 的工程师
⏱️ 阅读时间:约 20 分钟
💬 本文从"直接调 API 有什么问题"切入,介绍 LangChain 的核心定位与基础抽象,并手把手构建第一个可运行的 Chain
一、直接调 API 有什么问题?
在接触 LangChain 之前,大多数工程师构建 LLM 应用的方式是这样的:
# ❌ 直接调用 OpenAI API 的典型写法importopenai 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 安装依赖
pipinstalllangchain 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在代码入口处加载:
fromdotenvimportload_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 |
常用的三种如下:
fromlangchain_core.messagesimportHumanMessage,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 都继承自同一个基类,暴露相同的方法:
fromlangchain_openaiimportChatOpenAI# 初始化模型model=ChatOpenAI(model="gpt-4o-mini",temperature=0.7,# 生成随机性,0 为确定性输出max_tokens=1024,# 最大输出 token 数)# invoke:同步调用,返回 AIMessageresponse=model.invoke([HumanMessage(content="用一句话解释什么是向量数据库")])print(response.content)# 直接访问文本内容💡 切换到其他模型只需替换初始化行:
# 切换到 Anthropic Claudefromlangchain_anthropicimportChatAnthropic model=ChatAnthropic(model="claude-3-5-sonnet-20241022")# 切换到本地 Ollama 模型fromlangchain_ollamaimportChatOllama 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(),但增加了类型校验和可复用性:
fromlangchain_core.promptsimportChatPromptTemplate# 定义模板:{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 | 将模型输出解析为 Pythondict,可结合 Pydantic 指定 schema |
PydanticOutputParser | 将模型输出解析为 PydanticBaseModel对象,带类型校验 |
CommaSeparatedListOutputParser | 将逗号分隔的输出解析为 Pythonlist |
NumberedListOutputParser | 将编号列表输出解析为 Pythonlist |
XMLOutputParser | 将 XML 格式的输出解析为嵌套字典 |
DatetimeOutputParser | 将输出解析为 Pythondatetime对象 |
EnumOutputParser | 将输出解析为指定的枚举类型 |
OutputFixingParser | 包装其他 parser,当解析失败时自动调用模型修正格式 |
模型默认返回AIMessage对象,Output Parser负责将其转换为所需的数据类型:
fromlangchain_core.output_parsersimportStrOutputParser,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 | Parserchain=prompt|model|parser这行代码的含义是:
prompt接收输入字典,渲染出消息列表model接收消息列表,调用 LLM 返回AIMessageparser接收AIMessage,提取.content返回字符串
🤔 这里的
|不是位运算符,而是 LangChain 通过__or__方法重载的管道操作符,语义上等同于 Unix shell 的|——将左侧的输出作为右侧的输入。LCEL 的详细机制将在第二篇深入讲解。
五、第一个完整示例
将上述概念组合,构建一个多语言翻译助手:
fromdotenvimportload_dotenvfromlangchain_openaiimportChatOpenAIfromlangchain_core.promptsimportChatPromptTemplatefromlangchain_core.output_parsersimportStrOutputParser 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. 组合成 Chaintranslation_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})forrinresults:print(r)5.2 流式输出
对于需要实时展示生成过程的场景,使用stream方法:
# stream 返回一个生成器,逐 token 输出forchunkintranslation_chain.stream({"target_language":"英文","text":"这是一段需要实时翻译并流式输出的文本。"}):print(chunk,end="",flush=True)print()六、常见坑与最佳实践
坑一:直接打印 Chain 输出得到 AIMessage 对象
# ❌ 忘记加 Output Parserchain=prompt|model result=chain.invoke({...})print(result)# content='翻译结果' additional_kwargs={} response_metadata={...}# ✅ 加上 StrOutputParserchain=prompt|model|StrOutputParser()result=chain.invoke({...})print(result)# 翻译结果坑二:使用已废弃的旧式 API
# ❌ 旧式写法(LangChain 0.1.x 时代,现已废弃)fromlangchain.chainsimportLLMChain 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 为 Nonefromlangchain_openaiimportChatOpenAI model=ChatOpenAI()# AuthenticationError# ✅ 在程序入口处加载fromdotenvimportload_dotenv load_dotenv()# 必须在初始化模型之前调用fromlangchain_openaiimportChatOpenAI model=ChatOpenAI()七、总结
| 概念 | 类比 | 核心作用 |
|---|---|---|
HumanMessage/SystemMessage | 对话角色 | 统一消息格式,屏蔽不同模型的接口差异 |
ChatOpenAI | 模型插槽 | 统一调用接口,一行代码切换模型提供商 |
ChatPromptTemplate | 可复用模板 | 结构化提示词,支持动态变量注入 |
StrOutputParser | 输出适配器 | 将 AIMessage 转为所需数据类型 |
LCEL 管道| | 流水线 | 将各组件串联为完整的处理链 |
🎯 LangChain 的核心价值不在于"能做什么",而在于"如何让 LLM 应用的各个部分可组合、可替换、可测试"。掌握这套抽象体系,是构建生产级 LLM 应用的基础。
参考资料
- LangChain 官方文档
- LangChain Expression Language (LCEL)
- ChatOpenAI API 参考
- langchain-openai PyPI
下期预告
本篇通过prompt | model | parser初次展示了 LCEL 的管道写法,但这背后的机制远不止如此。
第二篇《LCEL:用管道的方式写 AI 逻辑》将深入 Runnable 接口,讲解invoke、stream、batch、ainvoke四种调用模式,以及并行执行、条件分支、错误重试等高级用法——这些是构建生产级 Chain 的必备技能。
