在使用 LangChain 构建智能应用时,我们常常会遇到这样的场景:需要将多个组件灵活组合,实现复杂的逻辑流程。这时候,LangChain Expression Language (LCEL) 就成为了我们手中的利器。但面对众多的 LCEL 原语,你是否也曾感到困惑?今天,我们就来系统整理 LCEL 的核心操作,打造一份实用的速查表,帮助你在开发中快速查阅、灵活运用。
在 LCEL 中,调用可运行对象是最基础的操作。我们可以通过Runnable.invoke()
同步调用,或使用Runnable.ainvoke()
异步调用:
python
from langchain_core.runnables import RunnableLambda
# 定义一个将输入转为字符串的可运行对象
runnable = RunnableLambda(lambda x: str(x))
# 同步调用
result = runnable.invoke(5)
print(result) # 输出: '5'
# 异步调用(需配合async/await)
# result = await runnable.ainvoke(5)
当需要处理多个输入时,批量操作能大大提高效率:
python
from langchain_core.runnables import RunnableLambda
runnable = RunnableLambda(lambda x: str(x))
# 同步批量处理
results = runnable.batch([7, 8, 9])
print(results) # 输出: ['7', '8', '9']
# 异步批量处理
# results = await runnable.abatch([7, 8, 9])
对于大型数据或需要实时处理的场景,流化操作是更好的选择:
python
from langchain_core.runnables import RunnableLambda
# 定义一个生成器函数,将输入逐个转为字符串
def func(x):
for y in x:
yield str(y)
runnable = RunnableLambda(func)
# 流化处理
for chunk in runnable.stream(range(5)):
print(chunk) # 依次输出: '0', '1', '2', '3', '4'
# 异步流化处理
# async for chunk in await runnable.astream(range(5)):
# print(chunk)
LCEL 的管道操作符|
让我们可以轻松构建顺序执行的流程:
python
from langchain_core.runnables import RunnableLambda
# 第一个可运行对象:将输入包装为字典
runnable1 = RunnableLambda(lambda x: {"foo": x})
# 第二个可运行对象:将输入重复两次
runnable2 = RunnableLambda(lambda x: [x] * 2)
# 组合成链
chain = runnable1 | runnable2
# 调用链
result = chain.invoke(2)
print(result) # 输出: [{'foo': 2}, {'foo': 2}]
当多个任务可以并行处理时,RunnableParallel
能显著提高效率:
python
from langchain_core.runnables import RunnableLambda, RunnableParallel
runnable1 = RunnableLambda(lambda x: {"foo": x})
runnable2 = RunnableLambda(lambda x: [x] * 2)
# 并行组合
chain = RunnableParallel(first=runnable1, second=runnable2)
# 调用并行链
result = chain.invoke(2)
print(result) # 输出: {'first': {'foo': 2}, 'second': [2, 2]}
通过RunnableLambda
,我们可以将任意函数转换为可运行对象:
python
from langchain_core.runnables import RunnableLambda
# 定义一个简单函数
def func(x):
return x + 5
# 转换为可运行对象
runnable = RunnableLambda(func)
# 调用
result = runnable.invoke(2)
print(result) # 输出: 7
RunnablePassthrough.assign
允许我们在保留原始输入的同时添加新的输出字段:
python
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
# 定义一个计算函数
runnable1 = RunnableLambda(lambda x: x["foo"] + 7)
# 合并字典
chain = RunnablePassthrough.assign(bar=runnable1)
# 调用
result = chain.invoke({"foo": 10})
print(result) # 输出: {'foo': 10, 'bar': 17}
RunnablePassthrough
可以确保原始输入包含在输出中:
python
from langchain_core.runnables import RunnableLambda, RunnableParallel, RunnablePassthrough
runnable1 = RunnableLambda(lambda x: x["foo"] + 7)
# 并行组合,其中一个分支保留原始输入
chain = RunnableParallel(bar=runnable1, baz=RunnablePassthrough())
# 调用
result = chain.invoke({"foo": 10})
print(result) # 输出: {'bar': 17, 'baz': {'foo': 10}}
Runnable.bind
让我们可以为可运行对象绑定默认参数:
python
from typing import Optional
from langchain_core.runnables import RunnableLambda
# 定义一个需要多个参数的函数
def func(main_arg: dict, other_arg: Optional[str] = None) -> dict:
if other_arg:
return {**main_arg, **{"foo": other_arg}}
return main_arg
runnable1 = RunnableLambda(func)
# 绑定默认参数
bound_runnable1 = runnable1.bind(other_arg="bye")
# 调用
result = bound_runnable1.invoke({"bar": "hello"})
print(result) # 输出: {'bar': 'hello', 'foo': 'bye'}
Runnable.with_fallbacks
可以为可运行对象添加备用方案:
python
from langchain_core.runnables import RunnableLambda
# 第一个可运行对象:尝试将输入加字符串"foo"
runnable1 = RunnableLambda(lambda x: x + "foo")
# 第二个可运行对象:作为 fallback
runnable2 = RunnableLambda(lambda x: str(x) + "foo")
# 添加 fallback 机制
chain = runnable1.with_fallbacks([runnable2])
# 调用(当runnable1失败时,自动使用runnable2)
result = chain.invoke(5)
print(result) # 输出: '5foo'
Runnable.with_retry
可以为可运行对象添加重试功能:
python
from langchain_core.runnables import RunnableLambda
counter = -1
# 定义一个可能失败的函数
def func(x):
global counter
counter += 1
print(f"attempt with {counter=}")
return x / counter
# 添加重试机制,最多尝试2次
chain = RunnableLambda(func).with_retry(stop_after_attempt=2)
# 调用
result = chain.invoke(2)
print(result) # 输出: 2.0(第二次尝试成功)
RunnableConfig
允许我们在调用时配置可运行对象的执行参数,以下代码在invoke
方法中传入config
参数,使用RunnableConfig
来设置最大并发数:
python
from langchain_core.runnables import RunnableLambda, RunnableParallel, RunnableConfig
runnable1 = RunnableLambda(lambda x: {"foo": x})
runnable2 = RunnableLambda(lambda x: [x] * 2)
runnable3 = RunnableLambda(lambda x: str(x))
# 并行组合三个可运行对象
chain = RunnableParallel(first=runnable1, second=runnable2, third=runnable3)
# 调用时配置最大并发数为2
result = chain.invoke(7, config=RunnableConfig(max_concurrency=2))
print(result) # 输出: {'first': {'foo': 7}, 'second': [7, 7], 'third': '7'}
Runnable.with_config
可以为可运行对象设置默认配置:
python
from langchain_core.runnables import RunnableLambda, RunnableParallel
runnable1 = RunnableLambda(lambda x: {"foo": x})
runnable2 = RunnableLambda(lambda x: [x] * 2)
runnable3 = RunnableLambda(lambda x: str(x))
# 并行组合
chain = RunnableParallel(first=runnable1, second=runnable2, third=runnable3)
# 添加默认配置
configured_chain = chain.with_config(RunnableConfig(max_concurrency=2))
# 调用
result = configured_chain.invoke(7)
print(result) # 输出: {'first': {'foo': 7}, 'second': [7, 7], 'third': '7'}
Runnable.
configurable_fields让我们可以在运行时配置可运行对象的属性,以下示例定义了FooRunnable
类,通过configurable_fields方法,使output_key
属性在运行时可配置:
python
from typing import Any, Optional
from langchain_core.runnables import ConfigurableField, RunnableConfig, RunnableSerializable
# 定义一个可配置输出键的可运行对象
class FooRunnable(RunnableSerializable[dict, dict]):
output_key: str
def invoke(
self, input: Any, config: Optional[RunnableConfig] = None, **kwargs: Any
) -> list:
return self._call_with_config(self.subtract_seven, input, config, **kwargs)
def subtract_seven(self, input: dict) -> dict:
return {self.output_key: input["foo"] - 7}
# 创建实例
runnable1 = FooRunnable(output_key="bar")
# 使output_key可配置
configurable_runnable1 = runnable1.configurable_fields(
output_key=ConfigurableField(id="output_key")
)
# 运行时配置output_key
result1 = configurable_runnable1.invoke(
{"foo": 10}, config={"configurable": {"output_key": "not bar"}}
)
print(result1) # 输出: {'not bar': 3}
# 使用默认配置
result2 = configurable_runnable1.invoke({"foo": 10})
print(result2) # 输出: {'bar': 3}
根据输入动态选择不同的可运行对象:
python
from langchain_core.runnables import RunnableLambda, RunnableParallel
runnable1 = RunnableLambda(lambda x: {"foo": x})
runnable2 = RunnableLambda(lambda x: [x] * 2)
# 根据输入值动态选择链
chain = RunnableLambda(lambda x: runnable1 if x > 6 else runnable2)
# 调用
result1 = chain.invoke(7)
print(result1) # 输出: {'foo': 7}
result2 = chain.invoke(5)
print(result2) # 输出: [5, 5]
Runnable.astream_events
允许我们获取可运行对象执行过程中的事件流:
python
import nest_asyncio
nest_asyncio.apply()
from langchain_core.runnables import RunnableLambda, RunnableParallel
runnable1 = RunnableLambda(lambda x: {"foo": x}, name="first")
# 定义一个生成事件的异步函数
async def func(x):
for _ in range(5):
yield x
runnable2 = RunnableLambda(func, name="second")
# 组合链
chain = runnable1 | runnable2
# 异步获取事件流
import asyncio
async def main():
async for event in chain.astream_events("bar", version="v2"):
print(f"event={event['event']} | name={event['name']} | data={event['data']}")
asyncio.run(main())
Runnable.batch_as_completed
让我们可以按任务完成的顺序获取结果:
python
import time
from langchain_core.runnables import RunnableLambda, RunnableParallel
# 定义一个带延迟的可运行对象
runnable1 = RunnableLambda(lambda x: time.sleep(x) or print(f"slept {x}"))
# 按完成顺序获取结果
for idx, result in runnable1.batch_as_completed([5, 1]):
print(idx, result) # 先输出1 None,再输出0 None
Runnable.pick
允许我们只保留输出字典中的指定字段:
python
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
runnable1 = RunnableLambda(lambda x: x["baz"] + 5)
# 组合链:先计算,再挑选字段
chain = RunnablePassthrough.assign(foo=runnable1).pick(["foo", "bar"])
# 调用
result = chain.invoke({"bar": "hi", "baz": 2})
print(result) # 输出: {'foo': 7, 'bar': 'hi'}
Runnable.map
可以声明式地创建批处理版本:
python
from langchain_core.runnables import RunnableLambda
runnable1 = RunnableLambda(lambda x: list(range(x)))
runnable2 = RunnableLambda(lambda x: x + 5)
# 组合链:先生成范围,再对每个元素加5
chain = runnable1 | runnable2.map()
# 调用
result = chain.invoke(3)
print(result) # 输出: [5, 6, 7]
Runnable.get_graph
可以帮助我们可视化可运行对象的结构:
python
from langchain_core.runnables import RunnableLambda, RunnableParallel
runnable1 = RunnableLambda(lambda x: {"foo": x})
runnable2 = RunnableLambda(lambda x: [x] * 2)
runnable3 = RunnableLambda(lambda x: str(x))
# 组合复杂链
chain = runnable1 | RunnableParallel(second=runnable2, third=runnable3)
# 获取图形表示并打印
graph = chain.get_graph()
graph.print_ascii()
Runnable.get_prompts
可以帮助我们获取链中包含的所有提示模板:
python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda
# 定义提示模板
prompt1 = ChatPromptTemplate.from_messages(
[("system", "good ai"), ("human", "{input}")]
)
prompt2 = ChatPromptTemplate.from_messages(
[
("system", "really good ai"),
("human", "{input}"),
("ai", "{ai_output}"),
("human", "{input2}"),
]
)
# 定义假的LLM
fake_llm = RunnableLambda(lambda prompt: "i am good ai")
# 组合链
chain = prompt1.assign(ai_output=fake_llm) | prompt2 | fake_llm
# 提取并打印所有提示
for i, prompt in enumerate(chain.get_prompts()):
print(f"**prompt {i=}**\n")
print(prompt.pretty_repr())
print("\n" * 3)
Runnable.with_listeners
可以让我们在可运行对象的生命周期中添加监听器:
python
import time
from langchain_core.runnables import RunnableLambda
from langchain_core.tracers.schemas import Run
# 定义开始和结束监听器
def on_start(run_obj: Run):
print("start_time:", run_obj.start_time)
def on_end(run_obj: Run):
print("end_time:", run_obj.end_time)
runnable1 = RunnableLambda(lambda x: time.sleep(x))
# 添加监听器
chain = runnable1.with_listeners(on_start=on_start, on_end=on_end)
# 调用
chain.invoke(2)
通过这份速查表,我们系统整理了 LCEL 的核心原语和用法,从基础的调用、批量处理,到复杂的组合、动态构建,再到高级的配置、事件流处理。LCEL 的强大之处在于它提供了一种统一的方式来表达和组合各种可运行对象,让我们可以轻松构建复杂的逻辑流程。
如果本文对你有帮助,别忘了点赞收藏,关注我,一起探索更高效的开发方式~