Langchain入门到精通0x02:ICEL

11 阅读21分钟

chain

langchain框架的基础组织单元,解决“如何把AI模型、工具、逻辑按顺序组合成一个可执行任务”的问题。本质上是一个调用语言模型或其他工具、并按照特定顺序处理输入输出的对象。它封装了“接收输入 -> 执行一系列操作 -> 返回输出”的完整流程。

LCEL

LCEL (LangChain Expression Language) :这是框架的声明式组合语法,解决“如何更优雅、更健壮地构建和调用上述链”的问题。是langchain在2023年引入的革命性特性。它提供了一种使用管道操作符 |声明式组合链的语法,极大地提升了代码的可读性、可维护性和内置了高级功能(如流式传输、异步、并行、重试等)。

  • 核心逻辑:LCEL让链的构建从“面向对象式的配置”转变为“函数式的流水线组装”。每一个组件(如提示词模板、模型、输出解析器)都是一个可调用的对象,通过 |连接,表示“将前一个的输出作为后一个的输入”。

基本方法

  • stream: 流式返回响应的块
  • invoke: 接受输入返回输出
  • batch: 接受批量输入返回输出列表,核心是并发优化

batch效率

invoke我们之前已经用过,stream也简单是一个流式输出。batch的核心是并发优化,那么我们就可以简单写一个demo来印证下batch相比invoke的提效到底有多少。

Coding
构造chain
# 1. 创建LCEL链
chain = (
    ChatPromptTemplate.from_template("用一句话介绍{topic}")
    | ChatTongyi()
    | StrOutputParser()
)

# 2. 准备批量输入
topics = ["人工智能", "区块链", "量子计算", "基因编辑"]
inputs = [{"topic": topic} for topic in topics] 
invoke
# 3. 单次调用计时  串行执行,一个个主题执行
start = time.time()
single_results = [chain.invoke({"topic": topic}) for topic in topics]
single_time = time.time() - start
batch
start = time.time()
# 注意事项
# API供应商可能有批量请求限制(如每分钟请求数)
# 输入列表中的所有字典必须有相同的键结构
# 批量处理不适合有状态的操作(如带记忆的对话链)

batch_results = chain.batch(inputs)  # 关键批量调用方法
batch_time = time.time() - start
结果
# 5. 结果对比
print(f"\n=== 单次调用耗时: {single_time:.2f}s ===")
for i, res in enumerate(single_results):
    print(f"{topics[i]}: {res}")

print(f"\n=== 批量调用耗时: {batch_time:.2f}s (加速 {single_time/batch_time:.1f}x) ===")
for i, res in enumerate(batch_results):
    print(f"{topics[i]}: {res}")

image.png

运行结果,正印证了batch的并行优化特性,他不仅仅是简单的批处理。

stream和batch

那stream又有什么特性呢?他和普通的invoke有何区别?

stream()方法返回一个生成器(Generator),可以逐块(chunk)地产生输出。这对于大语言模型生成文本、构建聊天机器人等需要实时反馈的场景至关重要。它能将首字符响应时间(Time to First Token, TTFT)优化到最佳,极大提升用户体验。

那么stream和batch到底该怎么选呢?我们结合invoke综合对比一下。

特性invokebatchstream
🧠技术原理同步阻塞的单次请求-响应,最简单、最直接的RPC模式。并发/并行的批量作业,利用I/O多路复用或后端批量推理接口,实现并发处理基于生成器(Generator)的增量处理。建立一种长连接或持续的数据通道,允许服务器在生成结果的过程中就逐步返回数据片段(如token)
🔑技术关键错误处理与重试。重点是保证单次调用的健壮性,如网络超时、模型降级等策略。1. 后端批量推理支持:这是效率提升的前提。 2. 批次大小(Batch Size)优化:寻找内存、延迟和吞吐量的最佳平衡点。 3. 动态批处理:高级系统能动态合并不同时间的请求1. 前后端协议:需使用SSE、WebSocket等支持流式的协议。 2. 增量解析:下游组件必须能处理不完整的数据块。 3. 中断与取消:需要支持用户中途取消生成。
🎯核心目标简单性、确定性与可靠性最大化吞吐量(Throughput)最小化延迟(Latency),尤其是首字延迟(TTFT
📊数据处理完整输入,完整输出批量输入,批量输出增量输入/输出
⚙️资源利用通常最低。请求间存在大量空闲的I/O等待时间(网络、磁盘),CPU/GPU经常处于闲置状态。最高。通过将计算任务密集打包,让GPU等昂贵硬件满负荷运转,摊薄了单次请求的固定开销。中等但更公平。计算资源随用随取,适合高并发在线服务,能更公平、及时地响应多个并发的流式请求。
😀用户体验“等待-结果”模式。可能存在等待焦虑。无实时交互的离线体验。适用于后台任务。“实时交互”模式。持续反馈,体验流畅、自然,符合人类对话习惯。
🚀应用场景1. 简单的单轮问答。 2. 执行一次性的、独立的工具调用。 3. 原型开发与功能测试。1. 离线数据处理:批量生成文档摘要、嵌入向量。 2. 报表生成:夜间批量分析日志,生成日报。 3. 模型评估:在测试集上批量运行模型以评估指标。1. 对话式AI:如ChatGPT,逐字生成回复。 2. 实时翻译/字幕:语音或文字流实时转换。 3. 代码补全:IDE中随着输入实时推荐代码。 4. 长文生成:逐步输出文章段落,让用户提前阅读。

func Vs chain

缘起

我们之前传统的编程中,一个集中的功能块被称为函数。当然ICEL中内置并自动连接了许多函数,那么当我们需要将一个自定义的函数运用到ICEL中该怎么办?

  • 函数:是执行特定任务的原子操作。它接收输入,返回输出,但其内部逻辑对调用者是不透明的,通常缺乏执行过程的可观测性,也难以与其他AI组件(如LLM调用、工具调用)无缝连接。

  • LCEL Chain:是使用LCEL语法(主要通过 |操作符)将多个可运行体​ 连接起来的工作流。其核心是 Runnable协议。任何实现了此协议的对象(如LLM模型、工具、甚至另一个Chain)都可以被连接。Chain的优势在于提供了标准的接口内置的流式处理日志与追踪,以及异步、批处理等能力

Runnable协议

可见,其转换核心便是 Runnable 协议。其本质:为所有能在LCEL管道(通过 |连接)中工作的对象,规定了一套必须实现的“通用语言”和“标准操作”

简单说,它是LCEL这个“生态系统”里的宪法,确保了千差万别的组件(从LLM调用、工具函数到简单的数据转换)能够用统一的方式进行交互、组合和执行。

协议核心的方法主要就是上面讲到的几个LCEL基本方法:

  1. invoke(input, config?) -> output​ (同步调用)
    • 功能:最基础的调用方式,输入一个值,返回一个值。
    • 工程意义:定义了组件最直接的、阻塞式的单次执行逻辑。
  2. ainvoke(input, config?) -> output​ (异步调用)
    • 功能:invoke的异步版本。
    • 工程意义:支撑高并发应用的关键。当链中有网络请求(如调用LLM API)时,使用此方法可避免阻塞,极大提升吞吐量。
  3. batch(inputs, config?) -> outputs​ (同步批处理)
    • 功能:接受一个输入列表,返回一个输出列表。注意:它不一定是简单的for循环,框架或具体实现可能会进行并行优化。
    • 工程意义:为批量处理任务提供标准化接口,是高效数据处理的基础。
  4. abatch(inputs, config?) -> outputs​ (异步批处理)
    • 功能:batch的异步版本,是处理大批量任务的推荐方式。
    • 工程意义:结合了批量与并发的优势,是实现高性能AI应用流水线的核心技术。
  5. stream(input, config?) -> Iterator[output]​ (流式输出)
    • 功能:返回一个生成器,逐步产出结果。这对于LLM生成长文本、实时返回中间结果至关重要。
    • 工程意义:实现了端到端的流式用户体验,是构建响应式应用的核心。

至于,将函数转换到LCEL中使用的方法。后面会着重讲解。

RunnableSequence

顺序处理,处理链的“传送带”。是LCEL中最基础、最常用的组件,用于顺序执行多个任务。它将前一个“可运行对象”(Runnable)的输出,作为后一个的输入。

  • 本质:定义了一个函数调用链 f(g(h(x)))。每个环节可以是LLM调用、提示词模板、输出解析器或任意函数。LCEL负责处理类型校验、异步支持、流式传输等底层复杂性。或者更简单地说就是我们以往用的|连接符。
  • case:
# 顺序执行每一个节点
chain = RunnableSequence(prompt,model,out)
# 等同于 | 连接符,如下
# chain = prompt | model | out

RunnableParallel

并行处理,任务分发的“多叉路口”。接收一个输入,同时分发给多个Runnable,各分支独立运行,最后将结果按指定键名汇总。

  • RunnableSequence组合:这是构建复杂Agent的核心模式。通常结构为:Parallel(并行收集信息)-> Sequence(串联分析决策)。
  • case:
# 1. 模拟两个“信息源”——通常它们是外部API、数据库查询或工具调用
def query_knowledge_base(product_name: str) -> str:
    """模拟查询产品知识库(可能访问数据库或内部文档)"""
    time.sleep(0.2)  # 模拟网络延迟
    knowledge = {
        "智能音箱X3": "这是我们的旗舰款智能音箱。核心特色:1. 搭载8核AI芯片,唤醒率99.9%;2. 支持全屋智能联动;3. 内置Hi-Fi级音响,获得金耳朵认证;4. 待机时间长达72小时。"
    }
    return knowledge.get(product_name, "未找到该产品的官方资料。")

def query_recent_feedback(product_name: str) -> str:
    """模拟查询近期用户评论(可能调用舆情分析API)"""
    time.sleep(0.3)  # 模拟另一个服务的延迟
    feedback_db = {
        "智能音箱X3": "最近30天用户评价精华:1. 音质受到普遍好评,低音表现突出;2. 与‘智慧家居’App偶尔出现连接不稳定(占比约5%的反馈);3. 新出的‘儿童模式’很受家庭用户欢迎。"
    }
    return feedback_db.get(product_name, "暂无该产品的近期用户反馈。")


# 2. 核心:构建 RunnableParallel 来并行收集
# 它接收一个字典,定义多个并行的执行分支
parallel_info_gathering = RunnableParallel({
    "official_info": RunnableLambda(lambda x: query_knowledge_base(x["product_name"])),
    "user_feedback": RunnableLambda(lambda x: query_recent_feedback(x["product_name"])),
    # RunnablePassthrough() 用于将原始输入(如product_name)也传递下去
    "product_name": RunnablePassthrough()
})

# 4. 构建 RunnableSequence 来串联分析与决策
# 这是一个提示词模板,它将接收parallel_info_gathering输出的所有信息
analysis_prompt = ChatPromptTemplate.from_template("""
你是一名专业的客服助理。请根据以下关于产品“{product_name}”的信息,生成一份给用户的回复要点。

【官方产品信息】
{official_info}

【近期用户反馈摘要】
{user_feedback}

---
生成要求:
1. 首先概括产品的核心优势(来自官方信息)。
2. 然后,提及用户反馈中注意到的亮点。
3. 最后,如果用户反馈中提到了任何潜在问题或顾虑,请用委婉、专业的方式在回复中有所提及或安抚。
4. 回复语言口语化,亲切,面向最终消费者。
请直接输出回复内容:
""")

# 5. 将并行和串行组合成完整的链
# 使用管道操作符 `|` 连接,形成:输入 -> Parallel -> Sequence -> 输出
agent_chain = (
        {"product_name": RunnablePassthrough()}  # 将用户输入转化为字典格式,键为"product_name"
        | parallel_info_gathering
        | analysis_prompt
        | ChatTongyi()
        | StrOutputParser()
)

RunnablePassthrough

LCEL中的 “透明管道”与“数据中继”,是一个什么也不做的“透明人”。他好比一个快递员,他只负责把文件原封不动地从一个部门送到下一个部门,不查看、不修改。

数据传递

就是啥也不做,原样传递。前面在RunnableParallel的示例代码中已经见过,这里的作用就是将原始输入(如product_name)原封不动地传递下去

parallel_info_gathering = RunnableParallel({
    "official_info": RunnableLambda(lambda x: query_knowledge_base(x["product_name"])),
    "user_feedback": RunnableLambda(lambda x: query_recent_feedback(x["product_name"])),
    # RunnablePassthrough() 用于将原始输入(如product_name)也传递下去
    "product_name": RunnablePassthrough()
})

数据增强-assign特性

利用assign特性将数据增强后继续传递。如下,执行后:

  • 原数据:{'k1': 'hello world'}
  • 新数据:{'k1': 'hello world', 'modified': 'hello world!!!'}
# 数据增强,增强后进行继续传递
chain = RunnableParallel(
    passed = RunnablePassthrough().assign(modified= lambda x: x["k1"]+"!!!"),
)
# 增强调用,需要使用字典格式
print(chain.invoke({"k1": "hello world"}))

RunnableLambda

函数包装器,接入旧世界的“万能适配器”。我们前面讲到的自定义函数在LCEL中使用就是需要使用这个。它包装一个普通函数,使其符合Runnable接口,从而能够与其他LCEL组件无缝衔接。

示例函数

# 创建一个Pydantic模型, 用于估算费用
class TripDetails(BaseModel):
    destination: str = Field(description="旅行目的地城市")
    duration: int = Field(description="旅行天数")
    estimated_cost_per_person: float = Field(description="估算人均成本", default=None)
    summary: str = Field(description="给用户的旅行建议摘要")

def calculate_total_cost(trip_details: TripDetails) -> TripDetails:
    """根据旅行天数和目的地,估算一个非常粗略的人均成本"""
    daily_cost = {"北京": 600, "东京": 1200}.get(trip_details.destination, 500)
    trip_details.estimated_cost_per_person = daily_cost * trip_details.duration
    return trip_details

方法1-RunnableLambda

# 将函数封装为Runnable
chain = prompt | model | out | RunnableLambda(calculate_total_cost)

方法2-@chain装饰器

# 使用@chain装饰器
@chain
def chain_calculate_total_cost(trip_info: dict) -> dict:
    return calculate_total_cost(trip_info)

# 直接使用装饰后的方法名,无需RunnableLambda
chain = prompt | model | out | chain_calculate_total_cost

其他代码

model = ChatTongyi()
out = PydanticOutputParser(pydantic_object=TripDetails)
format_instructions = out.get_format_instructions()
prompt = ChatPromptTemplate.from_template("""
你是一名专业的旅游助手。请根据以下关于旅游信息“{tripInfo}”,生成一份给用户的回复要点\n{format_instructions}。

【旅游信息】
{tripInfo}

""")
prompt = prompt.partial(format_instructions=format_instructions)

# 方法1
chain = prompt | model | out | RunnableLambda(calculate_total_cost)
# 方法2
# chain = prompt | model | out | chain_calculate_total_cost
result = chain.invoke({"tripInfo": "北京-东京 3天"})
print(result.model_dump()) # 查看完整的结构化结果
print("-" * 50)
print(result.summary) # 访问文本回复
print("-" * 50)
print(f"估算成本:{result.estimated_cost_per_person}") # 访问计算后的成本

RunnableMap

数据转换器,规整数据的“操作台”。常用于在链中顺序执行输入数据的转换,是“精加工,重新包装”的过程,核心目标在于为下一步做准备。常用语数据分析师整理和清洗已收集的数据。

  • RunnableMap与RunnableLambda的关键区别在于意图。Map明确表示“我将输入映射为一个新的字典”,这通常用于数据整形。而Lambda可以返回任何类型。在复杂链中,用Map来保证数据接口一致性,能使流程更清晰。

一个简单的🌰:电商商品信息处理流水线 - RunnableMap。场景:处理用户上传的商品信息,生成标准化商品详情页

image.png

示例数据

# 示例输入:用户上传的商品原始数据
raw_product_data = {
    "title": "Apple iPhone 15 Pro Max 256GB 原色钛金属 行货正品",
    "price": "8999.00",
    "seller": "Apple官方旗舰店",
    "specs": "6.7英寸, A17 Pro芯片, 4800万像素, 5倍长焦",
    "tags": "手机|苹果|iPhone|旗舰|5G",
    "description": "全新iPhone 15 Pro Max,采用航空级钛金属设计,史上最轻的Pro机型。",
    "timestamp": "2024-03-15T14:30:00Z"
}

多个处理函数

📢:数据处理流水线,这里每个步骤都依赖上一步的输出。

  • 提取基础信息
def extract_basic_info(data: Dict) -> Dict:
    """提取基础信息 - 假设我们需要品牌和型号"""
    title = data.get("title", "")
    # 简单解析品牌和型号
    if "iPhone" in title:
        brand = "Apple"
        model = "iPhone 15 Pro Max"
    elif "小米" in title or "Xiaomi" in title:
        brand = "Xiaomi"
        model = "未知型号"
    else:
        brand = "其他"
        model = "未知"

    return {
        "brand": brand,
        "model": model,
        "title_cleaned": title.replace("行货正品", "").strip()
    }
  • 解析价格信息
def parse_price_info(data: Dict) -> Dict:
    """解析价格信息 - 依赖基础信息中的品牌"""
    price_str = data.get("price", "0")
    try:
        price = float(price_str)
    except:
        price = 0.0

    # 根据品牌确定货币单位
    brand = data.get("brand", "未知")
    currency = "CNY" if brand in ["Apple", "Xiaomi", "华为"] else "USD"

    return {
        "price_numeric": price,
        "currency": currency,
        "price_formatted": f"{currency} {price:,.2f}"
    }
  • 分类商品
def categorize_product(data: Dict) -> Dict:
    """分类商品 - 依赖清理后的标题和价格"""
    title = data.get("title_cleaned", "")
    price = data.get("price_numeric", 0)

    category = "电子产品"
    subcategory = "手机"

    if price > 8000:
        price_segment = "高端"
    elif price > 3000:
        price_segment = "中端"
    else:
        price_segment = "入门"

    return {
        "category": category,
        "subcategory": subcategory,
        "price_segment": price_segment
    }

RunnableMap

# 1.2 使用RunnableMap构建数据处理流水线
# 注意:这里每个步骤都依赖上一步的输出
data_processing_pipeline = RunnableMap({
    "basic_info": RunnableLambda(extract_basic_info),
    "price_info": RunnableLambda(lambda x: parse_price_info({**x, **extract_basic_info(x)})),
    "category_info": RunnableLambda(lambda x: categorize_product({
        **x,
        **extract_basic_info(x),
        **parse_price_info({**x, **extract_basic_info(x)})
    })),
    "original_data": RunnablePassthrough()
})

print("执行RunnableMap数据清洗...")
start_time = time.time()
processed_data = data_processing_pipeline.invoke(raw_product_data)
map_time = time.time() - start_time

print(f"\n✅ RunnableMap处理结果(耗时: {map_time:.3f}秒):")
print("清洗后的数据:")
for key, value in processed_data.items():
    if key != "original_data":
        print(f"  {key}: {value}")

🤔和RunnableParallel混用思考???

RunnableMap处理有依赖的数据,如果这个【电商项目】继续展开还能做啥?

  1. 使用RunnableParallel并行获取:

    • 竞品信息
    • 市场分析
    • 库存和物流
    • SEO关键词
  2. 获取到后再使用RunnableMap,依赖前面的生成一个【电商项目xxx报告】

  3. 总结RunnableMap + RunnableParallel的混合使用场景:

    • 先用Map清洗/准备数据(清洗、验证、标准化)
    • 然后用Parallel并发独立操作(并发获取外部数据)
    • 最后用另一个Map整合结果(整合、丰富、格式化)

RunnableBranch

LCEL中实现条件逻辑动态路由的核心组件,是流程“决策树”。它是实现if-else逻辑的核心,其根据条件动态选择下一步执行哪个分支。它接收一个(条件, 分支)的列表和一个默认分支。例如,根据用户问题意图判断是走“问答分支”、“数据查询分支”还是“闲聊分支”。

在大模型应用中,简单地说就好比你问一个问题,RunnableBranch先判断这个问题属于哪个分治,然后调用哪个分治链来解决。废话少说上🌰:一个产品信息系统,包括价格查询、技术咨询、退款流程等。

条件判断函数

# 1. 定义条件判断函数
def classify_query(query_data: dict) -> str:
    """根据查询内容分类"""
    query = query_data.get("query", "").lower()

    if "价格" in query or "多少钱" in query or "cost" in query:
        return "price_inquiry"
    elif "故障" in query or "问题" in query or "error" in query:
        return "technical_issue"
    elif "退货" in query or "退款" in query or "return" in query:
        return "refund_request"
    elif "客服" in query or "人工" in query or "support" in query:
        return "human_support"
    else:
        return "general_inquiry"

分支链

价格咨询分支
# 价格查询分支
price_chain = (
        ChatPromptTemplate.from_template("""
    你是一个专业的销售顾问。用户询问价格信息。

    用户查询:{query}

    请以专业、友好的方式回答价格相关问题。
    如果知道具体价格,请明确告知。
    如果不知道,请提供获取价格的途径。
    """)
        | llm
        | StrOutputParser()
)
技术咨询分支
# 技术问题分支
tech_chain = (
        ChatPromptTemplate.from_template("""
    你是一个技术专家。用户遇到技术问题。

    用户查询:{query}

    请提供详细的技术解决方案。
    分步骤说明,确保用户能理解。
    如果问题复杂,建议联系技术支持。
    """)
        | llm
        | StrOutputParser()
)
退款分支
# 退款请求分支
refund_chain = (
        ChatPromptTemplate.from_template("""
    你是一个客服专员。用户希望退款或退货。

    用户查询:{query}

    请友好地解释退款政策。
    询问订单详细信息以便协助。
    提供明确的后续步骤。
    """)
        | llm
        | StrOutputParser()
)
默认查询分治

这里就像我们传统编程的switch,为了程序健壮性,总是有一个默认default。

# 通用查询分支
general_chain = (
        ChatPromptTemplate.from_template("""
    你是一个有用的助手。回答用户的通用查询。

    用户查询:{query}

    请提供有帮助的回答。
    """)
        | llm
        | StrOutputParser()
)

RunnableBranch

# 3. 创建RunnableBranch
# 格式:RunnableBranch( (条件1, 分支1), (条件2, 分支2), ..., 默认分支 )
query_router = RunnableBranch(
    # 条件是一个函数,接收输入数据,返回True/False
    (lambda x: classify_query(x) == "price_inquiry", price_chain),
    (lambda x: classify_query(x) == "technical_issue", tech_chain),
    (lambda x: classify_query(x) == "refund_request", refund_chain),
    # 默认分支(当所有条件都不满足时执行)
    general_chain
)

完整链

# 4. 构建完整链:接收查询 -> 路由 -> 处理
full_chain = RunnableLambda(
    lambda x: {"query": x}  # 将字符串包装为字典
) | query_router

测试代码

# 5. 测试不同查询
test_queries = [
    "iPhone 15的价格是多少?",
    "我的手机无法开机,怎么办?",
    "我想退货,流程是什么?",
    "转人工客服",
    "你们公司的营业时间是什么?",
    "推荐一款适合拍照的手机"
]

print("测试不同查询的路由结果:\n")
for i, query in enumerate(test_queries, 1):
    print(f"{i}. 查询: {query}")
    print("-" * 40)

    start_time = time.time()
    try:
        response = full_chain.invoke(query)
        elapsed = time.time() - start_time

        # 显示分类结果
        category = classify_query({"query": query})
        print(f"分类: {category}")
        print(f"响应时间: {elapsed:.2f}秒")
        print(f"回答: {response}")
    except Exception as e:
        print(f"错误: {e}")

    print("\n" + "=" * 60 + "\n")

测试结果

image.png

image.png

image.png

RunnableWithMessageHistory

对话的“记忆体”,这是构建对话式Agent的基石。今天不做扩展,后续我们在记忆模块再做专题学习。

综合对比

image.png

特性技术原理核心功能应用场景注意事项性能特点组合模式
RunnableSequence函数管道模式,将多个Runnable按顺序组合,前一个输出作为后一个输入顺序执行多个任务,形成处理流水线1. 多步骤处理流程
2. LLM调用链
3. 数据预处理+模型调用+后处理
1. 链条过长时调试困难
2. 错误会沿链传递
3. 需注意步骤间的数据类型匹配
总耗时=各步骤耗时之和,无并行优化基础组合单元,可与所有其他组件组合
RunnableParallel并行执行模式,将同一输入同时分发给多个独立分支,结果合并为字典并行执行多个独立任务,提升吞吐量1. 同时调用多个外部API
2. 并行查询多个数据源
3. 独立的数据处理任务
1. 分支间不能有数据依赖
2. 一个分支失败可能影响整个并行块
3. 需注意资源竞争
总耗时≈最慢分支耗时,显著提升IO密集型任务性能常与Sequence组合:Parallel收集 → Sequence处理
RunnableLambda适配器模式,将任意Python函数包装为Runnable接口集成自定义业务逻辑到LCEL链1. 调用外部API/数据库
2. 复杂业务规则计算
3. 数据验证与清洗
4. 格式转换
1. 过度使用破坏声明式风格
2. 函数应尽量保持纯函数特性
3. 需自行处理错误
取决于函数逻辑,可能成为性能瓶颈可插入任何需要自定义逻辑的位置
RunnableMap数据转换模式,对输入进行结构化转换,输出单个字典数据清洗、格式标准化、字段提取与映射1. 数据预处理
2. API响应格式化
3. 特征工程
4. 输入输出格式适配
1. 常与RunnableLambda混淆
2. 输出必须是字典
3. 是顺序执行,非并行
顺序执行,总耗时=各转换步骤之和常用于链的开头(数据准备)或结尾(结果格式化)
RunnableBranch条件路由模式,根据条件函数动态选择执行分支实现if-else逻辑,动态工作流路由1. 意图识别与路由
2. 多级决策树
3. 异常处理与降级
4. A/B测试路由
1. 条件顺序重要(短路评估)
2. 条件函数应轻量
3. 必须提供默认分支
4. 条件函数应是纯函数
条件判断开销+选中分支开销,条件函数应高效可构建复杂决策树,常与Parallel组合实现动态工作流
RunnablePassthrough恒等函数模式,原样传递输入,不做任何处理数据传递、上下文保留、占位符1. 在Parallel中保留原始输入
2. 链中传递上下文
3. 调试时检查中间状态
4. 配合.assign()动态添加字段
1. 常被误解为"无用"组件
2. 不提供并行能力
3. 可能意外传递可变对象引用
近乎零开销,是最轻量的Runnable常与Parallel组合保留原始数据,或用于链中数据传递

源码

github