LangChain RunnableSequence 获取中间输出的几种方案(替代SequentialChain获取中间结果)

python 复制代码
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from dotenv import load_dotenv
import os
load_dotenv()

llm=ChatOpenAI(
    model="qwen-turbo",
    base_url=os.getenv("OPENAI_API_BASE"),
    api_key=os.getenv("OPENAI_API_KEY"),
    temperature=0.6
)

# 第一步:生成餐厅名称
prompt_template_name = PromptTemplate(
    input_variables =['cuisine'],
    template = "我想开一家**{cuisine} 风味餐厅**,请为它取一个雅致好听的名字。"
)
name_chain = prompt_template_name | llm | StrOutputParser()

# 第二步:根据餐厅名生成菜单
prompt_template_items = PromptTemplate(
    input_variables = ['restaurant_name'],
    template="请为餐厅 {restaurant_name} 设计若干菜品"
)
food_items_chain = prompt_template_items | llm | StrOutputParser()


# ==================改造重点==================
full_chain = (
    {"cuisine": RunnableLambda(lambda x: x["input"])}
    | RunnablePassthrough.assign(restaurant_name = name_chain)
    # 此时数据结构: {"cuisine": "中国", "restaurant_name": "餐厅名字"}
    | RunnablePassthrough.assign(menu = RunnableLambda(lambda x: food_items_chain.invoke(x["restaurant_name"])))
)

result = full_chain.invoke({"input": "中国"})
print("完整结果字典:", result)
print("餐厅名称(中间结果):", result["restaurant_name"])
print("菜单(最终输出):", result["menu"])

LangChain RunnableSequence 获取中间输出的几种方案

先梳理你当前链路:

复制代码
{"cuisine": ...} → name_chain(生成餐厅名) → food_items_chain(生成菜单)

现在链路默认只会返回最后一步结果,拿不到中间生成的餐厅名称。下面给出由简单到工程化的 4 种方案,适配你的代码。

方案 1:使用 RunnablePassthrough.assign()【推荐,原生 LCEL 标准写法】

核心思路:把上一步输出保存到字典里往下传递,不会丢失中间值。

复制代码
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from dotenv import load_dotenv
import os
load_dotenv()

llm=ChatOpenAI(
    model="qwen-turbo",
    base_url=os.getenv("OPENAI_API_BASE"),
    api_key=os.getenv("OPENAI_API_KEY"),
    temperature=0.6
)

# 第一步:生成餐厅名称
prompt_template_name = PromptTemplate(
    input_variables =['cuisine'],
    template = "我想开一家**{cuisine} 风味餐厅**,请为它取一个雅致好听的名字。"
)
name_chain = prompt_template_name | llm | StrOutputParser()

# 第二步:根据餐厅名生成菜单
prompt_template_items = PromptTemplate(
    input_variables = ['restaurant_name'],
    template="请为餐厅 {restaurant_name} 设计若干菜品"
)
food_items_chain = prompt_template_items | llm | StrOutputParser()


# ==================改造重点==================
full_chain = (
    {"cuisine": RunnableLambda(lambda x: x["input"])}
    | RunnablePassthrough.assign(restaurant_name = name_chain)
    # 此时数据结构: {"cuisine": "中国", "restaurant_name": "餐厅名字"}
    | RunnablePassthrough.assign(menu = RunnableLambda(lambda x: food_items_chain.invoke(x["restaurant_name"])))
)

result = full_chain.invoke({"input": "中国"})
print("完整结果字典:", result)
print("餐厅名称(中间结果):", result["restaurant_name"])
print("菜单(最终输出):", result["menu"])

✅ 优点:纯 LCEL 语法、官方标准,链路所有中间数据保存在字典,invoke 返回完整结构体。

方案 2:手动封装,在 RunnableLambda 捕获中间变量(简单调试用)

适合临时打印中间值,不适合生产。

复制代码
full_chain = (
    {"cuisine": RunnableLambda(lambda x: x["input"])}
    | name_chain
    | RunnableLambda(lambda rest_name: (
        print(f"【中间餐厅名】{rest_name}"),  # 直接打印
        {"restaurant_name": rest_name}
    )[1])
    | food_items_chain
)

方案 3:使用回调 CallbackHandler 捕获所有 LLM 输出

适合全局监控整条链路所有 LLM 调用,跨 chain 统一捕获,缺点无法区分属于哪一步,适合日志系统。

复制代码
from langchain_core.callbacks import BaseCallbackHandler
class CaptureHandler(BaseCallbackHandler):
    def on_llm_end(self, response, **kwargs):
        print("LLM输出:", response.generations[0][0].text)
# invoke时传入
result = full_chain.invoke({"input": "中国"}, config={"callbacks":[CaptureHandler()]})

方案 4:拆分链路分步调用(最简单粗暴)

不串联执行,分步执行直接保存变量,适合开发调试:

复制代码
input_data = {"input": "中国"}
# 第一步
cuisine = input_data["input"]
rest_name = name_chain.invoke({"cuisine": cuisine})
print("餐厅名称:", rest_name)
# 第二步
menu = food_items_chain.invoke({"restaurant_name": rest_name})
print("菜单:", menu)

关键原理说明

  1. RunnableSequence(A | B | C) 的机制:
    • A 的输出 直接作为 B 的输入
    • 默认只会向下单向流转,不会自动保留历史中间值;
  2. RunnablePassthrough.assign() 的作用: 接收上游字典,执行里面定义的 runnable,把结果追加到原有字典继续向下传递,是 LCEL 保存中间状态的标准工具。

进阶:如果你希望最终返回结构化对象(餐厅名 + 菜单)

直接使用方案 1 的代码,返回值是字典:

复制代码
{
    "cuisine": "中国",
    "restaurant_name": "xxx酒楼",
    "menu": "菜品清单..."
}
相关推荐
早点睡啊Y3 小时前
深入学LangChain官方文档(二十二):Frontend 高级形态——Headless Tools、Time Travel 与 Generative UI
ui·langchain·状态模式
大郭鹏宇12 小时前
基于 LangGraph 构建智能分诊系统(一):项目概述与环境搭建
大数据·人工智能·microsoft·langchain
玉鸯13 小时前
Agent Hook:在概率推理之上,为 Agent 叠加确定性控制
python·langchain·agent
chaors16 小时前
DeepRearchSystem 0x00:初识
langchain·llm·ai编程
BerryS3N20 小时前
深度解析 LangChain V1.3:架构演进、核心源码剖析与企业级应用落地指南
架构·langchain
大郭鹏宇1 天前
基于 LangGraph 构建智能分诊系统(三):FastAPI 接口与 Gradio WebUI 实战
大数据·网络·数据库·人工智能·langchain
BraveWang1 天前
【LangChain 1.x】11、长期记忆|Store 跨会话持久化与向量搜索
langchain
bloxed2 天前
大模型应用-进阶核心技能【13课:第二阶段工程化与部署】
langchain·大模型应用
BraveWang2 天前
【LangChain 1.x】10、短期记忆|checkpointer 持久化与上下文治理三件套
langchain