个人主页: > 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 链。