摘要:从 Model Profiles 能力检测出发,梳理生产环境"管好模型"要做的几件事------用 RunnableConfig 配置每次调用、用 InMemoryRateLimiter 限流、用 usage_metadata 监控 token 与成本、用 max_retries、降级、熔断做连接弹性。配合 DeepSeek + OpenAI 双模型实测,讲清"profile 无数据不等于能力缺失"、"get_openai_callback 能统计 DeepSeek 的 token 却算不出成本"、"思考模式会多烧 reasoning token"等实战细节。
前言
前面六篇,我们一步步学会了"用"模型:第3篇统一初始化模型,第4篇用 invoke/stream/batch 调用它,第5篇让它按结构输出,第6篇让它调用工具。到这里,单从"调用大模型"的角度,基本够用了。
但要真把一个大模型应用送上生产,光"会用"还不够------你还得"管"好它:
- 这个模型到底支持哪些能力?上下文窗口多大?支不支持工具调用?
- 每次调用怎么打标签、挂回调,方便追踪?
- 模型调用太猛,触发平台限流怎么办?
- 一次调用花了多少 token、成本多少钱?
- 网络抖动、限流报错,怎么自动重试、降级?
本篇,会把这些与"模型管理"有关的事情一次性捋一遍。
传送门:【LangChain 1.x】06、Tool Calling|工具的四种定义方式与手动调用全流程
一、Model Profiles:模型能力检测
1.1 model.profile 是什么
每个模型支持的能力不一样:有的能看图、有的能调工具、有的上下文窗口特别大。
LangChain 1.0 给 chat 模型加了个 profile 属性,把这些能力以一个普通 dict 的形式暴露出来,方便你在代码里运行前判断模型支不支持某个特性。
数据多来自开源的 models.dev 项目,不同 provider 的集成填充程度不同。
python
from my_llm import deepseek_llm, openai_llm
print(deepseek_llm.profile) # DeepSeek
print(openai_llm.profile) # OpenAI
DeepSeek(社区集成):
css
None
OpenAI:
python
{'name': 'GPT-4o mini', 'max_input_tokens': 128000, 'max_output_tokens': 16384,
'text_inputs': True, 'image_inputs': True, 'audio_inputs': False, 'pdf_inputs': True,
'reasoning_output': False, 'tool_calling': True, 'structured_output': True,
'tool_choice': True, 'tool_call_streaming': True, ...}
第一个现象:DeepSeek 的 profile 是 None------它的社区集成没填 profile 数据;而 OpenAI 的 profile 很全。
1.2 能力检测
profile 是个 dict,用 .get() 安全取字段就行:
python
def check_capabilities(model):
p = model.profile or {}
print(f"最大输入 token: {p.get('max_input_tokens')}")
print(f"支持工具调用: {p.get('tool_calling')}")
print(f"支持结构化输出: {p.get('structured_output')}")
print(f"支持推理输出: {p.get('reasoning_output')}")
# 模态:profile 没有统一字段,拆成 text/image/audio/pdf/video_inputs
keys = ["text_inputs", "image_inputs", "audio_inputs", "pdf_inputs", "video_inputs"]
print(f"输入模态: {[k.replace('_inputs', '') for k in keys if p.get(k)]}")
DeepSeek(profile 为 None → 全是空):
makefile
最大输入 token: None
支持工具调用: None
支持结构化输出: None
支持推理输出: None
输入模态: []
OpenAI:
python
最大输入 token: 128000
支持工具调用: True
支持结构化输出: True
支持推理输出: False
输入模态: ['text', 'image', 'pdf']
1.3 实战:能力门控(三态)
既然拿到了模型能力相关的信息,这样就可以在运行前做必要的判断,比如:某功能依赖工具调用,就先检查再决定走哪条路。
但这里有个坑 :要区分 True / False / None 三态,None 表示"集成没填数据",不等于模型不支持。
python
def decide(model, field, feature):
p = model.profile or {}
val = p.get(field)
if val is True:
print(f"{feature} → 支持")
elif val is False:
print(f"{feature} → 不支持")
else:
print(f"{feature} → profile 无数据,无法判断")
decide(deepseek_llm, "structured_output", "DeepSeek 结构化输出")
decide(openai_llm, "structured_output", "OpenAI 结构化输出")
DeepSeek 结构化输出 → profile 无数据,无法判断
OpenAI 结构化输出 → 支持
如果只分"支持/不支持"两态,DeepSeek 会因为 profile 是 None 被判成"不支持"------这就错了 :第5篇实测 DeepSeek 明明能用 with_structured_output,第6篇也调用了工具。所以门控时千万别把 None 当 False,否则会冤枉模型。
一句话:profile 能帮你确认 OpenAI 这类填充完整的模型的能力;对 DeepSeek 这种 profile 缺失的,profile 只能告诉你"不知道",不能告诉你"不行"。
1.4 profile 可以补全/修改
profile 是普通 dict,数据缺失或过时时可以自己补。注意别原地改共享实例,用 model_copy 复制后再改:
python
capped = deepseek_llm.model_copy()
if capped.profile is None:
capped.profile = {}
capped.profile["max_input_tokens"] = 4096 # 手动补一个值
# 原实例 deepseek_llm.profile 不受影响
二、Invocation Config:调用配置
调用模型时(invoke/stream/batch),可以传一个 config 参数(类型是 RunnableConfig),在运行时动态配置本次调用的行为,不用在初始化时写死。
2.1 RunnableConfig:run_name / tags / metadata / callbacks
python
from langchain_core.callbacks import BaseCallbackHandler
from my_llm import deepseek_llm
class LoggingCallback(BaseCallbackHandler):
def on_llm_start(self, serialized, prompts, **kwargs):
run_name = kwargs.get("name") # 注意:run_name 在回调里键名是 "name"
tags = kwargs.get("tags", [])
metadata = kwargs.get("metadata", {})
print(f"[开始] run_name={run_name!r} tags={tags}")
print(f" metadata={metadata}")
def on_llm_end(self, response, **kwargs):
usage = (response.llm_output or {}).get("token_usage", {})
print(f"[结束] total_tokens={usage.get('total_tokens')}")
config = {
"run_name": "翻译任务",
"tags": ["translation", "v2"],
"metadata": {"user": "alice", "session": "abc123"},
"callbacks": [LoggingCallback()],
}
deepseek_llm.invoke("把'你好,世界'翻译成英文", config=config)
ini
[开始] run_name='翻译任务' tags=['translation', 'v2']
metadata={'user': 'alice', 'session': 'abc123', 'ls_provider': 'deepseek', 'ls_model_name': 'deepseek-v4-flash', ...}
[结束] total_tokens=18
几个要点:
- run_name:给这次调用起个名字,在 LangSmith、Langfuse 等 trace 工具里方便定位。
- tags / metadata:打标签、挂自定义键值对,子调用会继承,方便过滤检索。
- 你会看到 metadata 里除了自己传的
user/session,还有一批ls_*字段(ls_provider、ls_model_name等)------这是 LangChain 自动注入的追踪信息。 - 回调里取 run_name 要用
kwargs.get("name")(键名是name不是run_name);on_llm_end的response.llm_output里能拿到本次 token 用量。
config 还支持
max_concurrency(并发上限)、recursion_limit(递归深度,防 agent 死循环)等,本篇聚焦上面四项。
2.2 get_openai_callback:统计 token 与成本
get_openai_callback 是个上下文管理器,with 块里的调用会自动统计 token 和成本:
python
from langchain_community.callbacks import get_openai_callback
from my_llm import openai_llm, deepseek_llm
with get_openai_callback() as cb:
openai_llm.invoke("用一句话介绍 LangChain")
print(f"总 token: {cb.total_tokens},成本: ${cb.total_cost}")
bash
总 token: 45,成本: $2.115e-05
需要注意:get_openai_callback 函数名中虽然带 openai,但它统计的不止 OpenAI------因为 DeepSeek 的 API 本身是 OpenAI 兼容的,底层复用 OpenAI 客户端 ,所以 DeepSeek 的 token 也会被统计到。但是成本是按内置的 OpenAI 价目表算的,价目表中没有我们使用的 deepseek-v4-flash,所以 DeepSeek 的成本算出来是 0:
| 模型 | 总 token | 成本 |
|---|---|---|
| OpenAI gpt-4o-mini | 45 | $0.000021 |
| DeepSeek deepseek-v4-flash | 47 | $0.0 |
所以,这个工具能帮你计算 DeepSeek 消耗的 token 数量,但不能计算花了多少钱,要算成本还得自己用 token × 单价;
2.3 可配置模型:运行时切换 model / 参数
如果希望同一套代码,在运行时能够切换模型或参数(多环境、A/B 测试、成本路由),可以使用 init_chat_model 的 configurable_fields:
python
from langchain.chat_models import init_chat_model
config_model = init_chat_model(
model="gpt-4o-mini", model_provider="openai",
api_key=OPENAI_API_KEY, base_url=OPENAI_BASE_URL,
configurable_fields=("model", "temperature"), # 指定configurable_fields中的字段在运行时可配置
)
# 默认配置
print(config_model.invoke("一句话介绍你自己").content)
# 运行时切换 temperature
print(config_model.invoke("给'天空'起3个比喻", config={"configurable": {"temperature": 0}}).content)
# 运行时切换 model
print(config_model.invoke("一句话介绍你自己", config={"configurable": {"model": "gpt-4o-mini"}}).content)
markdown
我是一个人工智能助手,旨在回答问题、提供信息和帮助解决各种问题。
1. 天空如同一幅无边的画布,绘满了云朵的柔和与星星的闪烁。
2. 天空像一位温柔的母亲,怀抱着大地,给予生命以阳光和雨露。
3. 天空犹如一面巨大的镜子,反射着人们的梦想与希望,时而晴朗,时而阴霾。
我是一个人工智能助手,旨在提供信息和帮助,解答各种问题。
三次调用都正常返回了,说明运行时模型参数切换生效了
其中,temperature=0 的输出稳定,自我介绍类回答每次略有差异。
通过 config["configurable"] 能够在调用时覆盖默认的 model / temperature 配置。如果初始化时不指定 model,model 和 model_provider 默认就是可配置的。
三、Rate Limiting:速率限制
模型服务商都有调用速率上限,短时间发太多请求会收到 429 错误。
LangChain 内置了 InMemoryRateLimiter(令牌桶实现,线程安全),让开发者能够在"客户端"主动控制请求节奏。
3.1 创建限流器并接入模型
python
import time
from langchain_core.rate_limiters import InMemoryRateLimiter
from langchain.chat_models import init_chat_model
rate_limiter = InMemoryRateLimiter(
requests_per_second=0.5, # 每秒 0.5 个 token = 每 2 秒 1 个请求
max_bucket_size=1, # 桶最多 1 个 token,禁止突发连发
check_every_n_seconds=0.1, # 每 100ms 检查一次桶
)
model = init_chat_model(
model="deepseek-v4-flash",
model_provider="deepseek",
api_key=DEEPSEEK_API_KEY,
base_url=DEEPSEEK_BASE_URL,
rate_limiter=rate_limiter, # 关键:接入限流器
)
start = time.time()
for i in range(3):
model.invoke(f"用一个词回答:第{i+1}")
print(f"第 {i+1} 次完成,累计 {time.time() - start:.1f}s")
第 1 次完成,累计 3.3s
第 2 次完成,累计 4.8s
第 3 次完成,累计 6.6s
不限流的话,这 3 次请求几乎会连着发出去;
现在,每次间隔约 2 秒------这就是限流在起作用;
3.2 令牌桶三个参数
requests_per_second:每秒往桶里补几个 token,决定长期平均速率(0.5 = 平均每秒 0.5 次)。max_bucket_size:桶最多装几个 token,决定允许的瞬时突发(设 1 即禁止连发;设大一点可以应对流量小高峰)。check_every_n_seconds:检查桶的时间粒度(0.1 = 每 100ms 检查一次)。
调用模型前要先从桶里取 1 个 token:桶里有就立即发、桶里空就阻塞等补够。
3.3 共享限流器
限流器是线程安全的,可以让多个模型共用同一个,比如 DeepSeek 和 OpenAI 共用一个配额:
python
limiter = InMemoryRateLimiter(
requests_per_second=0.5,
max_bucket_size=1,
check_every_n_seconds=0.1
)
# 两个模型绑定同一个 limiter,从同一个桶取 token
m_ds = init_chat_model(
model="deepseek-v4-flash",
model_provider="deepseek",
api_key=DEEPSEEK_API_KEY,
base_url=DEEPSEEK_BASE_URL,
rate_limiter=limiter,
)
m_oai = init_chat_model(
model="gpt-4o-mini",
model_provider="openai",
api_key=OPENAI_API_KEY,
base_url=OPENAI_BASE_URL,
rate_limiter=limiter,
)
start = time.time()
m_ds.invoke("一个词:A")
print(f"DeepSeek 完成,累计 {time.time() - start:.1f}s")
m_oai.invoke("一个词:B")
print(f"OpenAI 完成,累计 {time.time() - start:.1f}s")
DeepSeek 完成,累计 4.3s
OpenAI 完成,累计 7.8s
两次调用间隔约 2 秒------因为两个模型从同一个桶取 token,DeepSeek 用掉一个之后,OpenAI 得等桶补满才能发。这就是共享限流器的作用:多个模型(或多个线程)共用一个配额,总速率被统一限制。
四、Token 使用监控
每次调用消耗了多少 token、花费了多少钱,是生产环境绕不开的事。模型返回的 AIMessage 也中会携带这些相关信息。
4.1 usage_metadata:最直接的 token 用量
python
r = deepseek_llm.invoke("用一句话解释什么是递归")
print(r.usage_metadata)
DeepSeek(关闭思考):
arduino
{'input_tokens': 9, 'output_tokens': 40, 'total_tokens': 49,
'input_token_details': {'cache_read': 0}, 'output_token_details': {}}
DeepSeek(思考模式):
arduino
{'input_tokens': 9, 'output_tokens': 60, 'total_tokens': 69,
'input_token_details': {'cache_read': 0}, 'output_token_details': {'reasoning': 35}}
有两个细节:
- 思考模式多烧了 reasoning token :
output_token_details.reasoning = 35,total 从 49 涨到 69,所以思考过程是要额外"花钱"的。 input_token_details.cache_read:能看到上下文缓存命中了多少(测试结果中是 0)。
4.2 response_metadata:附加信息
和 usage_metadata 一样,response_metadata 也挂在返回的 AIMessage 上,里面有模型名、finish_reason、原始 token_usage 等:
python
r = deepseek_llm.invoke("你好")
md = r.response_metadata or {}
print(f"model_name: {md.get('model_name')}")
print(f"finish_reason: {md.get('finish_reason')}")
print(f"token_usage: {md.get('token_usage')}")
yaml
model_name: deepseek-v4-flash
finish_reason: stop
token_usage: {'completion_tokens': 29, 'prompt_tokens': 5, 'total_tokens': 34, ...}
4.3 成本估算:token × 单价
第二章看到 get_openai_callback 算不出 DeepSeek 的成本,那就自己拿 usage_metadata 乘单价:
python
PRICE = { # USD / 1K tokens,示意值,实际以官网为准
"deepseek-v4-flash": {"input": 0.0001, "output": 0.0003},
"gpt-4o-mini": {"input": 0.00015, "output": 0.0006},
}
def calc(name, usage):
u = usage or {}
return u.get("input_tokens", 0) / 1000 * PRICE[name]["input"] \
+ u.get("output_tokens", 0) / 1000 * PRICE[name]["output"]
bash
DeepSeek: 输入 9 + 输出 46 → $0.000015
OpenAI: 输入 13 + 输出 33 → $0.000022
4.4 流式的坑:usage 只在最后一个 chunk
流式输出用 stream()(第4篇讲过),模型会一个个 chunk 返回。这里遍历所有 chunk,看看哪些带 usage_metadata:
python
chunks_with_usage = 0
last_usage = None
for chunk in openai_llm.stream("数到 5,每个数字单独一行"):
if chunk.usage_metadata: # 中间分片这里是 None,只有最后一个 chunk 有值
chunks_with_usage += 1
last_usage = chunk.usage_metadata
print(f"带 usage_metadata 的 chunk 数: {chunks_with_usage}")
print(f"最后一次 usage: {last_usage}")
css
带 usage_metadata 的 chunk 数: 1
最后一次 usage: {'input_tokens': 18, 'output_tokens': 11, 'total_tokens': 29, ...}
只有 1 个 chunk 带 usage,就是最后一个------中间的分片 chunk.usage_metadata 全是 None。所以流式场景下,token 用量要从最后一个 chunk 或累加后的完整消息取。
跨多次调用(batch、agent 循环)聚合统计总 token,更合适的做法是用 LangChain 1.0 的中间件统一拦截------这部分留到「中间件」篇讲,本篇只看单次。
五、连接弹性与错误处理
生产环境的网络不可能永远稳定:会抖动、会超时、会被限流。
这一章,讲怎么让模型调用"扛得住"------自动重试、超时控制、错误分类、降级和熔断。
5.1 max_retries:自动重试(指数退避)
LangChain 的 chat 模型默认就会对网络错误、429、5xx 自动重试(指数退避 + 抖动),max_retries 控制重试次数。用极短超时故意触发一个可重试错误(APITimeoutError),观察重试:
python
import time, logging
from langchain.chat_models import init_chat_model
# 重试默认是静默的,打开 openai 客户端日志才能看到重试过程
logging.basicConfig(level=logging.INFO)
logging.getLogger("httpx").setLevel(logging.WARNING) # 压掉 httpx 逐请求日志
model = init_chat_model(
model="deepseek-v4-flash", model_provider="deepseek",
api_key=DEEPSEEK_API_KEY, base_url=DEEPSEEK_BASE_URL,
timeout=0.001, # 极短超时,必然超时(触发可重试错误)
max_retries=2, # 重试 2 次
)
start = time.time()
try:
model.invoke("你好")
except Exception as e:
print(f"最终失败:{type(e).__name__},耗时 {time.time() - start:.1f}s")
vbnet
INFO:openai._base_client:Retrying request to /chat/completions in 0.448665 seconds
INFO:openai._base_client:Retrying request to /chat/completions in 0.866984 seconds
最终失败:APITimeoutError,耗时 1.4s
两条 Retrying 日志正好对应 max_retries=2 的两次重试,等待时间从 0.45s 涨到 0.87s------这就是指数退避(每次等待大致翻倍,还带点随机抖动);整个调用最终耗时 1.4s。默认重试是静默的,打开 openai 客户端的日志才能看到这个过程。
5.2 timeout:超时控制
timeout 设定单次请求的最长等待时间,超时就失败。把 max_retries 设 0,同样的极短超时会立即失败、不重试:
python
model = init_chat_model(
model="deepseek-v4-flash", model_provider="deepseek",
api_key=DEEPSEEK_API_KEY, base_url=DEEPSEEK_BASE_URL,
timeout=0.001,
max_retries=0, # 不重试
)
失败:APITimeoutError
耗时 0.016s(极短,说明超时了且没重试)
对比 5.1 和 5.2:同样的 timeout=0.001,5.1 设 max_retries=2,有两次 Retrying 重试日志、耗时 1.4s;5.2 设 max_retries=0,没有重试日志,直接失败(0.016s)------差额就是重试时的指数退避等待。
5.3 错误分类:哪些会重试,哪些不会
不是所有错误都重试。只有"临时性"错误才重试 (网络错误、429、5xx);客户端错误(401 鉴权失败、404)不会重试 ------因为重试也不会成功。用错误 key 触发 401,即使设 max_retries=5 也不会重试,立即失败:
python
model = init_chat_model(
model="deepseek-v4-flash", model_provider="deepseek",
api_key="sk-invalid-key", # 错误 key → 401
base_url=DEEPSEEK_BASE_URL,
max_retries=5, # 设了也没用,401 不重试
)
失败:AuthenticationError
耗时 0.18s(很快,401 是客户端错误,不重试)
| 错误类型 | 是否重试 | 例子 |
|---|---|---|
| 网络错误 / 超时 | 是 | APITimeoutError、连接错误 |
| 429(限流)/ 5xx(服务端错误) | 是 | RateLimitError、APIStatusError |
| 4xx 客户端错误(除 429/408) | 否 | AuthenticationError(401)、NotFoundError(404) |
5.4 降级(fallback):主模型失败切备用
重试解决"临时抖动",但模型要是持续不可用,重试也没用。这时用降级:主模型抛异常,就切到备用模型。
python
primary = init_chat_model(
model="deepseek-v4-flash", model_provider="deepseek",
api_key=DEEPSEEK_API_KEY, base_url=DEEPSEEK_BASE_URL,
timeout=0.001, max_retries=0, # 模拟主模型故障
)
backup = openai_llm
try:
resp = primary.invoke("用一句话介绍 LangChain")
print(f"主模型成功:{resp.content[:40]}")
except Exception:
print("主模型失败 → 降级到 OpenAI")
resp = backup.invoke("用一句话介绍 LangChain")
print(f"备用成功:{resp.content[:40]}")
scss
主模型失败:APITimeoutError → 降级到 OpenAI
备用(OpenAI)成功:LangChain 是一个用于构建与语言模型互动的应用程序的框架...
5.5 熔断(circuit breaker):别白白重试
重试和降级都是"失败后再处理"。但如果下游已经挂了,每次还去试一遍(重试 + 降级)也是浪费。
熔断器的思路是:连续失败到阈值就"跳闸",后续请求直接拒绝,不再调用下游。一个最小熔断器:
python
class SimpleCircuitBreaker:
def __init__(self, threshold=3):
self.threshold = threshold
self.failures = 0
self.opened = False
def record_failure(self):
self.failures += 1
if self.failures >= self.threshold:
self.opened = True
def record_success(self):
self.failures = 0
self.opened = False
def allow(self):
return not self.opened
用它来记录失败、判断是否放行:
python
cb = SimpleCircuitBreaker(threshold=3)
for i in range(3):
cb.record_failure()
print(f"第 {i + 1} 次失败,allow={cb.allow()}")
ini
第 1 次失败,allow=True
第 2 次失败,allow=True
第 3 次失败,allow=False ← 跳闸
放到大模型场景里,熔断器一般这样用------包在模型调用外层,连续失败到阈值就不再调用模型:
python
cb = SimpleCircuitBreaker(threshold=3)
bad_model = init_chat_model(
model="deepseek-v4-flash", model_provider="deepseek",
api_key=DEEPSEEK_API_KEY, base_url=DEEPSEEK_BASE_URL,
timeout=0.001, max_retries=0, # 极短超时,模拟一个"持续故障"的模型
)
def call_with_breaker(question):
if not cb.allow(): # 熔断了就直接返回,不调用模型
print("→ 熔断中,跳过本次调用(不再请求模型)")
return
try:
bad_model.invoke(question)
cb.record_success()
print("→ 调用成功")
except Exception:
cb.record_failure() # 失败累计,到阈值会跳闸
print(f"→ 调用失败,累计失败 {cb.failures} 次")
for i in range(5):
print(f"第 {i + 1} 次请求:")
call_with_breaker("你好")
第 1 次请求:
→ 调用失败,累计失败 1 次
第 2 次请求:
→ 调用失败,累计失败 2 次
第 3 次请求:
→ 调用失败,累计失败 3 次
第 4 次请求:
→ 熔断中,跳过本次调用(不再请求模型)
第 5 次请求:
→ 熔断中,跳过本次调用(不再请求模型)
前 3 次请求都真的调用了模型(并失败、累计),第 3 次后熔断器跳闸;第 4、5 次请求直接被拦下,根本不再请求模型------这就是熔断器的价值:下游已经挂了,就别再反复去试,省下无谓的调用和等待。
生产中通常还会加一个「半开」状态,定时放少量请求探测下游是否恢复;Python 可以直接用 pybreaker 等库,不必自己写。
一句话串起来:
max_retries+timeout应对临时抖动,fallback应对持续不可用,circuit breaker防止雪崩------四者配合才是完整的连接弹性方案。
六、总结
本篇,简单梳理了关于生产环境如何"管好模型"的几件事,并按"配置 → 限流 → 监控 → 容错"的链路串起来:
- Model Profiles(能力检测) :
model.profile是个 dict,暴露模型能力;注意 DeepSeek 等社区集成的 profile 可能是None,无数据 ≠ 不支持,门控时要分 True / False / None 三态。 - Invocation Config(调用配置) :
RunnableConfig在运行时配置 run_name / tags / metadata / callbacks;get_openai_callback统计 token 与成本;configurable_fields运行时切换 model / 参数。 - Rate Limiting(限流) :
InMemoryRateLimiter令牌桶,requests_per_second控平均速率、max_bucket_size控瞬时突发,可被多个模型共享。 - Token 监控(成本) :
usage_metadata看单次用量(思考模式会多出 reasoning token),需要自己用 token 乘单价计算成本;流式输出下 usage 只有在最后一个 chunk 中才有。 - 连接弹性(容错) :
max_retries+timeout应对临时抖动,fallback应对持续不可用,circuit breaker防雪崩;只有临时性错误(网络/429/5xx)才重试,4xx 客户端错误不重试。
此外,还有几个值得记住的实战点:
- profile 数据依赖集成:能用它确认 OpenAI 这类填充完整的模型的能力;但 DeepSeek profile 缺失时只能说明"不知道",不能说明"不行"。
get_openai_callback的边界:DeepSeek 走 OpenAI 兼容客户端,token 能统计;但成本按内置 OpenAI 价目表算,非 OpenAI 模型算出来是 0,需要自己拿 token × 单价算。- 思考模式是要花钱的 :
output_token_details.reasoning把思考消耗的 token 摊在明面上,调用越多、想得越深,成本越高。
至此,LangChain 1.x 围绕 Models 模块相关的内容就差不多了,共七篇:从认识 LangChain 1.x、快速上手,到模型初始化、调用方式、结构化输出、工具调用,再到本篇生产环境的模型管理,从"会用模型"到"管好模型";
接下来,开始介绍 LangChain 1.x 中 Agent 相关的内容,这部分也是新版 LangChain 的重要升级;
还没展开的动态模型选择 (
@wrap_model_call)和 token 监控的中间件实现,会放到 Agent 篇和 Agent 中间件一起讲。