LangChain 模型 I/O 深度解构:提示词、结构化输出与 LCEL 链式调用全解析
目录
一、 提示词模板(Prompts):告别手动拼装的“填空题”
C. MessagesPlaceholder(历史记忆插槽)
D. FewShotPromptTemplate(少样本模板)
二、 输出解析器(Output Parsers):把 AI 的“废话”变成结构化数据
策略一:靠 Prompt 道德约束(JsonOutputParser)
三、 链式调用(Chains 与 LCEL):把零件拼成自动流水线
A. 任务分发器:RunnableParallel (并行分叉)
B. 隐形背包:RunnablePassthrough (输入传递)
C. 万能转换头:RunnableLambda (自定义逻辑)
这篇教程,我们将进入 LangChain 最核心、也是最硬核的“三大件”:提示词模板(Prompts)、输出解析器(Output Parsers) 以及 链式调用(Chains/LCEL)。
很多新手在跑通了上一篇的基础调用后,立刻会面临一个残酷的现实:如果我想做一个自动爬取网页、总结摘要、并将其存入数据库的自动化工具,光靠
llm.invoke()是远远不够的。大模型总是喜欢在输出结果前后加一句“好的,这是为您生成的 JSON...”,这就足以让你的数据库入库代码全线崩溃。为了把大模型从一个“只会聊天的玩具”变成“严谨运转的流水线引擎”,我们需要完整的工程化抽象。今天,咱们不讲废话,用最接地气的大白话,把这套流水线的底层逻辑给你盘得清清楚楚。
一、 提示词模板(Prompts):告别手动拼装的“填空题”
1. 为什么不用 Python 的 f-string?
很多初学者最开始写提示词,喜欢直接用 Python 的 f"你好,{name}" 来拼接字符串。在简单的单次请求中这没问题,但如果你要开发一个复杂的 AI 应用,这会带来一场灾难。
提示词模板(Prompt Template)的核心思想是:将 Prompt 中的“固定部分”与“可变部分”彻底分离 。 它的核心价值在于:
-
复用与参数化:一次定义,到处复用,像调用函数一样传入变量 。
-
标准化与版本控制:把所有的提示词抽离出来统一管理,方便后续做 A/B 测试和提示词工程调优 。
在 LangChain 的设计中,所有的提示词模板都有一个标准的工作流:你传入一个变量字典(比如 {"topic": "AI"}),模板把变量塞进占位符,生成一个中间格式 PromptValue,最后这个格式会自动适配并发送给大模型 。
2. 四大核心模板类型,分别用在哪?
LangChain 提供了多种模板,不要被一堆英文类名吓到,它们分别对应着不同的业务场景 :
A. PromptTemplate(基础字符串模板)
最古老、最基础的模板,最终生成的是一段纯文本字符串。适用于那些不需要角色扮演的简单任务 。
from langchain_core.prompts import PromptTemplate
# 定义一个带占位符的模板
prompt = PromptTemplate.from_template("讲一个关于{topic}的{adjective}故事")
# 传入变量,生成最终提示词
formatted = prompt.invoke({"topic":"程序员", "adjective":"悲伤的"})
高阶心法:partial(部分变量固定)
有时候,你的模板里有 3 个变量,但有两个变量是早就确定好的,只有一个变量需要用户临时输入。这时候就可以用 partial 来“预填充”。
-
用
partial()方法:适合在程序运行中途,获取到某个值后临时固定下来 。 -
用
partial_variables参数:适合在代码一开头定义模板时,就把团队约定的“全局常量”(比如固定的输出风格)写死在里面 。
B. ChatPromptTemplate(聊天模板)
这是现代大模型开发绝对的主力。因为像 GPT-4、DeepSeek 这些现代模型,它们接收的不是一段长文本,而是带有角色标签(系统 System、人类 Human、AI)的消息列表 。
from langchain_core.prompts import ChatPromptTemplate
# 推荐使用元组列表来极速构建
chat_prompt = ChatPromptTemplate.from_messages([
("system", "你是一个精通{language}的资深架构师。"), # 设定人设
("human", "请解释一下{topic}的核心原理。"), # 用户提问
("ai", "好的,我将用最简单的语言为您解释。"), # 模拟AI历史回复
("human", "{question}") # 追问
])
这种写法极为清晰,system 用来定规则,human 用来提需求,ai 用来做历史对话占位 。
C. MessagesPlaceholder(历史记忆插槽)
在做上下文连续聊天的机器人时,用户的历史聊天记录长度是不固定的。你不能用一个简单的字符串占位符来放历史记录。MessagesPlaceholder 相当于在模板里挖了一个“大黑洞”,专门用来把整个列表的历史消息整体塞进去 。
from langchain_core.prompts import MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "你是AI助手"),
MessagesPlaceholder(variable_name="history"), # 给历史消息留的专属插槽
("human", "{input}")
])
提示:在最新版本中,你也可以直接用元组语法 ("placeholder", "{history}") 来代替上面那行冗长的代码,效果完全一样 。
D. FewShotPromptTemplate(少样本模板)
大模型有时候是个“死脑筋”,你把规则写得再清楚,它也会乱输出。最好的教导方式是“举个栗子”。少样本模板就是用来把“示例数据”和“用户问题”优雅地拼接在一起的 。通过提供多个输入输出的示例(Few-Shot),模型能瞬间领悟你的真实意图,精准作答 。
二、 输出解析器(Output Parsers):把 AI 的“废话”变成结构化数据
业务代码(比如 Python 或 Java)是极其严谨的,它们只认识 JSON 或对象。但大模型本质上是一个“顺口溜机器”,它即使给出了 JSON,前后也极大概率会带上“根据您的要求,下面是结果:”这样的寒暄废话。输出解析器就是用来扒掉这层废话外衣,提取纯净数据的。
目前行业内有两种主流策略:
策略一:靠 Prompt 道德约束(JsonOutputParser)
这是一种传统做法,它会在你的提示词末尾悄悄加上一段极其严厉的指令:“你必须且只能输出 JSON,绝对不能包含任何其他多余的文字,不能使用 Markdown 格式……” 。 这种方法通用性强,任何破模型都能用。但在参数较小的模型上,模型有时依然会“翻车”输出错乱格式 。
关键参数避坑:partial
-
当你设置
partial=True时,解析器允许“半成品”。哪怕模型的话还没说完(JSON 没闭合),它也会尽力把已生成的部分提取出来。这在“打字机流式输出”时非常有用 。 -
默认
partial=False时,解析器化身铁面判官。只要 JSON 缺一个逗号,立刻报错抛异常,绝不姑息。适合最终结果的严格校验 。
策略二:降维打击——厂商原生支持与 Pydantic 校验
现在主流的大厂(OpenAI, Gemini 等)都在 API 底层直接支持了“强制结构化输出”。这已经不再是靠语言威胁模型了,而是在生成机制上做限制 。
在 LangChain 中,你只需要掌握一招终极必杀技:with_structured_output()。只要用它,无论底层是哪家模型,代码永远一致 。
在用这个必杀技前,你必须了解 Python 的神级数据校验库:Pydantic。 Pydantic 允许你定义一个数据模型类,它会自动进行类型校验和转换(比如把字符串 "30" 自动转成整数 30)。如果数据不合法,它会抛出详细的错误 。
结合 Pydantic,你的代码可以写成这样极其优雅的形态:
from pydantic import BaseModel, Field, field_validator
# 1. 定义极其严谨的数据结构(这不仅是代码约束,也会被翻译给模型听)
class MovieReview(BaseModel):
title: str = Field(description="电影标题")
rating: int = Field(description="评分, 1-10分")
# 甚至可以手写复杂的自定义校验逻辑
@field_validator('rating')
def validate_rating(cls, v):
if v < 1 or v > 10:
raise ValueError('评分必须在1-10之间')
return v
# 2. 直接在模型对象上调用必杀技
structured_llm = llm.with_structured_output(schema=MovieReview)
# 3. 调用模型
result = structured_llm.invoke("评价一下电影《黑客帝国》")
# 4. 拿到的不再是字符串,而是完美的 Python 对象!
print(result.title) # 黑客帝国
print(result.rating) # 9
原理解析:with_structured_output 实际上是在底层把 Pydantic 类翻译成了严格的 JSON Schema,告诉大模型的底层 API:“就按这个骨架生成”。拿到结果后,再用 Pydantic 严丝合缝地做二次校验 。
三、 链式调用(Chains 与 LCEL):把零件拼成自动流水线
前面的 Prompt 和 Parser 都是独立的零件,怎么把它们无缝衔接起来?这就是 LangChain 最核心的革命性设计:LCEL(LangChain 表达式语言)。
1. 万物皆可 Runnable
在 LangChain 的底层逻辑中,无论是提示词、大模型还是解析器,它们都被强制实现了一个统一的接口:Runnable(可执行体)。 只要你是 Runnable,你就必然拥有以下能力 :
-
invoke:同步执行一次 。 -
batch:并发批量执行多条数据 。 -
stream:像打字机一样流式输出 。 -
ainvoke:支持 Pythonasync异步调用 。
接口的完全统一,让不同组件之间具备了“即插即用”的能力 。
2. LCEL 的魔法管道符 |
你不再需要写恶心的嵌套调用代码了。LCEL 借用了 Linux 命令行的管道符 | 概念,将上一个组件的输出,直接作为下一个组件的输入 。
# 这就是最经典的 LCEL 链式调用,一目了然
chain = prompt | model | output_parser
# 链条本身也是一个 Runnable,可以直接 invoke
result = chain.invoke({"question": "什么是量子计算?"})
第一步:把 {"question": "..."} 喂给 prompt 生成提示文案。 第二步:提示文案自动流进 model 得到 AI 原始回复。 第三步:原始回复流进 output_parser 被扒皮抽筋,变成纯净数据 。
3. 高阶流水线车间:Runnable 组合器
真实的业务流比这复杂得多。LangChain 提供了几个强大的“调度员”组合器 :
A. 任务分发器:RunnableParallel (并行分叉)
如果你的输入需要同时做两件事(比如一边算字符串长度,一边转大写),你可以用字典语法 {}。在 LCEL 中,只要链条里出现一个字典,系统就会自动开启并发多线程处理,效率极高 。
# 字典语法自动编译为 RunnableParallel
parallel_chain = {
"赏析意境": paragraph_1_chain,
"赏析含义": paragraph_2_chain
} | summary_chain # 两个分支并发跑完后,一起汇总给 summary_chain
这种设计让耗时的网络请求(比如同时去两个不同的库检索资料)实现了完美的并发等待 。
B. 隐形背包:RunnablePassthrough (输入传递)
有时候,第一步的输入在第二步还要用。比如 RAG(知识库问答)中,用户问了“公司年假几天?”,你的第一步去查了规章制度文档。在最后交给模型时,你不仅需要“规章制度文档”,还需要保留最初的那个“公司年假几天?”的问题。
RunnablePassthrough() 就像一个隐形背包,它能把原始输入原封不动地传递给下一环,同时允许你在旁边塞入新检索到的上下文 。
C. 万能转换头:RunnableLambda (自定义逻辑)
如果你想在链条中插入一段自己手写的 Python 代码(比如把某个字符串切片、清洗特殊字符),怎么把它塞进 LCEL 呢? 用 RunnableLambda 将你的普通函数“包装”成标准组件,它就可以和 prompt、model 一起用 | 缝合了 。
四、 走向实战:打造强韧的企业级链条
在企业级应用中,我们还需要解决两个极其现实的问题:记性不好和网络不稳。
1. 挂载外置脑核:添加对话历史
纯粹的 LCEL 链是没有记忆的。用户问第二句时,它早就忘了第一句。 使用 RunnableWithMessageHistory,你可以给这条链挂载一个数据库(通过 session_id 区分不同用户)。每次用户提问时,这个组件会自动去库里把该用户的历史聊天记录挖出来,塞进我们前面提到的 MessagesPlaceholder 专属插槽里;等模型回答完,它又会自动把最新的对话存入库中。完全无需你手动管理数据存取 。
2. 打不死的小强:重试与回退机制
大模型的 API 是极其不稳定的。超时、限流、格式生成错误是家常便饭。
在 LCEL 中,防御机制被简化到了极致:
-
无限复活(重试):
chain.with_retry(stop_after_attempt=3)。遇到报错自动重试 3 次,专治偶尔的网络抽风 。 -
备胎计划(回退):
primary_llm.with_fallbacks([fallback_llm])。比如主链用的是昂贵的 GPT-4o,一旦 OpenAI 服务器宕机,系统无缝切换到备用链(比如国产的 DeepSeek)进行处理,保证业务永不中断 。
至此,从“怎么把话说清楚(Prompts)”,到“怎么把数据抠出来(Parsers)”,再到“怎么把它们串成自动流水线(Chains)”,LangChain 最核心的工程化内功,你已经全部掌握。接下来,就是发挥想象力,用这些积木去构建真正改变行业的 AI 应用了。
更多推荐


所有评论(0)