langgraph-api 0.2.20__py3-none-any.whl → 0.2.23__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.

Potentially problematic release.


This version of langgraph-api might be problematic. Click here for more details.

langgraph_api/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.2.20"
1
+ __version__ = "0.2.23"
@@ -21,13 +21,24 @@ from langgraph_api.validation import (
21
21
  AssistantVersionChange,
22
22
  AssistantVersionsSearchRequest,
23
23
  )
24
+ from langgraph_runtime.checkpoint import Checkpointer
24
25
  from langgraph_runtime.database import connect
25
26
  from langgraph_runtime.ops import Assistants
26
27
  from langgraph_runtime.retry import retry_db
28
+ from langgraph_runtime.store import Store
27
29
 
28
30
  logger = structlog.stdlib.get_logger(__name__)
29
31
 
30
32
 
33
+ EXCLUDED_CONFIG_SCHEMA = (
34
+ "__pregel_checkpointer",
35
+ "__pregel_store",
36
+ "checkpoint_id",
37
+ "checkpoint_ns",
38
+ "thread_id",
39
+ )
40
+
41
+
31
42
  def _get_configurable_jsonschema(graph: Pregel) -> dict:
32
43
  """Get the JSON schema for the configurable part of the graph.
33
44
 
@@ -52,6 +63,9 @@ def _get_configurable_jsonschema(graph: Pregel) -> dict:
52
63
  if model_fields is not None and "configurable" in model_fields:
53
64
  configurable = TypeAdapter(model_fields["configurable"].annotation)
54
65
  json_schema = configurable.json_schema()
66
+ if json_schema:
67
+ for key in EXCLUDED_CONFIG_SCHEMA:
68
+ json_schema["properties"].pop(key, None)
55
69
  # The type name of the configurable type is not preserved.
56
70
  # We'll add it back to the schema if we can.
57
71
  if hasattr(graph, "config_type") and graph.config_type is not None:
@@ -174,36 +188,41 @@ async def get_assistant_graph(
174
188
  validate_uuid(assistant_id, "Invalid assistant ID: must be a UUID")
175
189
  async with connect() as conn:
176
190
  assistant_ = await Assistants.get(conn, assistant_id)
177
- assistant = await fetchone(assistant_)
178
- config = await ajson_loads(assistant["config"])
179
- async with get_graph(assistant["graph_id"], config) as graph:
180
- xray: bool | int = False
181
- xray_query = request.query_params.get("xray")
182
- if xray_query:
183
- if xray_query in ("true", "True"):
184
- xray = True
185
- elif xray_query in ("false", "False"):
186
- xray = False
187
- else:
188
- try:
189
- xray = int(xray_query)
190
- except ValueError:
191
- raise HTTPException(422, detail="Invalid xray value") from None
192
-
193
- if xray <= 0:
194
- raise HTTPException(422, detail="Invalid xray value") from None
195
-
196
- if isinstance(graph, BaseRemotePregel):
197
- drawable_graph = await graph.fetch_graph(xray=xray)
198
- return ApiResponse(drawable_graph.to_json())
199
-
200
- try:
201
- drawable_graph = await graph.aget_graph(xray=xray)
202
- return ApiResponse(drawable_graph.to_json())
203
- except NotImplementedError:
204
- raise HTTPException(
205
- 422, detail="The graph does not support visualization"
206
- ) from None
191
+ assistant = await fetchone(assistant_)
192
+ config = await ajson_loads(assistant["config"])
193
+ async with get_graph(
194
+ assistant["graph_id"],
195
+ config,
196
+ checkpointer=Checkpointer(conn),
197
+ store=Store(),
198
+ ) as graph:
199
+ xray: bool | int = False
200
+ xray_query = request.query_params.get("xray")
201
+ if xray_query:
202
+ if xray_query in ("true", "True"):
203
+ xray = True
204
+ elif xray_query in ("false", "False"):
205
+ xray = False
206
+ else:
207
+ try:
208
+ xray = int(xray_query)
209
+ except ValueError:
210
+ raise HTTPException(422, detail="Invalid xray value") from None
211
+
212
+ if xray <= 0:
213
+ raise HTTPException(422, detail="Invalid xray value") from None
214
+
215
+ if isinstance(graph, BaseRemotePregel):
216
+ drawable_graph = await graph.fetch_graph(xray=xray)
217
+ return ApiResponse(drawable_graph.to_json())
218
+
219
+ try:
220
+ drawable_graph = await graph.aget_graph(xray=xray)
221
+ return ApiResponse(drawable_graph.to_json())
222
+ except NotImplementedError:
223
+ raise HTTPException(
224
+ 422, detail="The graph does not support visualization"
225
+ ) from None
207
226
 
208
227
 
209
228
  @retry_db
@@ -215,35 +234,40 @@ async def get_assistant_subgraphs(
215
234
  validate_uuid(assistant_id, "Invalid assistant ID: must be a UUID")
216
235
  async with connect() as conn:
217
236
  assistant_ = await Assistants.get(conn, assistant_id)
218
- assistant = await fetchone(assistant_)
219
- config = await ajson_loads(assistant["config"])
220
- async with get_graph(assistant["graph_id"], config) as graph:
221
- namespace = request.path_params.get("namespace")
222
-
223
- if isinstance(graph, BaseRemotePregel):
224
- return ApiResponse(
225
- await graph.fetch_subgraphs(
226
- namespace=namespace,
227
- recurse=request.query_params.get("recurse", "False")
228
- in ("true", "True"),
229
- )
230
- )
231
-
232
- try:
233
- return ApiResponse(
234
- {
235
- ns: _graph_schemas(subgraph)
236
- async for ns, subgraph in graph.aget_subgraphs(
237
+ assistant = await fetchone(assistant_)
238
+ config = await ajson_loads(assistant["config"])
239
+ async with get_graph(
240
+ assistant["graph_id"],
241
+ config,
242
+ checkpointer=Checkpointer(conn),
243
+ store=Store(),
244
+ ) as graph:
245
+ namespace = request.path_params.get("namespace")
246
+
247
+ if isinstance(graph, BaseRemotePregel):
248
+ return ApiResponse(
249
+ await graph.fetch_subgraphs(
237
250
  namespace=namespace,
238
251
  recurse=request.query_params.get("recurse", "False")
239
252
  in ("true", "True"),
240
253
  )
241
- }
242
- )
243
- except NotImplementedError:
244
- raise HTTPException(
245
- 422, detail="The graph does not support visualization"
246
- ) from None
254
+ )
255
+
256
+ try:
257
+ return ApiResponse(
258
+ {
259
+ ns: _graph_schemas(subgraph)
260
+ async for ns, subgraph in graph.aget_subgraphs(
261
+ namespace=namespace,
262
+ recurse=request.query_params.get("recurse", "False")
263
+ in ("true", "True"),
264
+ )
265
+ }
266
+ )
267
+ except NotImplementedError:
268
+ raise HTTPException(
269
+ 422, detail="The graph does not support visualization"
270
+ ) from None
247
271
 
248
272
 
249
273
  @retry_db
@@ -255,54 +279,60 @@ async def get_assistant_schemas(
255
279
  validate_uuid(assistant_id, "Invalid assistant ID: must be a UUID")
256
280
  async with connect() as conn:
257
281
  assistant_ = await Assistants.get(conn, assistant_id)
258
- assistant = await fetchone(assistant_)
259
- config = await ajson_loads(assistant["config"])
260
- async with get_graph(assistant["graph_id"], config) as graph:
261
- if isinstance(graph, BaseRemotePregel):
262
- schemas = await graph.fetch_state_schema()
282
+ # TODO Implementa cache so we can de-dent and release this connection.
283
+ assistant = await fetchone(assistant_)
284
+ config = await ajson_loads(assistant["config"])
285
+ async with get_graph(
286
+ assistant["graph_id"],
287
+ config,
288
+ checkpointer=Checkpointer(conn),
289
+ store=Store(),
290
+ ) as graph:
291
+ if isinstance(graph, BaseRemotePregel):
292
+ schemas = await graph.fetch_state_schema()
293
+ return ApiResponse(
294
+ {
295
+ "graph_id": assistant["graph_id"],
296
+ "input_schema": schemas.get("input"),
297
+ "output_schema": schemas.get("output"),
298
+ "state_schema": schemas.get("state"),
299
+ "config_schema": schemas.get("config"),
300
+ }
301
+ )
302
+
303
+ try:
304
+ input_schema = graph.get_input_jsonschema()
305
+ except Exception as e:
306
+ logger.warning(
307
+ f"Failed to get input schema for graph {graph.name} with error: `{str(e)}`"
308
+ )
309
+ input_schema = None
310
+ try:
311
+ output_schema = graph.get_output_jsonschema()
312
+ except Exception as e:
313
+ logger.warning(
314
+ f"Failed to get output schema for graph {graph.name} with error: `{str(e)}`"
315
+ )
316
+ output_schema = None
317
+
318
+ state_schema = _state_jsonschema(graph)
319
+ try:
320
+ config_schema = _get_configurable_jsonschema(graph)
321
+ except Exception as e:
322
+ config_schema = None
323
+ logger.warning(
324
+ f"Failed to get config schema for graph {graph.name} with error: `{str(e)}`"
325
+ )
263
326
  return ApiResponse(
264
327
  {
265
328
  "graph_id": assistant["graph_id"],
266
- "input_schema": schemas.get("input"),
267
- "output_schema": schemas.get("output"),
268
- "state_schema": schemas.get("state"),
269
- "config_schema": schemas.get("config"),
329
+ "input_schema": input_schema,
330
+ "output_schema": output_schema,
331
+ "state_schema": state_schema,
332
+ "config_schema": config_schema,
270
333
  }
271
334
  )
272
335
 
273
- try:
274
- input_schema = graph.get_input_jsonschema()
275
- except Exception as e:
276
- logger.warning(
277
- f"Failed to get input schema for graph {graph.name} with error: `{str(e)}`"
278
- )
279
- input_schema = None
280
- try:
281
- output_schema = graph.get_output_jsonschema()
282
- except Exception as e:
283
- logger.warning(
284
- f"Failed to get output schema for graph {graph.name} with error: `{str(e)}`"
285
- )
286
- output_schema = None
287
-
288
- state_schema = _state_jsonschema(graph)
289
- try:
290
- config_schema = _get_configurable_jsonschema(graph)
291
- except Exception as e:
292
- config_schema = None
293
- logger.warning(
294
- f"Failed to get config schema for graph {graph.name} with error: `{str(e)}`"
295
- )
296
- return ApiResponse(
297
- {
298
- "graph_id": assistant["graph_id"],
299
- "input_schema": input_schema,
300
- "output_schema": output_schema,
301
- "state_schema": state_schema,
302
- "config_schema": config_schema,
303
- }
304
- )
305
-
306
336
 
307
337
  @retry_db
308
338
  async def patch_assistant(
langgraph_api/graph.py CHANGED
@@ -15,11 +15,13 @@ from typing import TYPE_CHECKING, Any, NamedTuple
15
15
  from uuid import UUID, uuid5
16
16
 
17
17
  import structlog
18
- from langchain_core.runnables.config import run_in_executor
18
+ from langchain_core.runnables.config import run_in_executor, var_child_runnable_config
19
19
  from langgraph.checkpoint.base import BaseCheckpointSaver
20
+ from langgraph.constants import CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_STORE
20
21
  from langgraph.graph import Graph
21
22
  from langgraph.pregel import Pregel
22
23
  from langgraph.store.base import BaseStore
24
+ from langgraph.utils.config import ensure_config
23
25
  from starlette.exceptions import HTTPException
24
26
 
25
27
  from langgraph_api import asyncio as lg_asyncio
@@ -109,26 +111,38 @@ async def get_graph(
109
111
  """Return the runnable."""
110
112
  assert_graph_exists(graph_id)
111
113
  value = GRAPHS[graph_id]
114
+ token = None
112
115
  if graph_id in FACTORY_ACCEPTS_CONFIG:
116
+ config = ensure_config(config)
117
+ if store is not None and not config["configurable"].get(CONFIG_KEY_STORE):
118
+ config["configurable"][CONFIG_KEY_STORE] = store
119
+ if checkpointer is not None and not config["configurable"].get(
120
+ CONFIG_KEY_CHECKPOINTER
121
+ ):
122
+ config["configurable"][CONFIG_KEY_CHECKPOINTER] = checkpointer
123
+ token = var_child_runnable_config.set(config)
113
124
  value = value(config) if FACTORY_ACCEPTS_CONFIG[graph_id] else value()
114
-
115
- async with _generate_graph(value) as graph_obj:
116
- if isinstance(graph_obj, Graph):
117
- graph_obj = graph_obj.compile()
118
- if not isinstance(graph_obj, Pregel | BaseRemotePregel):
119
- raise HTTPException(
120
- status_code=424,
121
- detail=f"Graph '{graph_id}' is not valid. Review graph registration.",
122
- )
123
- update = {
124
- "checkpointer": checkpointer,
125
- "store": store,
126
- }
127
- if graph_obj.name == "LangGraph":
128
- update["name"] = graph_id
129
- if isinstance(graph_obj, BaseRemotePregel):
130
- update["config"] = config
131
- yield graph_obj.copy(update=update)
125
+ try:
126
+ async with _generate_graph(value) as graph_obj:
127
+ if isinstance(graph_obj, Graph):
128
+ graph_obj = graph_obj.compile()
129
+ if not isinstance(graph_obj, Pregel | BaseRemotePregel):
130
+ raise HTTPException(
131
+ status_code=424,
132
+ detail=f"Graph '{graph_id}' is not valid. Review graph registration.",
133
+ )
134
+ update = {
135
+ "checkpointer": checkpointer,
136
+ "store": store,
137
+ }
138
+ if graph_obj.name == "LangGraph":
139
+ update["name"] = graph_id
140
+ if isinstance(graph_obj, BaseRemotePregel):
141
+ update["config"] = config
142
+ yield graph_obj.copy(update=update)
143
+ finally:
144
+ if token is not None:
145
+ var_child_runnable_config.reset(token)
132
146
 
133
147
 
134
148
  def graph_exists(graph_id: str) -> bool:
langgraph_api/stream.py CHANGED
@@ -119,7 +119,7 @@ async def astream_state(
119
119
  is_remote_pregel = isinstance(graph, BaseRemotePregel)
120
120
  if not is_remote_pregel:
121
121
  config["configurable"]["__pregel_node_finished"] = incr_nodes
122
- # TODO add node tracking for JS graphs
122
+
123
123
  # attach run_id to config
124
124
  # for attempts beyond the first, use a fresh, unique run_id
125
125
  config = {**config, "run_id": run["run_id"]} if attempt == 1 else config
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: langgraph-api
3
- Version: 0.2.20
3
+ Version: 0.2.23
4
4
  Summary:
5
5
  License: Elastic-2.0
6
6
  Author: Nuno Campos
@@ -1,7 +1,7 @@
1
1
  LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
2
- langgraph_api/__init__.py,sha256=PP1XN3LVw4efHZSwpFDbQRezvRqPg1JqZ--H2vUombw,23
2
+ langgraph_api/__init__.py,sha256=drfF0qMRjoB2RacyqP-NkNmDOIBrys6RWHvqSTpZLuc,23
3
3
  langgraph_api/api/__init__.py,sha256=YVzpbn5IQotvuuLG9fhS9QMrxXfP4s4EpEMG0n4q3Nw,5625
4
- langgraph_api/api/assistants.py,sha256=mcKaVeNG8hQAV4IQDhaukS7FgVqTIVQNTyti3GfK2KI,14649
4
+ langgraph_api/api/assistants.py,sha256=4_tZCXlOmFg-YwgtsCZPc1wr-3fexqJ6A3_2a5giF90,15811
5
5
  langgraph_api/api/mcp.py,sha256=RvRYgANqRzNQzSmgjNkq4RlKTtoEJYil04ot9lsmEtE,14352
6
6
  langgraph_api/api/meta.py,sha256=sTgkhE-DaFWpERG6F7KelZfDsmJAiVc4j5dg50tDkSo,2950
7
7
  langgraph_api/api/openapi.py,sha256=362m6Ny8wOwZ6HrDK9JAVUzPkyLYWKeV1E71hPOaA0U,11278
@@ -23,7 +23,7 @@ langgraph_api/command.py,sha256=3O9v3i0OPa96ARyJ_oJbLXkfO8rPgDhLCswgO9koTFA,768
23
23
  langgraph_api/config.py,sha256=AsEyWCZXl_x-NvFhbhM7wagspA7DRyoL-IY6f2dBpUo,10968
24
24
  langgraph_api/cron_scheduler.py,sha256=i87j4pJrcsmsqMKeKUs69gaAjrGaSM3pM3jnXdN5JDQ,2630
25
25
  langgraph_api/errors.py,sha256=Bu_i5drgNTyJcLiyrwVE_6-XrSU50BHf9TDpttki9wQ,1690
26
- langgraph_api/graph.py,sha256=YyWCPtoI9VDV0knjCMUFoH4r9OFVsAiv5K8FzbziMqs,21488
26
+ langgraph_api/graph.py,sha256=sn3rH_D51CZ9Ghd6pIb8LO0nc0oIc8H9RXGRWbiamQU,22256
27
27
  langgraph_api/http.py,sha256=gYbxxjY8aLnsXeJymcJ7G7Nj_yToOGpPYQqmZ1_ggfA,5240
28
28
  langgraph_api/js/.gitignore,sha256=l5yI6G_V6F1600I1IjiUKn87f4uYIrBAYU1MOyBBhg4,59
29
29
  langgraph_api/js/.prettierrc,sha256=0es3ovvyNIqIw81rPQsdt1zCQcOdBqyR_DMbFE4Ifms,19
@@ -85,7 +85,7 @@ langgraph_api/serde.py,sha256=TVsx2QQtepf8Wsgsabcku1NV4Vbugu4Oujmdnq4qMS0,3964
85
85
  langgraph_api/server.py,sha256=4P7GpXbE9m-sAV7rBQ4Gd3oFk6htNbL-tRQfICAFc2k,6837
86
86
  langgraph_api/sse.py,sha256=3jG_FZj8FI9r7xGWTqaAyDkmqf6P1NOu0EzGrcSOGYc,4033
87
87
  langgraph_api/state.py,sha256=8jx4IoTCOjTJuwzuXJKKFwo1VseHjNnw_CCq4x1SW14,2284
88
- langgraph_api/stream.py,sha256=BEDhdpoXIL6CpSOEUSYG5ZZC8b4DnyThHX5rHB3xGa4,12088
88
+ langgraph_api/stream.py,sha256=4wKeoLuxqy7m7iamONmP9Lw4vcHEMWPeQFV_PrJC5AY,12042
89
89
  langgraph_api/thread_ttl.py,sha256=-Ox8NFHqUH3wGNdEKMIfAXUubY5WGifIgCaJ7npqLgw,1762
90
90
  langgraph_api/tunneling/cloudflare.py,sha256=iKb6tj-VWPlDchHFjuQyep2Dpb-w2NGfJKt-WJG9LH0,3650
91
91
  langgraph_api/utils.py,sha256=92mSti9GfGdMRRWyESKQW5yV-75Z9icGHnIrBYvdypU,3619
@@ -97,8 +97,8 @@ langgraph_license/validation.py,sha256=ZKraAVJArAABKqrmHN-EN18ncoNUmRm500Yt1Sc7t
97
97
  langgraph_runtime/__init__.py,sha256=O4GgSmu33c-Pr8Xzxj_brcK5vkm70iNTcyxEjICFZxA,1075
98
98
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
99
99
  openapi.json,sha256=h2zgGTQD9houdruwRdNwZuAFo-w3eP_f6huQ6jRFp84,133801
100
- langgraph_api-0.2.20.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
101
- langgraph_api-0.2.20.dist-info/METADATA,sha256=QmTOcC7iTvhr_aoJ84OkNjaUd6CkKE5R35cVzQidB8I,4275
102
- langgraph_api-0.2.20.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
103
- langgraph_api-0.2.20.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
104
- langgraph_api-0.2.20.dist-info/RECORD,,
100
+ langgraph_api-0.2.23.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
101
+ langgraph_api-0.2.23.dist-info/METADATA,sha256=17XvJmMLdBhzcwrl06VdUWmqIFUgDbCb5_rUjfDrPwY,4275
102
+ langgraph_api-0.2.23.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
103
+ langgraph_api-0.2.23.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
104
+ langgraph_api-0.2.23.dist-info/RECORD,,