[LangChain之链]RunnableCallable——将“自由定义”的函数变成标准组件

在众多针对Runnable的继承类型中,个人认为最重要的莫过于RunnableCallable。有过LangChain开发经验的人都知道,LangChain的很多组件都可以写成函数的形式,比如工具、中间件、LangGraph的节点等,而且它们的签名相对"自由",其参数不仅仅用于提供外部输入,还可以用来注入一些运行时对象,比如RuntimeBaseStoreStreamWriterRunnableConfig等。它们很多都会转换成RunnableCallable对象来使用。

1. 构造函数

RunnableLambda类似,RunnableCallable用于封装提供的同步或者异步函数,将它们转换成支持LCEL链的标准组件。从如下所示的代码片段可以看出,我们提供的函数签名对输入参数和返回值都没有限制。

python 复制代码
class RunnableCallable(Runnable):
    def __init__(
        self,
        func: Callable[..., Any | Runnable] | None,
        afunc: Callable[..., Awaitable[Any | Runnable]] | None = None,
        *,
        name: str | None = None,
        tags: Sequence[str] | None = None,
        trace: bool = True,
        recurse: bool = True,
        explode_args: bool = False,
        **kwargs: Any,
    ) -> None

除了提供的用于执行目标操作的funcafunc参数外,RunnableCallable的构造函数还提供了其他的关键字参数:

  • name:给这个节点起个名,方便在跟踪记录或日志里识别。如果没有指定,func或者afunc的名称会作为其名称;
  • tags:附加到跟踪记录的标签;
  • trace:开启LangSmitch跟踪的开关。如果设为False,这个单元的执行过程将不会出现在监控链路中,适合高频、简单的逻辑以节省资源;
  • recurse: 递归开关。如果函数返回的依然是一个Runnable,是否继续自动执行它;
  • explode_args:参数解包。如果设为True,且输入是一个字典,它会将字典解包为关键字参数传给func;
  • kwargs: 预先填充的参数;

2. 针对Runtime和RunnableConfig的自动注入

如果提供的函数包含RuntimeRunnableConfig类型的参数,并且参数名分别设置为runtimeconfig,调用invoke/aninvoke方发并指定config参数为一个RunnableConfig对象,该对象将绑定为func/afunc的config参数,RunnableConfig中保存的Runtime(对应的Key为"__pregel_runtime",可以通过常量langgraph._internal._constants.CONFIG_KEY_RUNTIME得到它)将绑定为runtime参数。这也是为什么configruntime在很多地方作为保留参数的原因。如下的程序演示了针对这两个核心对象的参数注入。

python 复制代码
from langgraph._internal._runnable import RunnableCallable
from langgraph._internal._constants import  CONFIG_KEY_RUNTIME
from langchain_core.runnables import  RunnableConfig
from langgraph.runtime import Runtime

global_config: RunnableConfig = {
    "configurable": {
        CONFIG_KEY_RUNTIME: Runtime()
    }
}

def test_func(input:dict, config:RunnableConfig, runtime:Runtime)-> dict:
    assert config is global_config
    assert runtime is global_config.get("configurable", {}).get(CONFIG_KEY_RUNTIME)
    return input

runnable = RunnableCallable(func=test_func)
result = runnable.invoke(input ={"foo":"bar"},config=global_config)
assert result == {"foo":"bar"}

3. 针对其他参数的手工注入

除了针对RuntimeRunnableConfig的自动注入,封装函数的其他参数也可以通过在构造函数预填充的参数,或者调用invoke/aninvoke方法指定的关键字参数进行绑定。在如下这个演示实例中,test_func中的stream_writerstore参数就是分别通过这两种方式填充的。当我们调用StateGraph的add_conditional_edges方法添加"条件边",path参数指定的用于解析分支路径的函数中的storestream_writer参数就是采用这种方式注入的。

python 复制代码
from langgraph._internal._runnable import RunnableCallable
from langgraph._internal._constants import  CONFIG_KEY_RUNTIME
from langchain_core.runnables import  RunnableConfig
from langgraph.runtime import Runtime
from langgraph.store.base import BaseStore
from langgraph.types import StreamWriter
from typing import cast

global_config: RunnableConfig = {
    "configurable": {
        CONFIG_KEY_RUNTIME: Runtime()
    }
}

def test_func(input:dict, config:RunnableConfig, stream_writer: StreamWriter, store: BaseStore)-> dict:
    runtime = cast(Runtime, config.get("configurable", {}).get(CONFIG_KEY_RUNTIME))
    assert stream_writer is runtime.stream_writer
    assert store is runtime.store
    return input

runtime = cast(Runtime, global_config.get("configurable", {}).get(CONFIG_KEY_RUNTIME))
runnable = RunnableCallable(func=test_func, stream_writer=runtime.stream_writer) 
result = runnable.invoke(input ={"foo":"bar"},config=global_config,store=runtime.store)
assert result == {"foo":"bar"}

4. 递归执行

在默认情况下,如果指定函数返回一个Runnable,执行RunnableCallable时会以递归的防止执行它。我们可以在创建RunnableCallable时利用recurse参数关闭这一特性。RunnableCallable递归执行Runnable的能力体现在如下的演示程序中。

python 复制代码
from langgraph._internal._runnable import RunnableCallable
from langchain_core.runnables import  Runnable

log:list[str] = []

def foo(input:dict)->Runnable:
    log.append("foo")
    return RunnableCallable(bar)

def bar(input:dict)->Runnable:
    log.append("bar")
    return RunnableCallable(baz)

def baz(input:dict)->dict:
    log.append("baz")
    return input

runnable = RunnableCallable(func=foo) 
result = runnable.invoke(input ={"foo":"bar"})
assert result == {"foo":"bar"}
assert log == ["foo", "bar", "baz"]

log.clear()
runnable = RunnableCallable(func=foo, recurse = False)
result = runnable.invoke(input ={"foo":"bar"})
assert isinstance(result,Runnable)
assert log == ["foo"]

5. 参数拆包

如果调用构造函数时将explode_args参数设置为True,意味着调用invoke/ainvoke时指定的输入参数在传入封装的函数前,会先进行拆包。如下的程序演示了这一点:

python 复制代码
from langgraph._internal._runnable import RunnableCallable

def foobar(data: str, *, foo: str, bar: str) -> dict:
    return {"data":data,"foo": foo, "bar": bar}
input = (("foobar",), {"foo": "123", "bar": "456"})

runnable = RunnableCallable(func=foobar, explode_args=True)
result = runnable.invoke(input)
assert result == {"data": "foobar", "foo": "123", "bar": "456"}

runnable = RunnableCallable(foobar)
try:
    result = runnable.invoke(input)
    assert False, "Expected TypeError due to missing arguments"
except Exception as e:
    assert isinstance(e, TypeError)
    assert str(e) == "foobar() missing 2 required keyword-only arguments: 'foo' and 'bar'"
相关推荐
AI 编程助手GPT15 小时前
GPT-5.5与Claude Opus 4.7编程能力深度对比:2026年4月主流AI编程模型选型指南
大数据·人工智能·gpt·ai·ai编程
神仙别闹15 小时前
基于Python实现上下消化道病历分类
开发语言·python·分类
倦王15 小时前
langchain 尚硅谷day4-5 记忆缓存部分!
langchain
m0_7403524215 小时前
Layui如何解决表单select下拉框在移动端点击没反应
jvm·数据库·python
qq_3926906615 小时前
Scikit-learn怎么实现协同过滤推荐_利用NearestNeighbors找相似用户
jvm·数据库·python
dfdfadffa15 小时前
C#怎么使用TopLevel顶级语句 C#顶级语句怎么写如何省略Main方法简化控制台程序【语法】
jvm·数据库·python
qq_4135020215 小时前
Workerman vs Swoole:2026高性能PHP框架怎么选?
jvm·数据库·python
xingpanvip15 小时前
星盘接口开发文档:天象盘接口指南
android·开发语言·python·php·lua
zjy2777715 小时前
PHP源码对声卡有依赖吗_音频硬件无关性说明【方法】
jvm·数据库·python
2301_8180084415 小时前
PHP函数如何适配高密度服务器机箱_PHP在紧凑硬件布局优化【操作】
jvm·数据库·python