如果你调用过模型接口,大概率写过这样的代码:先把提示词拼进请求,再调用模型,然后把返回结果塞进下一个请求。单步任务看起来还行,可一旦加入知识库检索、多轮工具调用或输出格式校验,代码就会迅速膨胀成难以维护的过程式脚本。LangChain 的价值就在于此:它不重新发明模型能力,而是把模型调用、提示词管理、工具执行、结果解析这些环节抽象成可以任意拼接的组件,让你用统一接口描述一条完整的 AI 工作流。

LangChain 在链式应用里解决了什么问题
理解 LangChain 之前,可以先把它拆成两个部分:Lang 代表语言模型,Chain 代表链。所谓链式应用,并不是让模型一次回答所有问题,而是把复杂任务拆成多个步骤,每一步由模型或其他组件处理后,再把结果传给下一步。比如一个能解释陌生术语的应用,可能先由检索工具查词,再由模型根据查到的内容生成解释,最后用输出解析器把答案格式化成固定结构。这个过程如果全部手写,至少涉及提示词模板、请求封装、工具分发和结果清洗四块代码。
LangChain 通过两类核心对象降低复杂度。一类是组件,常见的有 ChatPromptTemplate、ChatOpenAI、StrOutputParser;另一类是链,它用管道操作符把组件串联起来。以 LangChain 表达式语言 LCEL 为例,prompt | llm | parser 这段代码本身就表示一个完整流程:先渲染提示词,再调用模型,最后解析输出。相比每次手动拼接字符串,链式写法的优势在于每个节点职责清晰、可以单独替换,也方便并行扩展和测试。
另一个容易忽视的问题是提示词版本管理。在真实项目中,提示词会频繁调整,例如加入角色设定、示例或输出约束。如果提示词散落在代码各处,维护成本会很高。ChatPromptTemplate 把提示词当作数据而不是字符串,你可以集中保存模板,再通过 invoke 方法传入变量。模型参数同样如此,温度、最大 token、模型名称等都可以在组件初始化时统一配置,避免每次请求时重复写参数。
最小可运行链:PromptTemplate 与 LLMChain
安装依赖是第一步,推荐使用 pip 安装三个包:pip install langchain langchain-openai python-dotenv。其中 langchain-openai 提供 OpenAI 模型适配,python-dotenv 用来读取环境变量中的 API Key。假设你已经有了可用的 OpenAI API Key,下面是一个能在本地直接运行的链。
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
prompt = ChatPromptTemplate.from_messages([
("system", "你是一名简洁的技术解释者。"),
("user", "请用两句话解释:{topic}")
])
chain = prompt | llm | StrOutputParser()
result = chain.invoke({"topic": "LangChain中的Chain"})
print(result)
这段代码里,ChatPromptTemplate.from_messages 接收一个消息列表,第一个元素是系统消息,第二个元素是用户消息。花括号中的 {topic} 是模板变量,执行 chain.invoke 时会被替换成实际值。随后结果传入 ChatOpenAI,模型完成推理后返回 ChatMessage 对象,最后 StrOutputParser 把它转换为普通字符串。链的定义只需要一行管道表达式,输入输出非常直观。
如果你在使用较旧版本的 LangChain,可能更熟悉 LLMChain 和 PromptTemplate 的写法。不过当前稳定版本已经推荐 LCEL 风格,旧的 LLMChain 虽然仍能运行,但在复杂组合和流式输出方面不如管道表达式灵活。入门时可以直接从 LCEL 开始,后续接触 RunnableParallel、RunnableBranch 等高级组件时也会更顺。
运行之前需要配置 API Key。Linux 或 macOS 下可以在终端执行 export OPENAI_API_KEY=你的密钥,Windows PowerShell 使用 $env:OPENAI_API_KEY=你的密钥。如果你使用 python-dotenv,可以在项目根目录创建 .env 文件,内容为 OPENAI_API_KEY=你的密钥,然后在代码开头调用 load_dotenv() 自动加载。这样密钥不会硬编码在源码中,也更适合团队协作。
加入自定义工具与多步链
实际应用很少只用一条提示词链。仍以术语解释为例,我们可以先查词库,查不到时再让模型自己组织答案。这样既降低了模型幻觉,也让输出结果更可控。LangChain 提供了工具机制,你可以用 @tool 装饰器把普通 Python 函数注册为模型可调用的工具。
from langchain_core.tools import tool
@tool
def get_word_definition(word: str) -> str:
"""根据输入词返回简单定义"""
definitions = {
"prompt": "给模型的一组输入指令",
"token": "文本被切分后的最小单元",
"chain": "多个处理步骤串联形成的执行流程"
}
return definitions.get(word.lower(), "暂时没有收录该词")
tools = [get_word_definition]
llm_with_tools = llm.bind_tools(tools)
注意这段代码中的 @tool 来自 langchain_core.tools,装饰器会把函数签名、类型注解和 docstring 一并交给模型,让模型判断何时调用工具。函数内部可以加中文注释,但更关键的是 docstring 要写清楚工具的作用,否则模型可能无法正确触发。绑定工具后,模型返回的消息中会包含一个 tool_calls 字段,程序可以据此执行实际函数,再把结果回传给模型生成最终回答。
如果你想手动控制步骤,还可以使用 RunnableLambda 把普通 Python 函数包装成链的一环。下面是一个两阶段链:第一阶段查词,第二阶段由模型根据查到的内容生成解释。这样模型只用处理自然语言生成,查询准确度由 Python 代码保障。
from langchain_core.runnables import RunnableLambda
def lookup_then_explain(inputs: dict) -> dict:
word = inputs["word"]
definition = get_word_definition.invoke({"word": word})
return {"word": word, "definition": definition}
second_prompt = ChatPromptTemplate.from_messages([
("system", "你是一名技术编辑,请根据给定的词和定义生成通俗解释。"),
("user", "词:{word}\n定义:{definition}")
])
full_chain = RunnableLambda(lookup_then_explain) | second_prompt | llm | StrOutputParser()
print(full_chain.invoke({"word": "prompt"}))
这里 RunnableLambda(lookup_then_explain) 把普通函数转换成可组合的链式节点。函数接收一个 dict,先调用工具查询定义,再把词和定义一起返回。第二个提示词模板接收到这两个变量后继续走模型和解析器。这样一条链里既有 Python 逻辑又有模型推理,边界非常清楚。后续若查词逻辑要从内存字典换成数据库或向量检索,只需替换 get_word_definition 内部实现,其他环节无需改动。
构建完整示例:关键词查询与解释应用
把上面的片段整合起来,就能得到一个结构完整的小应用:用户输入一个词,应用先查本地词库,再由模型生成两句话解释。为了方便看到链的各个阶段,可以在每个节点后打印中间结果,例如先打印查到的定义,再打印最终回答。下面是一个可以直接保存为 app.py 运行的完整版本。
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda
from langchain_core.tools import tool
load_dotenv()
@tool
def get_word_definition(word: str) -> str:
"""根据输入词返回简单定义"""
definitions = {
"prompt": "给模型的一组输入指令",
"token": "文本被切分后的最小单元",
"chain": "多个处理步骤串联形成的执行流程",
"vectorstore": "用于存储和检索向量表示的组件"
}
return definitions.get(word.lower(), "未收录,请模型根据常识回答")
def prepare_context(inputs: dict) -> dict:
word = inputs["word"]
definition = get_word_definition.invoke({"word": word})
return {"word": word, "definition": definition}
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.6)
explain_prompt = ChatPromptTemplate.from_messages([
("system", "你是一名耐心的人工智能讲师。"),
("user", "请结合定义,用两句话解释这个词:{word}\n定义:{definition}")
])
explain_chain = RunnableLambda(prepare_context) | explain_prompt | llm | StrOutputParser()
if __name__ == "__main__":
while True:
user_input = input("请输入要查询的词,输入 exit 退出:")
if user_input.strip().lower() == "exit":
break
answer = explain_chain.invoke({"word": user_input})
print("回答:", answer)
这段代码展示了链式应用最典型的模式:输入预处理、提示词渲染、模型推理、输出解析。终端交互部分用了简单的 while 循环,方便你反复测试不同单词。需要注意的是,get_word_definition.invoke 在工具被装饰后也可以像链一样直接调用,返回值是字符串。整个流程没有隐藏魔法,每个节点都可以单独替换或单独测试。
如果某个词不在本地词库,工具会返回“未收录,请模型根据常识回答”,这时模型仍然可以根据这个提示生成合理的解释。这样做的好处是即使知识库覆盖不全,应用也能继续工作,而不会直接报错或返回空值。真实项目中,你可以把本地字典替换成向量数据库检索、搜索引擎 API 或企业知识库接口,链的其余部分保持不变。
调试与扩展建议
刚接触 LangChain 时,最常见的问题是不知道某个节点到底输出了什么。因为链式调用把多个步骤压缩成一行,出错时堆栈信息可能不够直观。调试时可以先拆开链,逐步执行每个组件,比如先单独调用 prompt.invoke 看渲染结果,再调用 llm.invoke 看模型原始返回,最后加上解析器。每一步输出符合预期后再组合成链,能省下大量排查时间。
扩展方向主要有三个。第一是加入记忆,让多轮对话保持上下文,可以用 MessagesPlaceholder 在提示词中插入历史消息。第二是接入检索器,把本地文档切分后嵌入向量库,再通过 create_retrieval_chain 实现 RAG 问答。第三是增加输出校验,比如使用 Pydantic Output Parser 要求模型返回结构化 JSON,避免后处理时反复解析字符串。无论哪个方向,都遵循同一个思路:组件化、管道化、显式化。
最后需要提醒的是,API 调用会产生费用,调试时可以把温度调低、使用较小模型,并给测试循环加上退出条件。生产环境中还应为模型调用增加超时和重试机制。LangChain 本身不代替你管理成本,它只是让调用逻辑更清楚。弄清楚每一环的输入输出后,你就能根据业务需要自由裁剪和替换组件,而不会被困在某个固定框架里。