LangChain RunnableConfig 详解:运行时传参、并发控制与回调注入

个人主页: > for_ever_love__ <(欢迎各位大佬莅临😊)
其他栏目: > 大模型开发从0到1 <
其他栏目: > iOS项目总结大全 <
其他栏目: > 我想学python了 <
其他栏目: > iOS UI <

文章目录

  • [LangChain RunnableConfig 详解:运行时传参、并发控制与回调注入](#LangChain RunnableConfig 详解:运行时传参、并发控制与回调注入)
    • [一、RunnableConfig 长什么样](#一、RunnableConfig 长什么样)
    • [二、tags 和 metadata:给调用打标](#二、tags 和 metadata:给调用打标)
    • [三、max_concurrency:别把 API 打挂](#三、max_concurrency:别把 API 打挂)
    • 四、configurable:运行时换参数(最实用)
      • [4.1 configurable_fields:替换链上的字段](#4.1 configurable_fields:替换链上的字段)
      • [4.2 configurable_alternatives:整段替换](#4.2 configurable_alternatives:整段替换)
      • [4.3 在自定义 Runnable 里读 configurable](#4.3 在自定义 Runnable 里读 configurable)
    • [五、recursion_limit:LangGraph 的步数刹车](#五、recursion_limit:LangGraph 的步数刹车)
    • 六、callbacks:在运行时注入埋点
    • [七、config 会向下传递](#七、config 会向下传递)
    • [八、一个生产级的 config 模板](#八、一个生产级的 config 模板)
    • 九、常见坑
    • 十、小结

LangChain RunnableConfig 详解:运行时传参、并发控制与回调注入

链写好了,但运行时想临时换个模型、换个用户、换个并发数怎么办?

每次都重建一条链太笨了。

RunnableConfig 就是解决"同一条链,不同运行参数 "这个问题的。

这篇讲它能干什么,以及 configurable 这个最实用的字段。

一、RunnableConfig 长什么样

python 复制代码
config = {
    # 1. 每次调用的唯一标识
    "run_id": None,              # 一般不用手动设

    # 2. 打标签,便于在日志/追踪里筛选
    "tags": ["rag", "prod", "v2"],

    # 3. 附加的任意元数据
    "metadata": {"user_id": "u-123", "trace_id": "abc"},

    # 4. 限制最大并发(batch 时生效)
    "max_concurrency": 5,

    # 5. 回调(埋点、日志、流式)
    "callbacks": [my_handler],

    # 6. 【最常用】运行时可变的自定义参数
    "configurable": {"model": "gpt-4o", "user_level": "vip"},

    # 7. 递归深度上限(LangGraph)
    "recursion_limit": 12,
}

调用时传入:

python 复制代码
chain.invoke(inputs, config=config)
chain.batch(list_of_inputs, config=config)
await chain.ainvoke(inputs, config=config)

二、tags 和 metadata:给调用打标

python 复制代码
chain.invoke(
    {"question": "..."},
    config={
        "tags": ["customer-service", "tenant-a"],
        "metadata": {"user_id": "u-123", "session": "s-456"},
    },
)

这两个字段不会影响链的行为,纯粹是为了可观测性:

  • 在 LangSmith 里可以按 tag 筛选这批调用;
  • 在自定义 callback 里能读到 metadata 做统计。
python 复制代码
from langchain_core.callbacks import BaseCallbackHandler

class MyHandler(BaseCallbackHandler):
    def on_chain_start(self, serialized, inputs, *, run_id, tags=None, metadata=None, **kwargs):
        print(f"run={run_id} tags={tags} user={metadata.get('user_id')}")

生产环境强烈建议每次调用都带 metadata ------

用户投诉"刚才答错了"时,你靠 user_id 和 trace_id 才能定位到那次调用。

三、max_concurrency:别把 API 打挂

python 复制代码
# 100 个输入,但最多同时发 5 个请求
results = chain.batch(
    inputs_list,
    config={"max_concurrency": 5},
)

不设的话,batch 会一次性把所有请求发出去。

100 个并发请求大概率触发限流(429),然后重试,反而更慢。

怎么定值:

场景 建议
免费/低额度 API key 2~3
付费、有明确 RPM 限制 按限制的 70%~80%
本地模型 按 GPU/CPU 能力

比如你的 key 限制 3000 RPM,单次请求约 2 秒,

理论并发上限 ≈ 3000/60 × 2 = 100,取 70% 就是 70。

python 复制代码
# 异步批量更推荐
results = await chain.abatch(inputs_list, config={"max_concurrency": 10})

四、configurable:运行时换参数(最实用)

这是 RunnableConfig 里最有价值 的字段。

它让你把链的一部分做成"可配置",运行时再决定。

4.1 configurable_fields:替换链上的字段

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import ConfigurableField

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0).configurable_fields(
    model_name=ConfigurableField(
        id="model",
        name="模型名",
        description="运行时可以换成别的模型",
    ),
    temperature=ConfigurableField(
        id="temp",
        description="运行时可以调温度",
    ),
)

chain = ChatPromptTemplate.from_template("{q}") | llm | StrOutputParser()

# 默认跑
chain.invoke({"q": "你好"})

# 运行时换成强模型 + 有创意
chain.invoke(
    {"q": "写首诗"},
    config={"configurable": {"model": "gpt-4o", "temp": 0.9}},
)

同一条链,不用重建,运行时换模型、换温度。

4.2 configurable_alternatives:整段替换

不只是改参数,可以整段换成另一个实现:

python 复制代码
cheap = ChatOpenAI(model="gpt-4o-mini")
strong = ChatOpenAI(model="gpt-4o")

llm = cheap.configurable_alternatives(
    ConfigurableField(id="tier"),
    default_key="cheap",
    strong=strong,
)

chain = prompt | llm | parser

chain.invoke({"q": "..."})                                   # 默认便宜模型
chain.invoke({"q": "..."}, config={"configurable": {"tier": "strong"}})   # 换强模型

典型用法:简单问题走便宜模型,复杂问题走强模型,成本直降。

python 复制代码
def route_and_answer(question: str):
    tier = "strong" if is_complex(question) else "cheap"
    return chain.invoke({"q": question}, config={"configurable": {"tier": tier}})

4.3 在自定义 Runnable 里读 configurable

这是最灵活的用法。写节点时通过第二个参数拿到 config:

python 复制代码
from langchain_core.runnables import RunnableLambda

def my_step(inputs, config):
    # 从 configurable 里取运行时参数
    user_level = config["configurable"].get("user_level", "normal")
    max_results = 20 if user_level == "vip" else 5
    return search(inputs["query"], top_k=max_results)

step = RunnableLambda(my_step)

注意:函数必须接受两个参数 (inputs, config),

LangChain 会自动把 config 传进来。只写一个参数就拿不到 config。

在 LangGraph 节点里同理:

python 复制代码
def node(state, config):
    tenant = config["configurable"].get("tenant_id")
    ...

五、recursion_limit:LangGraph 的步数刹车

python 复制代码
agent.invoke(
    {"messages": [("user", q)]},
    config={"recursion_limit": 12},
)

生产必设 ,否则模型死循环会烧掉大量调用。

具体见前面 Agent 进阶那篇。

六、callbacks:在运行时注入埋点

python 复制代码
from langchain_core.callbacks import BaseCallbackHandler

class CostTracker(BaseCallbackHandler):
    def __init__(self):
        self.total_tokens = 0

    def on_llm_end(self, response, **kwargs):
        usage = response.llm_output.get("token_usage", {})
        self.total_tokens += usage.get("total_tokens", 0)

tracker = CostTracker()
chain.invoke({"q": "..."}, config={"callbacks": [tracker]})
print(tracker.total_tokens)

按调用注入比全局注入更灵活------你可以只给某些请求开追踪。

七、config 会向下传递

一个重要特性:config 会传给链里的每一个 Runnable ,

包括嵌套的子链。

python 复制代码
outer = ({"a": inner_chain_1, "b": inner_chain_2} | ...)

outer.invoke(x, config={"tags": ["outer"]})
# inner_chain_1 和 inner_chain_2 都会收到这个 config

这意味着:

  • 你在最外层设的 max_concurrency,对里面所有并发都生效;
  • 你在最外层设的 metadata,里面每个节点都能读到;
  • tags 会累加------子链的 tag 会追加到父链后面。

八、一个生产级的 config 模板

python 复制代码
import uuid

def build_config(user_id: str, session_id: str, tier: str = "cheap") -> dict:
    return {
        "tags": [f"tenant:{tenant_id(user_id)}", f"tier:{tier}"],
        "metadata": {
            "user_id": user_id,
            "session_id": session_id,
            "trace_id": str(uuid.uuid4()),
        },
        "max_concurrency": 5,
        "configurable": {
            "tier": tier,
            "user_id": user_id,
            "thread_id": session_id,     # LangGraph 用这个做记忆
        },
        "recursion_limit": 12,
        "callbacks": [metrics_handler],
    }

result = chain.invoke({"question": q}, config=build_config("u-123", "s-456"))

九、常见坑

现象 原因 解法
configurable 改了没生效 没用 configurable_fields 声明 先在链上声明可配置字段
Lambda 里拿不到 config 函数只写了一个参数 改成 def f(inputs, config)
batch 狂报 429 没设 max_concurrency 设成 5~10
追踪里分不清调用 没带 metadata 每次调用带 user_id / trace_id
recursion_limit 无效 只在 LangGraph 生效 普通链没有递归概念

十、小结

  • RunnableConfig 是"同一条链,不同运行参数"的解决方案;
  • tags / metadata 用于可观测性,生产每次调用都要带;
  • max_concurrency 必设,否则 batch 会一次性打爆 API;
  • configurable 最实用 :configurable_fields 改参数、
    configurable_alternatives 整段替换(便宜/强模型切换);
  • 自定义函数要写 def f(inputs, config) 两个参数才能拿到 config;
  • config 会向下传递给所有子链,一次设置处处生效。

下一篇讲自定义 Runnable------把任意 Python 逻辑接进 LCEL 链。

相关推荐
for_ever_love__1 小时前
LangChain 模型参数调优:temperature、max_tokens 这些到底怎么设
python·语言模型·langchain·aigc·模型参数·temperature·max_tokens
硅谷秋水2 小时前
EmbodiRSI:面向数据高效机器人适配的递归自我改进
人工智能·深度学习·语言模型·机器人
倔强的石头1062 小时前
多模态大模型架构_视觉语言模型VLM原理
人工智能·语言模型·自然语言处理
I Am a robert girl2 小时前
从路由信号到选择性复核:MoE 视觉语言模型的视觉重定位
人工智能·语言模型·自然语言处理·视觉语言模型·混合专家模型·路由信号·目标缺失接地
虎鲸不是鱼2 小时前
AI 自动化逆向(四)人在 AI 逆向流程里到底做什么
人工智能·大模型·llm·逆向工程·ai逆向
跟我学机器学习2 小时前
LangGraph教程:基于 LangGraph 构建自定义 RAG Agent
人工智能·深度学习·语言模型
SharpCJ2 小时前
Koog(4)—— Agent Skills 和 Tools
llm·agent
硅谷秋水3 小时前
MobiAgent:面向长时程移动操作任务的双-环递归策略自我提升方法
人工智能·深度学习·机器学习·语言模型·机器人
硅谷秋水3 小时前
RegenHarness:一种具备“证据门控”递归自我改进能力的机器人智能体框架
人工智能·深度学习·机器学习·语言模型·机器人