diff --git a/libs/sdk-py/langgraph_sdk/_async/threads.py b/libs/sdk-py/langgraph_sdk/_async/threads.py index a4439c22d..d43d352a4 100644 --- a/libs/sdk-py/langgraph_sdk/_async/threads.py +++ b/libs/sdk-py/langgraph_sdk/_async/threads.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections.abc import AsyncIterator, Mapping, Sequence -from typing import Any +from typing import Any, Literal, overload from langgraph_sdk._async.http import HttpClient from langgraph_sdk.schema import ( @@ -172,15 +172,52 @@ class ThreadsClient: "/threads", json=payload, headers=headers, params=params ) + @overload async def update( self, thread_id: str, *, metadata: Mapping[str, Any], ttl: int | Mapping[str, Any] | None = None, + return_minimal: Literal[False] = False, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, - ) -> Thread: + ) -> Thread: ... + + @overload + async def update( + self, + thread_id: str, + *, + metadata: Mapping[str, Any], + ttl: int | Mapping[str, Any] | None = None, + return_minimal: Literal[True], + headers: Mapping[str, str] | None = None, + params: QueryParamTypes | None = None, + ) -> None: ... + + @overload + async def update( + self, + thread_id: str, + *, + metadata: Mapping[str, Any], + ttl: int | Mapping[str, Any] | None = None, + return_minimal: bool, + headers: Mapping[str, str] | None = None, + params: QueryParamTypes | None = None, + ) -> Thread | None: ... + + async def update( + self, + thread_id: str, + *, + metadata: Mapping[str, Any], + ttl: int | Mapping[str, Any] | None = None, + return_minimal: bool = False, + headers: Mapping[str, str] | None = None, + params: QueryParamTypes | None = None, + ) -> Thread | None: """Update a thread. Args: @@ -189,11 +226,12 @@ class ThreadsClient: ttl: Optional time-to-live in minutes for the thread. You can pass an integer (minutes) or a mapping with keys `ttl` and optional `strategy` (defaults to "delete"). + return_minimal: If `True`, request a 204 response with no body. headers: Optional custom headers to include with the request. params: Optional query parameters to include with the request. Returns: - The created thread. + The updated thread, or `None` when `return_minimal=True`. ???+ example "Example Usage" @@ -212,10 +250,13 @@ class ThreadsClient: payload["ttl"] = {"ttl": ttl, "strategy": "delete"} else: payload["ttl"] = ttl + request_headers = dict(headers or {}) + if return_minimal: + request_headers["Prefer"] = "return=minimal" return await self.http.patch( f"/threads/{thread_id}", json=payload, - headers=headers, + headers=request_headers or None, params=params, ) diff --git a/libs/sdk-py/langgraph_sdk/_sync/threads.py b/libs/sdk-py/langgraph_sdk/_sync/threads.py index b1ebf6b59..d18aabecb 100644 --- a/libs/sdk-py/langgraph_sdk/_sync/threads.py +++ b/libs/sdk-py/langgraph_sdk/_sync/threads.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections.abc import Iterator, Mapping, Sequence -from typing import Any +from typing import Any, Literal, overload from langgraph_sdk._sync.http import SyncHttpClient from langgraph_sdk.schema import ( @@ -168,15 +168,52 @@ class SyncThreadsClient: return self.http.post("/threads", json=payload, headers=headers, params=params) + @overload def update( self, thread_id: str, *, metadata: Mapping[str, Any], ttl: int | Mapping[str, Any] | None = None, + return_minimal: Literal[False] = False, headers: Mapping[str, str] | None = None, params: QueryParamTypes | None = None, - ) -> Thread: + ) -> Thread: ... + + @overload + def update( + self, + thread_id: str, + *, + metadata: Mapping[str, Any], + ttl: int | Mapping[str, Any] | None = None, + return_minimal: Literal[True], + headers: Mapping[str, str] | None = None, + params: QueryParamTypes | None = None, + ) -> None: ... + + @overload + def update( + self, + thread_id: str, + *, + metadata: Mapping[str, Any], + ttl: int | Mapping[str, Any] | None = None, + return_minimal: bool, + headers: Mapping[str, str] | None = None, + params: QueryParamTypes | None = None, + ) -> Thread | None: ... + + def update( + self, + thread_id: str, + *, + metadata: Mapping[str, Any], + ttl: int | Mapping[str, Any] | None = None, + return_minimal: bool = False, + headers: Mapping[str, str] | None = None, + params: QueryParamTypes | None = None, + ) -> Thread | None: """Update a thread. Args: @@ -185,11 +222,12 @@ class SyncThreadsClient: ttl: Optional time-to-live in minutes for the thread. You can pass an integer (minutes) or a mapping with keys `ttl` and optional `strategy` (defaults to "delete"). + return_minimal: If `True`, request a 204 response with no body. headers: Optional custom headers to include with the request. params: Optional query parameters to include with the request. Returns: - The created `Thread`. + The updated `Thread`, or `None` when `return_minimal=True`. ???+ example "Example Usage" @@ -208,10 +246,13 @@ class SyncThreadsClient: payload["ttl"] = {"ttl": ttl, "strategy": "delete"} else: payload["ttl"] = ttl + request_headers = dict(headers or {}) + if return_minimal: + request_headers["Prefer"] = "return=minimal" return self.http.patch( f"/threads/{thread_id}", json=payload, - headers=headers, + headers=request_headers or None, params=params, ) diff --git a/libs/sdk-py/tests/test_threads_client.py b/libs/sdk-py/tests/test_threads_client.py new file mode 100644 index 000000000..34e8040be --- /dev/null +++ b/libs/sdk-py/tests/test_threads_client.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import json + +import httpx +import pytest + +from langgraph_sdk.client import ( + HttpClient, + SyncHttpClient, + SyncThreadsClient, + ThreadsClient, +) + + +@pytest.mark.asyncio +async def test_async_threads_update_return_minimal(): + async def handler(request: httpx.Request) -> httpx.Response: + assert request.method == "PATCH" + assert request.url.path == "/threads/thread_123" + assert request.headers["Prefer"] == "return=minimal" + assert json.loads(request.content) == {"metadata": {"foo": "bar"}} + return httpx.Response(204) + + transport = httpx.MockTransport(handler) + async with httpx.AsyncClient( + transport=transport, base_url="https://example.com" + ) as client: + http_client = HttpClient(client) + threads_client = ThreadsClient(http_client) + result = await threads_client.update( + "thread_123", + metadata={"foo": "bar"}, + return_minimal=True, + ) + + assert result is None + + +def test_sync_threads_update_return_minimal(): + def handler(request: httpx.Request) -> httpx.Response: + assert request.method == "PATCH" + assert request.url.path == "/threads/thread_123" + assert request.headers["Prefer"] == "return=minimal" + assert json.loads(request.content) == {"metadata": {"foo": "bar"}} + return httpx.Response(204) + + transport = httpx.MockTransport(handler) + with httpx.Client(transport=transport, base_url="https://example.com") as client: + http_client = SyncHttpClient(client) + threads_client = SyncThreadsClient(http_client) + result = threads_client.update( + "thread_123", + metadata={"foo": "bar"}, + return_minimal=True, + ) + + assert result is None