mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-22 01:35:28 -04:00
fix(code): restore caller's LangSmith API key in shell subprocess env (#4458)
Related: #4436 Fixed a credential leak where shell subprocesses spawned by Deep Agents Code inherited the agent's internal LangSmith API key override instead of the caller's original key. Subprocess environments now restore the caller's `LANGSMITH_API_KEY` (and LangChain alias) to the value they had before bootstrap, or remove the key entirely if the caller never set one. --- ## Why When a user launches Deep Agents Code with `DEEPAGENTS_CODE_LANGSMITH_API_KEY` set, bootstrap bridges that prefixed value onto the canonical `LANGSMITH_API_KEY` so the LangSmith SDK picks it up for the agent's own tracing. This overwrites the caller's original key in-process — fine for the agent itself, but the original value was never saved. As a result, shell commands run by the agent (via `execute`) inherited the override key, and the caller's own credential was irrecoverable in that subprocess environment. This is the same data-loss footgun that `restore_user_tracing_env` already guards for tracing *flags*. The fix applies the same save/restore pattern to tracing API *keys*: snapshot the caller's keys during `_ensure_bootstrap`, then restore them (or drop the propagated value if the caller had none) when preparing the shell subprocess environment in `create_cli_agent`.
This commit is contained in:
@@ -74,6 +74,7 @@ from deepagents_code.config import (
|
||||
get_default_coding_instructions,
|
||||
get_glyphs,
|
||||
get_langsmith_project_name,
|
||||
restore_user_tracing_api_keys,
|
||||
restore_user_tracing_env,
|
||||
settings,
|
||||
)
|
||||
@@ -1663,6 +1664,7 @@ def create_cli_agent(
|
||||
else:
|
||||
shell_env.pop("LANGSMITH_PROJECT", None)
|
||||
restore_user_tracing_env(shell_env)
|
||||
restore_user_tracing_api_keys(shell_env)
|
||||
# Re-apply a launch-time PYTHONPATH that was stripped from the server
|
||||
# interpreter but relayed for approval-gated `execute` commands.
|
||||
_apply_inherited_pythonpath(shell_env)
|
||||
@@ -1673,6 +1675,9 @@ def create_cli_agent(
|
||||
# `inherit_env=False`: `shell_env` is already a complete, curated
|
||||
# copy of `os.environ`. Inheriting again would re-copy `os.environ`
|
||||
# and resurrect the popped carrier var, leaking it into `execute`.
|
||||
# `restore_user_tracing_api_keys` above depends on this too: flipping
|
||||
# to `inherit_env=True` would re-copy the agent's overridden
|
||||
# `LANGSMITH_API_KEY` and undo the restore, leaking it into `execute`.
|
||||
backend = LocalShellBackend(
|
||||
root_dir=root_dir,
|
||||
virtual_mode=False,
|
||||
|
||||
@@ -58,6 +58,20 @@ class _BootstrapState:
|
||||
original_tracing_env: dict[str, str | None] = dataclass_field(default_factory=dict)
|
||||
"""Caller's tracing-enable env before Deep Agents Code mutates flags."""
|
||||
|
||||
original_tracing_api_keys: dict[str, str | None] = dataclass_field(
|
||||
default_factory=dict
|
||||
)
|
||||
"""Caller's tracing API keys before Deep Agents Code overwrites them.
|
||||
|
||||
Two bootstrap steps can overwrite the canonical `LANGSMITH_API_KEY` (and
|
||||
its `LANGCHAIN_API_KEY` alias): the `DEEPAGENTS_CODE_`-prefixed override and
|
||||
the `/auth`-stored key bridged on by `apply_stored_langsmith_auth`. Both run
|
||||
after this snapshot is captured. Without saving the originals, shell
|
||||
subprocesses inherit the agent's session key and the caller's own value is
|
||||
irrecoverable in-process. This mirrors the save/restore pattern used for
|
||||
tracing flags (`original_tracing_env`).
|
||||
"""
|
||||
|
||||
|
||||
_bootstrap_state = _BootstrapState()
|
||||
"""State captured and mutated by lazy bootstrap."""
|
||||
@@ -518,6 +532,25 @@ def restore_user_tracing_env(env: dict[str, str]) -> None:
|
||||
env[var] = value
|
||||
|
||||
|
||||
def restore_user_tracing_api_keys(env: dict[str, str]) -> None:
|
||||
"""Restore caller tracing API keys in an environment passed to user code.
|
||||
|
||||
Reverts both bootstrap overwrites of the canonical LangSmith key — the
|
||||
`DEEPAGENTS_CODE_`-prefixed override and the `/auth`-stored key — so shell
|
||||
subprocesses receive the caller's own key rather than the agent's session
|
||||
key. See `original_tracing_api_keys` for the rationale; this mirrors
|
||||
`restore_user_tracing_env`, which does the same for tracing flags.
|
||||
|
||||
Args:
|
||||
env: Environment mapping prepared for a child/user subprocess.
|
||||
"""
|
||||
for var, value in _bootstrap_state.original_tracing_api_keys.items():
|
||||
if value is None:
|
||||
env.pop(var, None)
|
||||
else:
|
||||
env[var] = value
|
||||
|
||||
|
||||
def _disable_orphaned_tracing() -> None:
|
||||
"""Disable LangSmith tracing when enabled without a usable API key.
|
||||
|
||||
@@ -729,6 +762,9 @@ def _ensure_bootstrap() -> None:
|
||||
_bootstrap_state.original_tracing_env = {
|
||||
var: os.environ.get(var) for var in _TRACING_ENABLE_ENV_VARS
|
||||
}
|
||||
_bootstrap_state.original_tracing_api_keys = {
|
||||
var: os.environ.get(var) for var in _TRACING_API_KEY_ENV_VARS
|
||||
}
|
||||
|
||||
# CRITICAL: Override LANGSMITH_PROJECT to route agent traces to a
|
||||
# separate project. LangSmith reads LANGSMITH_PROJECT at invocation
|
||||
|
||||
@@ -1958,6 +1958,45 @@ class TestCreateCliAgentProjectContext:
|
||||
|
||||
assert mock_shell.call_args.kwargs["env"]["LANGSMITH_PROJECT"] == user_project
|
||||
|
||||
def test_project_context_restores_user_shell_langsmith_api_key(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
"""The shell env is routed through `restore_user_tracing_api_keys`.
|
||||
|
||||
Guards the wiring in `create_cli_agent`'s local-shell branch: the env
|
||||
handed to `LocalShellBackend` must carry the caller's original
|
||||
`LANGSMITH_API_KEY` (the agent's in-process override reverted) and must
|
||||
drop a key the caller never set. Removing the restore call regresses
|
||||
both assertions, catching the exact key leak the restore prevents.
|
||||
"""
|
||||
import deepagents_code.config as config_mod
|
||||
|
||||
original_done = config_mod._bootstrap_state.done
|
||||
original_api_keys = dict(config_mod._bootstrap_state.original_tracing_api_keys)
|
||||
# Simulate a completed bootstrap: the caller had their own LANGSMITH key
|
||||
# (since overridden in-process) and never set a LANGCHAIN key.
|
||||
monkeypatch.setenv("LANGSMITH_API_KEY", "lsv2_agent_override")
|
||||
monkeypatch.setenv("LANGCHAIN_API_KEY", "lc_agent_override")
|
||||
config_mod._bootstrap_state.original_tracing_api_keys = {
|
||||
"LANGSMITH_API_KEY": "lsv2_user_original",
|
||||
"LANGCHAIN_API_KEY": None,
|
||||
}
|
||||
# Guard the seeded snapshot against an incidental bootstrap run.
|
||||
config_mod._bootstrap_state.done = True
|
||||
|
||||
try:
|
||||
mock_shell, _ = self._build_shell_agent(
|
||||
monkeypatch, tmp_path, user_langchain_project=None
|
||||
)
|
||||
env = mock_shell.call_args.kwargs["env"]
|
||||
# The caller's own key is restored, not the agent's override.
|
||||
assert env["LANGSMITH_API_KEY"] == "lsv2_user_original"
|
||||
# A key the caller never set is dropped, not leaked.
|
||||
assert "LANGCHAIN_API_KEY" not in env
|
||||
finally:
|
||||
config_mod._bootstrap_state.done = original_done
|
||||
config_mod._bootstrap_state.original_tracing_api_keys = original_api_keys
|
||||
|
||||
def test_cwd_sets_local_filesystem_root_dir_without_shell(
|
||||
self, tmp_path: Path
|
||||
) -> None:
|
||||
|
||||
@@ -4768,6 +4768,7 @@ class TestLazyModuleAttributes:
|
||||
|
||||
original_done = config_mod._bootstrap_state.done
|
||||
original_ls = config_mod._bootstrap_state.original_langsmith_project
|
||||
original_api_keys = dict(config_mod._bootstrap_state.original_tracing_api_keys)
|
||||
config_mod._bootstrap_state.done = False
|
||||
|
||||
try:
|
||||
@@ -4788,9 +4789,200 @@ class TestLazyModuleAttributes:
|
||||
|
||||
# Prefixed value wins — canonical is overwritten.
|
||||
assert os.environ["LANGSMITH_API_KEY"] == "lsv2_override"
|
||||
# The original canonical value is saved for subprocess restoration.
|
||||
assert (
|
||||
config_mod._bootstrap_state.original_tracing_api_keys[
|
||||
"LANGSMITH_API_KEY"
|
||||
]
|
||||
== "lsv2_original"
|
||||
)
|
||||
finally:
|
||||
config_mod._bootstrap_state.done = original_done
|
||||
config_mod._bootstrap_state.original_langsmith_project = original_ls
|
||||
config_mod._bootstrap_state.original_tracing_api_keys = original_api_keys
|
||||
|
||||
@pytest.mark.parametrize("canonical", ["LANGSMITH_API_KEY", "LANGCHAIN_API_KEY"])
|
||||
def test_restore_user_tracing_api_keys_recovers_original_value(
|
||||
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, canonical: str
|
||||
) -> None:
|
||||
"""Shell subprocess env gets the caller's original API key, not the override.
|
||||
|
||||
Parametrized over both members of `_TRACING_API_KEY_ENV_VARS` so the
|
||||
`LANGCHAIN_API_KEY` alias is covered, not just the primary var.
|
||||
"""
|
||||
import os
|
||||
|
||||
import deepagents_code.config as config_mod
|
||||
from deepagents_code.config import (
|
||||
_ensure_bootstrap,
|
||||
restore_user_tracing_api_keys,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"deepagents_code.model_config.DEFAULT_STATE_DIR", tmp_path / ".state"
|
||||
)
|
||||
|
||||
original_done = config_mod._bootstrap_state.done
|
||||
original_ls = config_mod._bootstrap_state.original_langsmith_project
|
||||
original_tracing = dict(config_mod._bootstrap_state.original_tracing_env)
|
||||
original_api_keys = dict(config_mod._bootstrap_state.original_tracing_api_keys)
|
||||
config_mod._bootstrap_state.done = False
|
||||
|
||||
try:
|
||||
# Isolate the key var under test so its sibling alias cannot mask it.
|
||||
for var in config_mod._TRACING_API_KEY_ENV_VARS:
|
||||
monkeypatch.delenv(var, raising=False)
|
||||
monkeypatch.delenv(f"DEEPAGENTS_CODE_{var}", raising=False)
|
||||
monkeypatch.setenv(canonical, "lsv2_original")
|
||||
monkeypatch.setenv(f"DEEPAGENTS_CODE_{canonical}", "lsv2_override")
|
||||
monkeypatch.setenv("LANGSMITH_TRACING", "true")
|
||||
monkeypatch.delenv("DEEPAGENTS_CODE_LANGSMITH_PROJECT", raising=False)
|
||||
|
||||
with (
|
||||
patch("deepagents_code.config._load_dotenv"),
|
||||
patch(
|
||||
"deepagents_code.project_utils.get_server_project_context",
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
_ensure_bootstrap()
|
||||
|
||||
# Bootstrap overwrote the canonical key with the prefixed value.
|
||||
assert os.environ[canonical] == "lsv2_override"
|
||||
|
||||
shell_env = os.environ.copy()
|
||||
restore_user_tracing_api_keys(shell_env)
|
||||
|
||||
# Shell subprocesses get the caller's original key back.
|
||||
assert shell_env[canonical] == "lsv2_original"
|
||||
finally:
|
||||
config_mod._bootstrap_state.done = original_done
|
||||
config_mod._bootstrap_state.original_langsmith_project = original_ls
|
||||
config_mod._bootstrap_state.original_tracing_env = original_tracing
|
||||
config_mod._bootstrap_state.original_tracing_api_keys = original_api_keys
|
||||
|
||||
def test_restore_user_tracing_api_keys_drops_unset_key(
|
||||
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""When the caller had no canonical key, restore removes it from shell env."""
|
||||
import os
|
||||
|
||||
import deepagents_code.config as config_mod
|
||||
from deepagents_code.config import (
|
||||
_ensure_bootstrap,
|
||||
restore_user_tracing_api_keys,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"deepagents_code.model_config.DEFAULT_STATE_DIR", tmp_path / ".state"
|
||||
)
|
||||
|
||||
original_done = config_mod._bootstrap_state.done
|
||||
original_ls = config_mod._bootstrap_state.original_langsmith_project
|
||||
original_tracing = dict(config_mod._bootstrap_state.original_tracing_env)
|
||||
original_api_keys = dict(config_mod._bootstrap_state.original_tracing_api_keys)
|
||||
config_mod._bootstrap_state.done = False
|
||||
|
||||
try:
|
||||
monkeypatch.delenv("LANGSMITH_API_KEY", raising=False)
|
||||
monkeypatch.setenv("DEEPAGENTS_CODE_LANGSMITH_API_KEY", "lsv2_prefixed")
|
||||
monkeypatch.delenv("DEEPAGENTS_CODE_LANGSMITH_PROJECT", raising=False)
|
||||
|
||||
with (
|
||||
patch("deepagents_code.config._load_dotenv"),
|
||||
patch(
|
||||
"deepagents_code.project_utils.get_server_project_context",
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
_ensure_bootstrap()
|
||||
|
||||
# Bootstrap propagated the prefixed key to canonical.
|
||||
assert os.environ["LANGSMITH_API_KEY"] == "lsv2_prefixed"
|
||||
|
||||
shell_env = os.environ.copy()
|
||||
restore_user_tracing_api_keys(shell_env)
|
||||
|
||||
# Caller had no key — the propagated value is removed from shell env.
|
||||
assert "LANGSMITH_API_KEY" not in shell_env
|
||||
finally:
|
||||
config_mod._bootstrap_state.done = original_done
|
||||
config_mod._bootstrap_state.original_langsmith_project = original_ls
|
||||
config_mod._bootstrap_state.original_tracing_env = original_tracing
|
||||
config_mod._bootstrap_state.original_tracing_api_keys = original_api_keys
|
||||
|
||||
def test_restore_user_tracing_api_keys_pops_auth_stored_key(
|
||||
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""A `/auth`-stored key is bridged onto the env but popped for shells.
|
||||
|
||||
The snapshot is captured *before* `apply_stored_langsmith_auth` bridges
|
||||
the stored key onto `LANGSMITH_API_KEY`, so the caller's original is
|
||||
`None` and restore pops the bridged key instead of leaking the agent's
|
||||
stored credential into `execute` subprocesses. Locks in the
|
||||
capture-before-bridge ordering that a bootstrap refactor could break.
|
||||
"""
|
||||
import os
|
||||
|
||||
import deepagents_code.config as config_mod
|
||||
from deepagents_code import auth_store
|
||||
from deepagents_code.config import (
|
||||
_ensure_bootstrap,
|
||||
restore_user_tracing_api_keys,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"deepagents_code.model_config.DEFAULT_STATE_DIR", tmp_path / ".state"
|
||||
)
|
||||
|
||||
original_done = config_mod._bootstrap_state.done
|
||||
original_ls = config_mod._bootstrap_state.original_langsmith_project
|
||||
original_tracing = dict(config_mod._bootstrap_state.original_tracing_env)
|
||||
original_api_keys = dict(config_mod._bootstrap_state.original_tracing_api_keys)
|
||||
config_mod._bootstrap_state.done = False
|
||||
|
||||
try:
|
||||
# No env or prefixed key — only a `/auth`-stored credential.
|
||||
for var in (
|
||||
"LANGSMITH_API_KEY",
|
||||
"LANGCHAIN_API_KEY",
|
||||
"DEEPAGENTS_CODE_LANGSMITH_API_KEY",
|
||||
"DEEPAGENTS_CODE_LANGCHAIN_API_KEY",
|
||||
"DEEPAGENTS_CODE_LANGSMITH_PROJECT",
|
||||
):
|
||||
monkeypatch.delenv(var, raising=False)
|
||||
auth_store.set_stored_key("langsmith", "lsv2_stored")
|
||||
|
||||
with (
|
||||
patch("deepagents_code.config._load_dotenv"),
|
||||
patch(
|
||||
"deepagents_code.project_utils.get_server_project_context",
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
_ensure_bootstrap()
|
||||
|
||||
# Bootstrap bridged the stored key onto the canonical env var...
|
||||
assert os.environ["LANGSMITH_API_KEY"] == "lsv2_stored"
|
||||
# ...but the caller had none, so the snapshot (taken before the
|
||||
# bridge) records it as absent.
|
||||
assert (
|
||||
config_mod._bootstrap_state.original_tracing_api_keys[
|
||||
"LANGSMITH_API_KEY"
|
||||
]
|
||||
is None
|
||||
)
|
||||
|
||||
shell_env = os.environ.copy()
|
||||
restore_user_tracing_api_keys(shell_env)
|
||||
|
||||
# The agent's stored credential is not leaked into shell subprocesses.
|
||||
assert "LANGSMITH_API_KEY" not in shell_env
|
||||
finally:
|
||||
config_mod._bootstrap_state.done = original_done
|
||||
config_mod._bootstrap_state.original_langsmith_project = original_ls
|
||||
config_mod._bootstrap_state.original_tracing_env = original_tracing
|
||||
config_mod._bootstrap_state.original_tracing_api_keys = original_api_keys
|
||||
|
||||
def test_bootstrap_warns_on_conflicting_override(
|
||||
self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
||||
@@ -4803,6 +4995,7 @@ class TestLazyModuleAttributes:
|
||||
|
||||
original_done = config_mod._bootstrap_state.done
|
||||
original_ls = config_mod._bootstrap_state.original_langsmith_project
|
||||
original_api_keys = dict(config_mod._bootstrap_state.original_tracing_api_keys)
|
||||
config_mod._bootstrap_state.done = False
|
||||
|
||||
try:
|
||||
@@ -4833,6 +5026,7 @@ class TestLazyModuleAttributes:
|
||||
finally:
|
||||
config_mod._bootstrap_state.done = original_done
|
||||
config_mod._bootstrap_state.original_langsmith_project = original_ls
|
||||
config_mod._bootstrap_state.original_tracing_api_keys = original_api_keys
|
||||
|
||||
def test_bootstrap_suppresses_override_warning(
|
||||
self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
||||
@@ -4846,6 +5040,7 @@ class TestLazyModuleAttributes:
|
||||
|
||||
original_done = config_mod._bootstrap_state.done
|
||||
original_ls = config_mod._bootstrap_state.original_langsmith_project
|
||||
original_api_keys = dict(config_mod._bootstrap_state.original_tracing_api_keys)
|
||||
config_mod._bootstrap_state.done = False
|
||||
|
||||
try:
|
||||
@@ -4874,6 +5069,7 @@ class TestLazyModuleAttributes:
|
||||
finally:
|
||||
config_mod._bootstrap_state.done = original_done
|
||||
config_mod._bootstrap_state.original_langsmith_project = original_ls
|
||||
config_mod._bootstrap_state.original_tracing_api_keys = original_api_keys
|
||||
|
||||
def test_bootstrap_no_warning_when_values_match(
|
||||
self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
||||
@@ -4887,6 +5083,7 @@ class TestLazyModuleAttributes:
|
||||
|
||||
original_done = config_mod._bootstrap_state.done
|
||||
original_ls = config_mod._bootstrap_state.original_langsmith_project
|
||||
original_api_keys = dict(config_mod._bootstrap_state.original_tracing_api_keys)
|
||||
config_mod._bootstrap_state.done = False
|
||||
|
||||
try:
|
||||
@@ -4917,6 +5114,7 @@ class TestLazyModuleAttributes:
|
||||
finally:
|
||||
config_mod._bootstrap_state.done = original_done
|
||||
config_mod._bootstrap_state.original_langsmith_project = original_ls
|
||||
config_mod._bootstrap_state.original_tracing_api_keys = original_api_keys
|
||||
|
||||
def test_bootstrap_propagates_empty_string(
|
||||
self, monkeypatch: pytest.MonkeyPatch
|
||||
@@ -5067,6 +5265,7 @@ class TestLazyModuleAttributes:
|
||||
original_done = config_mod._bootstrap_state.done
|
||||
original_ls = config_mod._bootstrap_state.original_langsmith_project
|
||||
original_tracing = dict(config_mod._bootstrap_state.original_tracing_env)
|
||||
original_api_keys = dict(config_mod._bootstrap_state.original_tracing_api_keys)
|
||||
config_mod._bootstrap_state.done = False
|
||||
|
||||
try:
|
||||
@@ -5107,6 +5306,7 @@ class TestLazyModuleAttributes:
|
||||
config_mod._bootstrap_state.done = original_done
|
||||
config_mod._bootstrap_state.original_langsmith_project = original_ls
|
||||
config_mod._bootstrap_state.original_tracing_env = original_tracing
|
||||
config_mod._bootstrap_state.original_tracing_api_keys = original_api_keys
|
||||
|
||||
def test_bootstrap_prefixed_langsmith_key_wins_over_stored_key(
|
||||
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
@@ -5125,6 +5325,7 @@ class TestLazyModuleAttributes:
|
||||
original_done = config_mod._bootstrap_state.done
|
||||
original_ls = config_mod._bootstrap_state.original_langsmith_project
|
||||
original_tracing = dict(config_mod._bootstrap_state.original_tracing_env)
|
||||
original_api_keys = dict(config_mod._bootstrap_state.original_tracing_api_keys)
|
||||
config_mod._bootstrap_state.done = False
|
||||
|
||||
try:
|
||||
@@ -5150,6 +5351,7 @@ class TestLazyModuleAttributes:
|
||||
config_mod._bootstrap_state.done = original_done
|
||||
config_mod._bootstrap_state.original_langsmith_project = original_ls
|
||||
config_mod._bootstrap_state.original_tracing_env = original_tracing
|
||||
config_mod._bootstrap_state.original_tracing_api_keys = original_api_keys
|
||||
|
||||
def test_scoped_tracing_opt_out_restores_user_tracing_for_shell_env(
|
||||
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
@@ -5168,6 +5370,7 @@ class TestLazyModuleAttributes:
|
||||
original_done = config_mod._bootstrap_state.done
|
||||
original_ls = config_mod._bootstrap_state.original_langsmith_project
|
||||
original_tracing = dict(config_mod._bootstrap_state.original_tracing_env)
|
||||
original_api_keys = dict(config_mod._bootstrap_state.original_tracing_api_keys)
|
||||
config_mod._bootstrap_state.done = False
|
||||
|
||||
try:
|
||||
@@ -5199,6 +5402,7 @@ class TestLazyModuleAttributes:
|
||||
config_mod._bootstrap_state.done = original_done
|
||||
config_mod._bootstrap_state.original_langsmith_project = original_ls
|
||||
config_mod._bootstrap_state.original_tracing_env = original_tracing
|
||||
config_mod._bootstrap_state.original_tracing_api_keys = original_api_keys
|
||||
|
||||
|
||||
class TestApplyDefaultLangsmithProject:
|
||||
|
||||
Reference in New Issue
Block a user