LangChain 模型配置文件(Model Profiles)
约束:LangChain 模型配置文件(Model Profiles)的代码规范
技术边界
model.profile是模型实例上的字典属性,暴露模型的能力和限制数据- 要求
langchain>=1.1,更低版本无此属性 - profile 数据主要来源为 models.dev 开 源项目,LangChain 集成包合并额外字段后随包发布
- profile 常用字段包括:
max_input_tokens、tool_calling、structured_output、image_inputs、reasoning_output等 - profile 是普通
dict,可直接读取、合并、覆盖 init_chat_model支持通过profile参数传入自定义 profile,覆盖默认数据- 通过
model.model_copy(update={"profile": new_profile})生成新实例,不修改共享的原实例 - 正式数据修复路径:models.dev PR →
profile_augmentations.tomlPR →langchain-model-profilesCLI 刷新 - beta 功能:profile 格式可能变更,不可做硬逻辑依赖
生成要求
1. 读取 Profile(最基本用法)
- 通过
model.profile直接访问,返回dict - 读取前不需要任何额外配置
profile = model.profile
max_tokens = profile.get("max_input_tokens", None)
supports_tools = profile.get("tool_calling", False)
2. 基于 Profile 做动态决策
- 使用
.get()方法读取字段,提供默认值 - 不可假设字 段一定存在
- 典型用法:
- 根据
max_input_tokens触发 summarization middleware - 根据
tool_calling/structured_output选择 Agent 的 response_format 策略 - 根据
image_inputs决定是否启用多模态输入
- 根据
3. 覆盖或补充 Profile(快速修复)
- 通过
init_chat_model(..., profile=custom_profile)在初始化时传入 - 更新已有实例时使用
model.model_copy(update={"profile": new_profile}),不可直接修改共享实例的model.profile - 合并字段时使用字典合并操作符:
new_profile = model.profile | {"key": "value"}
4. Profile 数据修复的正确路径(上游修复)
- 仅当 profile 数据缺失/陈旧/错误时走此路径
- 不走此路径不影响正常使用
反例
- ❌
if model.profile["structured_output"]:— 直接键访问,字段可能不存在导致 KeyError- ✅ FIX:
if model.profile.get("structured_output", False):
- ✅ FIX:
- ❌
model.profile["new_field"] = value— 直接修改共享实例的 profile- ✅ FIX:
new_profile = model.profile | {"new_field": value}; model.model_copy(update={"profile": new_profile})
- ✅ FIX:
- ❌ 硬编码
if model.model_name == "gpt-5.4": max_tokens = 400000做能力判断- ✅ FIX:
max_tokens = model.profile.get("max_input_tokens")
- ✅ FIX:
- ❌ 依赖 profile 字段做关键业务逻辑的硬分支(如字段不存在即报错)
- ✅ FIX:始终提供默认值,做降级处理
- ❌ 在
langchain<1.1版本上访问model.profile- ✅ FIX:检查 langchain 版本或使用 try/except 保护 多模态(Multimodal)
约束:LangChain 多模态(Multimodal)的代码规范
技术边界
- 多模态指模型可处理/返回文本以外的数据:图像、音频、视频
- 多模态输入通过消息的
content_blocks传入,非单独参数 - LangChain 聊天模型支持三种多模态数据格式:跨 provider 标准格式、OpenAI Chat Completions 格式、provider 原生格式
- 多模态输出同样通过
AIMessage.content_blocks返回,检查"type"字段区分内容类型 - 并非所有模型都支持多模态,需通 过
model.profile检查image_inputs/audio_inputs等字段
生成要求
1. 多模态输入(传入非文本数据)
- 必须通过 content blocks 格式传入,不可将 base64 字符串直接拼在
content文本中 - 跨 provider 标准格式使用
{"type": "image_url", "image_url": {"url": "..."}}等结构 - 如需支持多 provider,优先使用跨 provider 标准格式;仅针对特定 provider 时可用原生格式
2. 多模态输出(提取非文本数据)
- 从
response.content_blocks遍历,按block["type"]判断内容类型 - 类型判断必须处理未知类型(降级为忽略或日志记录)
for block in response.content_blocks:
if block["type"] == "text":
print(block["text"])
elif block["type"] == "image":
# block 包含 "base64" 和 "mime_type"
save_image(block["base64"], block["mime_type"])
elif block["type"] == "audio":
# block 包含 "base64" 和 "mime_type"
save_audio(block["base64"], block["mime_type"])
# 不认识的 type 做降级处理,不报错
3. 能力检查(必须执行)
- 调用多模态功能前,必须通过
model.profile检查对应能力 - 不支持时提供明确的降级提示,不可直接调用后捕获异常
profile = model.profile
if not profile.get("image_inputs", False):
# 降级处理:跳过图片、提示用户更换模型等
pass
反例
- ❌
model.invoke("描述这张图片: data:image/png;base64,...")— 将 base64 数据拼在文本中- ✅ FIX:使用 content blocks 格式传入图片数据
- ❌ 假设所有模型都支持多模态,不做能力检查
- ✅ FIX:调用前通过
model.profile.get("image_inputs")检查
- ✅ FIX:调用前通过
- ❌
block["base64"]— 直接访问字段,不检查block["type"]- ✅ FIX:先判断
block["type"] == "image"再提取对应字段
- ✅ FIX:先判断
- ❌ 对不认识的
block["type"]抛出异常- ✅ FIX:做降级处理,至少不阻塞后续块的处理
- ❌ 多模态输出时只读取
response.content,忽略content_blocks- ✅ FIX:遍历
response.content_blocks分别处理各类型块
- ✅ FIX:遍历
推理(Reasoning)
# 约束:LangChain 推理(Reasoning)的代码规范
## 技术边界
- 推理指模型在输出最终答案前进行的多步思考过程,不是最终回复本身
- 推理内容通过 `content_blocks` 中 `"type": "reasoning"` 的块暴露,与 `"type": "text"` 的块分离
- 部分模型支持通过参数控制推理力度:层级式(`"low"` / `"high"`)或 token 预算式(整数值)
- 部分模型支持完全关闭推理——关闭后 `content_blocks` 中不再出现 `"type": "reasoning"` 块
- 推理是 provider 专属功能,**非所有模型支持**,需通过 `model.profile` 检查 `reasoning_output` 字段
## 生成要求
### 1. 流式模式下提取推理内容
- 遍历 `chunk.content_blocks`,筛选 `block["type"] == "reasoning"` 的块
- 推理块中 `block.get("reasoning")` 为实际推理文本
- 推理块和文本块可能出现在同一个 chunk 中,必须分别处理
```python
for chunk in model.stream("prompt"):
for block in chunk.content_blocks:
if block["type"] == "reasoning" and (reasoning := block.get("reasoning")):
print(f"[Reasoning]: {reasoning}")
elif block["type"] == "text":
print(block["text"])
2. 非流式模式下提取完整推理
- 从
response.content_blocks中筛选推理块并拼接
response = model.invoke("prompt")
reasoning_steps = [
b.get("reasoning")
for b in response.content_blocks
if b["type"] == "reasoning"
]
full_reasoning = " ".join(reasoning_steps)
3. 推理力度控制(模型支持时)
- 通过模型初始化参数或
bind传入推理控制参数 - 层级式:
reasoning_effort="low"/reasoning_effort="high" - Token 预算式:具体参数名因 provider 而异,查阅对应集成文档
- 关闭推理:同样因 provider 而异,查阅对应集成文档
4. 能力检查
- 调用推理功能前通过
model.profile.get("reasoning_output")检查 - 不支持时做降级处理,不可假设推理块一 定存在
反例
- ❌ 从
chunk.text或response.content中手动正则匹配<thinking>...</thinking>标签来提取推理- ✅ FIX:从
content_blocks中筛选"type": "reasoning"块
- ✅ FIX:从
- ❌
chunk.text和content_blocks混用,用.text打印后又在.content_blocks中找推理- ✅ FIX:统一通过
content_blocks处理所有内容类型
- ✅ FIX:统一通过
- ❌ 假设所有模型都支持推理,不检查
reasoning_output- ✅ FIX:通过
model.profile.get("reasoning_output")检查
- ✅ FIX:通过
- ❌ 筛选推理块时直接用
block["reasoning"]键访问,不处理字段缺失- ✅ FIX:使用
block.get("reasoning")并提供默认值
- ✅ FIX:使用
- ❌ 推理参数随意设置,不看 provider 文档
- ✅ FIX:确认 provider 支持的推理参数名称和取值范围后再设置
本地模型(Local Models)
# 约束:LangChain 本地模型(Local Models)的代码规范
## 技术边界
- 本地模型运行在用户自有硬件上,不依赖云服务
- 典型场景:数据隐私要求、自定义模型、避免云服务成本
- [Ollama](https://ollama.com) 是最简单的本地模型运行方式,提供标准化的 HTTP API,需先安装并启动 Ollama 服务
- 使用 `init_chat_model("ollama:model_name")` 或 `ChatOllama` 连接本地模型
- 本地模型**不自动具备云模型的高级能力**(工具调用、结构化输出等需模型本身支持)
- 需安装 `langchain-ollama` 集成包
## 生成要求
### 1. 环境准备(代码外)
- 不在代码中安装 Ollama 或拉取模型,这在代码外提前完成
- 代码中不需要指定 Ollama 服务的 host/port,默认连接 `http://localhost:11434`
### 2. 模型初始化
```python
from langchain.chat_models import init_chat_model
model = init_chat_model("ollama:mistral")
model = init_chat_model("ollama:llama3.1")
3. 能力检查(必须)
- 本地模型的能力取决于拉取的模型本身,不可假设支持工具调用或结构化输出
- 调用前通过
model.profile检查对应能力 - 不支持时降级处理:简化 prompt、减少工具依赖或提示用户更换模型
反例
- ❌ 假设本地模型和云端模型能力相同,不做能力检查直接调
bind_tools- ✅ FIX:通过
model.profile.get("tool_calling")检查
- ✅ FIX:通过
- ❌
init_chat_model("mistral")— 不写 provider,被错误推断为云 provider- ✅ FIX:
init_chat_model("ollama:mistral")
- ✅ FIX:
- ❌ 代码中执行
subprocess.run("ollama pull ...")拉取模型- ✅ FIX:模型拉取在代码外提前完成,代码只负责调用
- ❌ 本地模型初始化不处理连接失败
- ✅ FIX:Ollama 未启动时
invoke会抛连接异常,需在调用处 try/except 处理 提示缓存(Prompt Caching)
- ✅ FIX:Ollama 未启动时
约束:LangChain 提示缓存(Prompt Caching)的代码规范
技术边界
- Prompt Caching 是指将 prompt 中重复出现的内容缓存,后续相同请求免除该部分 token 计费和推理耗时
- 分隐式和显式两种:隐式缓存 provider 自动处理,不需要代码干预;显式缓存需要在代码中手动标记缓存点
- 隐式缓存 provider:OpenAI、Gemini(默认启用,无感知)
- 显式缓存 provider:Anthropic(通过
cache_control字段)、ChatOpenAI(通过prompt_cache_key参数)、AWS Bedrock - 缓存通常在超过最小输入 token 阈值后才生效(各 provider 不同,一般 1024-4096 tokens)
- 缓存命中结果反映在
response.usage_metadata中的cache_read/cache_creation字段 - 不支持缓存的 provider 传入缓存参数会被忽略或报错
生成要求
1. 隐式缓存(OpenAI / Gemini)
- 代码中不写任何缓存相关参数,provider 自动处理
- 不可手动添加
cache_control或其他缓存标记
2. 显式缓存 — Anthropic(cache_control)
- 通过
SystemMessage的content块中设置"cache_control": {"type": "ephemeral"} - 缓存标记只对标记的内容块生效,不对整个 prompt 生效
- 同一个
SystemMessage中未被标记的块不缓存
from langchain.messages import SystemMessage
system_prompt = SystemMessage(
content=[
{"type": "text", "text": "简短指令,不缓存"},
{"type": "text", "text": "大段文本内容...", "cache_control": {"type": "ephemeral"}},
]
)
3. 显式缓存 — ChatOpenAI(prompt_cache_key)
- 通过
ChatOpenAI构造器或bind传入prompt_cache_key参数 prompt_cache_key为任意字符串,相同 key 的请求共享缓存- 不同 key 的请求不共享缓存
from langchain.chat_models import init_chat_model
model = init_chat_model("openai:gpt-5.4", prompt_cache_key="my-session-key")
4. 缓存验证
- 调用后检查
response.usage_metadata确认缓存是否命中 - 不依 赖缓存一定命中(首次调用无缓存、缓存过期、阈值未达到等)
反例
- ❌ 隐式缓存 provider(OpenAI)上手动设置
cache_control字段- ✅ FIX:隐式缓存 provider 不写任何缓存代码,自动处理
- ❌ 显式缓存 provider 上标记了整个 prompt 但不检查 token 量是否达到阈值
- ✅ FIX:缓存标记只放在长内容块上,短内容不标记(低于阈值的标记无效)
- ❌
response.usage_metadata中看不到缓存字段就判断缓存功能不工作- ✅ FIX:首次调用无缓存命中,
cache_read为 0 是正常现象
- ✅ FIX:首次调用无缓存命中,
- ❌ 把
cache_control放在非 Anthropic provider 的请求中- ✅ FIX:
cache_control是 Anthropic 专属字段,其他 provider 忽略或报错
- ✅ FIX:
- ❌
cache_control放在HumanMessage而非SystemMessage中- ✅ FIX:Anthropic 显式缓存标记在 system 消息中效果最好,user 消息中可能不生效
服务端工具调用(Server-Side Tool Use)
约束:LangChain 服务端工具调用(Server-Side Tool Use)的代码规范
技术边界
- 服务端工具调用:工具在 provider 服务器端执行,不在用户代码中执行
- 一次
invoke完成“调用工具→执行→生成答案”全流程,无需客户端手动执行工具和传递 ToolMessage - 工具通过 model 初始化参数或
bind_tools绑定,工具格式为 dict(如{"type": "web_search"}),非@tool装饰的函数 - 返回的
AIMessage.content_blocks中包含完整的服务端工具调用记录:server_tool_call(调用请求)→server_tool_result(执行结果)→text(最终答案) - 不是所有 provider 支持此功能,需查阅 provider 集成文档确认
- text 块可能包含
annotations字段(引用标注),指向搜索来源的 URL、标题等
生成要求
1. 服务端工具绑定
- 服务端工具使用 dict 格式定义,传入
model.bind_tools([tool_dict]) - 不可对服务端工具使用
@tool装饰器(那是客户端工具的定义方式)
from langchain.chat_models import init_chat_model
model = init_chat_model("gpt-5.4-mini")
tool = {"type": "web_search"}
model_with_tools = model.bind_tools([tool])
2. 结果提取
- 必须从
response.content_blocks遍历提取,不可从.tool_calls提取(这是客户端工具调用的字段) content_blocks中出现的类型:server_tool_call、server_tool_result、textserver_tool_call结构:{"type": "server_tool_call", "name": "...", "args": {...}, "id": "..."}server_tool_result结构:{"type": "server_tool_result", "tool_call_id": "...", "status": "success"}- text 块可能含
annotations引用数组
response = model_with_tools.invoke("今天有什么正面新闻?")
for block in response.content_blocks:
if block["type"] == "server_tool_call":
print(f"[调用工具]: {block['name']}")
elif block["type"] == "server_tool_result":
print(f"[工具完成]: {block['status']}")
elif block["type"] == "text":
print(f"[回答]: {block['text']}")
3. 消息管理
- 不要把服务端工具调用的 AIMessage 当作需要回复 ToolMessage 的消息
- 不要把
content_blocks中的server_tool_call当作客户端tool_calls去执行工具 - 服务端工具调用在单轮 对话内完成,消息列表追加 AIMessage 后可直接继续多轮对话
反例
- ❌ 用
@tool装饰器定义服务端工具函数- ✅ FIX:服务端工具用 dict 格式,如
{"type": "web_search"}
- ✅ FIX:服务端工具用 dict 格式,如
- ❌ 从
response.tool_calls提取服务端工具调用信息- ✅ FIX:从
response.content_blocks中筛选"type": "server_tool_call"的块
- ✅ FIX:从
- ❌ 看到
content_blocks中有 server_tool_call,就去执行工具并返回 ToolMessage- ✅ FIX:服务端已完成工具执行,不需要客户端再执行
- ❌ 把
server_tool_callblock 当成tool_calls来读取tool_call["name"]的字段结构- ✅ FIX:分别以
block["type"] == "server_tool_call"和block["type"] == "server_tool_result"独立处理
- ✅ FIX:分别以
- ❌ 忽略 text 块中的
annotations字段(如果存在)- ✅ FIX:当 block 的 text 有检索来源引用需求时提取
annotations信息 速率限制(Rate Limiting)
- ✅ FIX:当 block 的 text 有检索来源引用需求时提取
约束:LangChain 速率限制(Rate Limiting)的代码规范
技术边界
- 速率限制指 provider 对单位时间内调用次数的上限,超限 会返回速率限制错误(通常为 HTTP 429)
- LangChain 通过
rate_limiter参数在客户端控制请求速率,避免触发 provider 限制 - 内置
InMemoryRateLimiter是线程安全的,同进程多线程可共享 InMemoryRateLimiter三个核心参数:requests_per_second:每秒允许的请求数(如0.1= 每 10 秒 1 次)check_every_n_seconds:检查间隔(如0.1= 每 100ms 检查一次是否可发请求)max_bucket_size:最大突发容量(允许的瞬时并发请求数上限)
- 速率限制器仅限制请求频率,不限制请求大小(如 token 数量)
- 速率限制器通过
init_chat_model的rate_limiter参数传入,也可在模型类构造器中传入
生成要求
1. 导入与初始化
- 必须从
langchain_core.rate_limiters导入,不可从其他路径导入
from langchain_core.rate_limiters import InMemoryRateLimiter
rate_limiter = InMemoryRateLimiter(
requests_per_second=0.1,
check_every_n_seconds=0.1,
max_bucket_size=10,
)
2. 绑定到模型
- 通过
init_chat_model的rate_limiter参数传入 - 同一 rate_limiter 实例可被多个模型共享(线程安全)
model = init_chat_model(
model="gpt-5.4",
model_provider="openai",
rate_limiter=rate_limiter,
)
3. 参数选择指导
requests_per_second:根据 provider 的速率限制文档设置,通常设为略低于官方上限check_every_n_seconds:保持默认0.1(100ms),无需修改max_bucket_size:根据应用并发量设置,高并发场景适当调大以容纳突发流量
反例
- ❌ 不使用速率限制器,连续大量
invoke调用,依赖 provider 的 429 错误来被动限速- ✅ FIX:在初始化模型时绑定
InMemoryRateLimiter
- ✅ FIX:在初始化模型时绑定
- ❌
requests_per_second设为 provider 官方上限的精确值- ✅ FIX:设为略低于上限(如官方上限 1 req/s,设为 0.9),留缓冲空间
- ❌
from langchain.rate_limiters import InMemoryRateLimiter— 导入路径错误- ✅ FIX:
from langchain_core.rate_limiters import InMemoryRateLimiter
- ✅ FIX:
- ❌ 每个模型实例单独创建一个 rate_limiter,同一线程内重复限速
- ✅ FIX:同进程内共享一个
InMemoryRateLimiter实例
- ✅ FIX:同进程内共享一个
- ❌ 认为速率限制器可以限制 token 消耗
- ✅ FIX:速率限制器只限制请求频率,token 用量需通过
max_tokens等其他参数控制
- ✅ FIX:速率限制器只限制请求频率,token 用量需通过
LangChain 基础 URL 和代理设置
# 约束:LangChain 基础 URL 和代理设置的代码规范
## 技术边界
- `base_url` 用于指定 OpenAI 兼容 API 的服务地址,适用于第三方 provider(Together AI、vLLM 等)或本地部署的模型服务
- `base_url` 仅在 `model_provider="openai"` 或直接使用 `ChatOpenAI` 时支持
- `model_provider="openai"` 遵循官方 OpenAI API 规范,路由器/代理的 provider 特有字段可能丢失
- OpenRouter 和 LiteLLM 有专属集成(`langchain-openrouter`、`langchain-litellm`),不应用 `base_url` 方式连接
- HTTP 代理通过 provider 专属参数配置(如 `ChatOpenAI` 的 `openai_proxy` 参数),参数名因 provider 而异
- 代理支持并非所有集成都有,需查阅对应 provider 参考文档
- `api_key` 与 `base_url` 配合使用时必须显式传入,不可依赖环境变量默认值(因为 base_url 指向非官方服务)
## 生成要求
### 1. 自定义 Base URL(OpenAI 兼容 API)
- 使用 `init_chat_model` 时指定 `model_provider="openai"`
- 必须同时传入 `base_url` 和 `api_key`
- `model` 参数为第三方服务支持的模型名
```python
model = init_chat_model(
model="MODEL_NAME",
model_provider="openai",
base_url="https://your-custom-endpoint.com/v1",
api_key="YOUR_API_KEY",
)
2. 使用直接模型类时
- 参数名因 provider 而异,需查阅对应参考文档
ChatOpenAI支持base_url参数
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="gpt-5.4",
base_url="https://your-custom-endpoint.com/v1",
api_key="YOUR_API_KEY",
)
3. OpenRouter / LiteLLM
- 不使用
base_url参数 - 使用专属集成包和专属模型类
# OpenRouter
from langchain_openrouter import ChatOpenRouter
model = ChatOpenRouter(model="auto")
# LiteLLM
from langchain_litellm import ChatLiteLLM
model = ChatLiteLLM(model="gpt-5.4")
4. HTTP 代理配置
- 仅在部署环境需要通过代理访问外网时使用
- 参数名因 provider 而异,
ChatOpenAI使用openai_proxy
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="gpt-5.4",
openai_proxy="http://proxy.example.com:8080",
)
5. api_key 处理
- 使用
base_url时必须显式传入api_key,不依赖环境变量 - 不可将第三方 API Key 写入系统的
OPENAI_API_KEY环境变量(会导致与官方 OpenAI 服务冲突)
反例
- ❌ 用
base_url连接 OpenRouter:init_chat_model("openai:gpt-5.4", base_url="https://openrouter.ai/...")- ✅ FIX:使用
langchain-openrouter的ChatOpenRouter
- ✅ FIX:使用
- ❌
init_chat_model(model="...", model_provider="openai", base_url="...")— 缺api_key- ✅ FIX:同时显式传入
api_key
- ✅ FIX:同时显式传入
- ❌ 将第三方 API Key 设为环境变量
OPENAI_API_KEY,然后连接第三方 base_url- ✅ FIX:第三方 API Key 显式传入
api_key参数,避免污染官方 API Key 环境变量
- ✅ FIX:第三方 API Key 显式传入
- ❌
ChatAnthropic(model="...", openai_proxy="...")— 使用了不存在的参数名- ✅ FIX:查阅对应 provider 参考文档确认正确的代理参数名
- ❌ 用
base_url指向官方 OpenAI API 来做代理转发- ✅ FIX:代理转发用 HTTP 代理配置(如
openai_proxy),非base_url
- ✅ FIX:代理转发用 HTTP 代理配置(如
LangChain 对数概率(Log Probabilities)
# 约束:LangChain 对数概率(Log Probabilities)的代码规范
## 技术边界
- Log Probabilities 指模型生成每个 token 时的对数概率,反映模型对该 token 的确定程度
- 通过 `model.bind(logprobs=True)` 启用,非所有 provider 支持(需查阅对应集成文档)
- 结果存储在 `response.response_metadata["logprobs"]` 中,不在 `.content` 或 `.content_blocks` 中
- 每个 token 项包含:`token`(生成的 token 字符串)、`logprob`(对数概率,接近 0 表示高确定性,越负越不确定)、`top_logprobs`(该位置其他候选 token 及概率列表)
- **流式模式下不返回完整 logprobs**,需在非流式 `invoke` 后获取
- 仅用于调试、评估和优化,不用于运行时业务逻辑判断
## 生成要求
### 1. 启用 logprobs
- 通过 `.bind(logprobs=True)` 在模型初始化后绑定,不可在 `init_chat_model` 的 kwargs 中直接传入
```python
from langchain.chat_models import init_chat_model
model = init_chat_model(
model="gpt-5.4",
model_provider="openai",
).bind(logprobs=True)
2. 提取 logprobs
- 必须从
response.response_metadata["logprobs"]提取,不可从.content或其他路径 response_metadata是 dict,logprobs不一定存在——取决于 provider 是否支持和是否启用
response = model.invoke("Why do parrots talk?")
logprobs = response.response_metadata.get("logprobs")
if logprobs:
for token_info in logprobs:
token = token_info.get("token")
logprob = token_info.get("logprob")
top_candidates = token_info.get("top_logprobs", [])
print(f"Token: {token}, Logprob: {logprob}")
3. 高不确定性检测(调试用途)
logprob值异常低(如 < -5.0)的 token 是潜在问题点- 可遍历 logprobs 找出低概率 token 做标记
- 低概率 token 连续出现可能表明模型开始胡编
4. 兼容性处理
- 读取前检查
response_metadata中是否包含logprobs字段 - 不支持 logprobs 的 provider 绑定后不会报错,但
response_metadata中可能无此字段
反例
- ❌
init_chat_model("gpt-5.4", logprobs=True)— 直接将 logprobs 作为模型初始化参数- ✅ FIX:使用链式
.bind(logprobs=True)
- ✅ FIX:使用链式
- ❌
response.logprobs— 直接从 response 对象访问- ✅ FIX:
response.response_metadata["logprobs"]
- ✅ FIX:
- ❌ 流式模式下调用并期望获取完整 logprobs
- ✅ FIX:logprobs 在非流式
invoke后获取,流式模式下使用astream_events或累积后查看
- ✅ FIX:logprobs 在非流式
- ❌ 直接
response.response_metadata["logprobs"]键访问不检查字段存在性- ✅ FIX:使用
.get("logprobs")并处理 None
- ✅ FIX:使用
- ❌ 依赖 logprobs 做生产环境核心业务逻辑判断
- ✅ FIX:logprobs 仅用于离线调试和评估,不用于运行时决策 LangChain Token 用量追踪(Token Usage)
约束:LangChain Token 用量追踪(Token Usage)的代码规范
技术边界
- Token 用量信息由 provider 在调用响应中返回,存储在
AIMessage.response_metadata或usage_metadata中 - 不同 provider 返回的字段不同,但常见字段包括:
input_tokens、output_tokens、total_tokens - OpenAI 和 Azure OpenAI 在流式模式下需显式 opt-in 才返回 token 用量数据(查阅对应集成文档)
- LangChain 提供两种聚合多模型 token 用量的方式:回调处理器(
UsageMetadataCallbackHandler)和上下文管理器(get_usage_metadata_callback) - 回调处理器和上下文管理器的结果结构相同:
{model_name: {input_tokens, output_tokens, total_tokens, ...}} - 回调处理器是实例化的对象,可跨多次调用复用;上下文管理器通过
with语句限定作用域
生成要求
1. 单次调用 Token 用量提取
- 从
AIMessage对象上直接提取,不依赖回调系统 - 检查字段存在性:不同 provider 返回的字段名可能不同
response = model.invoke("Hello")
# 方式一:从 response_metadata 提取
usage = response.response_metadata.get("token_usage", {})
# 方式二:从 usage_metadata 提取
usage = response.usage_metadata
2. 多模型 Token 用量聚合 — 回调处理器方式
- 创建一个
UsageMetadataCallbackHandler实例 - 每次
invoke时通过config={"callbacks": [callback]}传入同一实例 - 调用结束后通过
callback.usage_metadata获取聚合结果
from langchain.chat_models import init_chat_model
from langchain_core.callbacks import UsageMetadataCallbackHandler
model_1 = init_chat_model("gpt-5.4-mini")
model_2 = init_chat_model("claude-haiku-4-5-20251001")
callback = UsageMetadataCallbackHandler()
model_1.invoke("Hello", config={"callbacks": [callback]})
model_2.invoke("Hello", config={"callbacks": [callback]})
print(callback.usage_metadata)
# {'gpt-5.4-mini': {...}, 'claude-haiku-4-5-20251001': {...}}
3. 多模型 Token 用量聚合 — 上下文管理器方式
- 使用
with语句限定追踪作用域 cb.usage_metadata在with块结束后访问
from langchain_core.callbacks import get_usage_metadata_callback
with get_usage_metadata_callback() as cb:
model_1.invoke("Hello")
model_2.invoke("Hello")
print(cb.usage_metadata)
4. 流式模式下的 Token 用量
- OpenAI / Azure OpenAI 流式调用需要额外配置才返回 token 数据
- 查阅对应集成文档确认 opt-in 方式
- 其他 provider 可能流式模式下不返回 token 数据
反例
- ❌ 期望所有 provider 都返回
total_tokens字段,不检查字段存在性- ✅ FIX:不同 provider 字段不同,通过
.get("total_tokens", 0)提供默认值
- ✅ FIX:不同 provider 字段不同,通过
- ❌ 每次
invoke创建新的UsageMetadataCallbackHandler实例- ✅ FIX:同一实例跨多次调用复用,结果在
usage_metadata中自动聚合
- ✅ FIX:同一实例跨多次调用复用,结果在
- ❌
config={"callback": callback}— 键名callback而非callbacks- ✅ FIX:
config={"callbacks": [callback]},值为列表且键名复数
- ✅ FIX:
- ❌ 从
response.content中正则提取 token 数字- ✅ FIX:从
response.response_metadata或response.usage_metadata中结构化提取
- ✅ FIX:从
- ❌ 上下文管理器方式:在
with块外访问cb.usage_metadata- ✅ FIX:必须在
with块内访问,或赋值给外部变量
- ✅ FIX:必须在
LangChain 调用配置(Invocation Config)
# 约束:LangChain 调用配置(Invocation Config)的代码规范
## 技术边界
- `config` 参数是一个 `RunnableConfig` 字典,在 `invoke`/`stream`/`batch` 调用时传入
- `config` 控制运行时的执行行为、回调、元数据追踪和并发控制,不影响模型本身的参数
- `config` 中的 `tags` 和 `metadata` 会被所有子调用继承;`run_name` 不被继承
- `callbacks` 传入的处理器在单次调用中生效,不与模型实例绑定
- `max_concurrency` 仅在 `batch()` 和 `batch_as_completed()` 时生效
- `recursion_limit` 限制链/图的递归深度,防止无限循环
- 所有 config 字段都是可选的,不传入时使用默认值
## 生成要求
### 1. 基本调用配置
- `config` 必须作为 `invoke`/`stream`/`batch` 的第二个参数传入(关键字参数形式)
- `config` 是一个 dict,内部字段名使用下划线命名
```python
response = model.invoke(
"Tell me a joke",
config={
"run_name": "joke_generation",
"tags": ["humor", "demo"],
"metadata": {"user_id": "123"},
},
)
2. 各字段使用约束
| 字段 | 使用要求 |
|---|---|
run_name | 字符串,用于标识本次调用。不可用于追踪跨调用的关联关系(不被继承) |
tags | 字符串列表,用于分类和过滤。如 ["production", "chatbot"]。所有子调用自动继承 |
metadata | 字典,自定义键值对。如 {"user_id": "123", "session_id": "abc"}。所有子调用自动继承 |
callbacks | 回调处理器列表,如 [UsageMetadataCallbackHandler()]。可复用同一实例跨多次调用 |
max_concurrency | 整数,仅 batch()/batch_as_completed() 时使用。如 config={"max_concurrency": 5} |
recursion_limit | 整数,限制链的最大递归深度。默认值足够大多数场景 |
3. 与 LangSmith 追踪配合
run_name在 LangSmith trace 中显示为本次 run 的名称tags在 LangSmith 中用于过滤和组织 tracemetadata在 LangSmith 中作为自定义字段附加到 trace 上
4. 多模型共享 config
callbacks列表中传入UsageMetadataCallbackHandler可跨模型聚合 token 用量- 同一 config 字典可复用于多次调用
反例
- ❌
model.invoke("prompt", run_name="test")— 将 config 字段作为独立关键字参数- ✅ FIX:
model.invoke("prompt", config={"run_name": "test"})
- ✅ FIX:
- ❌
config={"tags": "production"}— tags 是字符串而非字符串列表- ✅ FIX:
config={"tags": ["production"]}
- ✅ FIX:
- ❌
config={"run_name": "parent"}后期望子调用 run 名称也是"parent"- ✅ FIX:
run_name不被继承,子调用需单独设置
- ✅ FIX:
- ❌ 在
invoke中使用config={"max_concurrency": 5}— 对单次调用无意义- ✅ FIX:
max_concurrency仅在batch()/batch_as_completed()中生效
- ✅ FIX:
- ❌
config={"callbacks": callback}— callbacks 是单个对象而非列表- ✅ FIX:
config={"callbacks": [callback]},即使只有一个也要用列表包裹
- ✅ FIX:
LangChain 可配置模型(Configurable Models)
约束:LangChain 可配置模型(Configurable Models)的代码规范
技术边界
- 可配置模型在初始化时不指定具体模型名,在每次
invoke/stream时通过config动态指定 init_chat_model(temperature=0)不传model参数时,model和model_provider默认可配置configurable_fields参数可显式声明哪些字段在运行时可选配,未声明的字段使用初始化时的值config_prefix用于在多模型链中区分不同模型的可配置参数,避免命名冲突.bind_tools、.with_structured_output等声明式操作对可配置模型同样生效,最终调用的模型会执行绑定的工具/结构- 每次
invoke使用的模型可以不同,token 用量和 cost 按实际使用的模型计算
生成要求
1. 基础可配置模型
config中通过configurable子字典传入模型名configurable字段的键名:无config_prefix时使用参数名本身
model = init_chat_model(temperature=0)
model.invoke("prompt", config={"configurable": {"model": "gpt-5-nano"}})
model.invoke("prompt", config={"configurable": {"model": "claude-sonnet-4-6"}})
2. 有默认值的可配置模型 + config_prefix
- 初始化时指定默认 model
configurable_fields声明可被覆盖的字段config_prefix为可配置字段添加前缀- 使用
config_prefix时,configurable中的键名格式为{prefix}_{field_name}
model = init_chat_model(
model="gpt-5.4-mini", # 默认模型
temperature=0, # 默认温度
configurable_fields=("model", "model_provider", "temperature", "max_tokens"),
config_prefix="first",
)
# 使用默认值
model.invoke("prompt")
# 运行时覆盖
model.invoke("prompt", config={
"configurable": {
"first_model": "claude-sonnet-4-6",
"first_temperature": 0.5,
"first_max_tokens": 100,
}
})
3. 声明式操作与可配置模型
- 声明式方法先于
invoke执行,后于config生效 bind_tools绑定的工具对所有模型生效,不同模型共用同一工具集合
model = init_chat_model(temperature=0)
model_with_tools = model.bind_tools([GetWeather, GetPopulation])
model_with_tools.invoke("prompt", config={"configurable": {"model": "gpt-5.4-mini"}})
model_with_tools.invoke("prompt", config={"configurable": {"model": "claude-sonnet-4-6"}})
反例
- ❌ 传给
configurabledict 的键名不带config_prefix,但初始化时设置了 prefix- ✅ FIX:键名格式必须为
{prefix}_{field_name},如first_model
- ✅ FIX:键名格式必须为
- ❌
init_chat_model(temperature=0, config_prefix="first")— 设置了 prefix 但没有configurable_fields,不知道哪些字段可配- ✅ FIX :同时设置
configurable_fields=("model", "temperature")明确可配字段
- ✅ FIX :同时设置
- ❌ 使用
config_prefix时覆盖未在configurable_fields中声明的字段- ✅ FIX:只可覆盖
configurable_fields中列出的字段,否则参数被忽略
- ✅ FIX:只可覆盖
- ❌ 在
invoke的config中传入了一级键{"model": "..."}而非{"configurable": {"model": "..."}}- ✅ FIX:可配置参数必须嵌套在
configurable子字典中
- ✅ FIX:可配置参数必须嵌套在
- ❌ 两个可配置模型使用相同的
config_prefix- ✅ FIX:不同模型设置不同的
config_prefix,避免参数覆盖
- ✅ FIX:不同模型设置不同的