LANGCHAIN_TRACING_V2 与 LANGSMITH_TRACING:新旧变量并存时谁生效
旧教程让你写 export LANGCHAIN_TRACING_V2=true,官方新文档写的是 LANGSMITH_TRACING=true。两套名字在代码里都还有效,所以最常见的问题不是“写错了”,而是 .env、CI 变量、Dockerfile 或 IDE 运行配置里同时留着新旧两个,而你以为后写的、或者名字更新的那个会生效。
先看结论:开关上旧名字优先。已经设了 LANGSMITH_TRACING=false,只要环境里还留着 LANGCHAIN_TRACING_V2=true,追踪就仍然开着,每次调用都会挂上 LangChainTracer,在密钥和网络可用时,输入输出会照常发往 LangSmith。项目名、密钥和服务地址正好相反,新名字优先。这个结果在 langchain==0.3.27 和 langchain==1.4.7 下完全一致,升级到 v1 既不会让旧变量失效,也不会改变这个优先级。
适用范围
| 环境 | 已安装的关键包 | 用途 |
|---|---|---|
| 0.3.27(旧环境) | langchain==0.3.27、langchain-core==0.3.86、langsmith==0.14.12 | 旧教程当年的环境 |
| 1.4.7 | langchain==1.4.7、langchain-core==1.7.0、langsmith==0.14.12 | 升级后的环境 |
-
Python 3.12.12;
-
官方来源:Trace with LangChain (opens in a new tab) 和 Conditional tracing (opens in a new tab);
-
实验代码:站点仓库
labs/langsmith-tracing-env/;脚本不联网,不启动任何运行,也不需要 API Key。 -
覆盖:开关、项目名、密钥和服务地址这四类变量在新旧名字并存时取哪一个,以及代码里
tracing_context和ls.configure两种覆盖方式; -
未覆盖:真实的 LangSmith 服务(上报是否成功、密钥权限、采样);其他
langsmith版本;空字符串或只含空白的取值(只读过源码,没有放进实验);部署平台自行注入的变量。
新旧变量名对照
| 用途 | 旧教程里的名字 | 官方新文档的名字 | 两者并存时 |
|---|---|---|---|
| 打开或关闭追踪 | LANGCHAIN_TRACING_V2 | LANGSMITH_TRACING | 旧名字优先 |
| 项目名 | LANGCHAIN_PROJECT | LANGSMITH_PROJECT | 新名字优先 |
| 密钥 | LANGCHAIN_API_KEY | LANGSMITH_API_KEY | 新名字优先 |
| 服务地址 | LANGCHAIN_ENDPOINT | LANGSMITH_ENDPOINT | 新名字优先 |
只设置其中一个名字时,新旧都生效,两个环境下一致。所以旧教程里的变量在 v1 里不会报错,也不会失效,问题只出在并存。官方的两篇文档都只使用 LANGSMITH_ 开头的名字,没有讨论旧名字并存时怎么取舍,这部分是对照实验的结果。
开关:旧名字盖过新名字
下面是 env_precedence.py 在 1.4.7 环境里的输出,每一行是一组环境变量。enabled 是 langsmith 判断追踪是否开启的结果,tracer 表示 LangChain 的回调管理器有没有挂上 LangChainTracer,也就是这次调用会不会被记录:
langchain-core 1.7.0 | langsmith 0.14.12
[1] tracing switch
case enabled tracer
(nothing set) False off
LANGSMITH_TRACING=true True on
LANGCHAIN_TRACING_V2=true True on
LANGSMITH_TRACING=false + LANGCHAIN_TRACING_V2=true True on
LANGSMITH_TRACING=true + LANGCHAIN_TRACING_V2=false False off
LANGSMITH_TRACING=True False off
LANGSMITH_TRACING=1 False off
LANGCHAIN_TRACING_V2=true + tracing_context(enabled=False) False off
LANGCHAIN_TRACING_V2=true + ls.configure(enabled=False) False off
[2] project, key and endpoint
case value
LANGCHAIN_PROJECT=old-proj old-proj
LANGSMITH_PROJECT=new-proj + LANGCHAIN_PROJECT=old-proj new-proj
LANGCHAIN_API_KEY=key-old key-old
LANGSMITH_API_KEY=key-new + LANGCHAIN_API_KEY=key-old key-new
LANGCHAIN_ENDPOINT=http://localhost:9/old http://localhost:9/old
LANGSMITH_ENDPOINT=http://localhost:9/new + LANGCHAIN_ENDPOINT=http://localhost:9/old http://localhost:9/new0.3.27 环境的输出除了第一行的版本号,与上面逐行相同。重点看第四行和第五行:
LANGSMITH_TRACING=false加上LANGCHAIN_TRACING_V2=true,追踪是开着的。 这是线上最危险的一种:你按隐私要求把开关设成了false,旧变量却把它盖掉了,数据会继续外发(密钥和网络可用时),而且没有任何报错;- 反过来
LANGSMITH_TRACING=true加上LANGCHAIN_TRACING_V2=false,追踪是关着的。 你按新文档打开了追踪,却一条 trace 也看不到,原因同样是旧变量。
这个优先级来自 langsmith 的 tracing_is_enabled 函数。它在最后一步是这样读环境变量的:
# langsmith/utils.py (0.14.12), last step of tracing_is_enabled()
var_result = get_env_var("TRACING_V2", default=get_env_var("TRACING", default=""))
return var_result == "true"get_env_var 对每个名字依次查 LANGSMITH_ 和 LANGCHAIN_ 两个前缀。于是先查的是带 _V2 的名字:LANGSMITH_TRACING_V2,再是 LANGCHAIN_TRACING_V2。只有这两个都没设置,才去查不带 _V2 的 LANGSMITH_TRACING 和 LANGCHAIN_TRACING。带 _V2 的名字排在前面,与前缀新旧无关,所以旧教程里的 LANGCHAIN_TRACING_V2 总是压过新的 LANGSMITH_TRACING。
取值只认小写 true
表里的第六行和第七行:LANGSMITH_TRACING=True 和 LANGSMITH_TRACING=1 都不会打开追踪,enabled 是 False,也没有异常。判断条件是取值恰好等于小写的 true。在 .env 文件、CI 配置或编排文件里手写这个值时,不要写成 True、TRUE、1 或 yes。
项目、密钥、端点:新名字优先
另外三类变量各自只有一个名字,get_env_var 先查 LANGSMITH_ 前缀,没有再查 LANGCHAIN_。同一个输出的第二部分:
- 只设置旧名字时,旧名字生效,所以旧教程的
LANGCHAIN_PROJECT、LANGCHAIN_API_KEY和LANGCHAIN_ENDPOINT照常工作; - 新旧都设置时,取新名字。表里脚本用的
key-old、key-new是占位字符串,不是真实密钥。
开关取旧名字,其余取新名字,没有一条规则能同时记住两种情况。清理环境时,最稳妥的办法是把四类旧变量一起删掉,只保留 LANGSMITH_ 开头的名字。
在自己的环境里确认
下面的脚本回答两个问题:当前进程里追踪到底开没开,以及四个开关变量名各是什么值。它要在出问题的那个环境里运行,也就是同一个 shell、虚拟环境、容器或 IDE 运行配置:
"""Report whether tracing is on in this process and which variable names are set.
Run it in the same shell, virtual environment or container as the failing process. To reproduce a
leftover variable without touching your shell, pass NAME=VALUE pairs: they are applied before
langsmith is imported.
"""
import os
import sys
for pair in sys.argv[1:]:
name, _, value = pair.partition("=")
os.environ[name] = value
from langsmith.utils import tracing_is_enabled # noqa: E402 (must run after the variables are applied)
for name in ("LANGSMITH_TRACING", "LANGCHAIN_TRACING_V2", "LANGSMITH_TRACING_V2", "LANGCHAIN_TRACING"):
print(f"{name:<22}{os.environ.get(name, '(unset)')!r}")
print("tracing_is_enabled():", tracing_is_enabled())为了在不改动你的 shell 的前提下复现“新旧并存”,脚本接受 NAME=VALUE 参数,并在导入 langsmith 之前写入环境。下面是传入 LANGSMITH_TRACING=false LANGCHAIN_TRACING_V2=true 的输出:
LANGSMITH_TRACING 'false'
LANGCHAIN_TRACING_V2 'true'
LANGSMITH_TRACING_V2 '(unset)'
LANGCHAIN_TRACING '(unset)'
tracing_is_enabled(): True看到 LANGCHAIN_TRACING_V2 是 'true',而 LANGSMITH_TRACING 是 'false',最后一行仍然是 True,就是旧变量在起作用。找到来源的办法是在项目目录和部署配置里搜索 LANGCHAIN_:shell 配置文件、.env 文件、Dockerfile 的 ENV、编排文件、CI 的变量设置、IDE 的运行配置,以及代码里的 os.environ[...] = ...。
要可靠地关掉追踪
下表中的 ls 指 import langsmith as ls。
| 做法 | 在表里的依据 | 适合的场景 |
|---|---|---|
删除所有 LANGCHAIN_ 开头的追踪变量,只保留 LANGSMITH_TRACING | 第二、三行:只设一个名字时,新旧名字都能控制开关 | 长期做法,配置里只剩一套名字 |
代码里用 with ls.tracing_context(enabled=False): 包住调用 | 第八行:即使 LANGCHAIN_TRACING_V2=true 也关闭 | 某个租户、某类请求单独关闭 |
进程启动时调用 ls.configure(enabled=False) | 第九行:即使 LANGCHAIN_TRACING_V2=true 也关闭 | 无法立即清理环境变量时的兜底 |
官方的 Conditional tracing 页给出的优先级是:tracing_context(enabled=...) 最高,其次是 ls.configure(enabled=...),环境变量最低。表里第八、九行与这个顺序一致。代码层面的开关能压住环境变量,但它掩盖了环境里仍有旧变量的事实,换一种启动方式、换一个进程,旧变量还会生效,所以兜底之后仍然要清理。
失败、回滚与排障
| 现象 | 常见原因 | 处理 |
|---|---|---|
设了 LANGSMITH_TRACING=false,LangSmith 里仍有新的 trace | 环境里还留着 LANGCHAIN_TRACING_V2=true | 运行 check_tracing.py 确认,删除旧变量 |
设了 LANGSMITH_TRACING=true,却一条 trace 也没有 | LANGCHAIN_TRACING_V2=false 压住了它;或者取值写成了 True、1 | 运行 check_tracing.py,统一成小写 true |
| trace 出现在意料之外的项目里 | 新旧项目变量并存时取 LANGSMITH_PROJECT | 只保留 LANGSMITH_PROJECT |
| 两个变量都核对过了,仍然没有 trace | 实验没有覆盖服务端:密钥权限、网络、采样都可能是原因 | 按追踪上线前检查用本地接收端抓包,看请求有没有发出 |
删除旧变量前,先记下它们现在的取值(密钥不要写进工单或聊天记录)。删除之后如果追踪消失,说明原来的配置靠的是旧名字,补上 LANGSMITH_TRACING=true 即可。需要退回时,把旧变量放回去,行为就恢复成删除之前的样子。
复现脚本
env_precedence.py 是上面两个表的来源。每组变量都在一个全新的子进程里运行,因为 langsmith 会缓存部分环境变量的读取结果;子进程只询问库“会怎么做”,不启动任何运行,也不联网:
"""Which LangSmith environment variable wins when old (LANGCHAIN_*) and new (LANGSMITH_*) names are both set?
Every case runs in a fresh child process with a clean environment, because langsmith caches some
environment lookups. The child only asks the libraries what they would do: it never starts a run
and never touches the network.
"""
import json
import os
import subprocess
import sys
from importlib.metadata import version
CHILD = r'''
import json
import sys
import warnings
warnings.simplefilter("ignore")
import langsmith as ls
from langchain_core.callbacks.manager import CallbackManager
from langsmith import utils
def snapshot():
handlers = [type(h).__name__ for h in CallbackManager.configure().handlers]
return {
"tracing": utils.tracing_is_enabled(),
"tracer": "LangChainTracer" in handlers,
"project": utils.get_tracer_project(),
"api_key": utils.get_api_key(None),
"api_url": utils.get_api_url(None),
}
mode = sys.argv[1]
if mode == "context_off":
with ls.tracing_context(enabled=False):
result = snapshot()
elif mode == "configure_off":
ls.configure(enabled=False)
result = snapshot()
else:
result = snapshot()
print(json.dumps(result))
'''
# (label, environment variables, how the code overrides them)
SWITCH_CASES = [
("(nothing set)", {}, "env"),
("LANGSMITH_TRACING=true", {"LANGSMITH_TRACING": "true"}, "env"),
("LANGCHAIN_TRACING_V2=true", {"LANGCHAIN_TRACING_V2": "true"}, "env"),
("LANGSMITH_TRACING=false + LANGCHAIN_TRACING_V2=true", {"LANGSMITH_TRACING": "false", "LANGCHAIN_TRACING_V2": "true"}, "env"),
("LANGSMITH_TRACING=true + LANGCHAIN_TRACING_V2=false", {"LANGSMITH_TRACING": "true", "LANGCHAIN_TRACING_V2": "false"}, "env"),
("LANGSMITH_TRACING=True", {"LANGSMITH_TRACING": "True"}, "env"),
("LANGSMITH_TRACING=1", {"LANGSMITH_TRACING": "1"}, "env"),
("LANGCHAIN_TRACING_V2=true + tracing_context(enabled=False)", {"LANGCHAIN_TRACING_V2": "true"}, "context_off"),
("LANGCHAIN_TRACING_V2=true + ls.configure(enabled=False)", {"LANGCHAIN_TRACING_V2": "true"}, "configure_off"),
]
# (label, environment variables, snapshot field that the case is about)
SETTING_CASES = [
("LANGCHAIN_PROJECT=old-proj", {"LANGCHAIN_PROJECT": "old-proj"}, "project"),
("LANGSMITH_PROJECT=new-proj + LANGCHAIN_PROJECT=old-proj", {"LANGSMITH_PROJECT": "new-proj", "LANGCHAIN_PROJECT": "old-proj"}, "project"),
("LANGCHAIN_API_KEY=key-old", {"LANGCHAIN_API_KEY": "key-old"}, "api_key"),
("LANGSMITH_API_KEY=key-new + LANGCHAIN_API_KEY=key-old", {"LANGSMITH_API_KEY": "key-new", "LANGCHAIN_API_KEY": "key-old"}, "api_key"),
("LANGCHAIN_ENDPOINT=http://localhost:9/old", {"LANGCHAIN_ENDPOINT": "http://localhost:9/old"}, "api_url"),
(
"LANGSMITH_ENDPOINT=http://localhost:9/new + LANGCHAIN_ENDPOINT=http://localhost:9/old",
{"LANGSMITH_ENDPOINT": "http://localhost:9/new", "LANGCHAIN_ENDPOINT": "http://localhost:9/old"},
"api_url",
),
]
def run_case(extra: dict, mode: str) -> dict:
"""Run one case in a child process whose LangSmith variables are exactly `extra`."""
env = {k: v for k, v in os.environ.items() if not k.startswith(("LANGSMITH_", "LANGCHAIN_"))}
env.update(extra)
proc = subprocess.run([sys.executable, "-c", CHILD, mode], env=env, capture_output=True, text=True, timeout=60)
if proc.returncode != 0:
raise SystemExit(f"child failed for {extra}: {proc.stderr[-400:]}")
return json.loads(proc.stdout.strip().splitlines()[-1])
def main() -> None:
print(f"langchain-core {version('langchain-core')} | langsmith {version('langsmith')}")
print()
print("[1] tracing switch")
print(f"{'case':<62}{'enabled':<9}tracer")
for label, extra, mode in SWITCH_CASES:
got = run_case(extra, mode)
print(f"{label:<62}{got['tracing']!s:<9}{'on' if got['tracer'] else 'off'}")
print()
print("[2] project, key and endpoint")
print(f"{'case':<88}value")
for label, extra, field in SETTING_CASES:
print(f"{label:<88}{run_case(extra, 'env')[field]}")
if __name__ == "__main__":
main()依据与复现
- Trace with LangChain (opens in a new tab):官方示例使用
LANGSMITH_TRACING和LANGSMITH_PROJECT; - Conditional tracing (opens in a new tab):
tracing_context、ls.configure与环境变量的优先级; - 复现:先执行
labs/setup-envs.sh legacy v1重建环境,再执行python3 labs/run.py --lab langsmith-tracing-env; - 输出范围:文中输出全部来自站点仓库
labs/langsmith-tracing-env/在“适用范围”所列版本下的实际运行;优先级的原因来自对langsmith/utils.py中tracing_is_enabled与get_env_var的阅读; - 局限:结果对应
langsmith==0.14.12,其他版本的取舍顺序可能不同,升级后可以重新运行脚本确认; - 官方来源核对于 2026-10-27。
继续阅读
- 追踪打开之后,输入、输出、报错堆栈和元数据会外发什么,怎么脱敏:LangSmith 追踪上线前检查;
- 线上报错按原文对号入座:LangChain 生产故障速查;
- 旧教程里的 import、调用方式和参数名在 v1 里的对应写法:v1 迁移与旧写法对照。