12.LangChain 1.0+ 第二篇:Prompt、结构化输出与 LCEL 组件组合.发布清单

LangChain 1.0+ 第二篇:Prompt、结构化输出与 LCEL 组件组合

摘要:光会调用模型还不够,应用还需要把任务规则说清楚、让程序稳定拿到结构化数据。本文讲透 Prompt 输入协议、Structured Output 输出协议,以及把组件串成链的 LCEL 语法。


引子:调通了模型,然后呢

你已经会用 model.invoke() 调通一个大模型。但把一句用户问题直接丢给模型,它只会大概知道"要回答",不知道你的业务规则:排查步骤是什么、能不能编造、缺少信息时怎么办、要不要转人工。

另一头是程序:模型回答的自然语言,人看得懂,程序却不好稳定处理------取个电影评分,得靠正则去猜数字。

这两个问题,正是本文要讲的两套"协议":

  • Prompt 解决输入侧:如何把任务规则说清楚 → 输入协议
  • Structured Output 解决输出侧:如何让模型返回程序能处理的数据 → 输出协议
  • LCEL 解决组合侧:如何把 Prompt、Model、Parser 串成一条流水线

一句话记忆:Prompt 让模型知道该怎么做;Structured Output 让程序知道模型返回了什么;LCEL 让它们能串起来。

一、Prompt:把任务写成输入协议

1. 工程化 Prompt 的五类信息

工程化 Prompt 不是"写一段话",而是把任务规则结构化地讲清楚。通常包含五类信息:

要素 说明
角色 模型以什么身份回答
任务 这次要完成什么
上下文 可依据的资料、消息或工具结果
约束 哪些不能做,缺少信息时怎么办
输出 回答按什么形式给出

常见的五类错误,都对应这五要素的缺失:

  • 只写"你是一个助手",没有业务边界(缺约束)
  • 只要求"不要出错",没说缺信息时怎么办(缺约束)
  • 需要实时信息时,没要求使用工具或说明来源(缺上下文)
  • 只要求输出 JSON,但没定义字段(缺输出)
  • 所有约束都放 Prompt 里,却没在工具/Schema/代码层做校验(过度依赖 Prompt)

Prompt 不是让模型变聪明,而是让模型更清楚当前任务规则。

2. PromptTemplate:让 Prompt 可复用、可组合

每次手写完整 Prompt,会带来重复代码多、规则不一致、变量易漏传、难以和其他组件组合四个问题。

f-string 也能做字符串替换,但 PromptTemplate 的价值不在替换,而在结构和组合

  1. 保留 system / human 等消息角色结构
  2. 自动管理模板变量,缺变量时报错
  3. 可作为 Runnable,与 model、structured output 组合
  4. 适合长 Prompt 统一管理和复用
python 复制代码
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是{device_type}设备的客服助手,回答要简洁、准确。"),
    ("human", "用户问题:{question}"),
])

chain = prompt | model
response = chain.invoke({
    "device_type": "饮水机",
    "question": "不出水怎么办?"
})

执行流程:输入变量 → PromptTemplate 生成 messages → Model 生成回答

f-string 管字符串替换;PromptTemplate 管消息模板和组件组合。

二、Structured Output:让程序拿到可处理的数据

1. 为什么需要结构化输出

自然语言适合人看,程序却要"格式稳定"。同样一个电影信息,两种形态:

形式 示例
自然语言 《肖申克的救赎》上映于 1994 年,导演是弗兰克·德拉邦特,评分 9.7
结构化数据 {"title": "肖申克的救赎", "year": 1994, "director": "弗兰克·德拉邦特", "rating": 9.7}

Schema 就是"数据格式说明书",规定返回哪些字段、什么类型、什么含义。课程中主要用 Pydantic 定义:

python 复制代码
from pydantic import BaseModel, Field

class Movie(BaseModel):
    title: str = Field(..., description="电影名称")
    year: int = Field(..., description="上映年份")
    director: str = Field(..., description="导演")
    rating: float = Field(..., description="评分,满分 10 分")

一个关键认知:结构化输出解决的是"格式稳定",不保证事实正确或最新。实时信息应来自搜索、数据库或 API,而不是让模型猜。

2. with_structured_output:按 Schema 返回数据

调用方式只需要在模型上加一层:

python 复制代码
model_with_structure = model.with_structured_output(Movie)

response = model_with_structure.invoke("""
请从下面文本中抽取电影信息:
《肖申克的救赎》上映于 1994 年,导演是弗兰克·德拉邦特,评分 9.7 分。
""")

print(response.title)   # 肖申克的救赎
print(response.rating)  # 9.7

普通调用和结构化调用的差别:

vbscript 复制代码
普通调用:输入 → 模型 → AIMessage(response.content)
结构化调用:输入 → 模型 → 按 Schema 解析 → Pydantic 对象(response.title)

三个实用细节:

  • Field(description=...) 不只是给开发者看,也帮助模型理解字段含义。
  • include_raw=True 用于调试 :同时拿到原始消息和解析结果,返回 {"raw": AIMessage, "parsed": Movie, "parsing_error": None}
  • 返回结果可直接作为 Python 对象使用 ,程序拿着 .title.rating 就能写进数据库或往下传。

3. 与 Agent 的关系预告

with_structured_output 适合模型级单步任务 (信息抽取、分类、整理字段)。到了 Agent 阶段,结构化输出可以作为任务完成后的最终业务结果,例如:

json 复制代码
{
  "answer": "建议先检查电源和进水管...",
  "need_human": true,
  "reason": "用户描述可能涉及设备故障",
  "suggested_action": "转人工客服"
}

程序拿到这份结果,就能判断是否转人工、创建工单、记录故障------这正是"让程序知道模型返回了什么"在真实业务里的落点。

三、LCEL:Runnable 与组件组合

1. Runnable:统一调用接口

LangChain 里 Prompt、Model、Parser、Retriever、Agent 都有 invoke / stream / batch 方法,这不是巧合------它们都实现了同一套执行接口 Runnable

方法 说明
invoke 单次调用
stream 流式调用,边生成边返回
batch 批量调用
ainvoke 异步调用
python 复制代码
# 模型
response = model.invoke("你好,介绍一下 LangChain。")

# 流式:聊天 UI 打字机效果
for chunk in model.stream("写一段客服欢迎语。"):
    print(chunk.content, end="")

# Agent 也遵循同一接口
result = agent.invoke({"messages": [{"role": "user", "content": "帮我查订单状态"}]})

Runnable 解决的是"组件怎么被统一调用"的问题。

2. prompt | model 为什么能这样写

LCEL (LangChain Expression Language)是组合 Runnable 的表达式语法。prompt | model 里的 | 不是"或",而是把左边 Runnable 的输出,传给右边 Runnable 作为输入

python 复制代码
chain = prompt | model
# 等价于:
messages = prompt.invoke(input_data)
response = model.invoke(messages)

为什么 | 左右通常必须是 Runnable?因为 | 的本质是组件组合,LangChain 需要知道怎么执行 A 和 B------普通字符串没有 invoke 方法,不能参与链式组合。

普通函数想参与也可以,用 RunnableLambda 包装:

python 复制代码
from langchain_core.runnables import RunnableLambda

def add_prefix(text):
    return "用户问题:" + text

chain = RunnableLambda(add_prefix) | model

Runnable 解决"怎么执行";| 解决"怎么连接"。

3. Chain:多条 Runnable 串成流水线

Chain 就是多个 Runnable 按顺序组合形成的执行流程:

python 复制代码
chain = prompt | model          # Prompt → Model
chain = prompt | model | parser # Prompt → Model → Parser

执行时内部流程:输入 → prompt.invoke(...) → model.invoke(...) → parser.invoke(...) → 输出

Chain 不是新组件,而是把多个 Runnable 组成一条流水线。

4. LCEL 需要学到什么程度

对 LangChain 1.0+ 的 Agent 应用,新手不需要一开始系统学复杂 LCEL。分清楚重点:

内容 是否重点
invoke / stream / batch 需要掌握
prompt | model 需要掌握
RunnableLambda 了解即可
复杂 LCEL 分支、并行、路由 后面需要时再学
用 LCEL 替代 Agent 主线 不建议

原因:LangChain 1.0+ 的主线更偏向 Agent、Middleware、State 和 LangGraph。这一章只要能看懂 chain = prompt | model | parser,就达到目标。

四、小结

回到开头的两个问题:

  • 输入侧:Prompt 是输入协议,PromptTemplate 让它可以复用、可组合。
  • 输出侧 :Schema 是输出协议,with_structured_output 让程序拿到可处理的 Python 对象。
  • 组合侧 :Runnable 是统一执行接口,| 把组件串成 Chain。

一句话收束:Prompt 管输入;Schema 管输出;Runnable 管执行;| 管连接。

下一步,Agent 会把 Prompt、工具、结构化输出组织成完整任务,把这三块知识真正用起来。相关背景可延伸阅读 \[MCP模型上下文协议]、\[主流LLM落地框架]。

相关推荐
SomeB1oody2 小时前
【RustyML入门】2.13. 孤立森林
开发语言·后端·机器学习·rust·教程
苏三说技术3 小时前
为什么越来越多人用 Pi ? - 苏三说技术
后端
YHL3 小时前
🚀 SSE 服务器发送事件与 BFF 层实战
前端·后端
星火10243 小时前
【LangChain4j系列05】RAG 检索增强生成完整指南
人工智能·后端
技术长镜头3 小时前
别再只说“走 B+Tree”:一条 SQL 在 InnoDB 中的完整寻址过程
后端·mysql
SomeB1oody3 小时前
【RustyML入门】3.8. 正则化与归一化层
开发语言·后端·机器学习·rust·教程
zhiSiBuYu05174 小时前
Flask Session 与 Cookie 新手实战指南
后端·python·flask
2601_953988074 小时前
Ricon组态系统vs传统组态软件:为什么选择新一代Web组态平台
前端·后端·物联网·tcp/ip·数学建模·前端框架
IT_陈寒4 小时前
SpringBoot自动配置坑了我三天,原来漏了这个注解
前端·人工智能·后端