langgraph-api 0.0.15__py3-none-any.whl → 0.0.16__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/api/store.py +67 -15
- langgraph_api/asyncio.py +5 -0
- langgraph_api/auth/custom.py +20 -5
- langgraph_api/js/package.json +1 -1
- langgraph_api/js/remote_new.py +10 -2
- langgraph_api/js/remote_old.py +10 -2
- langgraph_api/js/tests/api.test.mts +90 -7
- langgraph_api/js/tests/graphs/agent.mts +2 -0
- langgraph_api/js/tests/graphs/delay.mts +5 -0
- langgraph_api/js/yarn.lock +4 -4
- langgraph_api/models/run.py +1 -0
- langgraph_api/queue.py +44 -4
- langgraph_api/route.py +8 -3
- langgraph_api/schema.py +1 -1
- langgraph_api/stream.py +8 -1
- langgraph_api/utils.py +11 -5
- {langgraph_api-0.0.15.dist-info → langgraph_api-0.0.16.dist-info}/METADATA +2 -2
- {langgraph_api-0.0.15.dist-info → langgraph_api-0.0.16.dist-info}/RECORD +23 -23
- langgraph_storage/ops.py +9 -2
- openapi.json +5 -5
- {langgraph_api-0.0.15.dist-info → langgraph_api-0.0.16.dist-info}/LICENSE +0 -0
- {langgraph_api-0.0.15.dist-info → langgraph_api-0.0.16.dist-info}/WHEEL +0 -0
- {langgraph_api-0.0.15.dist-info → langgraph_api-0.0.16.dist-info}/entry_points.txt +0 -0
langgraph_api/api/store.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from langgraph_sdk.auth import Auth
|
|
1
4
|
from starlette.responses import Response
|
|
2
5
|
from starlette.routing import BaseRoute
|
|
3
6
|
|
|
7
|
+
from langgraph_api.auth.custom import handle_event as _handle_event
|
|
4
8
|
from langgraph_api.route import ApiRequest, ApiResponse, ApiRoute
|
|
9
|
+
from langgraph_api.utils import get_auth_ctx
|
|
5
10
|
from langgraph_api.validation import (
|
|
6
11
|
StoreDeleteRequest,
|
|
7
12
|
StoreListNamespacesRequest,
|
|
@@ -21,6 +26,24 @@ def _validate_namespace(namespace: tuple[str, ...]) -> Response | None:
|
|
|
21
26
|
)
|
|
22
27
|
|
|
23
28
|
|
|
29
|
+
async def handle_event(
|
|
30
|
+
action: str,
|
|
31
|
+
value: Any,
|
|
32
|
+
) -> None:
|
|
33
|
+
ctx = get_auth_ctx()
|
|
34
|
+
if not ctx:
|
|
35
|
+
return
|
|
36
|
+
await _handle_event(
|
|
37
|
+
Auth.types.AuthContext(
|
|
38
|
+
user=ctx.user,
|
|
39
|
+
permissions=ctx.permissions,
|
|
40
|
+
resource="store",
|
|
41
|
+
action=action,
|
|
42
|
+
),
|
|
43
|
+
value,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
24
47
|
@retry_db
|
|
25
48
|
async def put_item(request: ApiRequest):
|
|
26
49
|
"""Store or update an item."""
|
|
@@ -28,9 +51,13 @@ async def put_item(request: ApiRequest):
|
|
|
28
51
|
namespace = tuple(payload["namespace"]) if payload.get("namespace") else ()
|
|
29
52
|
if err := _validate_namespace(namespace):
|
|
30
53
|
return err
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
54
|
+
handler_payload = {
|
|
55
|
+
"namespace": namespace,
|
|
56
|
+
"key": payload["key"],
|
|
57
|
+
"value": payload["value"],
|
|
58
|
+
}
|
|
59
|
+
await handle_event("put", handler_payload)
|
|
60
|
+
await Store().aput(namespace, handler_payload["key"], handler_payload["value"])
|
|
34
61
|
return Response(status_code=204)
|
|
35
62
|
|
|
36
63
|
|
|
@@ -43,6 +70,11 @@ async def get_item(request: ApiRequest):
|
|
|
43
70
|
key = request.query_params.get("key")
|
|
44
71
|
if not key:
|
|
45
72
|
return ApiResponse({"error": "Key is required"}, status_code=400)
|
|
73
|
+
handler_payload = {
|
|
74
|
+
"namespace": namespace,
|
|
75
|
+
"key": key,
|
|
76
|
+
}
|
|
77
|
+
await handle_event("get", handler_payload)
|
|
46
78
|
result = await Store().aget(namespace, key)
|
|
47
79
|
return ApiResponse(result.dict() if result is not None else None)
|
|
48
80
|
|
|
@@ -54,8 +86,12 @@ async def delete_item(request: ApiRequest):
|
|
|
54
86
|
namespace = tuple(payload["namespace"]) if payload.get("namespace") else ()
|
|
55
87
|
if err := _validate_namespace(namespace):
|
|
56
88
|
return err
|
|
57
|
-
|
|
58
|
-
|
|
89
|
+
handler_payload = {
|
|
90
|
+
"namespace": namespace,
|
|
91
|
+
"key": payload["key"],
|
|
92
|
+
}
|
|
93
|
+
await handle_event("delete", handler_payload)
|
|
94
|
+
await Store().adelete(handler_payload["namespace"], handler_payload["key"])
|
|
59
95
|
return Response(status_code=204)
|
|
60
96
|
|
|
61
97
|
|
|
@@ -70,12 +106,20 @@ async def search_items(request: ApiRequest):
|
|
|
70
106
|
limit = payload.get("limit") or 10
|
|
71
107
|
offset = payload.get("offset") or 0
|
|
72
108
|
query = payload.get("query")
|
|
109
|
+
handler_payload = {
|
|
110
|
+
"namespace": namespace_prefix,
|
|
111
|
+
"filter": filter,
|
|
112
|
+
"limit": limit,
|
|
113
|
+
"offset": offset,
|
|
114
|
+
"query": query,
|
|
115
|
+
}
|
|
116
|
+
await handle_event("search", handler_payload)
|
|
73
117
|
items = await Store().asearch(
|
|
74
|
-
|
|
75
|
-
filter=filter,
|
|
76
|
-
limit=limit,
|
|
77
|
-
offset=offset,
|
|
78
|
-
query=query,
|
|
118
|
+
handler_payload["namespace"],
|
|
119
|
+
filter=handler_payload["filter"],
|
|
120
|
+
limit=handler_payload["limit"],
|
|
121
|
+
offset=handler_payload["offset"],
|
|
122
|
+
query=handler_payload["query"],
|
|
79
123
|
)
|
|
80
124
|
return ApiResponse({"items": [item.dict() for item in items]})
|
|
81
125
|
|
|
@@ -93,12 +137,20 @@ async def list_namespaces(request: ApiRequest):
|
|
|
93
137
|
max_depth = payload.get("max_depth")
|
|
94
138
|
limit = payload.get("limit", 100)
|
|
95
139
|
offset = payload.get("offset", 0)
|
|
140
|
+
handler_payload = {
|
|
141
|
+
"namespace": prefix,
|
|
142
|
+
"suffix": suffix,
|
|
143
|
+
"max_depth": max_depth,
|
|
144
|
+
"limit": limit,
|
|
145
|
+
"offset": offset,
|
|
146
|
+
}
|
|
147
|
+
await handle_event("list_namespaces", handler_payload)
|
|
96
148
|
result = await Store().alist_namespaces(
|
|
97
|
-
prefix=
|
|
98
|
-
suffix=suffix,
|
|
99
|
-
max_depth=max_depth,
|
|
100
|
-
limit=limit,
|
|
101
|
-
offset=offset,
|
|
149
|
+
prefix=handler_payload["namespace"],
|
|
150
|
+
suffix=handler_payload["suffix"],
|
|
151
|
+
max_depth=handler_payload["max_depth"],
|
|
152
|
+
limit=handler_payload["limit"],
|
|
153
|
+
offset=handler_payload["offset"],
|
|
102
154
|
)
|
|
103
155
|
return ApiResponse({"namespaces": result})
|
|
104
156
|
|
langgraph_api/asyncio.py
CHANGED
|
@@ -60,6 +60,11 @@ async def wait_if_not_done(coro: Coroutine[Any, Any, T], done: ValueEvent) -> T:
|
|
|
60
60
|
try:
|
|
61
61
|
return await coro_task
|
|
62
62
|
except asyncio.CancelledError as e:
|
|
63
|
+
if e.args and asyncio.isfuture(e.args[-1]):
|
|
64
|
+
await logger.ainfo(
|
|
65
|
+
"Awaiting future upon cancellation", task=str(e.args[-1])
|
|
66
|
+
)
|
|
67
|
+
await e.args[-1]
|
|
63
68
|
if e.args and isinstance(e.args[0], Exception):
|
|
64
69
|
raise e.args[0] from None
|
|
65
70
|
raise
|
langgraph_api/auth/custom.py
CHANGED
|
@@ -398,6 +398,16 @@ class DotDict:
|
|
|
398
398
|
raise AttributeError(f"'DotDict' object has no attribute '{name}'")
|
|
399
399
|
return self._dict[name]
|
|
400
400
|
|
|
401
|
+
def __getitem__(self, key):
|
|
402
|
+
return self._dict[key]
|
|
403
|
+
|
|
404
|
+
def __setitem__(self, key, value):
|
|
405
|
+
self._dict[key] = value
|
|
406
|
+
if isinstance(value, dict):
|
|
407
|
+
setattr(self, key, DotDict(value))
|
|
408
|
+
else:
|
|
409
|
+
setattr(self, key, value)
|
|
410
|
+
|
|
401
411
|
def __deepcopy__(self, memo):
|
|
402
412
|
return DotDict(copy.deepcopy(self._dict))
|
|
403
413
|
|
|
@@ -457,6 +467,12 @@ class ProxyUser(BaseUser):
|
|
|
457
467
|
**d,
|
|
458
468
|
}
|
|
459
469
|
|
|
470
|
+
def __getitem__(self, key):
|
|
471
|
+
return self._user[key]
|
|
472
|
+
|
|
473
|
+
def __setitem__(self, key, value):
|
|
474
|
+
self._user[key] = value
|
|
475
|
+
|
|
460
476
|
def __getattr__(self, name: str) -> Any:
|
|
461
477
|
"""Proxy any other attributes to the underlying user object."""
|
|
462
478
|
return getattr(self._user, name)
|
|
@@ -481,10 +497,10 @@ def _normalize_auth_response(
|
|
|
481
497
|
user = response
|
|
482
498
|
permissions = []
|
|
483
499
|
|
|
484
|
-
return AuthCredentials(permissions),
|
|
500
|
+
return AuthCredentials(permissions), normalize_user(user)
|
|
485
501
|
|
|
486
502
|
|
|
487
|
-
def
|
|
503
|
+
def normalize_user(user: Any) -> BaseUser:
|
|
488
504
|
"""Normalize user into a BaseUser instance."""
|
|
489
505
|
if isinstance(user, BaseUser):
|
|
490
506
|
return user
|
|
@@ -571,9 +587,8 @@ def _get_handler(auth: Auth, ctx: Auth.types.AuthContext) -> Auth.types.Handler
|
|
|
571
587
|
]
|
|
572
588
|
for key in keys:
|
|
573
589
|
if key in auth._handlers:
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
] # Get the last defined, most specific handler
|
|
590
|
+
# Get the last defined, most specific handler
|
|
591
|
+
result = auth._handlers[key][-1]
|
|
577
592
|
auth._handler_cache[key] = result
|
|
578
593
|
return result
|
|
579
594
|
if auth._global_handlers:
|
langgraph_api/js/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"@hono/node-server": "^1.12.0",
|
|
11
11
|
"@hono/zod-validator": "^0.2.2",
|
|
12
12
|
"@langchain/core": "^0.3.22",
|
|
13
|
-
"@langchain/langgraph": "^0.2.
|
|
13
|
+
"@langchain/langgraph": "^0.2.35",
|
|
14
14
|
"@types/json-schema": "^7.0.15",
|
|
15
15
|
"@typescript/vfs": "^1.6.0",
|
|
16
16
|
"dedent": "^1.5.3",
|
langgraph_api/js/remote_new.py
CHANGED
|
@@ -23,6 +23,7 @@ from langgraph.pregel.types import PregelTask, StateSnapshot
|
|
|
23
23
|
from langgraph.store.base import GetOp, Item, ListNamespacesOp, PutOp, SearchOp
|
|
24
24
|
from langgraph.types import Command, Interrupt
|
|
25
25
|
from pydantic import BaseModel
|
|
26
|
+
from starlette.exceptions import HTTPException
|
|
26
27
|
from zmq.utils.monitor import recv_monitor_message
|
|
27
28
|
|
|
28
29
|
from langgraph_api.js.base import BaseRemotePregel
|
|
@@ -681,5 +682,12 @@ async def js_healthcheck():
|
|
|
681
682
|
try:
|
|
682
683
|
await _client_invoke("ok", {})
|
|
683
684
|
return True
|
|
684
|
-
except (RemoteException, TimeoutError, zmq.error.ZMQBaseError):
|
|
685
|
-
|
|
685
|
+
except (RemoteException, TimeoutError, zmq.error.ZMQBaseError) as exc:
|
|
686
|
+
logger.warning(
|
|
687
|
+
"JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
|
|
688
|
+
error=exc,
|
|
689
|
+
)
|
|
690
|
+
raise HTTPException(
|
|
691
|
+
status_code=500,
|
|
692
|
+
detail="JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
|
|
693
|
+
) from exc
|
langgraph_api/js/remote_old.py
CHANGED
|
@@ -22,6 +22,7 @@ from langgraph.store.base import GetOp, Item, ListNamespacesOp, PutOp, SearchOp
|
|
|
22
22
|
from langgraph.types import Command, Interrupt
|
|
23
23
|
from pydantic import BaseModel
|
|
24
24
|
from starlette.applications import Starlette
|
|
25
|
+
from starlette.exceptions import HTTPException
|
|
25
26
|
from starlette.requests import Request
|
|
26
27
|
from starlette.routing import Route
|
|
27
28
|
|
|
@@ -653,5 +654,12 @@ async def js_healthcheck():
|
|
|
653
654
|
res = await checkpointer_client.get("/ok")
|
|
654
655
|
res.raise_for_status()
|
|
655
656
|
return True
|
|
656
|
-
except httpx.HTTPError:
|
|
657
|
-
|
|
657
|
+
except httpx.HTTPError as exc:
|
|
658
|
+
logger.warning(
|
|
659
|
+
"JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
|
|
660
|
+
error=exc,
|
|
661
|
+
)
|
|
662
|
+
raise HTTPException(
|
|
663
|
+
status_code=500,
|
|
664
|
+
detail="JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
|
|
665
|
+
) from exc
|
|
@@ -753,13 +753,13 @@ describe("runs", () => {
|
|
|
753
753
|
messages = findLast(chunks, (i) => i.event === "values")?.data.messages;
|
|
754
754
|
|
|
755
755
|
const threadAfterInterrupt = await client.threads.get(thread.thread_id);
|
|
756
|
-
expect(threadAfterInterrupt.status).toBe("
|
|
756
|
+
expect(threadAfterInterrupt.status).toBe("interrupted");
|
|
757
757
|
|
|
758
758
|
expect(messages.at(-1)).not.toBeNull();
|
|
759
|
-
expect(messages.at(-1)?.content).toBe("
|
|
759
|
+
expect(messages.at(-1)?.content).toBe("begin");
|
|
760
760
|
|
|
761
761
|
const state = await client.threads.getState(thread.thread_id);
|
|
762
|
-
expect(state.next).toEqual([]);
|
|
762
|
+
expect(state.next).toEqual(["tool"]);
|
|
763
763
|
|
|
764
764
|
// continue after interrupt
|
|
765
765
|
chunks = await gatherIterator(
|
|
@@ -817,6 +817,7 @@ describe("runs", () => {
|
|
|
817
817
|
});
|
|
818
818
|
|
|
819
819
|
const modifiedThread = await client.threads.get(thread.thread_id);
|
|
820
|
+
expect(modifiedThread.status).toBe("interrupted");
|
|
820
821
|
expect(modifiedThread.metadata?.modified).toBe(true);
|
|
821
822
|
|
|
822
823
|
const stateAfterModify = await client.threads.getState<AgentState>(
|
|
@@ -836,22 +837,42 @@ describe("runs", () => {
|
|
|
836
837
|
})
|
|
837
838
|
);
|
|
838
839
|
|
|
840
|
+
const threadAfterContinue = await client.threads.get(thread.thread_id);
|
|
841
|
+
expect(threadAfterContinue.status).toBe("idle");
|
|
842
|
+
|
|
839
843
|
expect(chunks.filter((i) => i.event === "error").length).toBe(0);
|
|
840
844
|
messages = findLast(chunks, (i) => i.event === "values")?.data.messages;
|
|
841
845
|
|
|
842
|
-
expect(messages.length).toBe(
|
|
843
|
-
expect(messages[
|
|
846
|
+
expect(messages.length).toBe(4);
|
|
847
|
+
expect(messages[2].content).toBe(`tool_call__modified`);
|
|
844
848
|
expect(messages.at(-1)?.content).toBe("end");
|
|
845
849
|
|
|
846
850
|
// get the history
|
|
847
851
|
const history = await client.threads.getHistory<AgentState>(
|
|
848
852
|
thread.thread_id
|
|
849
853
|
);
|
|
850
|
-
expect(history.length).toBe(
|
|
854
|
+
expect(history.length).toBe(6);
|
|
851
855
|
expect(history[0].next.length).toBe(0);
|
|
852
|
-
expect(history[0].values.messages.length).toBe(
|
|
856
|
+
expect(history[0].values.messages.length).toBe(4);
|
|
853
857
|
expect(history.at(-1)?.next).toEqual(["__start__"]);
|
|
854
858
|
});
|
|
859
|
+
|
|
860
|
+
it.concurrent("interrupt before", async () => {
|
|
861
|
+
const assistant = await client.assistants.create({ graphId: "agent" });
|
|
862
|
+
let thread = await client.threads.create();
|
|
863
|
+
const input = {
|
|
864
|
+
messages: [{ type: "human", content: "foo", id: "initial-message" }],
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
await client.runs.wait(thread.thread_id, assistant.assistant_id, {
|
|
868
|
+
input,
|
|
869
|
+
interruptBefore: ["agent"],
|
|
870
|
+
config: globalConfig,
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
thread = await client.threads.get(thread.thread_id);
|
|
874
|
+
expect(thread.status).toBe("interrupted");
|
|
875
|
+
});
|
|
855
876
|
});
|
|
856
877
|
|
|
857
878
|
describe("shared state", () => {
|
|
@@ -1684,3 +1705,65 @@ describe("long running tasks", () => {
|
|
|
1684
1705
|
}
|
|
1685
1706
|
);
|
|
1686
1707
|
});
|
|
1708
|
+
|
|
1709
|
+
// Not implemented in JS yet
|
|
1710
|
+
describe.skip("command update state", () => {
|
|
1711
|
+
it("updates state via commands", async () => {
|
|
1712
|
+
const assistant = await client.assistants.create({ graphId: "agent" });
|
|
1713
|
+
const thread = await client.threads.create();
|
|
1714
|
+
|
|
1715
|
+
const input = { messages: [{ role: "human", content: "foo" }] };
|
|
1716
|
+
|
|
1717
|
+
// dict-based updates
|
|
1718
|
+
await client.runs.wait(thread.thread_id, assistant.assistant_id, {
|
|
1719
|
+
input,
|
|
1720
|
+
config: globalConfig,
|
|
1721
|
+
});
|
|
1722
|
+
let stream = await gatherIterator(
|
|
1723
|
+
client.runs.stream(thread.thread_id, assistant.assistant_id, {
|
|
1724
|
+
command: { update: { keyOne: "value3", keyTwo: "value4" } },
|
|
1725
|
+
config: globalConfig,
|
|
1726
|
+
})
|
|
1727
|
+
);
|
|
1728
|
+
expect(stream.filter((chunk) => chunk.event === "error")).toEqual([]);
|
|
1729
|
+
|
|
1730
|
+
let state = await client.threads.getState<{
|
|
1731
|
+
keyOne: string;
|
|
1732
|
+
keyTwo: string;
|
|
1733
|
+
}>(thread.thread_id);
|
|
1734
|
+
|
|
1735
|
+
expect(state.values).toMatchObject({
|
|
1736
|
+
keyOne: "value3",
|
|
1737
|
+
keyTwo: "value4",
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1740
|
+
// list-based updates
|
|
1741
|
+
await client.runs.wait(thread.thread_id, assistant.assistant_id, {
|
|
1742
|
+
input,
|
|
1743
|
+
config: globalConfig,
|
|
1744
|
+
});
|
|
1745
|
+
stream = await gatherIterator(
|
|
1746
|
+
client.runs.stream(thread.thread_id, assistant.assistant_id, {
|
|
1747
|
+
command: {
|
|
1748
|
+
update: [
|
|
1749
|
+
["keyOne", "value1"],
|
|
1750
|
+
["keyTwo", "value2"],
|
|
1751
|
+
],
|
|
1752
|
+
},
|
|
1753
|
+
config: globalConfig,
|
|
1754
|
+
})
|
|
1755
|
+
);
|
|
1756
|
+
|
|
1757
|
+
expect(stream.filter((chunk) => chunk.event === "error")).toEqual([]);
|
|
1758
|
+
|
|
1759
|
+
state = await client.threads.getState<{
|
|
1760
|
+
keyOne: string;
|
|
1761
|
+
keyTwo: string;
|
|
1762
|
+
}>(thread.thread_id);
|
|
1763
|
+
|
|
1764
|
+
expect(state.values).toMatchObject({
|
|
1765
|
+
keyOne: "value1",
|
|
1766
|
+
keyTwo: "value2",
|
|
1767
|
+
});
|
|
1768
|
+
});
|
|
1769
|
+
});
|
|
@@ -18,6 +18,8 @@ const GraphAnnotationOutput = Annotation.Root({
|
|
|
18
18
|
}),
|
|
19
19
|
sharedStateValue: Annotation<string | null>(),
|
|
20
20
|
interrupt: Annotation<boolean>(),
|
|
21
|
+
keyOne: Annotation<string | null>(),
|
|
22
|
+
keyTwo: Annotation<string | null>(),
|
|
21
23
|
});
|
|
22
24
|
|
|
23
25
|
const GraphAnnotationInput = Annotation.Root({
|
|
@@ -14,6 +14,11 @@ const StateSchema = Annotation.Root({
|
|
|
14
14
|
const longRunning = async (
|
|
15
15
|
state: typeof StateSchema.State
|
|
16
16
|
): Promise<typeof StateSchema.Update> => {
|
|
17
|
+
if (state.delay === -1) {
|
|
18
|
+
while (true) {
|
|
19
|
+
// hang the event loop
|
|
20
|
+
}
|
|
21
|
+
}
|
|
17
22
|
await new Promise((resolve) => setTimeout(resolve, state.delay));
|
|
18
23
|
return { messages: [`finished after ${state.delay}ms`] };
|
|
19
24
|
};
|
langgraph_api/js/yarn.lock
CHANGED
|
@@ -389,10 +389,10 @@
|
|
|
389
389
|
p-retry "4"
|
|
390
390
|
uuid "^9.0.0"
|
|
391
391
|
|
|
392
|
-
"@langchain/langgraph@^0.2.
|
|
393
|
-
version "0.2.
|
|
394
|
-
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.
|
|
395
|
-
integrity sha512
|
|
392
|
+
"@langchain/langgraph@^0.2.35":
|
|
393
|
+
version "0.2.35"
|
|
394
|
+
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.35.tgz#e2dc4d07b3080570ef63b1a5a98a4e2b1e1cc630"
|
|
395
|
+
integrity sha512-h209sOZGgbKpdkc+5WgjiBH0Fe8zLmPv+ff/RnXGEr+phrXwUNQnx5iu4HexVd7P6gxM9Ymt1iZBCBXpgRbK8A==
|
|
396
396
|
dependencies:
|
|
397
397
|
"@langchain/langgraph-checkpoint" "~0.0.13"
|
|
398
398
|
"@langchain/langgraph-sdk" "~0.0.21"
|
langgraph_api/models/run.py
CHANGED
|
@@ -191,6 +191,7 @@ async def create_valid_run(
|
|
|
191
191
|
user_id = get_user_id(user)
|
|
192
192
|
config["configurable"]["langgraph_auth_user"] = user
|
|
193
193
|
config["configurable"]["langgraph_auth_user_id"] = user_id
|
|
194
|
+
config["configurable"]["langgraph_auth_permissions"] = ctx.permissions
|
|
194
195
|
else:
|
|
195
196
|
user_id = None
|
|
196
197
|
run_coro = Runs.put(
|
langgraph_api/queue.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import AsyncGenerator
|
|
3
|
+
from contextlib import AsyncExitStack, asynccontextmanager
|
|
3
4
|
from datetime import UTC, datetime
|
|
4
5
|
from random import random
|
|
5
6
|
from typing import TypedDict, cast
|
|
@@ -7,6 +8,7 @@ from typing import TypedDict, cast
|
|
|
7
8
|
import structlog
|
|
8
9
|
from langgraph.pregel.debug import CheckpointPayload, TaskResultPayload
|
|
9
10
|
|
|
11
|
+
from langgraph_api.auth.custom import normalize_user
|
|
10
12
|
from langgraph_api.config import BG_JOB_NO_DELAY, STATS_INTERVAL_SECS
|
|
11
13
|
from langgraph_api.errors import (
|
|
12
14
|
UserInterrupt,
|
|
@@ -20,11 +22,16 @@ from langgraph_api.stream import (
|
|
|
20
22
|
astream_state,
|
|
21
23
|
consume,
|
|
22
24
|
)
|
|
23
|
-
from langgraph_api.utils import AsyncConnectionProto
|
|
25
|
+
from langgraph_api.utils import AsyncConnectionProto, set_auth_ctx, with_user
|
|
24
26
|
from langgraph_storage.database import connect
|
|
25
27
|
from langgraph_storage.ops import Runs, Threads
|
|
26
28
|
from langgraph_storage.retry import RETRIABLE_EXCEPTIONS
|
|
27
29
|
|
|
30
|
+
try:
|
|
31
|
+
from psycopg.errors import InFailedSqlTransaction
|
|
32
|
+
except ImportError:
|
|
33
|
+
InFailedSqlTransaction = ()
|
|
34
|
+
|
|
28
35
|
logger = structlog.stdlib.get_logger(__name__)
|
|
29
36
|
|
|
30
37
|
WORKERS: set[asyncio.Task] = set()
|
|
@@ -149,6 +156,24 @@ class WorkerResult(TypedDict):
|
|
|
149
156
|
run_ended_at: str | None
|
|
150
157
|
|
|
151
158
|
|
|
159
|
+
@asynccontextmanager
|
|
160
|
+
async def set_auth_ctx_for_run(run: Run) -> AsyncGenerator[None, None]:
|
|
161
|
+
try:
|
|
162
|
+
user = run["kwargs"]["config"]["configurable"]["langgraph_auth_user"]
|
|
163
|
+
permissions = run["kwargs"]["config"]["configurable"][
|
|
164
|
+
"langgraph_auth_permissions"
|
|
165
|
+
]
|
|
166
|
+
if user is not None:
|
|
167
|
+
user = normalize_user(user)
|
|
168
|
+
async with with_user(user, permissions):
|
|
169
|
+
yield None
|
|
170
|
+
else:
|
|
171
|
+
yield None
|
|
172
|
+
|
|
173
|
+
except KeyError:
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
|
|
152
177
|
async def worker(
|
|
153
178
|
timeout: float,
|
|
154
179
|
exit: AsyncExitStack,
|
|
@@ -165,7 +190,8 @@ async def worker(
|
|
|
165
190
|
webhook = run["kwargs"].pop("webhook", None)
|
|
166
191
|
run_started_at = datetime.now(UTC)
|
|
167
192
|
run_ended_at: str | None = None
|
|
168
|
-
|
|
193
|
+
|
|
194
|
+
async with set_auth_ctx_for_run(run), Runs.enter(run_id) as done, exit:
|
|
169
195
|
temporary = run["kwargs"].get("temporary", False)
|
|
170
196
|
run_created_at = run["created_at"].isoformat()
|
|
171
197
|
await logger.ainfo(
|
|
@@ -245,7 +271,20 @@ async def worker(
|
|
|
245
271
|
run_ended_at=run_ended_at,
|
|
246
272
|
run_exec_ms=ms(datetime.now(UTC), run_started_at),
|
|
247
273
|
)
|
|
248
|
-
|
|
274
|
+
try:
|
|
275
|
+
await Runs.delete(conn, run_id, thread_id=run["thread_id"])
|
|
276
|
+
except InFailedSqlTransaction as e:
|
|
277
|
+
await logger.ainfo(
|
|
278
|
+
"Ignoring rollback error",
|
|
279
|
+
run_id=str(run_id),
|
|
280
|
+
run_attempt=attempt,
|
|
281
|
+
run_created_at=run_created_at,
|
|
282
|
+
exc=str(e),
|
|
283
|
+
)
|
|
284
|
+
# We need to clean up the transaction early if we want to
|
|
285
|
+
# update the thread status with the same connection
|
|
286
|
+
await exit.aclose()
|
|
287
|
+
checkpoint = None # reset the checkpoint
|
|
249
288
|
except UserInterrupt as e:
|
|
250
289
|
exception = e
|
|
251
290
|
status = "interrupted"
|
|
@@ -292,6 +331,7 @@ async def worker(
|
|
|
292
331
|
run_exec_ms=ms(datetime.now(UTC), run_started_at),
|
|
293
332
|
)
|
|
294
333
|
await Runs.set_status(conn, run_id, "error")
|
|
334
|
+
set_auth_ctx(None, None)
|
|
295
335
|
# delete or set status of thread
|
|
296
336
|
if temporary:
|
|
297
337
|
await Threads.delete(conn, run["thread_id"])
|
langgraph_api/route.py
CHANGED
|
@@ -14,7 +14,7 @@ from starlette.routing import Route, compile_path, get_name
|
|
|
14
14
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
15
15
|
|
|
16
16
|
from langgraph_api.serde import json_dumpb
|
|
17
|
-
from langgraph_api.utils import
|
|
17
|
+
from langgraph_api.utils import get_auth_ctx, with_user
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def api_request_response(
|
|
@@ -116,5 +116,10 @@ class ApiRoute(Route):
|
|
|
116
116
|
async def handle(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
117
117
|
# https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope
|
|
118
118
|
scope["route"] = self.path
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
ctx = get_auth_ctx()
|
|
120
|
+
if ctx:
|
|
121
|
+
user, auth = ctx.user, ctx.permissions
|
|
122
|
+
else:
|
|
123
|
+
user, auth = scope.get("user"), scope.get("auth")
|
|
124
|
+
async with with_user(user, auth):
|
|
125
|
+
return await super().handle(scope, receive, send)
|
langgraph_api/schema.py
CHANGED
langgraph_api/stream.py
CHANGED
|
@@ -75,8 +75,15 @@ def _map_cmd(cmd: RunCommand) -> Command:
|
|
|
75
75
|
if goto is not None and not isinstance(goto, list):
|
|
76
76
|
goto = [cmd.get("goto")]
|
|
77
77
|
|
|
78
|
+
update = cmd.get("update")
|
|
79
|
+
if isinstance(update, tuple | list) and all(
|
|
80
|
+
isinstance(t, tuple | list) and len(t) == 2 and isinstance(t[0], str)
|
|
81
|
+
for t in update
|
|
82
|
+
):
|
|
83
|
+
update = [tuple(t) for t in update]
|
|
84
|
+
|
|
78
85
|
return Command(
|
|
79
|
-
update=
|
|
86
|
+
update=update,
|
|
80
87
|
goto=(
|
|
81
88
|
[
|
|
82
89
|
it if isinstance(it, str) else Send(it["node"], it["input"])
|
langgraph_api/utils.py
CHANGED
|
@@ -17,22 +17,28 @@ AuthContext = contextvars.ContextVar[Auth.types.BaseAuthContext | None](
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
@asynccontextmanager
|
|
20
|
-
async def with_user(
|
|
20
|
+
async def with_user(
|
|
21
|
+
user: BaseUser | None = None, auth: AuthCredentials | list[str] | None = None
|
|
22
|
+
):
|
|
21
23
|
current = get_auth_ctx()
|
|
22
24
|
set_auth_ctx(user, auth)
|
|
23
25
|
yield
|
|
24
26
|
if current is None:
|
|
25
27
|
return
|
|
26
|
-
set_auth_ctx(current.user, AuthCredentials(scopes=current.
|
|
28
|
+
set_auth_ctx(current.user, AuthCredentials(scopes=current.permissions))
|
|
27
29
|
|
|
28
30
|
|
|
29
|
-
def set_auth_ctx(
|
|
30
|
-
|
|
31
|
+
def set_auth_ctx(
|
|
32
|
+
user: BaseUser | None, auth: AuthCredentials | list[str] | None
|
|
33
|
+
) -> None:
|
|
34
|
+
if user is None and auth is None:
|
|
31
35
|
AuthContext.set(None)
|
|
32
36
|
else:
|
|
33
37
|
AuthContext.set(
|
|
34
38
|
Auth.types.BaseAuthContext(
|
|
35
|
-
permissions=
|
|
39
|
+
permissions=(
|
|
40
|
+
auth.scopes if isinstance(auth, AuthCredentials) else (auth or [])
|
|
41
|
+
),
|
|
36
42
|
user=user or SimpleUser(""),
|
|
37
43
|
)
|
|
38
44
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: langgraph-api
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.16
|
|
4
4
|
Summary:
|
|
5
5
|
License: Elastic-2.0
|
|
6
6
|
Author: Nuno Campos
|
|
@@ -16,7 +16,7 @@ Requires-Dist: jsonschema-rs (>=0.25.0,<0.26.0)
|
|
|
16
16
|
Requires-Dist: langchain-core (>=0.2.38,<0.4.0)
|
|
17
17
|
Requires-Dist: langgraph (>=0.2.56,<0.3.0)
|
|
18
18
|
Requires-Dist: langgraph-checkpoint (>=2.0.7,<3.0)
|
|
19
|
-
Requires-Dist: langgraph-sdk (>=0.1.
|
|
19
|
+
Requires-Dist: langgraph-sdk (>=0.1.51,<0.2.0)
|
|
20
20
|
Requires-Dist: langsmith (>=0.1.63,<0.3.0)
|
|
21
21
|
Requires-Dist: orjson (>=3.10.1)
|
|
22
22
|
Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
|
|
@@ -5,11 +5,11 @@ langgraph_api/api/assistants.py,sha256=nn-0Q5FTaEbdPq-oesrpVzqu223PDSzeejFy9fd5X
|
|
|
5
5
|
langgraph_api/api/meta.py,sha256=hueasWpTDQ6xYLo9Bzt2jhNH8XQRzreH8FTeFfnRoxQ,2700
|
|
6
6
|
langgraph_api/api/openapi.py,sha256=AUxfnD5hlRp7s-0g2hBC5dNSNk3HTwOLeJiF489DT44,2762
|
|
7
7
|
langgraph_api/api/runs.py,sha256=wAzPXi_kcYB9BcLBL4FXgkBohWwCPIpe4XERnsnWnsA,16042
|
|
8
|
-
langgraph_api/api/store.py,sha256=
|
|
8
|
+
langgraph_api/api/store.py,sha256=VzAJVOwO0IxosBB7km5TTf2rhlWGyPkVz_LpvbxetVY,5437
|
|
9
9
|
langgraph_api/api/threads.py,sha256=taU61XPcCEhBPCYPZcMDsgVDwwWUWJs8p-PrXFXWY48,8661
|
|
10
|
-
langgraph_api/asyncio.py,sha256=
|
|
10
|
+
langgraph_api/asyncio.py,sha256=2fOlx-cZvuj1gQ867Kw1R_wsBsl9jdHYHcUtK2a-x-U,6264
|
|
11
11
|
langgraph_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
langgraph_api/auth/custom.py,sha256=
|
|
12
|
+
langgraph_api/auth/custom.py,sha256=g_u7FdKm1Qj8eu_MZdnJeMsI4DlWyU5Mg0rPJzdOTSE,20913
|
|
13
13
|
langgraph_api/auth/langsmith/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
langgraph_api/auth/langsmith/backend.py,sha256=InScaL-HYCnxYEauhxU198gRZV9pJn9SzzBoR9Edn7g,2654
|
|
15
15
|
langgraph_api/auth/langsmith/client.py,sha256=eKchvAom7hdkUXauD8vHNceBDDUijrFgdTV8bKd7x4Q,3998
|
|
@@ -30,10 +30,10 @@ langgraph_api/js/client.mts,sha256=ksiytm222KTUWj92ZnajqFku_y2AkRmfENmKie5LSPw,2
|
|
|
30
30
|
langgraph_api/js/client.new.mts,sha256=9FrArkM20IeD176Q7u5lJNruaQXqAfAdDcqJkF0TPPA,23493
|
|
31
31
|
langgraph_api/js/errors.py,sha256=Cm1TKWlUCwZReDC5AQ6SgNIVGD27Qov2xcgHyf8-GXo,361
|
|
32
32
|
langgraph_api/js/global.d.ts,sha256=zR_zLYfpzyPfxpEFth5RgZoyfGulIXyZYPRf7cU0K0Y,106
|
|
33
|
-
langgraph_api/js/package.json,sha256=
|
|
33
|
+
langgraph_api/js/package.json,sha256=BGnWhiMMvxocEuPciTq14fEHp5NFmHo6fYV8q62n3t4,840
|
|
34
34
|
langgraph_api/js/remote.py,sha256=D9cqcEgXau-fm_trpNwCHMra5BXntgUa469lgs_a9JQ,622
|
|
35
|
-
langgraph_api/js/remote_new.py,sha256=
|
|
36
|
-
langgraph_api/js/remote_old.py,sha256=
|
|
35
|
+
langgraph_api/js/remote_new.py,sha256=T_Vr8459bax1C9xxqz_ZYmGivq5Vhspg2Iu9TL0Qc-Q,22707
|
|
36
|
+
langgraph_api/js/remote_old.py,sha256=2a-3ooAYUZs8aPsfnXafbBd4pP7lRmokoU7TiO7P9Js,22546
|
|
37
37
|
langgraph_api/js/schema.py,sha256=7idnv7URlYUdSNMBXQcw7E4SxaPxCq_Oxwnlml8q5ik,408
|
|
38
38
|
langgraph_api/js/server_sent_events.py,sha256=DLgXOHauemt7706vnfDUCG1GI3TidKycSizccdz9KgA,3702
|
|
39
39
|
langgraph_api/js/src/graph.mts,sha256=EO1ITYoKiUykzO_8V8mnQb6NYybooR1VXIovThZzywc,2998
|
|
@@ -45,11 +45,11 @@ langgraph_api/js/src/schema/types.template.mts,sha256=c-FA0Ykzp4KvPyYA6a-hDf60Kd
|
|
|
45
45
|
langgraph_api/js/src/utils/importMap.mts,sha256=pX4TGOyUpuuWF82kXcxcv3-8mgusRezOGe6Uklm2O5A,1644
|
|
46
46
|
langgraph_api/js/src/utils/pythonSchemas.mts,sha256=98IW7Z_VP7L_CHNRMb3_MsiV3BgLE2JsWQY_PQcRR3o,685
|
|
47
47
|
langgraph_api/js/src/utils/serde.mts,sha256=OuyyO9btvwWd55rU_H4x91dFEJiaPxL-lL9O6Zgo908,742
|
|
48
|
-
langgraph_api/js/tests/api.test.mts,sha256=
|
|
48
|
+
langgraph_api/js/tests/api.test.mts,sha256=2EpDEs888pJGdZnsyc76GdMp7uuRxM_SNlHBwITU-5I,55668
|
|
49
49
|
langgraph_api/js/tests/compose-postgres.yml,sha256=pbNfeqVUqhWILBuUdwAgQOYsVU_fgkCVm0YlTgU8og8,1721
|
|
50
50
|
langgraph_api/js/tests/graphs/.gitignore,sha256=26J8MarZNXh7snXD5eTpV3CPFTht5Znv8dtHYCLNfkw,12
|
|
51
|
-
langgraph_api/js/tests/graphs/agent.mts,sha256=
|
|
52
|
-
langgraph_api/js/tests/graphs/delay.mts,sha256=
|
|
51
|
+
langgraph_api/js/tests/graphs/agent.mts,sha256=fFHm9vW04UN_2mGcHVHqtFIvPhjyFurBg62DAW-GJd4,3889
|
|
52
|
+
langgraph_api/js/tests/graphs/delay.mts,sha256=CFneKxqI4bGGK0lYjSbe80QirowPQlsRSuhDUKfydhk,703
|
|
53
53
|
langgraph_api/js/tests/graphs/error.mts,sha256=l4tk89449dj1BnEF_0ZcfPt0Ikk1gl8L1RaSnRfr3xo,487
|
|
54
54
|
langgraph_api/js/tests/graphs/langgraph.json,sha256=frxd7ZWILdeMYSZgUBH6UO-IR7I2YJSOfOlx2mnO1sI,189
|
|
55
55
|
langgraph_api/js/tests/graphs/nested.mts,sha256=4G7jSOSaFVQAza-_ARbK-Iai1biLlF2DIPDZXf7PLIY,1245
|
|
@@ -58,22 +58,22 @@ langgraph_api/js/tests/graphs/weather.mts,sha256=A7mLK3xW8h5B-ZyJNAyX2M2fJJwzPJz
|
|
|
58
58
|
langgraph_api/js/tests/graphs/yarn.lock,sha256=q-1S--E5VWLYtkSv03shqtNzeDDv-N_J-N26FszLsjs,7903
|
|
59
59
|
langgraph_api/js/tests/parser.test.mts,sha256=3zAbboUNhI-cY3hj4Ssr7J-sQXCBTeeI1ItrkG0Ftuk,26257
|
|
60
60
|
langgraph_api/js/tests/utils.mts,sha256=2kTybJ3O7Yfe1q3ehDouqV54ibXkNzsPZ_wBZLJvY-4,421
|
|
61
|
-
langgraph_api/js/yarn.lock,sha256=
|
|
61
|
+
langgraph_api/js/yarn.lock,sha256=JtRgt5AXlsD4qBm1Nrtzxg-TLtZkFWtRKCvyS-V8CLg,103690
|
|
62
62
|
langgraph_api/lifespan.py,sha256=Uj7NV-NqxxD1fgx_umM9pVqclcy-VlqrIxDljyj2he0,1820
|
|
63
63
|
langgraph_api/logging.py,sha256=tiDNrEFwqaIdL5ywZv908OXlzzfXsPCws9GXeoFtBV8,3367
|
|
64
64
|
langgraph_api/metadata.py,sha256=mih2G7ScQxiqyUlbksVXkqR3Oo-pM1b6lXtzOsgR1sw,3044
|
|
65
65
|
langgraph_api/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
66
|
-
langgraph_api/models/run.py,sha256=
|
|
66
|
+
langgraph_api/models/run.py,sha256=qBN_w8mUpSbAZNVL1683_DfZHQ0zvYMNyqGg-g1sz6Y,9625
|
|
67
67
|
langgraph_api/patch.py,sha256=94ddcTSZJe22JcpjxiSNjFZdYVnmeoWjk4IX4iBSoyk,1249
|
|
68
|
-
langgraph_api/queue.py,sha256=
|
|
69
|
-
langgraph_api/route.py,sha256=
|
|
70
|
-
langgraph_api/schema.py,sha256=
|
|
68
|
+
langgraph_api/queue.py,sha256=nYtcjbqjwvELK5OXxD2aw5BWAlSJ-VPyCXSODMMXIj0,13353
|
|
69
|
+
langgraph_api/route.py,sha256=fM4qYCGbmH0a3_cV8uKocb1sLklehxO6HhdRXqLK6OM,4421
|
|
70
|
+
langgraph_api/schema.py,sha256=4aZCFY-dxd_nTot71bdcd9S8QCIgKajuRyj0p2QfgJ4,5291
|
|
71
71
|
langgraph_api/serde.py,sha256=VoJ7Z1IuqrQGXFzEP1qijAITtWCrmjtVqlCRuScjXJI,3533
|
|
72
72
|
langgraph_api/server.py,sha256=afHDnL6b_fAIu_q4icnK60a74lHTTZOMIe1egdhRXIk,1522
|
|
73
73
|
langgraph_api/sse.py,sha256=2wNodCOP2eg7a9mpSu0S3FQ0CHk2BBV_vv0UtIgJIcc,4034
|
|
74
74
|
langgraph_api/state.py,sha256=8jx4IoTCOjTJuwzuXJKKFwo1VseHjNnw_CCq4x1SW14,2284
|
|
75
|
-
langgraph_api/stream.py,sha256=
|
|
76
|
-
langgraph_api/utils.py,sha256=
|
|
75
|
+
langgraph_api/stream.py,sha256=Uygx6zcY5Wi9lBhRjtxqVDyLZSF1bsaqxg6mYoYVYcY,11900
|
|
76
|
+
langgraph_api/utils.py,sha256=fMl3DHOQEwAqkFtrnP0Alfbrqw1UvwZ_JVLm-WTSQJk,2654
|
|
77
77
|
langgraph_api/validation.py,sha256=McizHlz-Ez8Jhdbc79mbPSde7GIuf2Jlbjx2yv_l6dA,4475
|
|
78
78
|
langgraph_license/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
79
79
|
langgraph_license/middleware.py,sha256=_ODIYzQkymr6W9_Fp9wtf1kAQspnpsmr53xuzyF2GA0,612
|
|
@@ -81,15 +81,15 @@ langgraph_license/validation.py,sha256=Uu_G8UGO_WTlLsBEY0gTVWjRR4czYGfw5YAD3HLZo
|
|
|
81
81
|
langgraph_storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
82
82
|
langgraph_storage/checkpoint.py,sha256=V4t2GwYEJdPCHbhq_4Udhlv0TWKDzlMu_rlNPdTDc50,3589
|
|
83
83
|
langgraph_storage/database.py,sha256=Nr5zE9Fur3-tESkqe7xNXMf2QlBuw3H0CUie7jVa6Q4,6003
|
|
84
|
-
langgraph_storage/ops.py,sha256=
|
|
84
|
+
langgraph_storage/ops.py,sha256=7kmfm7EO7YbP_ItEjMvFPKPsM0a2X6RMhjaKof63HaQ,68206
|
|
85
85
|
langgraph_storage/queue.py,sha256=6cTZ0ubHu3S1T43yxHMVOwsQsDaJupByiU0sTUFFls8,3261
|
|
86
86
|
langgraph_storage/retry.py,sha256=uvYFuXJ-T6S1QY1ZwkZHyZQbsvS-Ab68LSbzbUUSI2E,696
|
|
87
87
|
langgraph_storage/store.py,sha256=D-p3cWc_umamkKp-6Cz3cAriSACpvM5nxUIvND6PuxE,2710
|
|
88
88
|
langgraph_storage/ttl_dict.py,sha256=FlpEY8EANeXWKo_G5nmIotPquABZGyIJyk6HD9u6vqY,1533
|
|
89
89
|
logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
|
|
90
|
-
openapi.json,sha256=
|
|
91
|
-
langgraph_api-0.0.
|
|
92
|
-
langgraph_api-0.0.
|
|
93
|
-
langgraph_api-0.0.
|
|
94
|
-
langgraph_api-0.0.
|
|
95
|
-
langgraph_api-0.0.
|
|
90
|
+
openapi.json,sha256=qf2Rw3ieawlAcSJu4mGonh9mNOb6solBD70CGL3w24A,124699
|
|
91
|
+
langgraph_api-0.0.16.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
|
|
92
|
+
langgraph_api-0.0.16.dist-info/METADATA,sha256=HLMkdfTmxuTMN59uOzDQ4VCRILZvgslETZShl2Mdruw,4041
|
|
93
|
+
langgraph_api-0.0.16.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
94
|
+
langgraph_api-0.0.16.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
|
|
95
|
+
langgraph_api-0.0.16.dist-info/RECORD,,
|
langgraph_storage/ops.py
CHANGED
|
@@ -1325,7 +1325,10 @@ class Runs(Authenticated):
|
|
|
1325
1325
|
thread = Thread(
|
|
1326
1326
|
thread_id=thread_id,
|
|
1327
1327
|
status="busy",
|
|
1328
|
-
metadata={
|
|
1328
|
+
metadata={
|
|
1329
|
+
"graph_id": assistant["graph_id"],
|
|
1330
|
+
"assistant_id": str(assistant_id),
|
|
1331
|
+
},
|
|
1329
1332
|
config=Runs._merge_jsonb(
|
|
1330
1333
|
assistant["config"],
|
|
1331
1334
|
config,
|
|
@@ -1345,7 +1348,11 @@ class Runs(Authenticated):
|
|
|
1345
1348
|
if existing_thread["status"] != "busy":
|
|
1346
1349
|
existing_thread["status"] = "busy"
|
|
1347
1350
|
existing_thread["metadata"] = Runs._merge_jsonb(
|
|
1348
|
-
existing_thread["metadata"],
|
|
1351
|
+
existing_thread["metadata"],
|
|
1352
|
+
{
|
|
1353
|
+
"graph_id": assistant["graph_id"],
|
|
1354
|
+
"assistant_id": str(assistant_id),
|
|
1355
|
+
},
|
|
1349
1356
|
)
|
|
1350
1357
|
existing_thread["config"] = Runs._merge_jsonb(
|
|
1351
1358
|
assistant["config"],
|
openapi.json
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"description": "A run is an invocation of a graph / assistant, with no state or memory persistence."
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
"name": "Crons (
|
|
25
|
+
"name": "Crons (Plus tier)",
|
|
26
26
|
"description": "A cron is a periodic run that recurs on a given schedule. The repeats can be isolated, or share state in a thread"
|
|
27
27
|
},
|
|
28
28
|
{
|
|
@@ -1473,7 +1473,7 @@
|
|
|
1473
1473
|
"/threads/{thread_id}/runs/crons": {
|
|
1474
1474
|
"post": {
|
|
1475
1475
|
"tags": [
|
|
1476
|
-
"Crons (
|
|
1476
|
+
"Crons (Plus tier)"
|
|
1477
1477
|
],
|
|
1478
1478
|
"summary": "Create Thread Cron",
|
|
1479
1479
|
"description": "Create a cron to schedule runs on a thread.",
|
|
@@ -2058,7 +2058,7 @@
|
|
|
2058
2058
|
"/runs/crons": {
|
|
2059
2059
|
"post": {
|
|
2060
2060
|
"tags": [
|
|
2061
|
-
"Crons (
|
|
2061
|
+
"Crons (Plus tier)"
|
|
2062
2062
|
],
|
|
2063
2063
|
"summary": "Create Cron",
|
|
2064
2064
|
"description": "Create a cron to schedule runs on new threads.",
|
|
@@ -2110,7 +2110,7 @@
|
|
|
2110
2110
|
"/runs/crons/search": {
|
|
2111
2111
|
"post": {
|
|
2112
2112
|
"tags": [
|
|
2113
|
-
"Crons (
|
|
2113
|
+
"Crons (Plus tier)"
|
|
2114
2114
|
],
|
|
2115
2115
|
"summary": "Search Crons",
|
|
2116
2116
|
"description": "Search all active crons",
|
|
@@ -2399,7 +2399,7 @@
|
|
|
2399
2399
|
"/runs/crons/{cron_id}": {
|
|
2400
2400
|
"delete": {
|
|
2401
2401
|
"tags": [
|
|
2402
|
-
"Crons (
|
|
2402
|
+
"Crons (Plus tier)"
|
|
2403
2403
|
],
|
|
2404
2404
|
"summary": "Delete Cron",
|
|
2405
2405
|
"description": "Delete a cron by ID.",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|