From b8540449b477aeff563e8867378c3888516763b4 Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:30:53 -0700 Subject: [PATCH] feat(sdk-py): add langsmith_tracing param to runs.create/stream/wait (#7431) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Adds a `langsmith_tracing` parameter to `runs.create()`, `runs.stream()`, and `runs.wait()` on both async and sync SDK clients - Accepts a `LangSmithTracing` TypedDict with optional `project_name` and `example_id` fields - Maps to the server's existing `langsmith_tracer` payload key, enabling users to route traces to specific LangSmith projects or associate with dataset examples from the SDK ## Test plan - [x] Unit tests verify payload serialization (langsmith_tracing → langsmith_tracer mapping) - [x] Unit tests verify key is excluded when param not provided - [x] Unit tests verify partial configs (project_name only) work - [x] API parity tests pass (async/sync client signatures match) - [x] Full test suite passes (86 tests) - [x] Lint + type check pass Release Notes: Added `langsmith_tracing` parameter to `runs.create()`, `runs.stream()`, and `runs.wait()` in the Python SDK, allowing users to route traces to a specific LangSmith project or associate with a dataset example. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Will Fu-Hinthorn Co-authored-by: Claude Opus 4.6 (1M context) --- libs/sdk-py/langgraph_sdk/_async/runs.py | 21 +++ libs/sdk-py/langgraph_sdk/_sync/runs.py | 21 +++ libs/sdk-py/langgraph_sdk/schema.py | 10 ++ libs/sdk-py/tests/test_langsmith_tracing.py | 146 ++++++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 libs/sdk-py/tests/test_langsmith_tracing.py diff --git a/libs/sdk-py/langgraph_sdk/_async/runs.py b/libs/sdk-py/langgraph_sdk/_async/runs.py index c6d1fa162..587d23e7d 100644 --- a/libs/sdk-py/langgraph_sdk/_async/runs.py +++ b/libs/sdk-py/langgraph_sdk/_async/runs.py @@ -26,6 +26,7 @@ from langgraph_sdk.schema import ( Durability, IfNotExists, Input, + LangSmithTracing, MultitaskStrategy, OnCompletionBehavior, QueryParamTypes, @@ -93,6 +94,7 @@ class RunsClient: multitask_strategy: MultitaskStrategy | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -124,6 +126,7 @@ class RunsClient: multitask_strategy: MultitaskStrategy | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -152,6 +155,7 @@ class RunsClient: if_not_exists: IfNotExists | None = None, webhook: str | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -180,6 +184,7 @@ class RunsClient: if_not_exists: IfNotExists | None = None, webhook: str | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -211,6 +216,7 @@ class RunsClient: multitask_strategy: MultitaskStrategy | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -250,6 +256,8 @@ class RunsClient: Must be either 'reject' (raise error if missing), or 'create' (create new thread). after_seconds: The number of seconds to wait before starting the run. Use to schedule future runs. + langsmith_tracing: LangSmith tracing configuration. Allows routing traces + to a specific project or associating with a dataset example. headers: Optional custom headers to include with the request. params: Optional query parameters to include with the request. on_run_created: Callback when a run is created. @@ -326,6 +334,7 @@ class RunsClient: "on_completion": on_completion, "after_seconds": after_seconds, "durability": durability, + "langsmith_tracer": langsmith_tracing, } endpoint = ( f"/threads/{thread_id}/runs/stream" @@ -371,6 +380,7 @@ class RunsClient: on_completion: OnCompletionBehavior | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -399,6 +409,7 @@ class RunsClient: multitask_strategy: MultitaskStrategy | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -427,6 +438,7 @@ class RunsClient: if_not_exists: IfNotExists | None = None, on_completion: OnCompletionBehavior | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -462,6 +474,8 @@ class RunsClient: Must be either 'reject' (raise error if missing), or 'create' (create new thread). after_seconds: The number of seconds to wait before starting the run. Use to schedule future runs. + langsmith_tracing: LangSmith tracing configuration. Allows routing traces + to a specific project or associating with a dataset example. headers: Optional custom headers to include with the request. on_run_created: Optional callback to call when a run is created. durability: The durability to use for the run. Values are "sync", "async", or "exit". @@ -572,6 +586,7 @@ class RunsClient: "on_completion": on_completion, "after_seconds": after_seconds, "durability": durability, + "langsmith_tracer": langsmith_tracing, } payload = {k: v for k, v in payload.items() if v is not None} @@ -626,6 +641,7 @@ class RunsClient: multitask_strategy: MultitaskStrategy | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, raise_error: bool = True, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, @@ -651,6 +667,7 @@ class RunsClient: on_completion: OnCompletionBehavior | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, raise_error: bool = True, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, @@ -678,6 +695,7 @@ class RunsClient: multitask_strategy: MultitaskStrategy | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, raise_error: bool = True, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, @@ -712,6 +730,8 @@ class RunsClient: Must be either 'reject' (raise error if missing), or 'create' (create new thread). after_seconds: The number of seconds to wait before starting the run. Use to schedule future runs. + langsmith_tracing: LangSmith tracing configuration. Allows routing traces + to a specific project or associating with a dataset example. headers: Optional custom headers to include with the request. on_run_created: Optional callback to call when a run is created. durability: The durability to use for the run. Values are "sync", "async", or "exit". @@ -798,6 +818,7 @@ class RunsClient: "on_completion": on_completion, "after_seconds": after_seconds, "durability": durability, + "langsmith_tracer": langsmith_tracing, } endpoint = ( f"/threads/{thread_id}/runs/wait" if thread_id is not None else "/runs/wait" diff --git a/libs/sdk-py/langgraph_sdk/_sync/runs.py b/libs/sdk-py/langgraph_sdk/_sync/runs.py index 1b52b8113..0a184d320 100644 --- a/libs/sdk-py/langgraph_sdk/_sync/runs.py +++ b/libs/sdk-py/langgraph_sdk/_sync/runs.py @@ -26,6 +26,7 @@ from langgraph_sdk.schema import ( Durability, IfNotExists, Input, + LangSmithTracing, MultitaskStrategy, OnCompletionBehavior, QueryParamTypes, @@ -92,6 +93,7 @@ class SyncRunsClient: multitask_strategy: MultitaskStrategy | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -122,6 +124,7 @@ class SyncRunsClient: multitask_strategy: MultitaskStrategy | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -151,6 +154,7 @@ class SyncRunsClient: if_not_exists: IfNotExists | None = None, webhook: str | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -180,6 +184,7 @@ class SyncRunsClient: if_not_exists: IfNotExists | None = None, webhook: str | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -211,6 +216,7 @@ class SyncRunsClient: multitask_strategy: MultitaskStrategy | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -250,6 +256,8 @@ class SyncRunsClient: Must be either 'reject' (raise error if missing), or 'create' (create new thread). after_seconds: The number of seconds to wait before starting the run. Use to schedule future runs. + langsmith_tracing: LangSmith tracing configuration. Allows routing traces + to a specific project or associating with a dataset example. headers: Optional custom headers to include with the request. on_run_created: Optional callback to call when a run is created. durability: The durability to use for the run. Values are "sync", "async", or "exit". @@ -321,6 +329,7 @@ class SyncRunsClient: "on_completion": on_completion, "after_seconds": after_seconds, "durability": durability, + "langsmith_tracer": langsmith_tracing, } endpoint = ( f"/threads/{thread_id}/runs/stream" @@ -366,6 +375,7 @@ class SyncRunsClient: on_completion: OnCompletionBehavior | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -394,6 +404,7 @@ class SyncRunsClient: multitask_strategy: MultitaskStrategy | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -422,6 +433,7 @@ class SyncRunsClient: if_not_exists: IfNotExists | None = None, on_completion: OnCompletionBehavior | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, on_run_created: Callable[[RunCreateMetadata], None] | None = None, @@ -457,6 +469,8 @@ class SyncRunsClient: Must be either 'reject' (raise error if missing), or 'create' (create new thread). after_seconds: The number of seconds to wait before starting the run. Use to schedule future runs. + langsmith_tracing: LangSmith tracing configuration. Allows routing traces + to a specific project or associating with a dataset example. headers: Optional custom headers to include with the request. on_run_created: Optional callback to call when a run is created. durability: The durability to use for the run. Values are "sync", "async", or "exit". @@ -567,6 +581,7 @@ class SyncRunsClient: "on_completion": on_completion, "after_seconds": after_seconds, "durability": durability, + "langsmith_tracer": langsmith_tracing, } payload = {k: v for k, v in payload.items() if v is not None} @@ -621,6 +636,7 @@ class SyncRunsClient: multitask_strategy: MultitaskStrategy | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, raise_error: bool = True, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, @@ -646,6 +662,7 @@ class SyncRunsClient: on_completion: OnCompletionBehavior | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, raise_error: bool = True, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, @@ -673,6 +690,7 @@ class SyncRunsClient: multitask_strategy: MultitaskStrategy | None = None, if_not_exists: IfNotExists | None = None, after_seconds: int | None = None, + langsmith_tracing: LangSmithTracing | None = None, raise_error: bool = True, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, @@ -707,6 +725,8 @@ class SyncRunsClient: Must be either 'reject' (raise error if missing), or 'create' (create new thread). after_seconds: The number of seconds to wait before starting the run. Use to schedule future runs. + langsmith_tracing: LangSmith tracing configuration. Allows routing traces + to a specific project or associating with a dataset example. raise_error: Whether to raise an error if the run fails. headers: Optional custom headers to include with the request. on_run_created: Optional callback to call when a run is created. @@ -796,6 +816,7 @@ class SyncRunsClient: "after_seconds": after_seconds, "raise_error": raise_error, "durability": durability, + "langsmith_tracer": langsmith_tracing, } def on_response(res: httpx.Response): diff --git a/libs/sdk-py/langgraph_sdk/schema.py b/libs/sdk-py/langgraph_sdk/schema.py index 6fffcec34..16b197db3 100644 --- a/libs/sdk-py/langgraph_sdk/schema.py +++ b/libs/sdk-py/langgraph_sdk/schema.py @@ -107,6 +107,16 @@ Durability = Literal["sync", "async", "exit"] - `"async"`: Changes are persisted asynchronously while the next step executes. - `"exit"`: Changes are persisted only when the graph exits.""" + +class LangSmithTracing(TypedDict, total=False): + """Configuration for LangSmith tracing.""" + + project_name: str + """The LangSmith project name to trace to.""" + example_id: str + """The LangSmith example/dataset ID to associate with the trace.""" + + All = Literal["*"] """Represents a wildcard or 'all' selector.""" diff --git a/libs/sdk-py/tests/test_langsmith_tracing.py b/libs/sdk-py/tests/test_langsmith_tracing.py new file mode 100644 index 000000000..db0fc2205 --- /dev/null +++ b/libs/sdk-py/tests/test_langsmith_tracing.py @@ -0,0 +1,146 @@ +"""Test that langsmith_tracing parameter is correctly mapped to langsmith_tracer in payloads.""" + +from __future__ import annotations + +from typing import Any +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from langgraph_sdk.schema import LangSmithTracing + + +@pytest.fixture +def tracing_config() -> LangSmithTracing: + return LangSmithTracing( + project_name="my-project", + example_id="example-123", + ) + + +class TestLangSmithTracingPayload: + """Verify langsmith_tracing param maps to langsmith_tracer in request payload.""" + + @pytest.mark.asyncio + async def test_async_create_includes_langsmith_tracer(self, tracing_config): + """Test that async create sends langsmith_tracer in payload.""" + from langgraph_sdk._async.runs import RunsClient + + captured: dict[str, Any] = {} + + async def mock_post(_path, *, json=None, **_kwargs): + captured["json"] = json + return {"run_id": "r1", "status": "pending"} + + http = MagicMock() + http.post = AsyncMock(side_effect=mock_post) + client = RunsClient(http) + + await client.create( + thread_id="t1", + assistant_id="a1", + langsmith_tracing=tracing_config, + ) + + assert "langsmith_tracer" in captured["json"] + assert captured["json"]["langsmith_tracer"] == { + "project_name": "my-project", + "example_id": "example-123", + } + + def test_sync_create_includes_langsmith_tracer(self, tracing_config): + """Test that sync create sends langsmith_tracer in payload.""" + from langgraph_sdk._sync.runs import SyncRunsClient + + captured: dict[str, Any] = {} + + def mock_post(_path, *, json=None, **_kwargs): + captured["json"] = json + return {"run_id": "r1", "status": "pending"} + + http = MagicMock() + http.post = MagicMock(side_effect=mock_post) + client = SyncRunsClient(http) + + client.create( + thread_id="t1", + assistant_id="a1", + langsmith_tracing=tracing_config, + ) + + assert "langsmith_tracer" in captured["json"] + assert captured["json"]["langsmith_tracer"] == { + "project_name": "my-project", + "example_id": "example-123", + } + + def test_sync_wait_includes_langsmith_tracer(self, tracing_config): + """Test that sync wait sends langsmith_tracer in payload.""" + from langgraph_sdk._sync.runs import SyncRunsClient + + captured: dict[str, Any] = {} + + def mock_request_reconnect(_path, _method, *, json=None, **_kwargs): + captured["json"] = json + return {"messages": []} + + http = MagicMock() + http.request_reconnect = MagicMock(side_effect=mock_request_reconnect) + client = SyncRunsClient(http) + + client.wait( + thread_id="t1", + assistant_id="a1", + langsmith_tracing=tracing_config, + ) + + assert "langsmith_tracer" in captured["json"] + assert captured["json"]["langsmith_tracer"] == { + "project_name": "my-project", + "example_id": "example-123", + } + + def test_create_without_langsmith_tracing_excludes_key(self): + """Test that langsmith_tracer is not in payload when not provided.""" + from langgraph_sdk._sync.runs import SyncRunsClient + + captured: dict[str, Any] = {} + + def mock_post(_path, *, json=None, **_kwargs): + captured["json"] = json + return {"run_id": "r1", "status": "pending"} + + http = MagicMock() + http.post = MagicMock(side_effect=mock_post) + client = SyncRunsClient(http) + + client.create( + thread_id="t1", + assistant_id="a1", + ) + + assert "langsmith_tracer" not in captured["json"] + + def test_langsmith_tracing_project_name_only(self): + """Test that langsmith_tracing works with only project_name.""" + from langgraph_sdk._sync.runs import SyncRunsClient + + captured: dict[str, Any] = {} + + def mock_post(_path, *, json=None, **_kwargs): + captured["json"] = json + return {"run_id": "r1", "status": "pending"} + + http = MagicMock() + http.post = MagicMock(side_effect=mock_post) + client = SyncRunsClient(http) + + client.create( + thread_id="t1", + assistant_id="a1", + langsmith_tracing={"project_name": "my-project"}, + ) + + assert captured["json"]["langsmith_tracer"] == { + "project_name": "my-project", + }