langgraph-sdk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langgraph_sdk/__init__.py +3 -0
- langgraph_sdk/client.py +267 -0
- langgraph_sdk/schema.py +110 -0
- langgraph_sdk-0.1.0.dist-info/METADATA +18 -0
- langgraph_sdk-0.1.0.dist-info/RECORD +6 -0
- langgraph_sdk-0.1.0.dist-info/WHEEL +4 -0
langgraph_sdk/client.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import AsyncIterator, List, NamedTuple, Optional, Union
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
import httpx_sse
|
|
6
|
+
from httpx._types import QueryParamTypes
|
|
7
|
+
|
|
8
|
+
from langgraph_sdk.schema import (
|
|
9
|
+
Assistant,
|
|
10
|
+
Config,
|
|
11
|
+
Metadata,
|
|
12
|
+
Run,
|
|
13
|
+
RunEvent,
|
|
14
|
+
StreamMode,
|
|
15
|
+
Thread,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_client(*, url: str = "http://localhost:8123") -> "LangServeClient":
|
|
20
|
+
client = httpx.AsyncClient(
|
|
21
|
+
base_url=url,
|
|
22
|
+
transport=httpx.AsyncHTTPTransport(retries=5),
|
|
23
|
+
timeout=httpx.Timeout(connect=5, read=60, write=60, pool=5),
|
|
24
|
+
)
|
|
25
|
+
return LangServeClient(client)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StreamPart(NamedTuple):
|
|
29
|
+
event: str
|
|
30
|
+
data: dict
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class LangServeClient:
|
|
34
|
+
def __init__(self, client: httpx.AsyncClient) -> None:
|
|
35
|
+
self.http = HttpClient(client)
|
|
36
|
+
self.assistants = AssistantsClient(self.http)
|
|
37
|
+
self.threads = ThreadsClient(self.http)
|
|
38
|
+
self.runs = RunsClient(self.http)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class HttpClient:
|
|
42
|
+
def __init__(self, client: httpx.AsyncClient) -> None:
|
|
43
|
+
self.client = client
|
|
44
|
+
|
|
45
|
+
async def get(self, path: str, *, params: QueryParamTypes = None) -> dict:
|
|
46
|
+
"""Make a GET request."""
|
|
47
|
+
r = await self.client.get(path, params=params)
|
|
48
|
+
try:
|
|
49
|
+
r.raise_for_status()
|
|
50
|
+
except httpx.HTTPStatusError as e:
|
|
51
|
+
if sys.version_info >= (3, 11):
|
|
52
|
+
e.add_note((await r.aread()).decode())
|
|
53
|
+
raise e
|
|
54
|
+
return r.json()
|
|
55
|
+
|
|
56
|
+
async def post(self, path: str, *, json: dict) -> dict:
|
|
57
|
+
"""Make a POST request."""
|
|
58
|
+
r = await self.client.post(path, json=json)
|
|
59
|
+
try:
|
|
60
|
+
r.raise_for_status()
|
|
61
|
+
except httpx.HTTPStatusError as e:
|
|
62
|
+
if sys.version_info >= (3, 11):
|
|
63
|
+
e.add_note((await r.aread()).decode())
|
|
64
|
+
raise e
|
|
65
|
+
return r.json()
|
|
66
|
+
|
|
67
|
+
async def put(self, path: str, *, json: dict) -> dict:
|
|
68
|
+
"""Make a PUT request."""
|
|
69
|
+
r = await self.client.put(path, json=json)
|
|
70
|
+
try:
|
|
71
|
+
r.raise_for_status()
|
|
72
|
+
except httpx.HTTPStatusError as e:
|
|
73
|
+
if sys.version_info >= (3, 11):
|
|
74
|
+
e.add_note((await r.aread()).decode())
|
|
75
|
+
raise e
|
|
76
|
+
return r.json()
|
|
77
|
+
|
|
78
|
+
async def delete(self, path: str) -> None:
|
|
79
|
+
"""Make a DELETE request."""
|
|
80
|
+
r = await self.client.delete(path)
|
|
81
|
+
try:
|
|
82
|
+
r.raise_for_status()
|
|
83
|
+
except httpx.HTTPStatusError as e:
|
|
84
|
+
if sys.version_info >= (3, 11):
|
|
85
|
+
e.add_note((await r.aread()).decode())
|
|
86
|
+
raise e
|
|
87
|
+
|
|
88
|
+
async def stream(
|
|
89
|
+
self, path: str, method: str, *, json: dict = None
|
|
90
|
+
) -> AsyncIterator[StreamPart]:
|
|
91
|
+
"""Stream the results of a request using SSE."""
|
|
92
|
+
async with httpx_sse.aconnect_sse(self.client, method, path, json=json) as sse:
|
|
93
|
+
try:
|
|
94
|
+
sse.response.raise_for_status()
|
|
95
|
+
except httpx.HTTPStatusError as e:
|
|
96
|
+
if sys.version_info >= (3, 11):
|
|
97
|
+
e.add_note((await sse.response.aread()).decode())
|
|
98
|
+
raise e
|
|
99
|
+
async for event in sse.aiter_sse():
|
|
100
|
+
yield StreamPart(event.event, event.json() if event.data else None)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class AssistantsClient:
|
|
104
|
+
def __init__(self, http: HttpClient) -> None:
|
|
105
|
+
self.http = http
|
|
106
|
+
|
|
107
|
+
async def get(self, assistant_id: str) -> Assistant:
|
|
108
|
+
"""Get an assistant by ID."""
|
|
109
|
+
return await self.http.get(f"/assistants/{assistant_id}")
|
|
110
|
+
|
|
111
|
+
async def create(
|
|
112
|
+
self,
|
|
113
|
+
graph_id: Optional[str],
|
|
114
|
+
config: Optional[Config] = None,
|
|
115
|
+
*,
|
|
116
|
+
metadata: Metadata = None,
|
|
117
|
+
) -> Assistant:
|
|
118
|
+
"""Create a new assistant."""
|
|
119
|
+
return await self.http.post(
|
|
120
|
+
"/assistants",
|
|
121
|
+
json={"metadata": metadata, "graph_id": graph_id, "config": config or {}},
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
async def upsert(
|
|
125
|
+
self,
|
|
126
|
+
assistant_id: str,
|
|
127
|
+
graph_id: str,
|
|
128
|
+
config: Optional[Config] = None,
|
|
129
|
+
*,
|
|
130
|
+
metadata: Metadata = None,
|
|
131
|
+
) -> Assistant:
|
|
132
|
+
"""Create or update an assistant."""
|
|
133
|
+
return await self.http.put(
|
|
134
|
+
f"/assistants/{assistant_id}",
|
|
135
|
+
json={"metadata": metadata, "graph_id": graph_id, "config": config or {}},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
async def search(
|
|
139
|
+
self, *, metadata: Metadata = None, limit: int = 10, offset: int = 0
|
|
140
|
+
) -> list[Assistant]:
|
|
141
|
+
"""Search for assistants."""
|
|
142
|
+
return await self.http.post(
|
|
143
|
+
"/assistants/search",
|
|
144
|
+
json={"metadata": metadata, "limit": limit, "offset": offset},
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class ThreadsClient:
|
|
149
|
+
def __init__(self, http: HttpClient) -> None:
|
|
150
|
+
self.http = http
|
|
151
|
+
|
|
152
|
+
async def get(self, thread_id: str) -> Thread:
|
|
153
|
+
"""Get a thread by ID."""
|
|
154
|
+
return await self.http.get(f"/threads/{thread_id}")
|
|
155
|
+
|
|
156
|
+
async def create(self, *, metadata: Metadata = None) -> Thread:
|
|
157
|
+
"""Create a new thread."""
|
|
158
|
+
return await self.http.post("/threads", json={"metadata": metadata})
|
|
159
|
+
|
|
160
|
+
async def upsert(self, thread_id: str, *, metadata: Metadata) -> Thread:
|
|
161
|
+
"""Create or update a thread."""
|
|
162
|
+
return await self.http.put(f"/threads/{thread_id}", json={"metadata": metadata})
|
|
163
|
+
|
|
164
|
+
async def delete(self, thread_id: str) -> None:
|
|
165
|
+
"""Delete a thread."""
|
|
166
|
+
await self.http.delete(f"/threads/{thread_id}")
|
|
167
|
+
|
|
168
|
+
async def search(
|
|
169
|
+
self, *, metadata: Metadata = None, limit: int = 10, offset: int = 0
|
|
170
|
+
) -> list[Thread]:
|
|
171
|
+
"""Search for threads."""
|
|
172
|
+
return await self.http.post(
|
|
173
|
+
"/threads/search",
|
|
174
|
+
json={"metadata": metadata, "limit": limit, "offset": offset},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
async def get_state(self, thread_id: str) -> dict:
|
|
178
|
+
"""Get the state of a thread."""
|
|
179
|
+
return await self.http.get(f"/threads/{thread_id}/state")
|
|
180
|
+
|
|
181
|
+
async def update_state(self, thread_id: Union[str, Config], values: dict) -> None:
|
|
182
|
+
"""Update the state of a thread."""
|
|
183
|
+
if isinstance(thread_id, dict):
|
|
184
|
+
config = thread_id
|
|
185
|
+
thread_id_: str = thread_id["configurable"]["thread_id"]
|
|
186
|
+
else:
|
|
187
|
+
thread_id_ = thread_id
|
|
188
|
+
config = None
|
|
189
|
+
return await self.http.post(
|
|
190
|
+
f"/threads/{thread_id_}/state", json={"values": values, "config": config}
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class RunsClient:
|
|
195
|
+
def __init__(self, http: HttpClient) -> None:
|
|
196
|
+
self.http = http
|
|
197
|
+
|
|
198
|
+
def stream(
|
|
199
|
+
self,
|
|
200
|
+
thread_id: str,
|
|
201
|
+
assistant_id: str,
|
|
202
|
+
*,
|
|
203
|
+
input: Optional[dict] = None,
|
|
204
|
+
stream_mode: StreamMode = "values",
|
|
205
|
+
metadata: Optional[dict] = None,
|
|
206
|
+
config: Optional[Config] = None,
|
|
207
|
+
interrupt_before: Optional[list[str]] = None,
|
|
208
|
+
interrupt_after: Optional[list[str]] = None,
|
|
209
|
+
) -> AsyncIterator[StreamPart]:
|
|
210
|
+
"""Create a run and stream the results."""
|
|
211
|
+
return self.http.stream(
|
|
212
|
+
f"/threads/{thread_id}/runs/stream",
|
|
213
|
+
"POST",
|
|
214
|
+
json={
|
|
215
|
+
"input": input,
|
|
216
|
+
"config": config,
|
|
217
|
+
"metadata": metadata,
|
|
218
|
+
"stream_mode": stream_mode,
|
|
219
|
+
"assistant_id": assistant_id,
|
|
220
|
+
"interrupt_before": interrupt_before,
|
|
221
|
+
"interrupt_after": interrupt_after,
|
|
222
|
+
},
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
async def create(
|
|
226
|
+
self,
|
|
227
|
+
thread_id: str,
|
|
228
|
+
assistant_id: str,
|
|
229
|
+
*,
|
|
230
|
+
input: Optional[dict] = None,
|
|
231
|
+
stream_mode: StreamMode = "values",
|
|
232
|
+
metadata: Optional[dict] = None,
|
|
233
|
+
config: Optional[Config] = None,
|
|
234
|
+
interrupt_before: Optional[list[str]] = None,
|
|
235
|
+
interrupt_after: Optional[list[str]] = None,
|
|
236
|
+
webhook: Optional[str] = None,
|
|
237
|
+
) -> Run:
|
|
238
|
+
"""Create a background run."""
|
|
239
|
+
return await self.http.post(
|
|
240
|
+
f"/threads/{thread_id}/runs",
|
|
241
|
+
json={
|
|
242
|
+
"input": input,
|
|
243
|
+
"config": config,
|
|
244
|
+
"metadata": metadata,
|
|
245
|
+
"stream_mode": stream_mode,
|
|
246
|
+
"assistant_id": assistant_id,
|
|
247
|
+
"interrupt_before": interrupt_before,
|
|
248
|
+
"interrupt_after": interrupt_after,
|
|
249
|
+
"webhook": webhook,
|
|
250
|
+
},
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
async def list(
|
|
254
|
+
self, thread_id: str, *, limit: int = 10, offset: int = 0
|
|
255
|
+
) -> List[Run]:
|
|
256
|
+
"""List runs."""
|
|
257
|
+
return await self.http.get(f"/threads/{thread_id}/runs")
|
|
258
|
+
|
|
259
|
+
async def get(self, thread_id: str, run_id: str) -> Run:
|
|
260
|
+
"""Get a run."""
|
|
261
|
+
return await self.http.get(f"/threads/{thread_id}/runs/{run_id}")
|
|
262
|
+
|
|
263
|
+
async def list_events(
|
|
264
|
+
self, thread_id: str, run_id: str, *, limit: int = 10, offset: int = 0
|
|
265
|
+
) -> List[RunEvent]:
|
|
266
|
+
"""List run events."""
|
|
267
|
+
return await self.http.get(f"/threads/{thread_id}/runs/{run_id}/events")
|
langgraph_sdk/schema.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any, Literal, Optional, Sequence, Union, TypedDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
Metadata = Optional[dict[str, Any]]
|
|
6
|
+
|
|
7
|
+
RunStatus = Literal["pending", "running", "error", "success", "timeout", "interrupted"]
|
|
8
|
+
|
|
9
|
+
StreamMode = Literal["values", "messages", "updates", "events", "debug"]
|
|
10
|
+
|
|
11
|
+
All = Literal["*"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Config(TypedDict, total=False):
|
|
15
|
+
tags: list[str]
|
|
16
|
+
"""
|
|
17
|
+
Tags for this call and any sub-calls (eg. a Chain calling an LLM).
|
|
18
|
+
You can use these to filter calls.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
recursion_limit: int
|
|
22
|
+
"""
|
|
23
|
+
Maximum number of times a call can recurse. If not provided, defaults to 25.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
configurable: dict[str, Any]
|
|
27
|
+
"""
|
|
28
|
+
Runtime values for attributes previously made configurable on this Runnable,
|
|
29
|
+
or sub-Runnables, through .configurable_fields() or .configurable_alternatives().
|
|
30
|
+
Check .output_schema() for a description of the attributes that have been made
|
|
31
|
+
configurable.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Assistant(TypedDict):
|
|
36
|
+
"""Assistant model."""
|
|
37
|
+
|
|
38
|
+
assistant_id: str
|
|
39
|
+
"""The ID of the assistant."""
|
|
40
|
+
graph_id: str
|
|
41
|
+
"""The ID of the graph."""
|
|
42
|
+
config: Config
|
|
43
|
+
"""The assistant config."""
|
|
44
|
+
created_at: datetime
|
|
45
|
+
"""The time the assistant was created."""
|
|
46
|
+
updated_at: datetime
|
|
47
|
+
"""The last time the assistant was updated."""
|
|
48
|
+
metadata: Metadata
|
|
49
|
+
"""The assistant metadata."""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Thread(TypedDict):
|
|
53
|
+
thread_id: str
|
|
54
|
+
"""The ID of the thread."""
|
|
55
|
+
created_at: datetime
|
|
56
|
+
"""The time the thread was created."""
|
|
57
|
+
updated_at: datetime
|
|
58
|
+
"""The last time the thread was updated."""
|
|
59
|
+
metadata: Metadata
|
|
60
|
+
"""The thread metadata."""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ThreadState(TypedDict):
|
|
64
|
+
values: Union[list[dict], dict[str, Any]]
|
|
65
|
+
"""The state values."""
|
|
66
|
+
next: Sequence[str]
|
|
67
|
+
"""The next nodes to execute. If empty, the thread is done until new input is
|
|
68
|
+
received."""
|
|
69
|
+
config: Config
|
|
70
|
+
"""Config used to fetch/use this state"""
|
|
71
|
+
parent_config: Optional[Config] = None
|
|
72
|
+
"""Config used to fetch the parent state, if any"""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Run(TypedDict):
|
|
76
|
+
run_id: str
|
|
77
|
+
"""The ID of the run."""
|
|
78
|
+
thread_id: str
|
|
79
|
+
"""The ID of the thread."""
|
|
80
|
+
assistant_id: str
|
|
81
|
+
"""The assistant that was used for this run."""
|
|
82
|
+
created_at: datetime
|
|
83
|
+
"""The time the run was created."""
|
|
84
|
+
updated_at: datetime
|
|
85
|
+
"""The last time the run was updated."""
|
|
86
|
+
status: RunStatus
|
|
87
|
+
"""The status of the run. One of 'pending', 'running', 'error', 'success'."""
|
|
88
|
+
metadata: Metadata
|
|
89
|
+
"""The run metadata."""
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class RunEvent(TypedDict):
|
|
93
|
+
event_id: str
|
|
94
|
+
"""The ID of the event."""
|
|
95
|
+
run_id: str
|
|
96
|
+
"""The ID of the run."""
|
|
97
|
+
received_at: datetime
|
|
98
|
+
"""The time the event was received."""
|
|
99
|
+
span_id: str
|
|
100
|
+
"""The ID of the span."""
|
|
101
|
+
event: str
|
|
102
|
+
"""The event type."""
|
|
103
|
+
name: str
|
|
104
|
+
"""The event name."""
|
|
105
|
+
data: dict
|
|
106
|
+
"""The event data."""
|
|
107
|
+
metadata: dict
|
|
108
|
+
"""The event metadata."""
|
|
109
|
+
tags: list[str]
|
|
110
|
+
"""The event tags."""
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: langgraph-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Nuno Campos
|
|
6
|
+
Author-email: nuno@langchain.dev
|
|
7
|
+
Requires-Python: >=3.9.0,<3.12
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Requires-Dist: httpx (==0.25.2)
|
|
13
|
+
Requires-Dist: httpx-sse (>=0.4.0,<0.5.0)
|
|
14
|
+
Requires-Dist: orjson (>=3.10.1,<4.0.0)
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# langserve
|
|
18
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
langgraph_sdk/__init__.py,sha256=7kBFjarQJyTa_vUq69lkihKIVj5Psy1EWd5NZgyqRzU,70
|
|
2
|
+
langgraph_sdk/client.py,sha256=O_ZED5nMUCzjMSkM8w3dRmgsr1t2Z6EwJ1gEEVe7wYs,8772
|
|
3
|
+
langgraph_sdk/schema.py,sha256=ZbNbY3F-IkSpg0N3baiUFJdBIw8u31xw_FCxP2MS3EY,2975
|
|
4
|
+
langgraph_sdk-0.1.0.dist-info/METADATA,sha256=fF5TLp4HIGOTrr3UYu4C_6nqT1ufbRfX76RqEVeZ5xs,519
|
|
5
|
+
langgraph_sdk-0.1.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
6
|
+
langgraph_sdk-0.1.0.dist-info/RECORD,,
|