langgraph-api 0.0.27__py3-none-any.whl → 0.0.28__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/__init__.py +2 -0
- langgraph_api/api/assistants.py +43 -13
- langgraph_api/api/meta.py +1 -1
- langgraph_api/api/runs.py +14 -1
- langgraph_api/api/ui.py +68 -0
- langgraph_api/asyncio.py +43 -4
- langgraph_api/auth/middleware.py +2 -2
- langgraph_api/config.py +14 -1
- langgraph_api/cron_scheduler.py +1 -1
- langgraph_api/graph.py +5 -0
- langgraph_api/http.py +24 -7
- langgraph_api/js/.gitignore +2 -0
- langgraph_api/js/build.mts +44 -1
- langgraph_api/js/client.mts +67 -31
- langgraph_api/js/global.d.ts +1 -0
- langgraph_api/js/package.json +11 -5
- langgraph_api/js/remote.py +662 -16
- langgraph_api/js/sse.py +138 -0
- langgraph_api/js/tests/api.test.mts +28 -0
- langgraph_api/js/tests/compose-postgres.yml +2 -2
- langgraph_api/js/tests/graphs/agent.css +1 -0
- langgraph_api/js/tests/graphs/agent.ui.tsx +10 -0
- langgraph_api/js/tests/graphs/package.json +2 -2
- langgraph_api/js/tests/graphs/yarn.lock +13 -13
- langgraph_api/js/yarn.lock +706 -1188
- langgraph_api/lifespan.py +15 -5
- langgraph_api/logging.py +9 -0
- langgraph_api/metadata.py +5 -1
- langgraph_api/middleware/http_logger.py +1 -1
- langgraph_api/patch.py +2 -0
- langgraph_api/queue_entrypoint.py +63 -0
- langgraph_api/schema.py +2 -0
- langgraph_api/stream.py +1 -0
- langgraph_api/webhook.py +42 -0
- langgraph_api/{queue.py → worker.py} +52 -166
- {langgraph_api-0.0.27.dist-info → langgraph_api-0.0.28.dist-info}/METADATA +2 -2
- {langgraph_api-0.0.27.dist-info → langgraph_api-0.0.28.dist-info}/RECORD +47 -44
- langgraph_storage/database.py +8 -22
- langgraph_storage/inmem_stream.py +108 -0
- langgraph_storage/ops.py +80 -57
- langgraph_storage/queue.py +126 -103
- langgraph_storage/retry.py +5 -1
- langgraph_storage/store.py +5 -1
- openapi.json +3 -3
- langgraph_api/js/client.new.mts +0 -875
- langgraph_api/js/remote_new.py +0 -694
- langgraph_api/js/remote_old.py +0 -670
- langgraph_api/js/server_sent_events.py +0 -126
- {langgraph_api-0.0.27.dist-info → langgraph_api-0.0.28.dist-info}/LICENSE +0 -0
- {langgraph_api-0.0.27.dist-info → langgraph_api-0.0.28.dist-info}/WHEEL +0 -0
- {langgraph_api-0.0.27.dist-info → langgraph_api-0.0.28.dist-info}/entry_points.txt +0 -0
langgraph_api/js/sse.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Adapted from httpx_sse to split lines on \n, \r, \r\n per the SSE spec."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import orjson
|
|
7
|
+
from langgraph_sdk.schema import StreamPart
|
|
8
|
+
|
|
9
|
+
BytesLike = bytes | bytearray | memoryview
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BytesLineDecoder:
|
|
13
|
+
"""
|
|
14
|
+
Handles incrementally reading lines from text.
|
|
15
|
+
|
|
16
|
+
Has the same behaviour as the stdllib bytes splitlines,
|
|
17
|
+
but handling the input iteratively.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
self.buffer = bytearray()
|
|
22
|
+
self.trailing_cr: bool = False
|
|
23
|
+
|
|
24
|
+
def decode(self, text: bytes) -> list[BytesLike]:
|
|
25
|
+
# See https://docs.python.org/3/glossary.html#term-universal-newlines
|
|
26
|
+
NEWLINE_CHARS = b"\n\r"
|
|
27
|
+
|
|
28
|
+
# We always push a trailing `\r` into the next decode iteration.
|
|
29
|
+
if self.trailing_cr:
|
|
30
|
+
text = b"\r" + text
|
|
31
|
+
self.trailing_cr = False
|
|
32
|
+
if text.endswith(b"\r"):
|
|
33
|
+
self.trailing_cr = True
|
|
34
|
+
text = text[:-1]
|
|
35
|
+
|
|
36
|
+
if not text:
|
|
37
|
+
# NOTE: the edge case input of empty text doesn't occur in practice,
|
|
38
|
+
# because other httpx internals filter out this value
|
|
39
|
+
return [] # pragma: no cover
|
|
40
|
+
|
|
41
|
+
trailing_newline = text[-1] in NEWLINE_CHARS
|
|
42
|
+
lines = text.splitlines()
|
|
43
|
+
|
|
44
|
+
if len(lines) == 1 and not trailing_newline:
|
|
45
|
+
# No new lines, buffer the input and continue.
|
|
46
|
+
self.buffer.extend(lines[0])
|
|
47
|
+
return []
|
|
48
|
+
|
|
49
|
+
if self.buffer:
|
|
50
|
+
# Include any existing buffer in the first portion of the
|
|
51
|
+
# splitlines result.
|
|
52
|
+
self.buffer.extend(lines[0])
|
|
53
|
+
lines = [self.buffer] + lines[1:]
|
|
54
|
+
self.buffer = bytearray()
|
|
55
|
+
|
|
56
|
+
if not trailing_newline:
|
|
57
|
+
# If the last segment of splitlines is not newline terminated,
|
|
58
|
+
# then drop it from our output and start a new buffer.
|
|
59
|
+
self.buffer.extend(lines.pop())
|
|
60
|
+
|
|
61
|
+
return lines
|
|
62
|
+
|
|
63
|
+
def flush(self) -> list[BytesLike]:
|
|
64
|
+
if not self.buffer and not self.trailing_cr:
|
|
65
|
+
return []
|
|
66
|
+
|
|
67
|
+
lines = [self.buffer]
|
|
68
|
+
self.buffer = bytearray()
|
|
69
|
+
self.trailing_cr = False
|
|
70
|
+
return lines
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class SSEDecoder:
|
|
74
|
+
def __init__(self) -> None:
|
|
75
|
+
self._event = ""
|
|
76
|
+
self._data = bytearray()
|
|
77
|
+
self._last_event_id = ""
|
|
78
|
+
self._retry: int | None = None
|
|
79
|
+
|
|
80
|
+
def decode(self, line: bytes) -> StreamPart | None:
|
|
81
|
+
# See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501
|
|
82
|
+
|
|
83
|
+
if not line:
|
|
84
|
+
if (
|
|
85
|
+
not self._event
|
|
86
|
+
and not self._data
|
|
87
|
+
and not self._last_event_id
|
|
88
|
+
and self._retry is None
|
|
89
|
+
):
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
sse = StreamPart(
|
|
93
|
+
event=self._event,
|
|
94
|
+
data=orjson.loads(self._data) if self._data else None,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# NOTE: as per the SSE spec, do not reset last_event_id.
|
|
98
|
+
self._event = ""
|
|
99
|
+
self._data = bytearray()
|
|
100
|
+
self._retry = None
|
|
101
|
+
|
|
102
|
+
return sse
|
|
103
|
+
|
|
104
|
+
if line.startswith(b":"):
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
fieldname, _, value = line.partition(b":")
|
|
108
|
+
|
|
109
|
+
if value.startswith(b" "):
|
|
110
|
+
value = value[1:]
|
|
111
|
+
|
|
112
|
+
if fieldname == b"event":
|
|
113
|
+
self._event = value.decode()
|
|
114
|
+
elif fieldname == b"data":
|
|
115
|
+
self._data.extend(value)
|
|
116
|
+
elif fieldname == b"id":
|
|
117
|
+
if b"\0" in value:
|
|
118
|
+
pass
|
|
119
|
+
else:
|
|
120
|
+
self._last_event_id = value.decode()
|
|
121
|
+
elif fieldname == b"retry":
|
|
122
|
+
try:
|
|
123
|
+
self._retry = int(value)
|
|
124
|
+
except (TypeError, ValueError):
|
|
125
|
+
pass
|
|
126
|
+
else:
|
|
127
|
+
pass # Field is ignored.
|
|
128
|
+
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def aiter_lines_raw(response: httpx.Response) -> AsyncIterator[BytesLike]:
|
|
133
|
+
decoder = BytesLineDecoder()
|
|
134
|
+
async for chunk in response.aiter_bytes():
|
|
135
|
+
for line in decoder.decode(chunk):
|
|
136
|
+
yield line
|
|
137
|
+
for line in decoder.flush():
|
|
138
|
+
yield line
|
|
@@ -461,6 +461,7 @@ describe("runs", () => {
|
|
|
461
461
|
{
|
|
462
462
|
input: { messages: [{ type: "human", content: "bar" }] },
|
|
463
463
|
config: globalConfig,
|
|
464
|
+
afterSeconds: 10,
|
|
464
465
|
}
|
|
465
466
|
);
|
|
466
467
|
|
|
@@ -1740,6 +1741,33 @@ describe("long running tasks", () => {
|
|
|
1740
1741
|
);
|
|
1741
1742
|
});
|
|
1742
1743
|
|
|
1744
|
+
it("unusual newline termination characters", async () => {
|
|
1745
|
+
const thread = await client.threads.create({
|
|
1746
|
+
metadata: { graph_id: "agent" },
|
|
1747
|
+
});
|
|
1748
|
+
|
|
1749
|
+
await client.threads.updateState(thread.thread_id, {
|
|
1750
|
+
values: {
|
|
1751
|
+
messages: [
|
|
1752
|
+
{
|
|
1753
|
+
type: "human",
|
|
1754
|
+
content:
|
|
1755
|
+
"Page break characters: \n\r\x0b\x0c\x1c\x1d\x1e\x85\u2028\u2029",
|
|
1756
|
+
},
|
|
1757
|
+
],
|
|
1758
|
+
},
|
|
1759
|
+
});
|
|
1760
|
+
|
|
1761
|
+
const history = await client.threads.getHistory<{
|
|
1762
|
+
messages: { type: string; content: string }[];
|
|
1763
|
+
}>(thread.thread_id);
|
|
1764
|
+
expect(history.length).toBe(1);
|
|
1765
|
+
expect(history[0].values.messages.length).toBe(1);
|
|
1766
|
+
expect(history[0].values.messages[0].content).toBe(
|
|
1767
|
+
"Page break characters: \n\r\x0b\x0c\x1c\x1d\x1e\x85\u2028\u2029"
|
|
1768
|
+
);
|
|
1769
|
+
});
|
|
1770
|
+
|
|
1743
1771
|
// Not implemented in JS yet
|
|
1744
1772
|
describe.skip("command update state", () => {
|
|
1745
1773
|
it("updates state via commands", async () => {
|
|
@@ -30,7 +30,7 @@ services:
|
|
|
30
30
|
build:
|
|
31
31
|
context: graphs
|
|
32
32
|
dockerfile_inline: |
|
|
33
|
-
FROM langchain/langgraphjs-api
|
|
33
|
+
FROM langchain/langgraphjs-api:${NODE_VERSION:-20}
|
|
34
34
|
ADD . /deps/graphs
|
|
35
35
|
WORKDIR /deps/graphs
|
|
36
36
|
RUN yarn install --frozen-lockfile
|
|
@@ -52,6 +52,6 @@ services:
|
|
|
52
52
|
environment:
|
|
53
53
|
REDIS_URI: redis://langgraph-redis:6379
|
|
54
54
|
DATABASE_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable
|
|
55
|
-
N_JOBS_PER_WORKER: "
|
|
55
|
+
N_JOBS_PER_WORKER: "5"
|
|
56
56
|
LANGGRAPH_CLOUD_LICENSE_KEY: ${LANGGRAPH_CLOUD_LICENSE_KEY}
|
|
57
57
|
FF_JS_ZEROMQ_ENABLED: ${FF_JS_ZEROMQ_ENABLED}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
resolved "https://registry.yarnpkg.com/@cfworker/json-schema/-/json-schema-4.1.0.tgz#cc114da98c23b12f4cd4673ce8a076be24e0233c"
|
|
8
8
|
integrity sha512-/vYKi/qMxwNsuIJ9WGWwM2rflY40ZenK3Kh4uR5vB9/Nz12Y7IUN/Xf4wDA7vzPfw0VNh3b/jz4+MjcVgARKJg==
|
|
9
9
|
|
|
10
|
-
"@langchain/core@^0.3.
|
|
11
|
-
version "0.3.
|
|
12
|
-
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.3.
|
|
13
|
-
integrity sha512-
|
|
10
|
+
"@langchain/core@^0.3.40":
|
|
11
|
+
version "0.3.42"
|
|
12
|
+
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.3.42.tgz#f1fa38425626d8efe9fe2ee51d36c91506632363"
|
|
13
|
+
integrity sha512-pT/jC5lqWK3YGDq8dQwgKoa6anqAhMtG1x5JbnrOj9NdaLeBbCKBDQ+/Ykzk3nZ8o+0UMsaXNZo7IVL83VVjHg==
|
|
14
14
|
dependencies:
|
|
15
15
|
"@cfworker/json-schema" "^4.0.2"
|
|
16
16
|
ansi-styles "^5.0.0"
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
zod "^3.22.4"
|
|
26
26
|
zod-to-json-schema "^3.22.3"
|
|
27
27
|
|
|
28
|
-
"@langchain/langgraph-checkpoint@~0.0.
|
|
29
|
-
version "0.0.
|
|
30
|
-
resolved "https://registry.yarnpkg.com/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-0.0.
|
|
31
|
-
integrity sha512-
|
|
28
|
+
"@langchain/langgraph-checkpoint@~0.0.16":
|
|
29
|
+
version "0.0.16"
|
|
30
|
+
resolved "https://registry.yarnpkg.com/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-0.0.16.tgz#e996f31d5da8ce67b2a9bf3dc64c4c0e05f01d72"
|
|
31
|
+
integrity sha512-B50l7w9o9353drHsdsD01vhQrCJw0eqvYeXid7oKeoj1Yye+qY90r97xuhiflaYCZHM5VEo2oaizs8oknerZsQ==
|
|
32
32
|
dependencies:
|
|
33
33
|
uuid "^10.0.0"
|
|
34
34
|
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
p-retry "4"
|
|
43
43
|
uuid "^9.0.0"
|
|
44
44
|
|
|
45
|
-
"@langchain/langgraph@^0.2.
|
|
46
|
-
version "0.2.
|
|
47
|
-
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.
|
|
48
|
-
integrity sha512
|
|
45
|
+
"@langchain/langgraph@^0.2.49":
|
|
46
|
+
version "0.2.54"
|
|
47
|
+
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.54.tgz#f57a9b471808c122ee5ae4506ed05cc75f1578bd"
|
|
48
|
+
integrity sha512-+P2rU0Qz6bBCNPXOSV8WeUpLRTvhu8fQuzMYR2MqWsbbfmZrfmLxqtVWPHkmr5khx/txxFy1vOBAy+KwZ94mrg==
|
|
49
49
|
dependencies:
|
|
50
|
-
"@langchain/langgraph-checkpoint" "~0.0.
|
|
50
|
+
"@langchain/langgraph-checkpoint" "~0.0.16"
|
|
51
51
|
"@langchain/langgraph-sdk" "~0.0.32"
|
|
52
52
|
uuid "^10.0.0"
|
|
53
53
|
zod "^3.23.8"
|