initial commit
This commit is contained in:
20
venv/Lib/site-packages/langgraph_sdk/_sync/__init__.py
Normal file
20
venv/Lib/site-packages/langgraph_sdk/_sync/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""Sync client exports."""
|
||||
|
||||
from langgraph_sdk._sync.assistants import SyncAssistantsClient
|
||||
from langgraph_sdk._sync.client import SyncLangGraphClient, get_sync_client
|
||||
from langgraph_sdk._sync.cron import SyncCronClient
|
||||
from langgraph_sdk._sync.http import SyncHttpClient
|
||||
from langgraph_sdk._sync.runs import SyncRunsClient
|
||||
from langgraph_sdk._sync.store import SyncStoreClient
|
||||
from langgraph_sdk._sync.threads import SyncThreadsClient
|
||||
|
||||
__all__ = [
|
||||
"SyncAssistantsClient",
|
||||
"SyncCronClient",
|
||||
"SyncHttpClient",
|
||||
"SyncLangGraphClient",
|
||||
"SyncRunsClient",
|
||||
"SyncStoreClient",
|
||||
"SyncThreadsClient",
|
||||
"get_sync_client",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
731
venv/Lib/site-packages/langgraph_sdk/_sync/assistants.py
Normal file
731
venv/Lib/site-packages/langgraph_sdk/_sync/assistants.py
Normal file
@@ -0,0 +1,731 @@
|
||||
"""Synchronous client for managing assistants in LangGraph."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any, Literal, cast, overload
|
||||
|
||||
import httpx
|
||||
|
||||
from langgraph_sdk._sync.http import SyncHttpClient
|
||||
from langgraph_sdk.schema import (
|
||||
Assistant,
|
||||
AssistantSelectField,
|
||||
AssistantSortBy,
|
||||
AssistantsSearchResponse,
|
||||
AssistantVersion,
|
||||
Config,
|
||||
Context,
|
||||
GraphSchema,
|
||||
Json,
|
||||
OnConflictBehavior,
|
||||
QueryParamTypes,
|
||||
SortOrder,
|
||||
Subgraphs,
|
||||
)
|
||||
|
||||
|
||||
class SyncAssistantsClient:
|
||||
"""Client for managing assistants in LangGraph synchronously.
|
||||
|
||||
This class provides methods to interact with assistants, which are versioned configurations of your graph.
|
||||
|
||||
???+ example "Example"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
assistant = client.assistants.get("assistant_id_123")
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(self, http: SyncHttpClient) -> None:
|
||||
self.http = http
|
||||
|
||||
def get(
|
||||
self,
|
||||
assistant_id: str,
|
||||
*,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> Assistant:
|
||||
"""Get an assistant by ID.
|
||||
|
||||
Args:
|
||||
assistant_id: The ID of the assistant to get OR the name of the graph (to use the default assistant).
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
`Assistant` Object.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
assistant = client.assistants.get(
|
||||
assistant_id="my_assistant_id"
|
||||
)
|
||||
print(assistant)
|
||||
```
|
||||
|
||||
```shell
|
||||
----------------------------------------------------
|
||||
|
||||
{
|
||||
'assistant_id': 'my_assistant_id',
|
||||
'graph_id': 'agent',
|
||||
'created_at': '2024-06-25T17:10:33.109781+00:00',
|
||||
'updated_at': '2024-06-25T17:10:33.109781+00:00',
|
||||
'config': {},
|
||||
'context': {},
|
||||
'metadata': {'created_by': 'system'}
|
||||
}
|
||||
```
|
||||
|
||||
"""
|
||||
return self.http.get(
|
||||
f"/assistants/{assistant_id}", headers=headers, params=params
|
||||
)
|
||||
|
||||
def get_graph(
|
||||
self,
|
||||
assistant_id: str,
|
||||
*,
|
||||
xray: int | bool = False,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Get the graph of an assistant by ID.
|
||||
|
||||
Args:
|
||||
assistant_id: The ID of the assistant to get the graph of.
|
||||
xray: Include graph representation of subgraphs. If an integer value is provided, only subgraphs with a depth less than or equal to the value will be included.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
The graph information for the assistant in JSON format.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
graph_info = client.assistants.get_graph(
|
||||
assistant_id="my_assistant_id"
|
||||
)
|
||||
print(graph_info)
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
{
|
||||
'nodes':
|
||||
[
|
||||
{'id': '__start__', 'type': 'schema', 'data': '__start__'},
|
||||
{'id': '__end__', 'type': 'schema', 'data': '__end__'},
|
||||
{'id': 'agent','type': 'runnable','data': {'id': ['langgraph', 'utils', 'RunnableCallable'],'name': 'agent'}},
|
||||
],
|
||||
'edges':
|
||||
[
|
||||
{'source': '__start__', 'target': 'agent'},
|
||||
{'source': 'agent','target': '__end__'}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
"""
|
||||
query_params = {"xray": xray}
|
||||
if params:
|
||||
query_params.update(params)
|
||||
return self.http.get(
|
||||
f"/assistants/{assistant_id}/graph", params=query_params, headers=headers
|
||||
)
|
||||
|
||||
def get_schemas(
|
||||
self,
|
||||
assistant_id: str,
|
||||
*,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> GraphSchema:
|
||||
"""Get the schemas of an assistant by ID.
|
||||
|
||||
Args:
|
||||
assistant_id: The ID of the assistant to get the schema of.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
GraphSchema: The graph schema for the assistant.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
schema = client.assistants.get_schemas(
|
||||
assistant_id="my_assistant_id"
|
||||
)
|
||||
print(schema)
|
||||
```
|
||||
```shell
|
||||
----------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
{
|
||||
'graph_id': 'agent',
|
||||
'state_schema':
|
||||
{
|
||||
'title': 'LangGraphInput',
|
||||
'$ref': '#/definitions/AgentState',
|
||||
'definitions':
|
||||
{
|
||||
'BaseMessage':
|
||||
{
|
||||
'title': 'BaseMessage',
|
||||
'description': 'Base abstract Message class. Messages are the inputs and outputs of ChatModels.',
|
||||
'type': 'object',
|
||||
'properties':
|
||||
{
|
||||
'content':
|
||||
{
|
||||
'title': 'Content',
|
||||
'anyOf': [
|
||||
{'type': 'string'},
|
||||
{'type': 'array','items': {'anyOf': [{'type': 'string'}, {'type': 'object'}]}}
|
||||
]
|
||||
},
|
||||
'additional_kwargs':
|
||||
{
|
||||
'title': 'Additional Kwargs',
|
||||
'type': 'object'
|
||||
},
|
||||
'response_metadata':
|
||||
{
|
||||
'title': 'Response Metadata',
|
||||
'type': 'object'
|
||||
},
|
||||
'type':
|
||||
{
|
||||
'title': 'Type',
|
||||
'type': 'string'
|
||||
},
|
||||
'name':
|
||||
{
|
||||
'title': 'Name',
|
||||
'type': 'string'
|
||||
},
|
||||
'id':
|
||||
{
|
||||
'title': 'Id',
|
||||
'type': 'string'
|
||||
}
|
||||
},
|
||||
'required': ['content', 'type']
|
||||
},
|
||||
'AgentState':
|
||||
{
|
||||
'title': 'AgentState',
|
||||
'type': 'object',
|
||||
'properties':
|
||||
{
|
||||
'messages':
|
||||
{
|
||||
'title': 'Messages',
|
||||
'type': 'array',
|
||||
'items': {'$ref': '#/definitions/BaseMessage'}
|
||||
}
|
||||
},
|
||||
'required': ['messages']
|
||||
}
|
||||
}
|
||||
},
|
||||
'config_schema':
|
||||
{
|
||||
'title': 'Configurable',
|
||||
'type': 'object',
|
||||
'properties':
|
||||
{
|
||||
'model_name':
|
||||
{
|
||||
'title': 'Model Name',
|
||||
'enum': ['anthropic', 'openai'],
|
||||
'type': 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
'context_schema':
|
||||
{
|
||||
'title': 'Context',
|
||||
'type': 'object',
|
||||
'properties':
|
||||
{
|
||||
'model_name':
|
||||
{
|
||||
'title': 'Model Name',
|
||||
'enum': ['anthropic', 'openai'],
|
||||
'type': 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
"""
|
||||
return self.http.get(
|
||||
f"/assistants/{assistant_id}/schemas", headers=headers, params=params
|
||||
)
|
||||
|
||||
def get_subgraphs(
|
||||
self,
|
||||
assistant_id: str,
|
||||
namespace: str | None = None,
|
||||
recurse: bool = False,
|
||||
*,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> Subgraphs:
|
||||
"""Get the schemas of an assistant by ID.
|
||||
|
||||
Args:
|
||||
assistant_id: The ID of the assistant to get the schema of.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
Subgraphs: The graph schema for the assistant.
|
||||
|
||||
"""
|
||||
get_params = {"recurse": recurse}
|
||||
if params:
|
||||
get_params = {**get_params, **dict(params)}
|
||||
if namespace is not None:
|
||||
return self.http.get(
|
||||
f"/assistants/{assistant_id}/subgraphs/{namespace}",
|
||||
params=get_params,
|
||||
headers=headers,
|
||||
)
|
||||
else:
|
||||
return self.http.get(
|
||||
f"/assistants/{assistant_id}/subgraphs",
|
||||
params=get_params,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
def create(
|
||||
self,
|
||||
graph_id: str | None,
|
||||
config: Config | None = None,
|
||||
*,
|
||||
context: Context | None = None,
|
||||
metadata: Json = None,
|
||||
assistant_id: str | None = None,
|
||||
if_exists: OnConflictBehavior | None = None,
|
||||
name: str | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
description: str | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> Assistant:
|
||||
"""Create a new assistant.
|
||||
|
||||
Useful when graph is configurable and you want to create different assistants based on different configurations.
|
||||
|
||||
Args:
|
||||
graph_id: The ID of the graph the assistant should use. The graph ID is normally set in your langgraph.json configuration.
|
||||
config: Configuration to use for the graph.
|
||||
context: Static context to add to the assistant.
|
||||
!!! version-added "Added in version 0.6.0"
|
||||
metadata: Metadata to add to assistant.
|
||||
assistant_id: Assistant ID to use, will default to a random UUID if not provided.
|
||||
if_exists: How to handle duplicate creation. Defaults to 'raise' under the hood.
|
||||
Must be either 'raise' (raise error if duplicate), or 'do_nothing' (return existing assistant).
|
||||
name: The name of the assistant. Defaults to 'Untitled' under the hood.
|
||||
headers: Optional custom headers to include with the request.
|
||||
description: Optional description of the assistant.
|
||||
The description field is available for langgraph-api server version>=0.0.45
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
The created assistant.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
assistant = client.assistants.create(
|
||||
graph_id="agent",
|
||||
context={"model_name": "openai"},
|
||||
metadata={"number":1},
|
||||
assistant_id="my-assistant-id",
|
||||
if_exists="do_nothing",
|
||||
name="my_name"
|
||||
)
|
||||
```
|
||||
"""
|
||||
payload: dict[str, Any] = {
|
||||
"graph_id": graph_id,
|
||||
}
|
||||
if config:
|
||||
payload["config"] = config
|
||||
if context:
|
||||
payload["context"] = context
|
||||
if metadata:
|
||||
payload["metadata"] = metadata
|
||||
if assistant_id:
|
||||
payload["assistant_id"] = assistant_id
|
||||
if if_exists:
|
||||
payload["if_exists"] = if_exists
|
||||
if name:
|
||||
payload["name"] = name
|
||||
if description:
|
||||
payload["description"] = description
|
||||
return self.http.post(
|
||||
"/assistants", json=payload, headers=headers, params=params
|
||||
)
|
||||
|
||||
def update(
|
||||
self,
|
||||
assistant_id: str,
|
||||
*,
|
||||
graph_id: str | None = None,
|
||||
config: Config | None = None,
|
||||
context: Context | None = None,
|
||||
metadata: Json = None,
|
||||
name: str | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
description: str | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> Assistant:
|
||||
"""Update an assistant.
|
||||
|
||||
Use this to point to a different graph, update the configuration, or change the metadata of an assistant.
|
||||
|
||||
Args:
|
||||
assistant_id: Assistant to update.
|
||||
graph_id: The ID of the graph the assistant should use.
|
||||
The graph ID is normally set in your langgraph.json configuration. If `None`, assistant will keep pointing to same graph.
|
||||
config: Configuration to use for the graph.
|
||||
context: Static context to add to the assistant.
|
||||
!!! version-added "Added in version 0.6.0"
|
||||
metadata: Metadata to merge with existing assistant metadata.
|
||||
name: The new name for the assistant.
|
||||
headers: Optional custom headers to include with the request.
|
||||
description: Optional description of the assistant.
|
||||
The description field is available for langgraph-api server version>=0.0.45
|
||||
|
||||
Returns:
|
||||
The updated assistant.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
assistant = client.assistants.update(
|
||||
assistant_id='e280dad7-8618-443f-87f1-8e41841c180f',
|
||||
graph_id="other-graph",
|
||||
context={"model_name": "anthropic"},
|
||||
metadata={"number":2}
|
||||
)
|
||||
```
|
||||
"""
|
||||
payload: dict[str, Any] = {}
|
||||
if graph_id:
|
||||
payload["graph_id"] = graph_id
|
||||
if config is not None:
|
||||
payload["config"] = config
|
||||
if context is not None:
|
||||
payload["context"] = context
|
||||
if metadata:
|
||||
payload["metadata"] = metadata
|
||||
if name:
|
||||
payload["name"] = name
|
||||
if description:
|
||||
payload["description"] = description
|
||||
return self.http.patch(
|
||||
f"/assistants/{assistant_id}",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
params=params,
|
||||
)
|
||||
|
||||
def delete(
|
||||
self,
|
||||
assistant_id: str,
|
||||
*,
|
||||
delete_threads: bool = False,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> None:
|
||||
"""Delete an assistant.
|
||||
|
||||
Args:
|
||||
assistant_id: The assistant ID to delete.
|
||||
delete_threads: If true, delete all threads with `metadata.assistant_id`
|
||||
matching this assistant, along with runs and checkpoints belonging to
|
||||
those threads.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
`None`
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
client.assistants.delete(
|
||||
assistant_id="my_assistant_id"
|
||||
)
|
||||
```
|
||||
|
||||
"""
|
||||
query_params: dict[str, Any] = {}
|
||||
if delete_threads:
|
||||
query_params["delete_threads"] = True
|
||||
if params:
|
||||
query_params.update(params)
|
||||
self.http.delete(
|
||||
f"/assistants/{assistant_id}",
|
||||
headers=headers,
|
||||
params=query_params or None,
|
||||
)
|
||||
|
||||
@overload
|
||||
def search(
|
||||
self,
|
||||
*,
|
||||
metadata: Json = None,
|
||||
graph_id: str | None = None,
|
||||
name: str | None = None,
|
||||
limit: int = 10,
|
||||
offset: int = 0,
|
||||
sort_by: AssistantSortBy | None = None,
|
||||
sort_order: SortOrder | None = None,
|
||||
select: list[AssistantSelectField] | None = None,
|
||||
response_format: Literal["object"],
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> AssistantsSearchResponse: ...
|
||||
|
||||
@overload
|
||||
def search(
|
||||
self,
|
||||
*,
|
||||
metadata: Json = None,
|
||||
graph_id: str | None = None,
|
||||
name: str | None = None,
|
||||
limit: int = 10,
|
||||
offset: int = 0,
|
||||
sort_by: AssistantSortBy | None = None,
|
||||
sort_order: SortOrder | None = None,
|
||||
select: list[AssistantSelectField] | None = None,
|
||||
response_format: Literal["array"] = "array",
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> list[Assistant]: ...
|
||||
|
||||
def search(
|
||||
self,
|
||||
*,
|
||||
metadata: Json = None,
|
||||
graph_id: str | None = None,
|
||||
name: str | None = None,
|
||||
limit: int = 10,
|
||||
offset: int = 0,
|
||||
sort_by: AssistantSortBy | None = None,
|
||||
sort_order: SortOrder | None = None,
|
||||
select: list[AssistantSelectField] | None = None,
|
||||
response_format: Literal["array", "object"] = "array",
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> AssistantsSearchResponse | list[Assistant]:
|
||||
"""Search for assistants.
|
||||
|
||||
Args:
|
||||
metadata: Metadata to filter by. Exact match filter for each KV pair.
|
||||
graph_id: The ID of the graph to filter by.
|
||||
The graph ID is normally set in your langgraph.json configuration.
|
||||
name: The name of the assistant to filter by.
|
||||
The filtering logic will match assistants where 'name' is a substring (case insensitive) of the assistant name.
|
||||
limit: The maximum number of results to return.
|
||||
offset: The number of results to skip.
|
||||
sort_by: The field to sort by.
|
||||
sort_order: The order to sort by.
|
||||
select: Specific assistant fields to include in the response.
|
||||
response_format: Controls the response shape. Use `"array"` (default)
|
||||
to return a bare list of assistants, or `"object"` to return
|
||||
a mapping containing assistants plus pagination metadata.
|
||||
Defaults to "array", though this default will be changed to "object" in a future release.
|
||||
headers: Optional custom headers to include with the request.
|
||||
|
||||
Returns:
|
||||
A list of assistants (when `response_format="array"`) or a mapping
|
||||
with the assistants and the next pagination cursor (when
|
||||
`response_format="object"`).
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
response = client.assistants.search(
|
||||
metadata = {"name":"my_name"},
|
||||
graph_id="my_graph_id",
|
||||
limit=5,
|
||||
offset=5,
|
||||
response_format="object",
|
||||
)
|
||||
assistants = response["assistants"]
|
||||
next_cursor = response["next"]
|
||||
```
|
||||
"""
|
||||
if response_format not in ("array", "object"):
|
||||
raise ValueError("response_format must be 'array' or 'object'")
|
||||
payload: dict[str, Any] = {
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
}
|
||||
if metadata:
|
||||
payload["metadata"] = metadata
|
||||
if graph_id:
|
||||
payload["graph_id"] = graph_id
|
||||
if name:
|
||||
payload["name"] = name
|
||||
if sort_by:
|
||||
payload["sort_by"] = sort_by
|
||||
if sort_order:
|
||||
payload["sort_order"] = sort_order
|
||||
if select:
|
||||
payload["select"] = select
|
||||
next_cursor: str | None = None
|
||||
|
||||
def capture_pagination(response: httpx.Response) -> None:
|
||||
nonlocal next_cursor
|
||||
next_cursor = response.headers.get("X-Pagination-Next")
|
||||
|
||||
assistants = cast(
|
||||
list[Assistant],
|
||||
self.http.post(
|
||||
"/assistants/search",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
params=params,
|
||||
on_response=capture_pagination if response_format == "object" else None,
|
||||
),
|
||||
)
|
||||
if response_format == "object":
|
||||
return {"assistants": assistants, "next": next_cursor}
|
||||
return assistants
|
||||
|
||||
def count(
|
||||
self,
|
||||
*,
|
||||
metadata: Json = None,
|
||||
graph_id: str | None = None,
|
||||
name: str | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> int:
|
||||
"""Count assistants matching filters.
|
||||
|
||||
Args:
|
||||
metadata: Metadata to filter by. Exact match for each key/value.
|
||||
graph_id: Optional graph id to filter by.
|
||||
name: Optional name to filter by.
|
||||
The filtering logic will match assistants where 'name' is a substring (case insensitive) of the assistant name.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
int: Number of assistants matching the criteria.
|
||||
"""
|
||||
payload: dict[str, Any] = {}
|
||||
if metadata:
|
||||
payload["metadata"] = metadata
|
||||
if graph_id:
|
||||
payload["graph_id"] = graph_id
|
||||
if name:
|
||||
payload["name"] = name
|
||||
return self.http.post(
|
||||
"/assistants/count", json=payload, headers=headers, params=params
|
||||
)
|
||||
|
||||
def get_versions(
|
||||
self,
|
||||
assistant_id: str,
|
||||
metadata: Json = None,
|
||||
limit: int = 10,
|
||||
offset: int = 0,
|
||||
*,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> list[AssistantVersion]:
|
||||
"""List all versions of an assistant.
|
||||
|
||||
Args:
|
||||
assistant_id: The assistant ID to get versions for.
|
||||
metadata: Metadata to filter versions by. Exact match filter for each KV pair.
|
||||
limit: The maximum number of versions to return.
|
||||
offset: The number of versions to skip.
|
||||
headers: Optional custom headers to include with the request.
|
||||
|
||||
Returns:
|
||||
A list of assistants.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
assistant_versions = client.assistants.get_versions(
|
||||
assistant_id="my_assistant_id"
|
||||
)
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
}
|
||||
if metadata:
|
||||
payload["metadata"] = metadata
|
||||
return self.http.post(
|
||||
f"/assistants/{assistant_id}/versions",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
params=params,
|
||||
)
|
||||
|
||||
def set_latest(
|
||||
self,
|
||||
assistant_id: str,
|
||||
version: int,
|
||||
*,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> Assistant:
|
||||
"""Change the version of an assistant.
|
||||
|
||||
Args:
|
||||
assistant_id: The assistant ID to delete.
|
||||
version: The version to change to.
|
||||
headers: Optional custom headers to include with the request.
|
||||
|
||||
Returns:
|
||||
`Assistant` Object.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
new_version_assistant = client.assistants.set_latest(
|
||||
assistant_id="my_assistant_id",
|
||||
version=3
|
||||
)
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
payload: dict[str, Any] = {"version": version}
|
||||
|
||||
return self.http.post(
|
||||
f"/assistants/{assistant_id}/latest",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
params=params,
|
||||
)
|
||||
127
venv/Lib/site-packages/langgraph_sdk/_sync/client.py
Normal file
127
venv/Lib/site-packages/langgraph_sdk/_sync/client.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""Sync LangGraph client."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from types import TracebackType
|
||||
|
||||
import httpx
|
||||
|
||||
from langgraph_sdk._shared.types import TimeoutTypes
|
||||
from langgraph_sdk._shared.utilities import NOT_PROVIDED, _get_headers
|
||||
from langgraph_sdk._sync.assistants import SyncAssistantsClient
|
||||
from langgraph_sdk._sync.cron import SyncCronClient
|
||||
from langgraph_sdk._sync.http import SyncHttpClient
|
||||
from langgraph_sdk._sync.runs import SyncRunsClient
|
||||
from langgraph_sdk._sync.store import SyncStoreClient
|
||||
from langgraph_sdk._sync.threads import SyncThreadsClient
|
||||
|
||||
|
||||
def get_sync_client(
|
||||
*,
|
||||
url: str | None = None,
|
||||
api_key: str | None = NOT_PROVIDED,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
timeout: TimeoutTypes | None = None,
|
||||
) -> SyncLangGraphClient:
|
||||
"""Get a synchronous LangGraphClient instance.
|
||||
|
||||
Args:
|
||||
url: The URL of the LangGraph API.
|
||||
api_key: API key for authentication. Can be:
|
||||
- A string: use this exact API key
|
||||
- `None`: explicitly skip loading from environment variables
|
||||
- Not provided (default): auto-load from environment in this order:
|
||||
1. `LANGGRAPH_API_KEY`
|
||||
2. `LANGSMITH_API_KEY`
|
||||
3. `LANGCHAIN_API_KEY`
|
||||
headers: Optional custom headers
|
||||
timeout: Optional timeout configuration for the HTTP client.
|
||||
Accepts an httpx.Timeout instance, a float (seconds), or a tuple of timeouts.
|
||||
Tuple format is (connect, read, write, pool)
|
||||
If not provided, defaults to connect=5s, read=300s, write=300s, and pool=5s.
|
||||
Returns:
|
||||
SyncLangGraphClient: The top-level synchronous client for accessing AssistantsClient,
|
||||
ThreadsClient, RunsClient, and CronClient.
|
||||
|
||||
???+ example "Example"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_sync_client
|
||||
|
||||
# get top-level synchronous LangGraphClient
|
||||
client = get_sync_client(url="http://localhost:8123")
|
||||
|
||||
# example usage: client.<model>.<method_name>()
|
||||
assistant = client.assistants.get(assistant_id="some_uuid")
|
||||
```
|
||||
|
||||
???+ example "Skip auto-loading API key from environment:"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_sync_client
|
||||
|
||||
# Don't load API key from environment variables
|
||||
client = get_sync_client(
|
||||
url="http://localhost:8123",
|
||||
api_key=None
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
if url is None:
|
||||
url = "http://localhost:8123"
|
||||
|
||||
transport = httpx.HTTPTransport(retries=5)
|
||||
client = httpx.Client(
|
||||
base_url=url,
|
||||
transport=transport,
|
||||
timeout=(
|
||||
httpx.Timeout(timeout) # type: ignore[arg-type]
|
||||
if timeout is not None
|
||||
else httpx.Timeout(connect=5, read=300, write=300, pool=5)
|
||||
),
|
||||
headers=_get_headers(api_key, headers),
|
||||
)
|
||||
return SyncLangGraphClient(client)
|
||||
|
||||
|
||||
class SyncLangGraphClient:
|
||||
"""Synchronous client for interacting with the LangGraph API.
|
||||
|
||||
This class provides synchronous access to LangGraph API endpoints for managing
|
||||
assistants, threads, runs, cron jobs, and data storage.
|
||||
|
||||
???+ example "Example"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
assistant = client.assistants.get("asst_123")
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(self, client: httpx.Client) -> None:
|
||||
self.http = SyncHttpClient(client)
|
||||
self.assistants = SyncAssistantsClient(self.http)
|
||||
self.threads = SyncThreadsClient(self.http)
|
||||
self.runs = SyncRunsClient(self.http)
|
||||
self.crons = SyncCronClient(self.http)
|
||||
self.store = SyncStoreClient(self.http)
|
||||
|
||||
def __enter__(self) -> SyncLangGraphClient:
|
||||
"""Enter the sync context manager."""
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> None:
|
||||
"""Exit the sync context manager."""
|
||||
self.close()
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the underlying HTTP client."""
|
||||
if hasattr(self, "http"):
|
||||
self.http.client.close()
|
||||
498
venv/Lib/site-packages/langgraph_sdk/_sync/cron.py
Normal file
498
venv/Lib/site-packages/langgraph_sdk/_sync/cron.py
Normal file
@@ -0,0 +1,498 @@
|
||||
"""Synchronous cron client for LangGraph SDK."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from collections.abc import Mapping, Sequence
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from langgraph_sdk._sync.http import SyncHttpClient
|
||||
from langgraph_sdk.schema import (
|
||||
All,
|
||||
Config,
|
||||
Context,
|
||||
Cron,
|
||||
CronSelectField,
|
||||
CronSortBy,
|
||||
Durability,
|
||||
Input,
|
||||
OnCompletionBehavior,
|
||||
QueryParamTypes,
|
||||
Run,
|
||||
SortOrder,
|
||||
StreamMode,
|
||||
)
|
||||
|
||||
|
||||
class SyncCronClient:
|
||||
"""Synchronous client for managing cron jobs in LangGraph.
|
||||
|
||||
This class provides methods to create and manage scheduled tasks (cron jobs) for automated graph executions.
|
||||
|
||||
???+ example "Example"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:8123")
|
||||
cron_job = client.crons.create_for_thread(thread_id="thread_123", assistant_id="asst_456", schedule="0 * * * *")
|
||||
```
|
||||
|
||||
!!! note "Feature Availability"
|
||||
|
||||
The crons client functionality is not supported on all licenses.
|
||||
Please check the relevant license documentation for the most up-to-date
|
||||
details on feature availability.
|
||||
"""
|
||||
|
||||
def __init__(self, http_client: SyncHttpClient) -> None:
|
||||
self.http = http_client
|
||||
|
||||
def create_for_thread(
|
||||
self,
|
||||
thread_id: str,
|
||||
assistant_id: str,
|
||||
*,
|
||||
schedule: str,
|
||||
input: Input | None = None,
|
||||
metadata: Mapping[str, Any] | None = None,
|
||||
config: Config | None = None,
|
||||
context: Context | None = None,
|
||||
checkpoint_during: bool | None = None, # deprecated
|
||||
interrupt_before: All | list[str] | None = None,
|
||||
interrupt_after: All | list[str] | None = None,
|
||||
webhook: str | None = None,
|
||||
multitask_strategy: str | None = None,
|
||||
end_time: datetime | None = None,
|
||||
enabled: bool | None = None,
|
||||
stream_mode: StreamMode | Sequence[StreamMode] | None = None,
|
||||
stream_subgraphs: bool | None = None,
|
||||
stream_resumable: bool | None = None,
|
||||
durability: Durability | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> Run:
|
||||
"""Create a cron job for a thread.
|
||||
|
||||
Args:
|
||||
thread_id: the thread ID to run the cron job on.
|
||||
assistant_id: The assistant ID or graph name to use for the cron job.
|
||||
If using graph name, will default to first assistant created from that graph.
|
||||
schedule: The cron schedule to execute this job on.
|
||||
Schedules are interpreted in UTC.
|
||||
input: The input to the graph.
|
||||
metadata: Metadata to assign to the cron job runs.
|
||||
config: The configuration for the assistant.
|
||||
context: Static context to add to the assistant.
|
||||
!!! version-added "Added in version 0.6.0"
|
||||
checkpoint_during: (deprecated) Whether to checkpoint during the run (or only at the end/interruption).
|
||||
interrupt_before: Nodes to interrupt immediately before they get executed.
|
||||
interrupt_after: Nodes to Nodes to interrupt immediately after they get executed.
|
||||
webhook: Webhook to call after LangGraph API call is done.
|
||||
multitask_strategy: Multitask strategy to use.
|
||||
Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'.
|
||||
end_time: The time to stop running the cron job. If not provided, the cron job will run indefinitely.
|
||||
enabled: Whether the cron job is enabled. By default, it is considered enabled.
|
||||
stream_mode: The stream mode(s) to use.
|
||||
stream_subgraphs: Whether to stream output from subgraphs.
|
||||
stream_resumable: Whether to persist the stream chunks in order to resume the stream later.
|
||||
durability: Durability level for the run. Must be one of 'sync', 'async', or 'exit'.
|
||||
"async" means checkpoints are persisted async while next graph step executes, replaces checkpoint_during=True
|
||||
"sync" means checkpoints are persisted sync after graph step executes, replaces checkpoint_during=False
|
||||
"exit" means checkpoints are only persisted when the run exits, does not save intermediate steps
|
||||
headers: Optional custom headers to include with the request.
|
||||
|
||||
Returns:
|
||||
The cron `Run`.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:8123")
|
||||
cron_run = client.crons.create_for_thread(
|
||||
thread_id="my-thread-id",
|
||||
assistant_id="agent",
|
||||
schedule="27 15 * * *",
|
||||
input={"messages": [{"role": "user", "content": "hello!"}]},
|
||||
metadata={"name":"my_run"},
|
||||
context={"model_name": "openai"},
|
||||
interrupt_before=["node_to_stop_before_1","node_to_stop_before_2"],
|
||||
interrupt_after=["node_to_stop_after_1","node_to_stop_after_2"],
|
||||
webhook="https://my.fake.webhook.com",
|
||||
multitask_strategy="interrupt",
|
||||
enabled=True
|
||||
)
|
||||
```
|
||||
"""
|
||||
if checkpoint_during is not None:
|
||||
warnings.warn(
|
||||
"`checkpoint_during` is deprecated and will be removed in a future version. Use `durability` instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
payload = {
|
||||
"schedule": schedule,
|
||||
"input": input,
|
||||
"config": config,
|
||||
"metadata": metadata,
|
||||
"context": context,
|
||||
"assistant_id": assistant_id,
|
||||
"interrupt_before": interrupt_before,
|
||||
"interrupt_after": interrupt_after,
|
||||
"checkpoint_during": checkpoint_during,
|
||||
"webhook": webhook,
|
||||
"multitask_strategy": multitask_strategy,
|
||||
"end_time": end_time.isoformat() if end_time else None,
|
||||
"enabled": enabled,
|
||||
"stream_mode": stream_mode,
|
||||
"stream_subgraphs": stream_subgraphs,
|
||||
"stream_resumable": stream_resumable,
|
||||
"durability": durability,
|
||||
}
|
||||
payload = {k: v for k, v in payload.items() if v is not None}
|
||||
return self.http.post(
|
||||
f"/threads/{thread_id}/runs/crons",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
params=params,
|
||||
)
|
||||
|
||||
def create(
|
||||
self,
|
||||
assistant_id: str,
|
||||
*,
|
||||
schedule: str,
|
||||
input: Input | None = None,
|
||||
metadata: Mapping[str, Any] | None = None,
|
||||
config: Config | None = None,
|
||||
context: Context | None = None,
|
||||
checkpoint_during: bool | None = None, # deprecated
|
||||
interrupt_before: All | list[str] | None = None,
|
||||
interrupt_after: All | list[str] | None = None,
|
||||
webhook: str | None = None,
|
||||
on_run_completed: OnCompletionBehavior | None = None,
|
||||
multitask_strategy: str | None = None,
|
||||
end_time: datetime | None = None,
|
||||
enabled: bool | None = None,
|
||||
stream_mode: StreamMode | Sequence[StreamMode] | None = None,
|
||||
stream_subgraphs: bool | None = None,
|
||||
stream_resumable: bool | None = None,
|
||||
durability: Durability | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> Run:
|
||||
"""Create a cron run.
|
||||
|
||||
Args:
|
||||
assistant_id: The assistant ID or graph name to use for the cron job.
|
||||
If using graph name, will default to first assistant created from that graph.
|
||||
schedule: The cron schedule to execute this job on.
|
||||
Schedules are interpreted in UTC.
|
||||
input: The input to the graph.
|
||||
metadata: Metadata to assign to the cron job runs.
|
||||
config: The configuration for the assistant.
|
||||
context: Static context to add to the assistant.
|
||||
!!! version-added "Added in version 0.6.0"
|
||||
checkpoint_during: (deprecated) Whether to checkpoint during the run (or only at the end/interruption).
|
||||
interrupt_before: Nodes to interrupt immediately before they get executed.
|
||||
interrupt_after: Nodes to Nodes to interrupt immediately after they get executed.
|
||||
webhook: Webhook to call after LangGraph API call is done.
|
||||
on_run_completed: What to do with the thread after the run completes.
|
||||
Must be one of 'delete' (default) or 'keep'. 'delete' removes the thread
|
||||
after execution. 'keep' creates a new thread for each execution but does not
|
||||
clean them up. Clients are responsible for cleaning up kept threads.
|
||||
multitask_strategy: Multitask strategy to use.
|
||||
Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'.
|
||||
end_time: The time to stop running the cron job. If not provided, the cron job will run indefinitely.
|
||||
enabled: Whether the cron job is enabled. By default, it is considered enabled.
|
||||
stream_mode: The stream mode(s) to use.
|
||||
stream_subgraphs: Whether to stream output from subgraphs.
|
||||
stream_resumable: Whether to persist the stream chunks in order to resume the stream later.
|
||||
durability: Durability level for the run. Must be one of 'sync', 'async', or 'exit'.
|
||||
"async" means checkpoints are persisted async while next graph step executes, replaces checkpoint_during=True
|
||||
"sync" means checkpoints are persisted sync after graph step executes, replaces checkpoint_during=False
|
||||
"exit" means checkpoints are only persisted when the run exits, does not save intermediate steps
|
||||
headers: Optional custom headers to include with the request.
|
||||
|
||||
Returns:
|
||||
The cron `Run`.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:8123")
|
||||
cron_run = client.crons.create(
|
||||
assistant_id="agent",
|
||||
schedule="27 15 * * *",
|
||||
input={"messages": [{"role": "user", "content": "hello!"}]},
|
||||
metadata={"name":"my_run"},
|
||||
context={"model_name": "openai"},
|
||||
checkpoint_during=True,
|
||||
interrupt_before=["node_to_stop_before_1","node_to_stop_before_2"],
|
||||
interrupt_after=["node_to_stop_after_1","node_to_stop_after_2"],
|
||||
webhook="https://my.fake.webhook.com",
|
||||
multitask_strategy="interrupt",
|
||||
enabled=True
|
||||
)
|
||||
```
|
||||
|
||||
"""
|
||||
if checkpoint_during is not None:
|
||||
warnings.warn(
|
||||
"`checkpoint_during` is deprecated and will be removed in a future version. Use `durability` instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
payload = {
|
||||
"schedule": schedule,
|
||||
"input": input,
|
||||
"config": config,
|
||||
"metadata": metadata,
|
||||
"context": context,
|
||||
"assistant_id": assistant_id,
|
||||
"interrupt_before": interrupt_before,
|
||||
"interrupt_after": interrupt_after,
|
||||
"webhook": webhook,
|
||||
"checkpoint_during": checkpoint_during,
|
||||
"on_run_completed": on_run_completed,
|
||||
"multitask_strategy": multitask_strategy,
|
||||
"end_time": end_time.isoformat() if end_time else None,
|
||||
"enabled": enabled,
|
||||
"stream_mode": stream_mode,
|
||||
"stream_subgraphs": stream_subgraphs,
|
||||
"stream_resumable": stream_resumable,
|
||||
"durability": durability,
|
||||
}
|
||||
payload = {k: v for k, v in payload.items() if v is not None}
|
||||
return self.http.post(
|
||||
"/runs/crons", json=payload, headers=headers, params=params
|
||||
)
|
||||
|
||||
def delete(
|
||||
self,
|
||||
cron_id: str,
|
||||
*,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> None:
|
||||
"""Delete a cron.
|
||||
|
||||
Args:
|
||||
cron_id: The cron ID to delete.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
`None`
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:8123")
|
||||
client.crons.delete(
|
||||
cron_id="cron_to_delete"
|
||||
)
|
||||
```
|
||||
|
||||
"""
|
||||
self.http.delete(f"/runs/crons/{cron_id}", headers=headers, params=params)
|
||||
|
||||
def update(
|
||||
self,
|
||||
cron_id: str,
|
||||
*,
|
||||
schedule: str | None = None,
|
||||
end_time: datetime | None = None,
|
||||
input: Input | None = None,
|
||||
metadata: Mapping[str, Any] | None = None,
|
||||
config: Config | None = None,
|
||||
context: Context | None = None,
|
||||
webhook: str | None = None,
|
||||
interrupt_before: All | list[str] | None = None,
|
||||
interrupt_after: All | list[str] | None = None,
|
||||
on_run_completed: OnCompletionBehavior | None = None,
|
||||
enabled: bool | None = None,
|
||||
stream_mode: StreamMode | Sequence[StreamMode] | None = None,
|
||||
stream_subgraphs: bool | None = None,
|
||||
stream_resumable: bool | None = None,
|
||||
durability: Durability | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> Cron:
|
||||
"""Update a cron job by ID.
|
||||
|
||||
Args:
|
||||
cron_id: The cron ID to update.
|
||||
schedule: The cron schedule to execute this job on.
|
||||
Schedules are interpreted in UTC.
|
||||
end_time: The end date to stop running the cron.
|
||||
input: The input to the graph.
|
||||
metadata: Metadata to assign to the cron job runs.
|
||||
config: The configuration for the assistant.
|
||||
context: Static context added to the assistant.
|
||||
webhook: Webhook to call after LangGraph API call is done.
|
||||
interrupt_before: Nodes to interrupt immediately before they get executed.
|
||||
interrupt_after: Nodes to interrupt immediately after they get executed.
|
||||
on_run_completed: What to do with the thread after the run completes.
|
||||
Must be one of 'delete' or 'keep'. 'delete' removes the thread
|
||||
after execution. 'keep' creates a new thread for each execution but does not
|
||||
clean them up.
|
||||
enabled: Enable or disable the cron job.
|
||||
stream_mode: The stream mode(s) to use.
|
||||
stream_subgraphs: Whether to stream output from subgraphs.
|
||||
stream_resumable: Whether to persist the stream chunks in order to resume the stream later.
|
||||
durability: Durability level for the run. Must be one of 'sync', 'async', or 'exit'.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
The updated cron job.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:8123")
|
||||
updated_cron = client.crons.update(
|
||||
cron_id="1ef3cefa-4c09-6926-96d0-3dc97fd5e39b",
|
||||
schedule="0 10 * * *",
|
||||
enabled=False,
|
||||
)
|
||||
```
|
||||
|
||||
"""
|
||||
payload = {
|
||||
"schedule": schedule,
|
||||
"end_time": end_time.isoformat() if end_time else None,
|
||||
"input": input,
|
||||
"metadata": metadata,
|
||||
"config": config,
|
||||
"context": context,
|
||||
"webhook": webhook,
|
||||
"interrupt_before": interrupt_before,
|
||||
"interrupt_after": interrupt_after,
|
||||
"on_run_completed": on_run_completed,
|
||||
"enabled": enabled,
|
||||
"stream_mode": stream_mode,
|
||||
"stream_subgraphs": stream_subgraphs,
|
||||
"stream_resumable": stream_resumable,
|
||||
"durability": durability,
|
||||
}
|
||||
payload = {k: v for k, v in payload.items() if v is not None}
|
||||
return self.http.patch(
|
||||
f"/runs/crons/{cron_id}",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
params=params,
|
||||
)
|
||||
|
||||
def search(
|
||||
self,
|
||||
*,
|
||||
assistant_id: str | None = None,
|
||||
thread_id: str | None = None,
|
||||
enabled: bool | None = None,
|
||||
limit: int = 10,
|
||||
offset: int = 0,
|
||||
sort_by: CronSortBy | None = None,
|
||||
sort_order: SortOrder | None = None,
|
||||
select: list[CronSelectField] | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> list[Cron]:
|
||||
"""Get a list of cron jobs.
|
||||
|
||||
Args:
|
||||
assistant_id: The assistant ID or graph name to search for.
|
||||
thread_id: the thread ID to search for.
|
||||
enabled: Whether the cron job is enabled.
|
||||
limit: The maximum number of results to return.
|
||||
offset: The number of results to skip.
|
||||
headers: Optional custom headers to include with the request.
|
||||
|
||||
Returns:
|
||||
The list of cron jobs returned by the search,
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:8123")
|
||||
cron_jobs = client.crons.search(
|
||||
assistant_id="my_assistant_id",
|
||||
thread_id="my_thread_id",
|
||||
enabled=True,
|
||||
limit=5,
|
||||
offset=5,
|
||||
)
|
||||
print(cron_jobs)
|
||||
```
|
||||
|
||||
```shell
|
||||
----------------------------------------------------------
|
||||
|
||||
[
|
||||
{
|
||||
'cron_id': '1ef3cefa-4c09-6926-96d0-3dc97fd5e39b',
|
||||
'assistant_id': 'my_assistant_id',
|
||||
'thread_id': 'my_thread_id',
|
||||
'user_id': None,
|
||||
'payload':
|
||||
{
|
||||
'input': {'start_time': ''},
|
||||
'schedule': '4 * * * *',
|
||||
'assistant_id': 'my_assistant_id'
|
||||
},
|
||||
'schedule': '4 * * * *',
|
||||
'next_run_date': '2024-07-25T17:04:00+00:00',
|
||||
'end_time': None,
|
||||
'created_at': '2024-07-08T06:02:23.073257+00:00',
|
||||
'updated_at': '2024-07-08T06:02:23.073257+00:00'
|
||||
}
|
||||
]
|
||||
```
|
||||
"""
|
||||
payload = {
|
||||
"assistant_id": assistant_id,
|
||||
"thread_id": thread_id,
|
||||
"enabled": enabled,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
}
|
||||
if sort_by:
|
||||
payload["sort_by"] = sort_by
|
||||
if sort_order:
|
||||
payload["sort_order"] = sort_order
|
||||
if select:
|
||||
payload["select"] = select
|
||||
payload = {k: v for k, v in payload.items() if v is not None}
|
||||
return self.http.post(
|
||||
"/runs/crons/search", json=payload, headers=headers, params=params
|
||||
)
|
||||
|
||||
def count(
|
||||
self,
|
||||
*,
|
||||
assistant_id: str | None = None,
|
||||
thread_id: str | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> int:
|
||||
"""Count cron jobs matching filters.
|
||||
|
||||
Args:
|
||||
assistant_id: Assistant ID to filter by.
|
||||
thread_id: Thread ID to filter by.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
int: Number of crons matching the criteria.
|
||||
"""
|
||||
payload: dict[str, Any] = {}
|
||||
if assistant_id:
|
||||
payload["assistant_id"] = assistant_id
|
||||
if thread_id:
|
||||
payload["thread_id"] = thread_id
|
||||
return self.http.post(
|
||||
"/runs/crons/count", json=payload, headers=headers, params=params
|
||||
)
|
||||
296
venv/Lib/site-packages/langgraph_sdk/_sync/http.py
Normal file
296
venv/Lib/site-packages/langgraph_sdk/_sync/http.py
Normal file
@@ -0,0 +1,296 @@
|
||||
"""Synchronous HTTP client for LangGraph API."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import warnings
|
||||
from collections.abc import Callable, Iterator, Mapping
|
||||
from typing import Any, cast
|
||||
|
||||
import httpx
|
||||
import orjson
|
||||
|
||||
from langgraph_sdk._shared.utilities import _orjson_default
|
||||
from langgraph_sdk.errors import _raise_for_status_typed
|
||||
from langgraph_sdk.schema import QueryParamTypes, StreamPart
|
||||
from langgraph_sdk.sse import SSEDecoder, iter_lines_raw
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SyncHttpClient:
|
||||
"""Handle synchronous requests to the LangGraph API.
|
||||
|
||||
Provides error messaging and content handling enhancements above the
|
||||
underlying httpx client, mirroring the interface of [HttpClient](#HttpClient)
|
||||
but for sync usage.
|
||||
|
||||
Attributes:
|
||||
client (httpx.Client): Underlying HTTPX sync client.
|
||||
"""
|
||||
|
||||
def __init__(self, client: httpx.Client) -> None:
|
||||
self.client = client
|
||||
|
||||
def get(
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
params: QueryParamTypes | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
on_response: Callable[[httpx.Response], None] | None = None,
|
||||
) -> Any:
|
||||
"""Send a `GET` request."""
|
||||
r = self.client.get(path, params=params, headers=headers)
|
||||
if on_response:
|
||||
on_response(r)
|
||||
_raise_for_status_typed(r)
|
||||
return _decode_json(r)
|
||||
|
||||
def post(
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
json: dict[str, Any] | list | None,
|
||||
params: QueryParamTypes | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
on_response: Callable[[httpx.Response], None] | None = None,
|
||||
) -> Any:
|
||||
"""Send a `POST` request."""
|
||||
if json is not None:
|
||||
request_headers, content = _encode_json(json)
|
||||
else:
|
||||
request_headers, content = {}, b""
|
||||
if headers:
|
||||
request_headers.update(headers)
|
||||
r = self.client.post(
|
||||
path, headers=request_headers, content=content, params=params
|
||||
)
|
||||
if on_response:
|
||||
on_response(r)
|
||||
_raise_for_status_typed(r)
|
||||
return _decode_json(r)
|
||||
|
||||
def put(
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
json: dict,
|
||||
params: QueryParamTypes | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
on_response: Callable[[httpx.Response], None] | None = None,
|
||||
) -> Any:
|
||||
"""Send a `PUT` request."""
|
||||
request_headers, content = _encode_json(json)
|
||||
if headers:
|
||||
request_headers.update(headers)
|
||||
|
||||
r = self.client.put(
|
||||
path, headers=request_headers, content=content, params=params
|
||||
)
|
||||
if on_response:
|
||||
on_response(r)
|
||||
_raise_for_status_typed(r)
|
||||
return _decode_json(r)
|
||||
|
||||
def patch(
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
json: dict,
|
||||
params: QueryParamTypes | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
on_response: Callable[[httpx.Response], None] | None = None,
|
||||
) -> Any:
|
||||
"""Send a `PATCH` request."""
|
||||
request_headers, content = _encode_json(json)
|
||||
if headers:
|
||||
request_headers.update(headers)
|
||||
r = self.client.patch(
|
||||
path, headers=request_headers, content=content, params=params
|
||||
)
|
||||
if on_response:
|
||||
on_response(r)
|
||||
_raise_for_status_typed(r)
|
||||
return _decode_json(r)
|
||||
|
||||
def delete(
|
||||
self,
|
||||
path: str,
|
||||
*,
|
||||
json: Any | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
on_response: Callable[[httpx.Response], None] | None = None,
|
||||
) -> None:
|
||||
"""Send a `DELETE` request."""
|
||||
r = self.client.request(
|
||||
"DELETE", path, json=json, params=params, headers=headers
|
||||
)
|
||||
if on_response:
|
||||
on_response(r)
|
||||
_raise_for_status_typed(r)
|
||||
|
||||
def request_reconnect(
|
||||
self,
|
||||
path: str,
|
||||
method: str,
|
||||
*,
|
||||
json: dict[str, Any] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
on_response: Callable[[httpx.Response], None] | None = None,
|
||||
reconnect_limit: int = 5,
|
||||
) -> Any:
|
||||
"""Send a request that automatically reconnects to Location header."""
|
||||
request_headers, content = _encode_json(json)
|
||||
if headers:
|
||||
request_headers.update(headers)
|
||||
with self.client.stream(
|
||||
method, path, headers=request_headers, content=content, params=params
|
||||
) as r:
|
||||
if on_response:
|
||||
on_response(r)
|
||||
try:
|
||||
r.raise_for_status()
|
||||
except httpx.HTTPStatusError as e:
|
||||
body = r.read().decode()
|
||||
if sys.version_info >= (3, 11):
|
||||
e.add_note(body)
|
||||
else:
|
||||
logger.error(f"Error from langgraph-api: {body}", exc_info=e)
|
||||
raise e
|
||||
loc = r.headers.get("location")
|
||||
if reconnect_limit <= 0 or not loc:
|
||||
return _decode_json(r)
|
||||
try:
|
||||
return _decode_json(r)
|
||||
except httpx.HTTPError:
|
||||
warnings.warn(
|
||||
f"Request failed, attempting reconnect to Location: {loc}",
|
||||
stacklevel=2,
|
||||
)
|
||||
r.close()
|
||||
return self.request_reconnect(
|
||||
loc,
|
||||
"GET",
|
||||
headers=request_headers,
|
||||
# don't pass on_response so it's only called once
|
||||
reconnect_limit=reconnect_limit - 1,
|
||||
)
|
||||
|
||||
def stream(
|
||||
self,
|
||||
path: str,
|
||||
method: str,
|
||||
*,
|
||||
json: dict[str, Any] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
on_response: Callable[[httpx.Response], None] | None = None,
|
||||
) -> Iterator[StreamPart]:
|
||||
"""Stream the results of a request using SSE."""
|
||||
if json is not None:
|
||||
request_headers, content = _encode_json(json)
|
||||
else:
|
||||
request_headers, content = {}, None
|
||||
request_headers["Accept"] = "text/event-stream"
|
||||
request_headers["Cache-Control"] = "no-store"
|
||||
if headers:
|
||||
request_headers.update(headers)
|
||||
|
||||
reconnect_headers = {
|
||||
key: value
|
||||
for key, value in request_headers.items()
|
||||
if key.lower() not in {"content-length", "content-type"}
|
||||
}
|
||||
|
||||
last_event_id: str | None = None
|
||||
reconnect_path: str | None = None
|
||||
reconnect_attempts = 0
|
||||
max_reconnect_attempts = 5
|
||||
|
||||
while True:
|
||||
current_headers = dict(
|
||||
request_headers if reconnect_path is None else reconnect_headers
|
||||
)
|
||||
if last_event_id is not None:
|
||||
current_headers["Last-Event-ID"] = last_event_id
|
||||
|
||||
current_method = method if reconnect_path is None else "GET"
|
||||
current_content = content if reconnect_path is None else None
|
||||
current_params = params if reconnect_path is None else None
|
||||
|
||||
retry = False
|
||||
with self.client.stream(
|
||||
current_method,
|
||||
reconnect_path or path,
|
||||
headers=current_headers,
|
||||
content=current_content,
|
||||
params=current_params,
|
||||
) as res:
|
||||
if reconnect_path is None and on_response:
|
||||
on_response(res)
|
||||
# check status
|
||||
_raise_for_status_typed(res)
|
||||
# check content type
|
||||
content_type = res.headers.get("content-type", "").partition(";")[0]
|
||||
if "text/event-stream" not in content_type:
|
||||
raise httpx.TransportError(
|
||||
"Expected response header Content-Type to contain 'text/event-stream', "
|
||||
f"got {content_type!r}"
|
||||
)
|
||||
|
||||
reconnect_location = res.headers.get("location")
|
||||
if reconnect_location:
|
||||
reconnect_path = reconnect_location
|
||||
|
||||
decoder = SSEDecoder()
|
||||
try:
|
||||
for line in iter_lines_raw(res):
|
||||
sse = decoder.decode(cast(bytes, line).rstrip(b"\n"))
|
||||
if sse is not None:
|
||||
if decoder.last_event_id is not None:
|
||||
last_event_id = decoder.last_event_id
|
||||
if sse.event or sse.data is not None:
|
||||
yield sse
|
||||
except httpx.HTTPError:
|
||||
# httpx.TransportError inherits from HTTPError, so transient
|
||||
# disconnects during streaming land here.
|
||||
if reconnect_path is None:
|
||||
raise
|
||||
retry = True
|
||||
else:
|
||||
if sse := decoder.decode(b""):
|
||||
if decoder.last_event_id is not None:
|
||||
last_event_id = decoder.last_event_id
|
||||
if sse.event or sse.data is not None:
|
||||
# See async stream implementation for rationale on
|
||||
# skipping empty flush events.
|
||||
yield sse
|
||||
if retry:
|
||||
reconnect_attempts += 1
|
||||
if reconnect_attempts > max_reconnect_attempts:
|
||||
raise httpx.TransportError(
|
||||
"Exceeded maximum SSE reconnection attempts"
|
||||
)
|
||||
continue
|
||||
break
|
||||
|
||||
|
||||
def _encode_json(json: Any) -> tuple[dict[str, str], bytes]:
|
||||
body = orjson.dumps(
|
||||
json,
|
||||
_orjson_default,
|
||||
orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NON_STR_KEYS,
|
||||
)
|
||||
content_length = str(len(body))
|
||||
content_type = "application/json"
|
||||
headers = {"Content-Length": content_length, "Content-Type": content_type}
|
||||
return headers, body
|
||||
|
||||
|
||||
def _decode_json(r: httpx.Response) -> Any:
|
||||
body = r.read()
|
||||
return orjson.loads(body) if body else None
|
||||
1060
venv/Lib/site-packages/langgraph_sdk/_sync/runs.py
Normal file
1060
venv/Lib/site-packages/langgraph_sdk/_sync/runs.py
Normal file
File diff suppressed because it is too large
Load Diff
313
venv/Lib/site-packages/langgraph_sdk/_sync/store.py
Normal file
313
venv/Lib/site-packages/langgraph_sdk/_sync/store.py
Normal file
@@ -0,0 +1,313 @@
|
||||
"""Synchronous store client for LangGraph SDK."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import Any, Literal
|
||||
|
||||
from langgraph_sdk._shared.utilities import _provided_vals
|
||||
from langgraph_sdk._sync.http import SyncHttpClient
|
||||
from langgraph_sdk.schema import (
|
||||
Item,
|
||||
ListNamespaceResponse,
|
||||
QueryParamTypes,
|
||||
SearchItemsResponse,
|
||||
)
|
||||
|
||||
|
||||
class SyncStoreClient:
|
||||
"""A client for synchronous operations on a key-value store.
|
||||
|
||||
Provides methods to interact with a remote key-value store, allowing
|
||||
storage and retrieval of items within namespaced hierarchies.
|
||||
|
||||
???+ example "Example"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024"))
|
||||
client.store.put_item(["users", "profiles"], "user123", {"name": "Alice", "age": 30})
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(self, http: SyncHttpClient) -> None:
|
||||
self.http = http
|
||||
|
||||
def put_item(
|
||||
self,
|
||||
namespace: Sequence[str],
|
||||
/,
|
||||
key: str,
|
||||
value: Mapping[str, Any],
|
||||
index: Literal[False] | list[str] | None = None,
|
||||
ttl: int | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> None:
|
||||
"""Store or update an item.
|
||||
|
||||
Args:
|
||||
namespace: A list of strings representing the namespace path.
|
||||
key: The unique identifier for the item within the namespace.
|
||||
value: A dictionary containing the item's data.
|
||||
index: Controls search indexing - None (use defaults), False (disable), or list of field paths to index.
|
||||
ttl: Optional time-to-live in minutes for the item, or None for no expiration.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
`None`
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:8123")
|
||||
client.store.put_item(
|
||||
["documents", "user123"],
|
||||
key="item456",
|
||||
value={"title": "My Document", "content": "Hello World"}
|
||||
)
|
||||
```
|
||||
"""
|
||||
for label in namespace:
|
||||
if "." in label:
|
||||
raise ValueError(
|
||||
f"Invalid namespace label '{label}'. Namespace labels cannot contain periods ('.')."
|
||||
)
|
||||
payload = {
|
||||
"namespace": namespace,
|
||||
"key": key,
|
||||
"value": value,
|
||||
"index": index,
|
||||
"ttl": ttl,
|
||||
}
|
||||
self.http.put(
|
||||
"/store/items", json=_provided_vals(payload), headers=headers, params=params
|
||||
)
|
||||
|
||||
def get_item(
|
||||
self,
|
||||
namespace: Sequence[str],
|
||||
/,
|
||||
key: str,
|
||||
*,
|
||||
refresh_ttl: bool | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> Item:
|
||||
"""Retrieve a single item.
|
||||
|
||||
Args:
|
||||
key: The unique identifier for the item.
|
||||
namespace: Optional list of strings representing the namespace path.
|
||||
refresh_ttl: Whether to refresh the TTL on this read operation. If `None`, uses the store's default behavior.
|
||||
headers: Optional custom headers to include with the request.
|
||||
|
||||
Returns:
|
||||
The retrieved item.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:8123")
|
||||
item = client.store.get_item(
|
||||
["documents", "user123"],
|
||||
key="item456",
|
||||
)
|
||||
print(item)
|
||||
```
|
||||
|
||||
```shell
|
||||
----------------------------------------------------------------
|
||||
|
||||
{
|
||||
'namespace': ['documents', 'user123'],
|
||||
'key': 'item456',
|
||||
'value': {'title': 'My Document', 'content': 'Hello World'},
|
||||
'created_at': '2024-07-30T12:00:00Z',
|
||||
'updated_at': '2024-07-30T12:00:00Z'
|
||||
}
|
||||
```
|
||||
"""
|
||||
for label in namespace:
|
||||
if "." in label:
|
||||
raise ValueError(
|
||||
f"Invalid namespace label '{label}'. Namespace labels cannot contain periods ('.')."
|
||||
)
|
||||
|
||||
query_params = {"key": key, "namespace": ".".join(namespace)}
|
||||
if refresh_ttl is not None:
|
||||
query_params["refresh_ttl"] = refresh_ttl
|
||||
if params:
|
||||
query_params.update(params)
|
||||
return self.http.get("/store/items", params=query_params, headers=headers)
|
||||
|
||||
def delete_item(
|
||||
self,
|
||||
namespace: Sequence[str],
|
||||
/,
|
||||
key: str,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> None:
|
||||
"""Delete an item.
|
||||
|
||||
Args:
|
||||
key: The unique identifier for the item.
|
||||
namespace: Optional list of strings representing the namespace path.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
`None`
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:8123")
|
||||
client.store.delete_item(
|
||||
["documents", "user123"],
|
||||
key="item456",
|
||||
)
|
||||
```
|
||||
"""
|
||||
self.http.delete(
|
||||
"/store/items",
|
||||
json={"key": key, "namespace": namespace},
|
||||
headers=headers,
|
||||
params=params,
|
||||
)
|
||||
|
||||
def search_items(
|
||||
self,
|
||||
namespace_prefix: Sequence[str],
|
||||
/,
|
||||
filter: Mapping[str, Any] | None = None,
|
||||
limit: int = 10,
|
||||
offset: int = 0,
|
||||
query: str | None = None,
|
||||
refresh_ttl: bool | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> SearchItemsResponse:
|
||||
"""Search for items within a namespace prefix.
|
||||
|
||||
Args:
|
||||
namespace_prefix: List of strings representing the namespace prefix.
|
||||
filter: Optional dictionary of key-value pairs to filter results.
|
||||
limit: Maximum number of items to return (default is 10).
|
||||
offset: Number of items to skip before returning results (default is 0).
|
||||
query: Optional query for natural language search.
|
||||
refresh_ttl: Whether to refresh the TTL on items returned by this search. If `None`, uses the store's default behavior.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
A list of items matching the search criteria.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:8123")
|
||||
items = client.store.search_items(
|
||||
["documents"],
|
||||
filter={"author": "John Doe"},
|
||||
limit=5,
|
||||
offset=0
|
||||
)
|
||||
print(items)
|
||||
```
|
||||
```shell
|
||||
----------------------------------------------------------------
|
||||
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"namespace": ["documents", "user123"],
|
||||
"key": "item789",
|
||||
"value": {
|
||||
"title": "Another Document",
|
||||
"author": "John Doe"
|
||||
},
|
||||
"created_at": "2024-07-30T12:00:00Z",
|
||||
"updated_at": "2024-07-30T12:00:00Z"
|
||||
},
|
||||
# ... additional items ...
|
||||
]
|
||||
}
|
||||
```
|
||||
"""
|
||||
payload = {
|
||||
"namespace_prefix": namespace_prefix,
|
||||
"filter": filter,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"query": query,
|
||||
"refresh_ttl": refresh_ttl,
|
||||
}
|
||||
return self.http.post(
|
||||
"/store/items/search",
|
||||
json=_provided_vals(payload),
|
||||
headers=headers,
|
||||
params=params,
|
||||
)
|
||||
|
||||
def list_namespaces(
|
||||
self,
|
||||
prefix: list[str] | None = None,
|
||||
suffix: list[str] | None = None,
|
||||
max_depth: int | None = None,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
*,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> ListNamespaceResponse:
|
||||
"""List namespaces with optional match conditions.
|
||||
|
||||
Args:
|
||||
prefix: Optional list of strings representing the prefix to filter namespaces.
|
||||
suffix: Optional list of strings representing the suffix to filter namespaces.
|
||||
max_depth: Optional integer specifying the maximum depth of namespaces to return.
|
||||
limit: Maximum number of namespaces to return (default is 100).
|
||||
offset: Number of namespaces to skip before returning results (default is 0).
|
||||
headers: Optional custom headers to include with the request.
|
||||
|
||||
Returns:
|
||||
A list of namespaces matching the criteria.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:8123")
|
||||
namespaces = client.store.list_namespaces(
|
||||
prefix=["documents"],
|
||||
max_depth=3,
|
||||
limit=10,
|
||||
offset=0
|
||||
)
|
||||
print(namespaces)
|
||||
```
|
||||
|
||||
```shell
|
||||
----------------------------------------------------------------
|
||||
|
||||
[
|
||||
["documents", "user123", "reports"],
|
||||
["documents", "user456", "invoices"],
|
||||
...
|
||||
]
|
||||
```
|
||||
"""
|
||||
payload = {
|
||||
"prefix": prefix,
|
||||
"suffix": suffix,
|
||||
"max_depth": max_depth,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
}
|
||||
return self.http.post(
|
||||
"/store/namespaces",
|
||||
json=_provided_vals(payload),
|
||||
headers=headers,
|
||||
params=params,
|
||||
)
|
||||
721
venv/Lib/site-packages/langgraph_sdk/_sync/threads.py
Normal file
721
venv/Lib/site-packages/langgraph_sdk/_sync/threads.py
Normal file
@@ -0,0 +1,721 @@
|
||||
"""Synchronous client for managing threads in LangGraph."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator, Mapping, Sequence
|
||||
from typing import Any
|
||||
|
||||
from langgraph_sdk._sync.http import SyncHttpClient
|
||||
from langgraph_sdk.schema import (
|
||||
Checkpoint,
|
||||
Json,
|
||||
OnConflictBehavior,
|
||||
PruneStrategy,
|
||||
QueryParamTypes,
|
||||
SortOrder,
|
||||
StreamPart,
|
||||
Thread,
|
||||
ThreadSelectField,
|
||||
ThreadSortBy,
|
||||
ThreadState,
|
||||
ThreadStatus,
|
||||
ThreadStreamMode,
|
||||
ThreadUpdateStateResponse,
|
||||
)
|
||||
|
||||
|
||||
class SyncThreadsClient:
|
||||
"""Synchronous client for managing threads in LangGraph.
|
||||
|
||||
This class provides methods to create, retrieve, and manage threads,
|
||||
which represent conversations or stateful interactions.
|
||||
|
||||
???+ example "Example"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
thread = client.threads.create(metadata={"user_id": "123"})
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(self, http: SyncHttpClient) -> None:
|
||||
self.http = http
|
||||
|
||||
def get(
|
||||
self,
|
||||
thread_id: str,
|
||||
*,
|
||||
include: Sequence[str] | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> Thread:
|
||||
"""Get a thread by ID.
|
||||
|
||||
Args:
|
||||
thread_id: The ID of the thread to get.
|
||||
include: Additional fields to include in the response.
|
||||
Supported values: `"ttl"`.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
`Thread` object.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
thread = client.threads.get(
|
||||
thread_id="my_thread_id"
|
||||
)
|
||||
print(thread)
|
||||
```
|
||||
```shell
|
||||
-----------------------------------------------------
|
||||
|
||||
{
|
||||
'thread_id': 'my_thread_id',
|
||||
'created_at': '2024-07-18T18:35:15.540834+00:00',
|
||||
'updated_at': '2024-07-18T18:35:15.540834+00:00',
|
||||
'metadata': {'graph_id': 'agent'}
|
||||
}
|
||||
```
|
||||
|
||||
"""
|
||||
query_params: dict[str, Any] = {}
|
||||
if include:
|
||||
query_params["include"] = ",".join(include)
|
||||
if params:
|
||||
query_params.update(params)
|
||||
return self.http.get(
|
||||
f"/threads/{thread_id}",
|
||||
headers=headers,
|
||||
params=query_params or None,
|
||||
)
|
||||
|
||||
def create(
|
||||
self,
|
||||
*,
|
||||
metadata: Json = None,
|
||||
thread_id: str | None = None,
|
||||
if_exists: OnConflictBehavior | None = None,
|
||||
supersteps: Sequence[dict[str, Sequence[dict[str, Any]]]] | None = None,
|
||||
graph_id: str | None = None,
|
||||
ttl: int | Mapping[str, Any] | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> Thread:
|
||||
"""Create a new thread.
|
||||
|
||||
Args:
|
||||
metadata: Metadata to add to thread.
|
||||
thread_id: ID of thread.
|
||||
If `None`, ID will be a randomly generated UUID.
|
||||
if_exists: How to handle duplicate creation. Defaults to 'raise' under the hood.
|
||||
Must be either 'raise' (raise error if duplicate), or 'do_nothing' (return existing thread).
|
||||
supersteps: Apply a list of supersteps when creating a thread, each containing a sequence of updates.
|
||||
Each update has `values` or `command` and `as_node`. Used for copying a thread between deployments.
|
||||
graph_id: Optional graph ID to associate with the thread.
|
||||
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").
|
||||
headers: Optional custom headers to include with the request.
|
||||
|
||||
Returns:
|
||||
The created `Thread`.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
thread = client.threads.create(
|
||||
metadata={"number":1},
|
||||
thread_id="my-thread-id",
|
||||
if_exists="raise"
|
||||
)
|
||||
```
|
||||
)
|
||||
"""
|
||||
payload: dict[str, Any] = {}
|
||||
if thread_id:
|
||||
payload["thread_id"] = thread_id
|
||||
if metadata or graph_id:
|
||||
payload["metadata"] = {
|
||||
**(metadata or {}),
|
||||
**({"graph_id": graph_id} if graph_id else {}),
|
||||
}
|
||||
if if_exists:
|
||||
payload["if_exists"] = if_exists
|
||||
if supersteps:
|
||||
payload["supersteps"] = [
|
||||
{
|
||||
"updates": [
|
||||
{
|
||||
"values": u["values"],
|
||||
"command": u.get("command"),
|
||||
"as_node": u["as_node"],
|
||||
}
|
||||
for u in s["updates"]
|
||||
]
|
||||
}
|
||||
for s in supersteps
|
||||
]
|
||||
if ttl is not None:
|
||||
if isinstance(ttl, (int, float)):
|
||||
payload["ttl"] = {"ttl": ttl, "strategy": "delete"}
|
||||
else:
|
||||
payload["ttl"] = ttl
|
||||
|
||||
return self.http.post("/threads", json=payload, headers=headers, params=params)
|
||||
|
||||
def update(
|
||||
self,
|
||||
thread_id: str,
|
||||
*,
|
||||
metadata: Mapping[str, Any],
|
||||
ttl: int | Mapping[str, Any] | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> Thread:
|
||||
"""Update a thread.
|
||||
|
||||
Args:
|
||||
thread_id: ID of thread to update.
|
||||
metadata: Metadata to merge with existing thread metadata.
|
||||
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").
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
The created `Thread`.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
thread = client.threads.update(
|
||||
thread_id="my-thread-id",
|
||||
metadata={"number":1},
|
||||
ttl=43_200,
|
||||
)
|
||||
```
|
||||
"""
|
||||
payload: dict[str, Any] = {"metadata": metadata}
|
||||
if ttl is not None:
|
||||
if isinstance(ttl, (int, float)):
|
||||
payload["ttl"] = {"ttl": ttl, "strategy": "delete"}
|
||||
else:
|
||||
payload["ttl"] = ttl
|
||||
return self.http.patch(
|
||||
f"/threads/{thread_id}",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
params=params,
|
||||
)
|
||||
|
||||
def delete(
|
||||
self,
|
||||
thread_id: str,
|
||||
*,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> None:
|
||||
"""Delete a thread.
|
||||
|
||||
Args:
|
||||
thread_id: The ID of the thread to delete.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
`None`
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client.threads.delete(
|
||||
thread_id="my_thread_id"
|
||||
)
|
||||
```
|
||||
|
||||
"""
|
||||
self.http.delete(f"/threads/{thread_id}", headers=headers, params=params)
|
||||
|
||||
def search(
|
||||
self,
|
||||
*,
|
||||
metadata: Json = None,
|
||||
values: Json = None,
|
||||
ids: Sequence[str] | None = None,
|
||||
status: ThreadStatus | None = None,
|
||||
limit: int = 10,
|
||||
offset: int = 0,
|
||||
sort_by: ThreadSortBy | None = None,
|
||||
sort_order: SortOrder | None = None,
|
||||
select: list[ThreadSelectField] | None = None,
|
||||
extract: dict[str, str] | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> list[Thread]:
|
||||
"""Search for threads.
|
||||
|
||||
Args:
|
||||
metadata: Thread metadata to filter on.
|
||||
values: State values to filter on.
|
||||
ids: List of thread IDs to filter by.
|
||||
status: Thread status to filter on.
|
||||
Must be one of 'idle', 'busy', 'interrupted' or 'error'.
|
||||
limit: Limit on number of threads to return.
|
||||
offset: Offset in threads table to start search from.
|
||||
sort_by: Sort by field.
|
||||
sort_order: Sort order.
|
||||
select: List of fields to include in the response.
|
||||
extract: Dictionary mapping aliases to JSONB paths to extract
|
||||
from thread data. Paths use dot notation for nested keys and
|
||||
bracket notation for array indices (e.g.,
|
||||
`{"last_msg": "values.messages[-1]"}`). Extracted values are
|
||||
returned in an `extracted` field on each thread. Maximum 10
|
||||
paths per request.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
List of the threads matching the search parameters.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
threads = client.threads.search(
|
||||
metadata={"number":1},
|
||||
status="interrupted",
|
||||
limit=15,
|
||||
offset=5
|
||||
)
|
||||
```
|
||||
"""
|
||||
payload: dict[str, Any] = {
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
}
|
||||
if metadata:
|
||||
payload["metadata"] = metadata
|
||||
if values:
|
||||
payload["values"] = values
|
||||
if ids:
|
||||
payload["ids"] = ids
|
||||
if status:
|
||||
payload["status"] = status
|
||||
if sort_by:
|
||||
payload["sort_by"] = sort_by
|
||||
if sort_order:
|
||||
payload["sort_order"] = sort_order
|
||||
if select:
|
||||
payload["select"] = select
|
||||
if extract:
|
||||
payload["extract"] = extract
|
||||
return self.http.post(
|
||||
"/threads/search", json=payload, headers=headers, params=params
|
||||
)
|
||||
|
||||
def count(
|
||||
self,
|
||||
*,
|
||||
metadata: Json = None,
|
||||
values: Json = None,
|
||||
status: ThreadStatus | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> int:
|
||||
"""Count threads matching filters.
|
||||
|
||||
Args:
|
||||
metadata: Thread metadata to filter on.
|
||||
values: State values to filter on.
|
||||
status: Thread status to filter on.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
int: Number of threads matching the criteria.
|
||||
"""
|
||||
payload: dict[str, Any] = {}
|
||||
if metadata:
|
||||
payload["metadata"] = metadata
|
||||
if values:
|
||||
payload["values"] = values
|
||||
if status:
|
||||
payload["status"] = status
|
||||
return self.http.post(
|
||||
"/threads/count", json=payload, headers=headers, params=params
|
||||
)
|
||||
|
||||
def copy(
|
||||
self,
|
||||
thread_id: str,
|
||||
*,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> None:
|
||||
"""Copy a thread.
|
||||
|
||||
Args:
|
||||
thread_id: The ID of the thread to copy.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
`None`
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
client.threads.copy(
|
||||
thread_id="my_thread_id"
|
||||
)
|
||||
```
|
||||
|
||||
"""
|
||||
return self.http.post(
|
||||
f"/threads/{thread_id}/copy", json=None, headers=headers, params=params
|
||||
)
|
||||
|
||||
def prune(
|
||||
self,
|
||||
thread_ids: Sequence[str],
|
||||
*,
|
||||
strategy: PruneStrategy = "delete",
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Prune threads by ID.
|
||||
|
||||
Args:
|
||||
thread_ids: List of thread IDs to prune.
|
||||
strategy: The prune strategy. `"delete"` removes threads entirely.
|
||||
`"keep_latest"` prunes old checkpoints but keeps threads and their
|
||||
latest state. Defaults to `"delete"`.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
A dict containing `pruned_count` (number of threads pruned).
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
result = client.threads.prune(
|
||||
thread_ids=["thread_1", "thread_2"],
|
||||
)
|
||||
print(result) # {'pruned_count': 2}
|
||||
```
|
||||
|
||||
"""
|
||||
payload: dict[str, Any] = {
|
||||
"thread_ids": thread_ids,
|
||||
}
|
||||
if strategy != "delete":
|
||||
payload["strategy"] = strategy
|
||||
return self.http.post(
|
||||
"/threads/prune", json=payload, headers=headers, params=params
|
||||
)
|
||||
|
||||
def get_state(
|
||||
self,
|
||||
thread_id: str,
|
||||
checkpoint: Checkpoint | None = None,
|
||||
checkpoint_id: str | None = None, # deprecated
|
||||
*,
|
||||
subgraphs: bool = False,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> ThreadState:
|
||||
"""Get the state of a thread.
|
||||
|
||||
Args:
|
||||
thread_id: The ID of the thread to get the state of.
|
||||
checkpoint: The checkpoint to get the state of.
|
||||
subgraphs: Include subgraphs states.
|
||||
headers: Optional custom headers to include with the request.
|
||||
|
||||
Returns:
|
||||
The thread of the state.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
thread_state = client.threads.get_state(
|
||||
thread_id="my_thread_id",
|
||||
checkpoint_id="my_checkpoint_id"
|
||||
)
|
||||
print(thread_state)
|
||||
```
|
||||
|
||||
```shell
|
||||
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
{
|
||||
'values': {
|
||||
'messages': [
|
||||
{
|
||||
'content': 'how are you?',
|
||||
'additional_kwargs': {},
|
||||
'response_metadata': {},
|
||||
'type': 'human',
|
||||
'name': None,
|
||||
'id': 'fe0a5778-cfe9-42ee-b807-0adaa1873c10',
|
||||
'example': False
|
||||
},
|
||||
{
|
||||
'content': "I'm doing well, thanks for asking! I'm an AI assistant created by Anthropic to be helpful, honest, and harmless.",
|
||||
'additional_kwargs': {},
|
||||
'response_metadata': {},
|
||||
'type': 'ai',
|
||||
'name': None,
|
||||
'id': 'run-159b782c-b679-4830-83c6-cef87798fe8b',
|
||||
'example': False,
|
||||
'tool_calls': [],
|
||||
'invalid_tool_calls': [],
|
||||
'usage_metadata': None
|
||||
}
|
||||
]
|
||||
},
|
||||
'next': [],
|
||||
'checkpoint':
|
||||
{
|
||||
'thread_id': 'e2496803-ecd5-4e0c-a779-3226296181c2',
|
||||
'checkpoint_ns': '',
|
||||
'checkpoint_id': '1ef4a9b8-e6fb-67b1-8001-abd5184439d1'
|
||||
}
|
||||
'metadata':
|
||||
{
|
||||
'step': 1,
|
||||
'run_id': '1ef4a9b8-d7da-679a-a45a-872054341df2',
|
||||
'source': 'loop',
|
||||
'writes':
|
||||
{
|
||||
'agent':
|
||||
{
|
||||
'messages': [
|
||||
{
|
||||
'id': 'run-159b782c-b679-4830-83c6-cef87798fe8b',
|
||||
'name': None,
|
||||
'type': 'ai',
|
||||
'content': "I'm doing well, thanks for asking! I'm an AI assistant created by Anthropic to be helpful, honest, and harmless.",
|
||||
'example': False,
|
||||
'tool_calls': [],
|
||||
'usage_metadata': None,
|
||||
'additional_kwargs': {},
|
||||
'response_metadata': {},
|
||||
'invalid_tool_calls': []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
'user_id': None,
|
||||
'graph_id': 'agent',
|
||||
'thread_id': 'e2496803-ecd5-4e0c-a779-3226296181c2',
|
||||
'created_by': 'system',
|
||||
'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},
|
||||
'created_at': '2024-07-25T15:35:44.184703+00:00',
|
||||
'parent_config':
|
||||
{
|
||||
'thread_id': 'e2496803-ecd5-4e0c-a779-3226296181c2',
|
||||
'checkpoint_ns': '',
|
||||
'checkpoint_id': '1ef4a9b8-d80d-6fa7-8000-9300467fad0f'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
"""
|
||||
if checkpoint:
|
||||
return self.http.post(
|
||||
f"/threads/{thread_id}/state/checkpoint",
|
||||
json={"checkpoint": checkpoint, "subgraphs": subgraphs},
|
||||
headers=headers,
|
||||
params=params,
|
||||
)
|
||||
elif checkpoint_id:
|
||||
get_params = {"subgraphs": subgraphs}
|
||||
if params:
|
||||
get_params = {**get_params, **dict(params)}
|
||||
return self.http.get(
|
||||
f"/threads/{thread_id}/state/{checkpoint_id}",
|
||||
params=get_params,
|
||||
headers=headers,
|
||||
)
|
||||
else:
|
||||
get_params = {"subgraphs": subgraphs}
|
||||
if params:
|
||||
get_params = {**get_params, **dict(params)}
|
||||
return self.http.get(
|
||||
f"/threads/{thread_id}/state",
|
||||
params=get_params,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
def update_state(
|
||||
self,
|
||||
thread_id: str,
|
||||
values: dict[str, Any] | Sequence[dict] | None,
|
||||
*,
|
||||
as_node: str | None = None,
|
||||
checkpoint: Checkpoint | None = None,
|
||||
checkpoint_id: str | None = None, # deprecated
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> ThreadUpdateStateResponse:
|
||||
"""Update the state of a thread.
|
||||
|
||||
Args:
|
||||
thread_id: The ID of the thread to update.
|
||||
values: The values to update the state with.
|
||||
as_node: Update the state as if this node had just executed.
|
||||
checkpoint: The checkpoint to update the state of.
|
||||
headers: Optional custom headers to include with the request.
|
||||
|
||||
Returns:
|
||||
Response after updating a thread's state.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
|
||||
response = await client.threads.update_state(
|
||||
thread_id="my_thread_id",
|
||||
values={"messages":[{"role": "user", "content": "hello!"}]},
|
||||
as_node="my_node",
|
||||
)
|
||||
print(response)
|
||||
|
||||
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
{
|
||||
'checkpoint': {
|
||||
'thread_id': 'e2496803-ecd5-4e0c-a779-3226296181c2',
|
||||
'checkpoint_ns': '',
|
||||
'checkpoint_id': '1ef4a9b8-e6fb-67b1-8001-abd5184439d1',
|
||||
'checkpoint_map': {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
"""
|
||||
payload: dict[str, Any] = {
|
||||
"values": values,
|
||||
}
|
||||
if checkpoint_id:
|
||||
payload["checkpoint_id"] = checkpoint_id
|
||||
if checkpoint:
|
||||
payload["checkpoint"] = checkpoint
|
||||
if as_node:
|
||||
payload["as_node"] = as_node
|
||||
return self.http.post(
|
||||
f"/threads/{thread_id}/state", json=payload, headers=headers, params=params
|
||||
)
|
||||
|
||||
def get_history(
|
||||
self,
|
||||
thread_id: str,
|
||||
*,
|
||||
limit: int = 10,
|
||||
before: str | Checkpoint | None = None,
|
||||
metadata: Mapping[str, Any] | None = None,
|
||||
checkpoint: Checkpoint | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> list[ThreadState]:
|
||||
"""Get the state history of a thread.
|
||||
|
||||
Args:
|
||||
thread_id: The ID of the thread to get the state history for.
|
||||
checkpoint: Return states for this subgraph. If empty defaults to root.
|
||||
limit: The maximum number of states to return.
|
||||
before: Return states before this checkpoint.
|
||||
metadata: Filter states by metadata key-value pairs.
|
||||
headers: Optional custom headers to include with the request.
|
||||
|
||||
Returns:
|
||||
The state history of the `Thread`.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
|
||||
thread_state = client.threads.get_history(
|
||||
thread_id="my_thread_id",
|
||||
limit=5,
|
||||
before="my_timestamp",
|
||||
metadata={"name":"my_name"}
|
||||
)
|
||||
```
|
||||
|
||||
"""
|
||||
payload: dict[str, Any] = {
|
||||
"limit": limit,
|
||||
}
|
||||
if before:
|
||||
payload["before"] = before
|
||||
if metadata:
|
||||
payload["metadata"] = metadata
|
||||
if checkpoint:
|
||||
payload["checkpoint"] = checkpoint
|
||||
return self.http.post(
|
||||
f"/threads/{thread_id}/history",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
params=params,
|
||||
)
|
||||
|
||||
def join_stream(
|
||||
self,
|
||||
thread_id: str,
|
||||
*,
|
||||
stream_mode: ThreadStreamMode | Sequence[ThreadStreamMode] = "run_modes",
|
||||
last_event_id: str | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
params: QueryParamTypes | None = None,
|
||||
) -> Iterator[StreamPart]:
|
||||
"""Get a stream of events for a thread.
|
||||
|
||||
Args:
|
||||
thread_id: The ID of the thread to get the stream for.
|
||||
last_event_id: The ID of the last event to get.
|
||||
headers: Optional custom headers to include with the request.
|
||||
params: Optional query parameters to include with the request.
|
||||
|
||||
Returns:
|
||||
An iterator of stream parts.
|
||||
|
||||
???+ example "Example Usage"
|
||||
|
||||
```python
|
||||
|
||||
for chunk in client.threads.join_stream(
|
||||
thread_id="my_thread_id",
|
||||
last_event_id="my_event_id",
|
||||
stream_mode="run_modes",
|
||||
):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
"""
|
||||
query_params = {
|
||||
"stream_mode": stream_mode,
|
||||
}
|
||||
if params:
|
||||
query_params.update(params)
|
||||
return self.http.stream(
|
||||
f"/threads/{thread_id}/stream",
|
||||
"GET",
|
||||
headers={
|
||||
**({"Last-Event-ID": last_event_id} if last_event_id else {}),
|
||||
**(headers or {}),
|
||||
},
|
||||
params=query_params,
|
||||
)
|
||||
Reference in New Issue
Block a user