mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-21 17:25:26 -04:00
fix(code): dcode doctor shows not configured for unset tracing (#4318)
`dcode doctor` now distinguishes unconfigured LangSmith tracing (`not configured`) from an explicit opt-out (`disabled`), and reports credentials as `not set`. --- `dcode doctor` previously rendered the Tracing line as `disabled` whenever a tracing flag wasn't truthy, conflating "no LangSmith tracing flag set" with a deliberate opt-out. It now reports `not configured` when no flag is set and `disabled` only when a flag is explicitly set to a recognized falsy token (`0`/`false`/`no`/`off`). The Credentials line now reads `not set` instead of `not configured`. Made by [Open SWE](https://openswe.vercel.app/agents/3f92fdb3-3d1d-a985-9b9e-4ad1c8406400) --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
@@ -2731,6 +2731,47 @@ def _tracing_enabled_from(env: dict[str, str]) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _tracing_explicitly_disabled_from(env: dict[str, str]) -> bool:
|
||||
"""Return whether a tracing flag is explicitly set to a recognized off value.
|
||||
|
||||
True only when tracing is not enabled and at least one tracing-enable flag
|
||||
carries a falsy token (`0`/`false`/`no`/`off`). An empty flag usually reads
|
||||
as "not configured" rather than "disabled", except when an empty prefixed
|
||||
bridged flag shadows a canonical truthy flag and therefore disables tracing.
|
||||
|
||||
Args:
|
||||
env: Environment mapping to read.
|
||||
"""
|
||||
from deepagents_code._env_vars import classify_env_bool
|
||||
from deepagents_code.model_config import _ENV_PREFIX
|
||||
|
||||
if _tracing_enabled_from(env):
|
||||
return False
|
||||
|
||||
def _is_off(raw: str | None) -> bool:
|
||||
if raw is None or not raw.strip():
|
||||
return False
|
||||
return classify_env_bool(raw) is False
|
||||
|
||||
def _empty_prefixed_shadow_disables(var: str) -> bool:
|
||||
prefixed = f"{_ENV_PREFIX}{var}"
|
||||
if prefixed not in env or env[prefixed].strip():
|
||||
return False
|
||||
canonical = env.get(var)
|
||||
return canonical is not None and classify_env_bool(canonical) is True
|
||||
|
||||
for var in _TRACING_BRIDGED_ENABLE_ENV_VARS:
|
||||
if _is_off(_resolve_env_var_from(env, var)) or _empty_prefixed_shadow_disables(
|
||||
var
|
||||
):
|
||||
return True
|
||||
return any(
|
||||
_is_off(env.get(var))
|
||||
for var in _TRACING_ENABLE_ENV_VARS
|
||||
if var not in _TRACING_BRIDGED_ENABLE_ENV_VARS
|
||||
)
|
||||
|
||||
|
||||
def _tracing_enabled() -> bool:
|
||||
"""Return whether tracing is (or will be) enabled, prefix-aware."""
|
||||
return _tracing_enabled_from(dict(os.environ))
|
||||
@@ -2821,6 +2862,9 @@ class TracingStatus:
|
||||
enabled: bool
|
||||
"""Whether a tracing flag is truthy in the environment."""
|
||||
|
||||
explicitly_disabled: bool
|
||||
"""Whether a tracing flag is explicitly set to a falsy value (vs. unset)."""
|
||||
|
||||
has_credentials: bool
|
||||
"""Whether an API key or profile credential is resolvable."""
|
||||
|
||||
@@ -2836,6 +2880,21 @@ class TracingStatus:
|
||||
replica_project: str | None
|
||||
"""Extra project agent runs are mirrored to, if configured."""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Reject the contradictory enabled/explicitly-disabled pair.
|
||||
|
||||
`enabled` and `explicitly_disabled` model a tri-state (enabled /
|
||||
explicitly disabled / not configured), so both being true is
|
||||
meaningless. Fail loud at construction rather than letting the illegal
|
||||
state flow through to the `dcode doctor` renderer.
|
||||
|
||||
Raises:
|
||||
ValueError: If both `enabled` and `explicitly_disabled` are true.
|
||||
"""
|
||||
if self.enabled and self.explicitly_disabled:
|
||||
msg = "tracing cannot be both enabled and explicitly disabled"
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def get_tracing_status() -> TracingStatus:
|
||||
"""Summarize LangSmith tracing configuration for diagnostics.
|
||||
@@ -2855,6 +2914,7 @@ def get_tracing_status() -> TracingStatus:
|
||||
project, project_is_default = _resolve_tracing_project_from(env)
|
||||
return TracingStatus(
|
||||
enabled=enabled,
|
||||
explicitly_disabled=_tracing_explicitly_disabled_from(env),
|
||||
has_credentials=has_credentials,
|
||||
endpoint=endpoint,
|
||||
project=project,
|
||||
|
||||
@@ -267,7 +267,9 @@ def _format_tracing_project(status: TracingStatus) -> str:
|
||||
def _collect_tracing() -> DiagnosticSection:
|
||||
"""Collect LangSmith tracing status from env and profile (offline).
|
||||
|
||||
Credentials are reported as configured/not configured only — the API key
|
||||
Tracing reads `enabled` when a flag is truthy, `disabled` only when a flag
|
||||
is explicitly set to a falsy value, and `not configured` when no flag is set.
|
||||
Credentials are reported as configured/not set only — the API key
|
||||
value is never read or printed. The `Credentials` item is flagged as a
|
||||
problem only when tracing is enabled without a key and without a custom
|
||||
endpoint, mirroring the runtime's orphaned-tracing guard (a keyless
|
||||
@@ -280,11 +282,17 @@ def _collect_tracing() -> DiagnosticSection:
|
||||
|
||||
status = get_tracing_status()
|
||||
creds_required = status.enabled and status.endpoint is None
|
||||
if status.enabled:
|
||||
tracing_value = "enabled"
|
||||
elif status.explicitly_disabled:
|
||||
tracing_value = "disabled"
|
||||
else:
|
||||
tracing_value = "not configured"
|
||||
items = [
|
||||
DiagnosticItem("Tracing", "enabled" if status.enabled else "disabled"),
|
||||
DiagnosticItem("Tracing", tracing_value),
|
||||
DiagnosticItem(
|
||||
"Credentials",
|
||||
"configured" if status.has_credentials else "not configured",
|
||||
"configured" if status.has_credentials else "not set",
|
||||
ok=status.has_credentials or not creds_required,
|
||||
),
|
||||
DiagnosticItem("Project", _format_tracing_project(status)),
|
||||
|
||||
@@ -2333,12 +2333,56 @@ class TestGetTracingStatus:
|
||||
with patch.dict("os.environ", self._CLEAN, clear=False):
|
||||
status = get_tracing_status()
|
||||
assert status.enabled is False
|
||||
assert status.explicitly_disabled is False
|
||||
assert status.has_credentials is False
|
||||
assert status.endpoint is None
|
||||
assert status.project == LANGSMITH_PROJECT_DEFAULT
|
||||
assert status.project_is_default is True
|
||||
assert status.replica_project is None
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("var", "token", "expected_enabled", "expected_disabled"),
|
||||
[
|
||||
# Non-bridged flags (bare `env.get` path) set to each falsy token.
|
||||
("LANGSMITH_TRACING_V2", "false", False, True),
|
||||
("LANGSMITH_TRACING_V2", "0", False, True),
|
||||
("LANGSMITH_TRACING_V2", "no", False, True),
|
||||
("LANGSMITH_TRACING_V2", "off", False, True),
|
||||
("LANGCHAIN_TRACING", "false", False, True),
|
||||
# Prefixed bridged falsy flag — exercises the prefix-aware
|
||||
# `_resolve_env_var_from` off path; doctor runs pre-bootstrap.
|
||||
("DEEPAGENTS_CODE_LANGSMITH_TRACING", "false", False, True),
|
||||
# An empty flag reads as not configured, not an explicit opt-out.
|
||||
("LANGSMITH_TRACING_V2", "", False, False),
|
||||
# An unrecognized token is neither enabled nor an explicit opt-out.
|
||||
("LANGSMITH_TRACING_V2", "maybe", False, False),
|
||||
# A truthy flag is enabled and never reported as explicitly disabled.
|
||||
("LANGSMITH_TRACING_V2", "true", True, False),
|
||||
],
|
||||
)
|
||||
def test_explicit_disable_matrix(
|
||||
self,
|
||||
var: str,
|
||||
token: str,
|
||||
expected_enabled: bool,
|
||||
expected_disabled: bool,
|
||||
) -> None:
|
||||
"""Each flag/token combination resolves to the right tri-state.
|
||||
|
||||
Exercises both the bare-`env.get` path (non-bridged vars) and the
|
||||
prefix-aware `_resolve_env_var_from` path (the prefixed bridged flag),
|
||||
across every recognized falsy token plus the empty and unrecognized
|
||||
cases.
|
||||
"""
|
||||
from deepagents_code.config import get_tracing_status
|
||||
|
||||
env = dict(self._CLEAN)
|
||||
env[var] = token
|
||||
with patch.dict("os.environ", env, clear=False):
|
||||
status = get_tracing_status()
|
||||
assert status.enabled is expected_enabled
|
||||
assert status.explicitly_disabled is expected_disabled
|
||||
|
||||
def test_prefixed_flag_and_key_are_detected(self) -> None:
|
||||
"""`DEEPAGENTS_CODE_`-prefixed tracing/key vars resolve like the runtime.
|
||||
|
||||
@@ -2461,6 +2505,7 @@ class TestGetTracingStatus:
|
||||
with patch.dict("os.environ", env, clear=False):
|
||||
status = get_tracing_status()
|
||||
assert status.enabled is False
|
||||
assert status.explicitly_disabled is True
|
||||
|
||||
def test_canonical_non_bridged_flag_enables(self) -> None:
|
||||
"""A canonical, non-bridged flag (`LANGSMITH_TRACING_V2`) enables tracing."""
|
||||
@@ -2548,6 +2593,21 @@ class TestGetTracingStatus:
|
||||
status = get_tracing_status()
|
||||
assert status.replica_project == "replica-a"
|
||||
|
||||
def test_enabled_and_explicitly_disabled_is_rejected(self) -> None:
|
||||
"""The contradictory enabled/disabled pair fails loud at construction."""
|
||||
from deepagents_code.config import TracingStatus
|
||||
|
||||
with pytest.raises(ValueError, match="both enabled and explicitly disabled"):
|
||||
TracingStatus(
|
||||
enabled=True,
|
||||
explicitly_disabled=True,
|
||||
has_credentials=False,
|
||||
endpoint=None,
|
||||
project=None,
|
||||
project_is_default=False,
|
||||
replica_project=None,
|
||||
)
|
||||
|
||||
|
||||
class TestQuietSdkTracingLogging:
|
||||
"""Tests for _quiet_sdk_tracing_logging()."""
|
||||
|
||||
@@ -94,6 +94,7 @@ class TestCollectTracing:
|
||||
|
||||
defaults: dict[str, object] = {
|
||||
"enabled": False,
|
||||
"explicitly_disabled": False,
|
||||
"has_credentials": False,
|
||||
"endpoint": None,
|
||||
"project": None,
|
||||
@@ -105,16 +106,24 @@ class TestCollectTracing:
|
||||
with patch("deepagents_code.config.get_tracing_status", return_value=status):
|
||||
return _collect_tracing()
|
||||
|
||||
def test_disabled_is_healthy(self) -> None:
|
||||
"""A disabled, keyless setup is informational, not a failure."""
|
||||
def test_not_configured_is_healthy(self) -> None:
|
||||
"""An unconfigured, keyless setup is informational, not a failure."""
|
||||
section = self._section(enabled=False, project="deepagents-code")
|
||||
assert section.title == "Tracing"
|
||||
assert section.ok is True
|
||||
labels = {item.label: item.value for item in section.items}
|
||||
assert labels["Tracing"] == "disabled"
|
||||
assert labels["Credentials"] == "not configured"
|
||||
assert labels["Tracing"] == "not configured"
|
||||
assert labels["Credentials"] == "not set"
|
||||
assert labels["Project"] == "deepagents-code"
|
||||
|
||||
def test_explicitly_disabled_reads_disabled(self) -> None:
|
||||
"""An explicit opt-out reads `disabled`, not `not configured`."""
|
||||
section = self._section(enabled=False, explicitly_disabled=True)
|
||||
assert section.ok is True
|
||||
labels = {item.label: item.value for item in section.items}
|
||||
assert labels["Tracing"] == "disabled"
|
||||
assert labels["Credentials"] == "not set"
|
||||
|
||||
def test_default_project_is_marked(self) -> None:
|
||||
"""An unconfigured project shows the default marker."""
|
||||
section = self._section(project="deepagents-code", project_is_default=True)
|
||||
|
||||
Reference in New Issue
Block a user