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": "菜品清单..."
}
相关推荐
在努力的前端小白16 小时前
基于FastAPI+LangChain+LangGraph+Agent+多模态的AI Agent 有渔渔具网站
人工智能·langchain·vue·fastapi
用户94218127613419 小时前
两条线:LangGraph 如何把状态流和控制流拆开(AI 生成)
langchain
gugugu.19 小时前
同一个 ReAct,两种手写方式:LangChain 文本协议 vs LangGraph 状态图
ai·langchain
掘黄金的打工人20 小时前
LangChain PromptTemplate 提示词模板
langchain·ai编程
日日大王学编程1 天前
deerflow-memory-design-blog
python·langchain·开源
前端精髓1 天前
动态选择工具
python·langchain
云和数据.ChenGuang2 天前
深入解析 Python 的 dir():从用法到 CPython 源码实现
开发语言·人工智能·python·langchain
hqk2 天前
AI Agent 学习总结:从模型到 Harness
langchain·aigc
日日大王学编程2 天前
开源Agent项目记忆设计决策对比
python·ai·langchain·开源·ai编程
前端精髓3 天前
结构化输出with_structured_output(抽卡)
python·langchain