fix(core): don't send a default project with agent-addressed runs

When LANGSMITH_AGENT_ID / LANGSMITH_AGENT_ENVIRONMENT address runs to a
LangSmith agent and no project is configured, LangChainTracer fell back to
the invented "default" project and assigned it to every run. The run then
carried both agent_id and session_name, which the API rejects with a 400,
dropping the trace.

With agent addressing active, fall back only to a project the user
configured. The agent helpers are looked up with getattr since they only
exist in langsmith>=0.14.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kiewan VillatelandClaude Opus 5.5 committed 2026-09-25 10:14:13 +02:00
1 parent 38cee0db98
commit 99ff594d5e
2 files changed
+90 -1

No files matched your search

+22 -1
View File
@@ -81,6 +81,27 @@ def get_client() -> Client:
return rt.get_cached_client()
def _get_default_project_name() -> str | None:
"""Get the project to trace to when none is passed to the tracer.
When the environment addresses runs to a LangSmith agent
(`LANGSMITH_AGENT_ID` / `LANGSMITH_AGENT_ENVIRONMENT`), only a project the
user configured is returned. Falling back to the invented `'default'`
project would send it alongside the agent, and the API rejects a run
addressed to both.
Agent addressing requires `langsmith>=0.14.0`; older versions have no agent
to address, so the helpers are looked up rather than imported.
"""
get_agent_id = getattr(ls_utils, "get_tracer_agent_id", None)
get_agent_environment = getattr(ls_utils, "get_tracer_agent_environment", None)
if (get_agent_id is not None and get_agent_id() is not None) or (
get_agent_environment is not None and get_agent_environment() is not None
):
return ls_utils.get_tracer_project(return_default_value=False)
return ls_utils.get_tracer_project()
def _get_executor() -> ThreadPoolExecutor:
"""Get the executor."""
global _EXECUTOR # noqa: PLW0603
@@ -168,7 +189,7 @@ class LangChainTracer(BaseTracer):
self.example_id = (
UUID(example_id) if isinstance(example_id, str) else example_id
)
self.project_name = project_name or ls_utils.get_tracer_project()
self.project_name = project_name or _get_default_project_name()
self.client = client or get_client()
self.tags = tags or []
self.latest_run: Run | None = None
@@ -8,6 +8,7 @@ from uuid import UUID
import pytest
from langsmith import Client
from langsmith import utils as ls_utils
from langsmith.run_trees import RunTree
from langsmith.utils import get_env_var, get_tracer_project
@@ -154,6 +155,73 @@ def test_correct_get_tracer_project(
assert projects == [expected_project_name]
def _clear_tracer_env_caches() -> None:
getters: tuple[Any, ...] = (
get_env_var,
get_tracer_project,
getattr(ls_utils, "get_tracer_agent_id", None),
getattr(ls_utils, "get_tracer_agent_environment", None),
)
for getter in getters:
if hasattr(getter, "cache_clear"):
getter.cache_clear()
@pytest.mark.parametrize(
("envvars", "expected_project_name"),
[
({"LANGSMITH_AGENT_ID": "my-agent"}, None),
({"LANGSMITH_AGENT_ENVIRONMENT": "development"}, None),
(
{
"LANGSMITH_AGENT_ID": "my-agent",
"LANGSMITH_AGENT_ENVIRONMENT": "development",
},
None,
),
(
{
"LANGSMITH_AGENT_ID": "my-agent",
"LANGSMITH_AGENT_ENVIRONMENT": "development",
"LANGSMITH_PROJECT": "configured",
},
"configured",
),
],
ids=[
"no 'default' project with agent id",
"no 'default' project with agent environment",
"no 'default' project with agent pair",
"keep a configured project with agent pair",
],
)
def test_tracer_project_with_agent_addressing(
envvars: dict[str, str], expected_project_name: str | None
) -> None:
if not hasattr(ls_utils, "get_tracer_agent_environment"):
pytest.skip("agent addressing requires langsmith>=0.14.0")
_clear_tracer_env_caches()
try:
with pytest.MonkeyPatch.context() as mp:
for k in (
"LANGSMITH_PROJECT",
"LANGCHAIN_PROJECT",
"LANGCHAIN_SESSION",
"HOSTED_LANGSERVE_PROJECT_NAME",
"LANGSMITH_AGENT_ID",
"LANGSMITH_AGENT_ENVIRONMENT",
):
mp.delenv(k, raising=False)
for k, v in envvars.items():
mp.setenv(k, v)
tracer = LangChainTracer(client=unittest.mock.MagicMock(spec=Client))
assert tracer.project_name == expected_project_name
finally:
# The env getters are cached, so leave nothing behind for later tests.
_clear_tracer_env_caches()
@pytest.mark.parametrize(
("generations", "expected"),
[