# 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()。它们都和“运行时配置”有关,但适用边界并不一样。

Runnable bind 参数合并流程

从流程上看,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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

这里 stop=["world"] 不需要每次调用时都传。它已经被绑定到了这个模型 Runnable 上。

可以把 bind() 理解成:

原始 Runnable
  + 默认 kwargs
  -> 新 Runnable
1
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,
)
1
2
3
4
5
6
7

再比如 OpenAI 工具调用里,你可能会绑定工具参数、输出格式或 stop words。

json_llm = ChatOpenAI(model="gpt-4.1-mini").bind(
    response_format={"type": "json_object"},
)
1
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)
1
2
3
4
5
6
7
8
9
10

如果这样调用:

weather.invoke({"location": "广州", "unit": "摄氏度"})
1

传给 get_weather 的第一个参数可能会是整个 dict,而不是自动拆成 location 和 unit。

一种写法是用 bind() 固定第二个参数:

weather = RunnableLambda(get_weather).bind(unit="摄氏度")

result = weather.invoke("广州")
1
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)
1
2
3
4
5
6
7

这种方式更清晰,也更容易做参数校验。

# bind 的底层直觉

bind() 不会修改原始 Runnable,而是返回一个新的 Runnable。

可以抽象成:

runnable.bind(stop=["\n"])
  -> RunnableBinding(bound=runnable, kwargs={"stop": ["\n"]})
1
2

执行时:

输入 input
  -> 合并绑定 kwargs
  -> 调用原始 Runnable
  -> 返回输出
1
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",
        },
    }
)
1
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 的作用")
1
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("分析这个迁移方案的生产风险")
1
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"],
    }
1
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)
1
2

链路本身保持稳定,差异交给运行时配置。

# 和 RunnableWithMessageHistory 的关系

RunnableWithMessageHistory 也依赖运行时配置。

它通过:

config={"configurable": {"session_id": "chat-1001"}}
1

拿到会话 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",
    )
)
1
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"],
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

执行链路:

answer = chain.invoke(
    {"query": request.query},
    config=build_runnable_config(user, request),
)
1
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 测试、模型路由和成本控制。

参考: