目录

一、 提示词模板(Prompts):告别手动拼装的“填空题”

1. 为什么不用 Python 的 f-string?

2. 四大核心模板类型,分别用在哪?

A. PromptTemplate(基础字符串模板)

B. ChatPromptTemplate(聊天模板)

C. MessagesPlaceholder(历史记忆插槽)

D. FewShotPromptTemplate(少样本模板)

二、 输出解析器(Output Parsers):把 AI 的“废话”变成结构化数据

策略一:靠 Prompt 道德约束(JsonOutputParser)

策略二:降维打击——厂商原生支持与 Pydantic 校验

三、 链式调用(Chains 与 LCEL):把零件拼成自动流水线

1. 万物皆可 Runnable

2. LCEL 的魔法管道符 |

3. 高阶流水线车间:Runnable 组合器

A. 任务分发器:RunnableParallel (并行分叉)

B. 隐形背包:RunnablePassthrough (输入传递)

C. 万能转换头:RunnableLambda (自定义逻辑)

四、 走向实战:打造强韧的企业级链条

1. 挂载外置脑核:添加对话历史

2. 打不死的小强:重试与回退机制


        这篇教程,我们将进入 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:支持 Python async 异步调用 。

接口的完全统一,让不同组件之间具备了“即插即用”的能力 。

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 将你的普通函数“包装”成标准组件,它就可以和 promptmodel 一起用 | 缝合了 。

四、 走向实战:打造强韧的企业级链条

在企业级应用中,我们还需要解决两个极其现实的问题:记性不好网络不稳

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 应用了。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐