# LangChain Runnable 运行时参数绑定:bind、configurable_fields 与生产配置
Runnable 的强大之处,不只是能用 | 把 Prompt、Model、Parser 串起来,还在于它能把“运行时配置”从业务代码里抽出来。
一个真实 LLM 应用里,同一条链可能在不同场景下使用不同参数:
- 普通问答使用较低
temperature,保证稳定。 - 创意写作使用较高
temperature,提高发散性。 - 某些模型调用需要设置
stop。 - 不同租户使用不同模型供应商。
- 调试时要加 tags、metadata、run_name。
- A/B 测试时要动态切换模型。
如果把这些参数都写死在链路里,代码很快会变得难维护。LangChain 提供了几种相关能力:bind()、with_config()、configurable_fields()、configurable_alternatives()。它们都和“运行时配置”有关,但适用边界并不一样。

从流程上看,bind() 会生成一个新的 Runnable,把绑定的参数放入 Runnable.kwargs。后续执行 invoke、stream、batch 等方法时,运行时传入的 kwargs 会和绑定参数合并;如果同名参数同时出现,要明确覆盖顺序和预期行为。
# bind 解决什么问题
bind() 用来给一个 Runnable 预绑定默认调用参数。
最典型的例子是给模型绑定 stop:
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个严格按要求输出的助手。"),
("human", "{query}"),
]
)
llm = ChatOpenAI(model="gpt-4.1-mini")
chain = prompt | llm.bind(stop=["world"]) | StrOutputParser()
content = chain.invoke({"query": "Hello world"})
print(content)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这里 stop=["world"] 不需要每次调用时都传。它已经被绑定到了这个模型 Runnable 上。
可以把 bind() 理解成:
原始 Runnable
+ 默认 kwargs
-> 新 Runnable
2
3
后续执行 invoke、stream、batch 时,这些 kwargs 会自动合并到调用中。
# bind 适合哪些场景
bind() 适合在构建链路时就确定的参数。
例如:
stable_llm = ChatOpenAI(model="gpt-4.1-mini").bind(
temperature=0,
)
creative_llm = ChatOpenAI(model="gpt-4.1-mini").bind(
temperature=0.9,
)
2
3
4
5
6
7
再比如 OpenAI 工具调用里,你可能会绑定工具参数、输出格式或 stop words。
json_llm = ChatOpenAI(model="gpt-4.1-mini").bind(
response_format={"type": "json_object"},
)
2
3
它适合“构建阶段确定”的默认值,不适合“每个请求动态变化”的业务参数。
# RunnableLambda 和多参数函数
这里有一个很重要的点:RunnableLambda 包装普通函数时,invoke() 通常只有一个输入值。如果原函数有多个参数,不能指望把 dict 自动拆成多个位置参数。
例如:
import random
from langchain_core.runnables import RunnableLambda
def get_weather(location: str, unit: str) -> str:
return f"{location} 天气为 {random.randint(24, 40)}{unit}"
weather = RunnableLambda(get_weather)
2
3
4
5
6
7
8
9
10
如果这样调用:
weather.invoke({"location": "广州", "unit": "摄氏度"})
传给 get_weather 的第一个参数可能会是整个 dict,而不是自动拆成 location 和 unit。
一种写法是用 bind() 固定第二个参数:
weather = RunnableLambda(get_weather).bind(unit="摄氏度")
result = weather.invoke("广州")
2
3
更生产化的写法,是让 RunnableLambda 接收一个 dict,并在函数内部显式读取字段:
def get_weather(input: dict) -> str:
location = input["location"]
unit = input.get("unit", "摄氏度")
return f"{location} 天气为 {random.randint(24, 40)}{unit}"
weather = RunnableLambda(get_weather)
2
3
4
5
6
7
这种方式更清晰,也更容易做参数校验。
# bind 的底层直觉
bind() 不会修改原始 Runnable,而是返回一个新的 Runnable。
可以抽象成:
runnable.bind(stop=["\n"])
-> RunnableBinding(bound=runnable, kwargs={"stop": ["\n"]})
2
执行时:
输入 input
-> 合并绑定 kwargs
-> 调用原始 Runnable
-> 返回输出
2
3
4
所以 bind() 是一种“局部固化参数”的方式。它不会改变 Prompt 变量,也不会改变 config["configurable"],更不是配置中心。
# bind 和 with_config 的区别
bind() 绑定的是传给 Runnable 调用逻辑的 kwargs。
with_config() 绑定的是运行配置,例如 tags、metadata、run_name、callbacks、max_concurrency 等。
chain = chain.with_config(
{
"run_name": "answer_with_history",
"tags": ["chat", "production"],
"metadata": {
"feature": "qa",
"version": "2026-08",
},
}
)
2
3
4
5
6
7
8
9
10
它们关注点不同:
| 方法 | 关注点 | 示例 |
|---|---|---|
bind() | 传给底层 Runnable 的调用参数 | stop、temperature、tools |
with_config() | Runnable 执行过程的配置 | tags、metadata、callbacks、run_name |
不要用 with_config() 传业务输入,也不要用 bind() 代替 trace metadata。
# 真正的运行时可配置:configurable_fields
如果参数需要每次调用动态变化,更推荐 configurable_fields()。
例如把模型的 max_tokens 暴露成可配置字段:
from langchain_core.runnables import ConfigurableField
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="gpt-4.1-mini",
max_tokens=256,
).configurable_fields(
max_tokens=ConfigurableField(
id="output_token_number",
name="输出 token 数",
description="控制模型最多输出多少 token",
)
)
result = llm.with_config(
configurable={
"output_token_number": 512,
}
).invoke("用三句话解释 Flask-Migrate 的作用")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这里的关键是:你先声明哪些字段允许被运行时覆盖,然后在调用时通过 configurable 设置值。
这比在业务代码里到处 if else 构造不同模型更干净。
# 动态切换模型:configurable_alternatives
如果要在运行时切换不同模型或不同 Runnable,使用 configurable_alternatives()。
from langchain_core.runnables import ConfigurableField
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4.1-mini").configurable_alternatives(
ConfigurableField(id="llm"),
default_key="openai",
fast=ChatOpenAI(model="gpt-4.1-mini"),
strong=ChatOpenAI(model="gpt-5.5"),
)
answer = model.with_config(
configurable={
"llm": "strong",
}
).invoke("分析这个迁移方案的生产风险")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这种写法非常适合:
- 免费版和付费版切换模型。
- 普通问题走便宜模型,复杂问题走强模型。
- 多供应商容灾。
- A/B 测试。
- 不同租户配置不同模型。
# 生产配置不要散落在链路里
生产项目里,模型参数通常来自应用配置、租户配置、实验配置或请求策略。
可以把策略封装成一个函数:
def build_config_for_request(user, request) -> dict:
if user.plan == "enterprise":
llm_key = "strong"
max_tokens = 1024
else:
llm_key = "fast"
max_tokens = 512
return {
"configurable": {
"llm": llm_key,
"output_token_number": max_tokens,
},
"metadata": {
"tenant_id": user.tenant_id,
"user_id": user.id,
"plan": user.plan,
},
"tags": ["chat"],
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
调用时:
config = build_config_for_request(user, request)
answer = chain.invoke({"query": request.query}, config=config)
2
链路本身保持稳定,差异交给运行时配置。
# 和 RunnableWithMessageHistory 的关系
RunnableWithMessageHistory 也依赖运行时配置。
它通过:
config={"configurable": {"session_id": "chat-1001"}}
拿到会话 ID,再调用 get_session_history(session_id)。
所以运行时配置不只用于模型参数,也用于框架层的执行逻辑,例如:
- 会话 ID。
- 租户 ID。
- 用户 ID。
- 模型选择。
- 输出 token 限制。
- trace metadata。
这也是为什么生产里要把 config 当成一等公民,而不是临时参数包。
# 问题
Runnable 动态参数最常见的问题,是混淆了几类参数。
第一,把业务输入、模型参数、运行配置混在一个 dict 里。业务输入应该进 chain input;模型可配置项应该进 configurable;trace 信息应该进 metadata。
第二,滥用 bind()。bind() 适合构建时固定默认参数,不适合每个请求临时改参数。如果每次请求都不一样,应优先考虑 configurable_fields()。
第三,把不可信配置直接暴露给前端。比如让用户直接传 model_name、max_tokens、temperature,可能导致成本失控或绕过产品策略。
第四,配置没有可观测性。线上回答异常时,如果 trace 里看不到本次使用的模型、temperature、max_tokens、stop、租户策略,就很难排查。
# 拓展
可以把 Runnable 配置拓展成统一的运行时配置层。
建议至少分四类:
- input:用户问题、业务参数、检索条件。
- configurable:允许 LangChain Runnable 动态切换的字段。
- metadata:trace、审计、租户、用户、实验信息。
- tags:链路分类、场景标签、环境标签。
进一步可以接入:
- 租户级模型配置。
- A/B 测试配置。
- 限流和成本预算。
- 灰度发布。
- 多模型 fallback。
- LangSmith tracing。
这样 Runnable 配置就不只是 API 小技巧,而是 LLM 应用的运行时控制面。
# 实际生产是否使用
会使用,而且非常常见。
bind() 常用于固化某条链的默认模型参数,例如 stop、tools、response_format。with_config() 常用于 trace metadata、tags、callbacks。configurable_fields() 和 configurable_alternatives() 更适合生产里的动态模型参数和模型切换。
但生产里不会把这些配置完全交给用户输入。通常会由服务端根据租户、套餐、场景、实验策略生成配置。
# 现在是否抛弃
没有抛弃。
bind() 仍然是 Runnable 的基础能力,适合绑定默认 kwargs。configurable_fields()、configurable_alternatives() 也是当前 LangChain Core 里支持的动态配置能力。
需要调整的是理解方式:bind() 不是唯一的“动态运行时参数”方案。它偏构建期绑定;真正每次调用动态变化的配置,更适合用 configurable 系列方法和 with_config()。
# 最新生产如何实现
最新生产实现建议这样分层。
构建链路时声明可配置项:
from langchain_core.runnables import ConfigurableField
from langchain_openai import ChatOpenAI
base_model = ChatOpenAI(model="gpt-4.1-mini").configurable_alternatives(
ConfigurableField(id="llm"),
default_key="fast",
fast=ChatOpenAI(model="gpt-4.1-mini"),
strong=ChatOpenAI(model="gpt-5.5"),
)
model = base_model.configurable_fields(
max_tokens=ConfigurableField(
id="max_output_tokens",
name="最大输出 token",
)
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
请求进入时由服务端生成 config:
def build_runnable_config(user, request):
return {
"configurable": {
"llm": "strong" if user.plan == "enterprise" else "fast",
"max_output_tokens": 1024 if request.long_answer else 512,
},
"metadata": {
"tenant_id": user.tenant_id,
"user_id": user.id,
"feature": "chat",
},
"tags": ["langchain", "chat"],
}
2
3
4
5
6
7
8
9
10
11
12
13
执行链路:
answer = chain.invoke(
{"query": request.query},
config=build_runnable_config(user, request),
)
2
3
4
生产里还要加白名单、成本上限、默认值、配置审计和 trace。所有可变参数都应该可追踪,否则动态配置会变成线上问题的黑箱。
# 总结
bind() 的价值,是给 Runnable 预绑定默认调用参数,让链路构建更清晰。它适合固定 stop、tools、response_format、默认 temperature 这类参数。
但生产里的动态运行时控制,不能只靠 bind()。更完整的方案是:
bind()固化默认 kwargs。with_config()注入运行配置、metadata、tags。configurable_fields()暴露可动态覆盖字段。configurable_alternatives()动态切换模型或 Runnable。
把这些边界分清楚,Runnable 链路才能既稳定,又能支持多租户、A/B 测试、模型路由和成本控制。
参考: