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

@@ -1,6 +1,7 @@
1
1
  from typing import Any
2
2
  from uuid import uuid4
3
3
 
4
+ import structlog
4
5
  from langchain_core.runnables.utils import create_model
5
6
  from langgraph.pregel import Pregel
6
7
  from starlette.exceptions import HTTPException
@@ -23,6 +24,8 @@ from langgraph_storage.database import connect
23
24
  from langgraph_storage.ops import Assistants
24
25
  from langgraph_storage.retry import retry_db
25
26
 
27
+ logger = structlog.stdlib.get_logger(__name__)
28
+
26
29
 
27
30
  def _state_jsonschema(graph: Pregel) -> dict | None:
28
31
  fields: dict = {}
@@ -39,11 +42,17 @@ def _state_jsonschema(graph: Pregel) -> dict | None:
39
42
  def _graph_schemas(graph: Pregel) -> dict:
40
43
  try:
41
44
  input_schema = graph.get_input_jsonschema()
42
- except Exception:
45
+ except Exception as e:
46
+ logger.warning(
47
+ f"Failed to get input schema for graph {graph.name} with error: `{str(e)}`"
48
+ )
43
49
  input_schema = None
44
50
  try:
45
51
  output_schema = graph.get_output_jsonschema()
46
- except Exception:
52
+ except Exception as e:
53
+ logger.warning(
54
+ f"Failed to get output schema for graph {graph.name} with error: `{str(e)}`"
55
+ )
47
56
  output_schema = None
48
57
  state_schema = _state_jsonschema(graph)
49
58
  try:
@@ -52,7 +61,10 @@ def _graph_schemas(graph: Pregel) -> dict:
52
61
  if "configurable" in graph.config_schema().__fields__
53
62
  else {}
54
63
  )
55
- except Exception:
64
+ except Exception as e:
65
+ logger.warning(
66
+ f"Failed to get config schema for graph {graph.name} with error: `{str(e)}`"
67
+ )
56
68
  config_schema = None
57
69
  return {
58
70
  "input_schema": input_schema,
@@ -141,7 +153,13 @@ async def get_assistant_graph(
141
153
  if isinstance(graph, BaseRemotePregel):
142
154
  drawable_graph = await graph.fetch_graph(xray=xray)
143
155
  return ApiResponse(drawable_graph.to_json())
144
- return ApiResponse(graph.get_graph(xray=xray).to_json())
156
+
157
+ try:
158
+ return ApiResponse(graph.get_graph(xray=xray).to_json())
159
+ except NotImplementedError:
160
+ raise HTTPException(
161
+ 422, detail="The graph does not support visualization"
162
+ ) from None
145
163
 
146
164
 
147
165
  @retry_db
@@ -167,16 +185,21 @@ async def get_assistant_subgraphs(
167
185
  )
168
186
  )
169
187
 
170
- return ApiResponse(
171
- {
172
- ns: _graph_schemas(subgraph)
173
- async for ns, subgraph in graph.aget_subgraphs(
174
- namespace=namespace,
175
- recurse=request.query_params.get("recurse", "False")
176
- in ("true", "True"),
177
- )
178
- }
179
- )
188
+ try:
189
+ return ApiResponse(
190
+ {
191
+ ns: _graph_schemas(subgraph)
192
+ async for ns, subgraph in graph.aget_subgraphs(
193
+ namespace=namespace,
194
+ recurse=request.query_params.get("recurse", "False")
195
+ in ("true", "True"),
196
+ )
197
+ }
198
+ )
199
+ except NotImplementedError:
200
+ raise HTTPException(
201
+ 422, detail="The graph does not support visualization"
202
+ ) from None
180
203
 
181
204
 
182
205
  @retry_db
@@ -205,11 +228,17 @@ async def get_assistant_schemas(
205
228
 
206
229
  try:
207
230
  input_schema = graph.get_input_schema().schema()
208
- except Exception:
231
+ except Exception as e:
232
+ logger.warning(
233
+ f"Failed to get input schema for graph {graph.name} with error: `{str(e)}`"
234
+ )
209
235
  input_schema = None
210
236
  try:
211
237
  output_schema = graph.get_output_schema().schema()
212
- except Exception:
238
+ except Exception as e:
239
+ logger.warning(
240
+ f"Failed to get output schema for graph {graph.name} with error: `{str(e)}`"
241
+ )
213
242
  output_schema = None
214
243
 
215
244
  state_schema = _state_jsonschema(graph)
@@ -219,8 +248,11 @@ async def get_assistant_schemas(
219
248
  if "configurable" in graph.config_schema().__fields__
220
249
  else {}
221
250
  )
222
- except Exception:
251
+ except Exception as e:
223
252
  config_schema = None
253
+ logger.warning(
254
+ f"Failed to get config schema for graph {graph.name} with error: `{str(e)}`"
255
+ )
224
256
  return ApiResponse(
225
257
  {
226
258
  "graph_id": assistant["graph_id"],
@@ -16,7 +16,6 @@ from starlette.authentication import (
16
16
  AuthenticationBackend,
17
17
  AuthenticationError,
18
18
  BaseUser,
19
- SimpleUser,
20
19
  )
21
20
  from starlette.concurrency import run_in_threadpool
22
21
  from starlette.exceptions import HTTPException
@@ -478,6 +477,11 @@ class ProxyUser(BaseUser):
478
477
  return getattr(self._user, name)
479
478
 
480
479
 
480
+ class SimpleUser(ProxyUser):
481
+ def __init__(self, username: str):
482
+ super().__init__(DotDict({"identity": username}))
483
+
484
+
481
485
  def _normalize_auth_response(
482
486
  response: Any,
483
487
  ) -> tuple[AuthCredentials, BaseUser]:
@@ -2,11 +2,19 @@ from starlette.authentication import (
2
2
  AuthCredentials,
3
3
  AuthenticationBackend,
4
4
  BaseUser,
5
- UnauthenticatedUser,
5
+ )
6
+ from starlette.authentication import (
7
+ UnauthenticatedUser as StarletteUnauthenticatedUser,
6
8
  )
7
9
  from starlette.requests import HTTPConnection
8
10
 
9
11
 
12
+ class UnauthenticatedUser(StarletteUnauthenticatedUser):
13
+ @property
14
+ def identity(self) -> str:
15
+ return ""
16
+
17
+
10
18
  class NoopAuthBackend(AuthenticationBackend):
11
19
  async def authenticate(
12
20
  self, conn: HTTPConnection
langgraph_api/config.py CHANGED
@@ -24,7 +24,12 @@ CORS_ALLOW_ORIGINS = env("CORS_ALLOW_ORIGINS", cast=CommaSeparatedStrings, defau
24
24
 
25
25
  # queue
26
26
 
27
- BG_JOB_NO_DELAY = env("BG_JOB_NO_DELAY", cast=bool, default=False)
27
+ BG_JOB_NO_DELAY = env("BG_JOB_NO_DELAY", cast=bool, default=None)
28
+ BG_JOB_DELAY = env("BG_JOB_DELAY", cast=float, default=0.5)
29
+ if BG_JOB_NO_DELAY is True:
30
+ BG_JOB_DELAY = 0
31
+
32
+
28
33
  N_JOBS_PER_WORKER = env("N_JOBS_PER_WORKER", cast=int, default=10)
29
34
  BG_JOB_TIMEOUT_SECS = env("BG_JOB_TIMEOUT_SECS", cast=float, default=3600)
30
35
  FF_CRONS_ENABLED = env("FF_CRONS_ENABLED", cast=bool, default=True)
@@ -3,10 +3,10 @@ from random import random
3
3
 
4
4
  import structlog
5
5
  from langchain_core.runnables.config import run_in_executor
6
- from starlette.authentication import SimpleUser
7
6
 
8
7
  from langgraph_api.models.run import create_valid_run
9
- from langgraph_api.utils import next_cron_date, with_user
8
+ from langgraph_api.queue import set_auth_ctx_for_run
9
+ from langgraph_api.utils import next_cron_date
10
10
  from langgraph_storage.database import connect
11
11
  from langgraph_storage.ops import Crons
12
12
  from langgraph_storage.retry import retry_db
@@ -23,10 +23,13 @@ async def cron_scheduler():
23
23
  try:
24
24
  async with connect() as conn:
25
25
  async for cron in Crons.next(conn):
26
- async with with_user(SimpleUser(str(cron["user_id"])), None):
26
+ run_payload = cron["payload"]
27
+
28
+ async with set_auth_ctx_for_run(
29
+ run_payload, user_id=cron["user_id"]
30
+ ):
27
31
  logger.debug(f"Scheduling cron run {cron}")
28
32
  try:
29
- run_payload = cron["payload"]
30
33
  run = await create_valid_run(
31
34
  conn,
32
35
  thread_id=(
@@ -446,6 +446,7 @@ const StreamModeSchema = z.union([
446
446
  const ExtraStreamModeSchema = z.union([
447
447
  StreamModeSchema,
448
448
  z.literal("messages"),
449
+ z.literal("messages-tuple"),
449
450
  ]);
450
451
 
451
452
  const StreamEventsPayload = z.object({
@@ -476,11 +477,14 @@ async function* streamEventsRequest(
476
477
  ? payload.stream_mode
477
478
  : [payload.stream_mode];
478
479
 
479
- const graphStreamMode: Set<"updates" | "debug" | "values"> = new Set();
480
+ const graphStreamMode: Set<"updates" | "debug" | "values" | "messages"> =
481
+ new Set();
480
482
  if (payload.stream_mode) {
481
483
  for (const mode of userStreamMode) {
482
484
  if (mode === "messages") {
483
485
  graphStreamMode.add("values");
486
+ } else if (mode === "messages-tuple") {
487
+ graphStreamMode.add("messages");
484
488
  } else {
485
489
  graphStreamMode.add(mode);
486
490
  }
@@ -504,6 +504,7 @@ const StreamModeSchema = z.union([
504
504
  const ExtraStreamModeSchema = z.union([
505
505
  StreamModeSchema,
506
506
  z.literal("messages"),
507
+ z.literal("messages-tuple"),
507
508
  ]);
508
509
 
509
510
  const StreamEventsPayload = z.object({
@@ -534,11 +535,14 @@ async function* streamEventsRequest(
534
535
  ? payload.stream_mode
535
536
  : [payload.stream_mode];
536
537
 
537
- const graphStreamMode: Set<"updates" | "debug" | "values"> = new Set();
538
+ const graphStreamMode: Set<"updates" | "debug" | "values" | "messages"> =
539
+ new Set();
538
540
  if (payload.stream_mode) {
539
541
  for (const mode of userStreamMode) {
540
542
  if (mode === "messages") {
541
543
  graphStreamMode.add("values");
544
+ } else if (mode === "messages-tuple") {
545
+ graphStreamMode.add("messages");
542
546
  } else {
543
547
  graphStreamMode.add(mode);
544
548
  }
@@ -22,7 +22,7 @@
22
22
  "undici": "^6.19.7",
23
23
  "uuid": "^10.0.0",
24
24
  "winston": "^3.15.0",
25
- "zeromq": "^6.1.1",
25
+ "zeromq": "^6.3.0",
26
26
  "zod": "^3.23.8"
27
27
  },
28
28
  "devDependencies": {
@@ -686,6 +686,40 @@ describe("runs", () => {
686
686
  expect(run.status).toBe("success");
687
687
  });
688
688
 
689
+ it.concurrent("stream messages tuple", async () => {
690
+ const assistant = await client.assistants.create({ graphId: "agent" });
691
+ const thread = await client.threads.create();
692
+ const input = {
693
+ messages: [{ type: "human", content: "foo", id: "initial-message" }],
694
+ };
695
+ const stream = await client.runs.stream(
696
+ thread.thread_id,
697
+ assistant.assistant_id,
698
+ { input, streamMode: "messages-tuple", config: globalConfig }
699
+ );
700
+
701
+ const chunks = await gatherIterator(stream);
702
+ const runId = findLast(chunks, (i) => i.event === "metadata")?.data.run_id;
703
+ expect(runId).not.toBeNull();
704
+
705
+ const messages = chunks
706
+ .filter((i) => i.event === "messages")
707
+ .map((i) => i.data[0]);
708
+
709
+ expect(messages).toHaveLength("begin".length + "end".length + 1);
710
+ expect(messages).toMatchObject([
711
+ ..."begin".split("").map((c) => ({ content: c })),
712
+ { content: "tool_call__begin" },
713
+ ..."end".split("").map((c) => ({ content: c })),
714
+ ]);
715
+
716
+ const seenEventTypes = new Set(chunks.map((i) => i.event));
717
+ expect(seenEventTypes).toEqual(new Set(["metadata", "messages"]));
718
+
719
+ const run = await client.runs.get(thread.thread_id, runId as string);
720
+ expect(run.status).toBe("success");
721
+ });
722
+
689
723
  it.concurrent("stream mixed modes", async () => {
690
724
  const assistant = await client.assistants.create({ graphId: "agent" });
691
725
  const thread = await client.threads.create();
@@ -6,11 +6,13 @@ import {
6
6
  END,
7
7
  messagesStateReducer,
8
8
  SharedValue,
9
- LangGraphRunnableConfig,
10
9
  interrupt,
10
+ type LangGraphRunnableConfig,
11
11
  } from "@langchain/langgraph";
12
12
  import { FakeListChatModel } from "@langchain/core/utils/testing";
13
-
13
+ import { ChatGenerationChunk } from "@langchain/core/outputs";
14
+ import { v4 as uuidv4 } from "uuid";
15
+ import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager";
14
16
  const GraphAnnotationOutput = Annotation.Root({
15
17
  messages: Annotation<BaseMessage[]>({
16
18
  reducer: messagesStateReducer,
@@ -20,6 +22,7 @@ const GraphAnnotationOutput = Annotation.Root({
20
22
  interrupt: Annotation<boolean>(),
21
23
  keyOne: Annotation<string | null>(),
22
24
  keyTwo: Annotation<string | null>(),
25
+ sleep: Annotation<number | null>(),
23
26
  });
24
27
 
25
28
  const GraphAnnotationInput = Annotation.Root({
@@ -28,13 +31,58 @@ const GraphAnnotationInput = Annotation.Root({
28
31
  sharedStateFromStoreConfig: Annotation<Record<string, any> | null>,
29
32
  });
30
33
 
34
+ class StableFakeListChatModel extends FakeListChatModel {
35
+ streamMessageId: string = uuidv4();
36
+
37
+ async *_streamResponseChunks(
38
+ _messages: BaseMessage[],
39
+ options: this["ParsedCallOptions"],
40
+ runManager?: CallbackManagerForLLMRun
41
+ ): AsyncGenerator<ChatGenerationChunk> {
42
+ const response = this._currentResponse();
43
+ this._incrementResponse();
44
+ this.streamMessageId = uuidv4();
45
+
46
+ if (this.emitCustomEvent) {
47
+ await runManager?.handleCustomEvent("some_test_event", {
48
+ someval: true,
49
+ });
50
+ }
51
+
52
+ for await (const text of response) {
53
+ await this._sleepIfRequested();
54
+ if (options?.thrownErrorString) {
55
+ throw new Error(options.thrownErrorString);
56
+ }
57
+ const chunk = this._createResponseChunk(text);
58
+
59
+ // ensure stable ID
60
+ chunk.message.id = this.streamMessageId;
61
+ chunk.message.lc_kwargs.id = this.streamMessageId;
62
+
63
+ yield chunk;
64
+
65
+ void runManager?.handleLLMNewToken(
66
+ text,
67
+ undefined,
68
+ undefined,
69
+ undefined,
70
+ undefined,
71
+ { chunk }
72
+ );
73
+ }
74
+ }
75
+ }
76
+
31
77
  // For shared state
32
78
  const namespace = ["sharedState", "data"];
33
79
  const key = "user_id";
34
80
 
35
81
  const modelMap: Record<string, FakeListChatModel> = {};
36
82
  const getModel = (threadId: string) => {
37
- modelMap[threadId] ??= new FakeListChatModel({ responses: ["begin", "end"] });
83
+ modelMap[threadId] ??= new StableFakeListChatModel({
84
+ responses: ["begin", "end"],
85
+ });
38
86
  return modelMap[threadId];
39
87
  };
40
88
 
@@ -44,6 +92,11 @@ const agentNode = async (
44
92
  ) => {
45
93
  if (state.interrupt) interrupt("i want to interrupt");
46
94
 
95
+ if (state.sleep != null && state.messages.at(-1)?.getType() === "human") {
96
+ const sleep = state.sleep;
97
+ await new Promise((resolve) => setTimeout(resolve, sleep * 1000));
98
+ }
99
+
47
100
  const model = getModel(config.configurable?.thread_id ?? "$");
48
101
  const response = await model.invoke(state.messages);
49
102
  const sharedStateValue = state.sharedState?.data?.user_id ?? null;
@@ -1486,10 +1486,10 @@ nanoid@^3.3.7:
1486
1486
  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
1487
1487
  integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
1488
1488
 
1489
- node-addon-api@^8.2.1:
1490
- version "8.2.1"
1491
- resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.2.1.tgz#43a993f110b88e22ba48bcd65e16b92165a6b002"
1492
- integrity sha512-vmEOvxwiH8tlOcv4SyE8RH34rI5/nWVaigUeAUPawC6f0+HoDthwI0vkMu4tbtsZrXq6QXFfrkhjofzKEs5tpA==
1489
+ node-addon-api@^8.3.0:
1490
+ version "8.3.0"
1491
+ resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.3.0.tgz#ec3763f18befc1cdf66d11e157ce44d5eddc0603"
1492
+ integrity sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==
1493
1493
 
1494
1494
  node-int64@^0.4.0:
1495
1495
  version "0.4.0"
@@ -2173,13 +2173,13 @@ yallist@^4.0.0:
2173
2173
  resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
2174
2174
  integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
2175
2175
 
2176
- zeromq@^6.1.1:
2177
- version "6.1.2"
2178
- resolved "https://registry.yarnpkg.com/zeromq/-/zeromq-6.1.2.tgz#89f1cae83f1a4615dab02dbc7881b10ea1a33c73"
2179
- integrity sha512-5lqsW2UXnKhFhBbIm0wzrRnZmWaGI2b2bm4E03rYMK1c1jBMN0wbPnuyqqhGg7QkzBUmpqf+LyjLhg2TRJmvow==
2176
+ zeromq@^6.3.0:
2177
+ version "6.3.0"
2178
+ resolved "https://registry.yarnpkg.com/zeromq/-/zeromq-6.3.0.tgz#19ffa286d128cc317847b5d615c079a67e3bae48"
2179
+ integrity sha512-PG61AT4Y37NGJHSrp5SG2m4cKtRirso/5mm4Yf7l+upgNZZAugrGdhENsM5IcLS+WVVzbWVjEsLKpgLj/31SWQ==
2180
2180
  dependencies:
2181
2181
  "@aminya/cmake-ts" "^0.3.0-aminya.7"
2182
- node-addon-api "^8.2.1"
2182
+ node-addon-api "^8.3.0"
2183
2183
 
2184
2184
  zod-to-json-schema@^3.22.3:
2185
2185
  version "3.23.5"
@@ -8,7 +8,7 @@ from langgraph.checkpoint.base.id import uuid6
8
8
  from starlette.authentication import BaseUser
9
9
  from starlette.exceptions import HTTPException
10
10
 
11
- from langgraph_api.graph import get_assistant_id
11
+ from langgraph_api.graph import GRAPHS, get_assistant_id
12
12
  from langgraph_api.schema import (
13
13
  All,
14
14
  Config,
@@ -92,7 +92,13 @@ def ensure_ids(
92
92
  assistant_id if isinstance(assistant_id, UUID) else UUID(assistant_id)
93
93
  ]
94
94
  except ValueError:
95
- raise HTTPException(status_code=422, detail="Invalid assistant ID") from None
95
+ keys = ", ".join(GRAPHS.keys())
96
+ raise HTTPException(
97
+ status_code=422,
98
+ detail=f"Invalid assistant: '{assistant_id}'. Must be either:\n"
99
+ f"- A valid assistant UUID, or\n"
100
+ f"- One of the registered graphs: {keys}",
101
+ ) from None
96
102
  if thread_id:
97
103
  try:
98
104
  results.append(
langgraph_api/queue.py CHANGED
@@ -2,14 +2,13 @@ import asyncio
2
2
  from collections.abc import AsyncGenerator
3
3
  from contextlib import AsyncExitStack, asynccontextmanager
4
4
  from datetime import UTC, datetime
5
- from random import random
6
5
  from typing import TypedDict, cast
7
6
 
8
7
  import structlog
9
8
  from langgraph.pregel.debug import CheckpointPayload, TaskResultPayload
10
9
 
11
- from langgraph_api.auth.custom import normalize_user
12
- from langgraph_api.config import BG_JOB_NO_DELAY, STATS_INTERVAL_SECS
10
+ from langgraph_api.auth.custom import SimpleUser, normalize_user
11
+ from langgraph_api.config import BG_JOB_DELAY, STATS_INTERVAL_SECS
13
12
  from langgraph_api.errors import (
14
13
  UserInterrupt,
15
14
  UserRollback,
@@ -127,13 +126,13 @@ async def queue(concurrency: int, timeout: float):
127
126
  else:
128
127
  semaphore.release()
129
128
  await exit.aclose()
130
- await asyncio.sleep(0 if BG_JOB_NO_DELAY else random())
129
+ await asyncio.sleep(BG_JOB_DELAY)
131
130
  except Exception as exc:
132
131
  # keep trying to run the scheduler indefinitely
133
132
  logger.exception("Background worker scheduler failed", exc_info=exc)
134
133
  semaphore.release()
135
134
  await exit.aclose()
136
- await asyncio.sleep(0 if BG_JOB_NO_DELAY else random())
135
+ await asyncio.sleep(BG_JOB_DELAY)
137
136
  finally:
138
137
  logger.info("Shutting down background workers")
139
138
  for task in WORKERS:
@@ -157,12 +156,13 @@ class WorkerResult(TypedDict):
157
156
 
158
157
 
159
158
  @asynccontextmanager
160
- async def set_auth_ctx_for_run(run: Run) -> AsyncGenerator[None, None]:
159
+ async def set_auth_ctx_for_run(
160
+ run_kwargs: dict, user_id: str | None = None
161
+ ) -> AsyncGenerator[None, None]:
162
+ # user_id is a fallback.
161
163
  try:
162
- user = run["kwargs"]["config"]["configurable"]["langgraph_auth_user"]
163
- permissions = run["kwargs"]["config"]["configurable"][
164
- "langgraph_auth_permissions"
165
- ]
164
+ user = run_kwargs["config"]["configurable"]["langgraph_auth_user"]
165
+ permissions = run_kwargs["config"]["configurable"]["langgraph_auth_permissions"]
166
166
  if user is not None:
167
167
  user = normalize_user(user)
168
168
  async with with_user(user, permissions):
@@ -171,6 +171,16 @@ async def set_auth_ctx_for_run(run: Run) -> AsyncGenerator[None, None]:
171
171
  yield None
172
172
 
173
173
  except KeyError:
174
+ if user_id is not None:
175
+ await logger.ainfo(
176
+ "Setting auth to backup user_id",
177
+ user_id=user_id,
178
+ )
179
+ async with with_user(SimpleUser(user_id)):
180
+ yield None
181
+ else:
182
+ yield None
183
+ except Exception:
174
184
  pass
175
185
 
176
186
 
@@ -191,7 +201,7 @@ async def worker(
191
201
  run_started_at = datetime.now(UTC)
192
202
  run_ended_at: str | None = None
193
203
 
194
- async with set_auth_ctx_for_run(run), Runs.enter(run_id) as done, exit:
204
+ async with set_auth_ctx_for_run(run["kwargs"]), Runs.enter(run_id) as done, exit:
195
205
  temporary = run["kwargs"].get("temporary", False)
196
206
  run_created_at = run["created_at"].isoformat()
197
207
  await logger.ainfo(
langgraph_api/stream.py CHANGED
@@ -130,7 +130,7 @@ async def astream_state(
130
130
  stream_modes_set: set[StreamMode] = set(stream_mode) - {"events"}
131
131
  if "debug" not in stream_modes_set:
132
132
  stream_modes_set.add("debug")
133
- if "messages-tuple" in stream_modes_set:
133
+ if "messages-tuple" in stream_modes_set and not isinstance(graph, BaseRemotePregel):
134
134
  stream_modes_set.remove("messages-tuple")
135
135
  stream_modes_set.add("messages")
136
136
  # attach attempt metadata
langgraph_api/utils.py CHANGED
@@ -6,9 +6,11 @@ from datetime import datetime
6
6
  from typing import Any, Protocol, TypeAlias, TypeVar
7
7
 
8
8
  from langgraph_sdk import Auth
9
- from starlette.authentication import AuthCredentials, BaseUser, SimpleUser
9
+ from starlette.authentication import AuthCredentials, BaseUser
10
10
  from starlette.exceptions import HTTPException
11
11
 
12
+ from langgraph_api.auth.custom import SimpleUser
13
+
12
14
  T = TypeVar("T")
13
15
  Row: TypeAlias = dict[str, Any]
14
16
  AuthContext = contextvars.ContextVar[Auth.types.BaseAuthContext | None](
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langgraph-api
3
- Version: 0.0.16
3
+ Version: 0.0.17
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
2
  langgraph_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  langgraph_api/api/__init__.py,sha256=zAdS_0jgjmCJK6E0VwEuNcNRkaknMXfQ2C_OES5tEI4,2066
4
- langgraph_api/api/assistants.py,sha256=nn-0Q5FTaEbdPq-oesrpVzqu223PDSzeejFy9fd5Xjw,11599
4
+ langgraph_api/api/assistants.py,sha256=9wngelDC9vnSs2vYGTHDSCLf5KNds-6mgP1BWnfoY2M,12865
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
@@ -9,16 +9,16 @@ langgraph_api/api/store.py,sha256=VzAJVOwO0IxosBB7km5TTf2rhlWGyPkVz_LpvbxetVY,54
9
9
  langgraph_api/api/threads.py,sha256=taU61XPcCEhBPCYPZcMDsgVDwwWUWJs8p-PrXFXWY48,8661
10
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=g_u7FdKm1Qj8eu_MZdnJeMsI4DlWyU5Mg0rPJzdOTSE,20913
12
+ langgraph_api/auth/custom.py,sha256=juCwlVxjWnCzcWI6Amae_4NfIOFtY39kaJ_zPF5AJaw,21025
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
16
16
  langgraph_api/auth/middleware.py,sha256=qc7SbaFoeWaqxS1wbjZ2PPQ4iI2p9T0shWL7c6g0ed4,1636
17
- langgraph_api/auth/noop.py,sha256=vDJmzG2vArJxVzdHePvrJWahEa0dvGnhc2LEMMeiFz0,391
17
+ langgraph_api/auth/noop.py,sha256=Bk6Nf3p8D_iMVy_OyfPlyiJp_aEwzL-sHrbxoXpCbac,586
18
18
  langgraph_api/auth/studio_user.py,sha256=FzFQRROKDlA9JjtBuwyZvk6Mbwno5M9RVYjDO6FU3F8,186
19
19
  langgraph_api/cli.py,sha256=7vQQiD3F50r-8KkbuFjwIz8LLbdKUTd4xZGUJPiO3yQ,11688
20
- langgraph_api/config.py,sha256=HPNSCb-owhu_sU8KBLbTbKLdGIPGVLcSgdvKFKXg7uE,2816
21
- langgraph_api/cron_scheduler.py,sha256=CybK-9Jwopi_scObTHRyB7gyb0KjC4gqaT2GLe-WOFg,2587
20
+ langgraph_api/config.py,sha256=sJuzbE08y87bdg8SDJbBxlhfh6dUhwVEH15irsXbPig,2926
21
+ langgraph_api/cron_scheduler.py,sha256=MW41-TSGUe5OuXycFTy7Ax7ypxHVAv-0ImLonRT8h8o,2629
22
22
  langgraph_api/errors.py,sha256=Bu_i5drgNTyJcLiyrwVE_6-XrSU50BHf9TDpttki9wQ,1690
23
23
  langgraph_api/graph.py,sha256=FombjYQkqj8jrXJFEVkl3m2UyFcq5nSVNswR2HoRsQY,16385
24
24
  langgraph_api/http.py,sha256=XrbyxpjtfSvnaWWh5ZLGpgZmY83WoDCrP_1GPguNiXI,4712
@@ -26,11 +26,11 @@ langgraph_api/http_logger.py,sha256=Sxo_q-65tElauRvkzVLt9lJojgNdgtcHGBYD0IRyX7M,
26
26
  langgraph_api/js/.gitignore,sha256=qAah3Fq0HWAlfRj5ktZyC6QRQIsAolGLRGcRukA1XJI,33
27
27
  langgraph_api/js/base.py,sha256=BpE8-xkUp8HFPRjSKx1tfUQubvoV4jYl6OwZdre3veI,209
28
28
  langgraph_api/js/build.mts,sha256=sAZXB3xVSoG8exJ1ZEytFyikRmEsXJ_0OlLjDDgD79o,1342
29
- langgraph_api/js/client.mts,sha256=ksiytm222KTUWj92ZnajqFku_y2AkRmfENmKie5LSPw,22519
30
- langgraph_api/js/client.new.mts,sha256=9FrArkM20IeD176Q7u5lJNruaQXqAfAdDcqJkF0TPPA,23493
29
+ langgraph_api/js/client.mts,sha256=yFc4aeV5wm7N_z_K8gIukVXo6pHUBnp2Mlryl9B-yrA,22654
30
+ langgraph_api/js/client.new.mts,sha256=54n0xc9SSDnrJDPsN_JcUKcRJJyQVIYuUrkKw3XDcio,23628
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=BGnWhiMMvxocEuPciTq14fEHp5NFmHo6fYV8q62n3t4,840
33
+ langgraph_api/js/package.json,sha256=i2wjOE0_u1DdUvlc7Ulgj-tg06kjKCsFL99nQKF7vQk,840
34
34
  langgraph_api/js/remote.py,sha256=D9cqcEgXau-fm_trpNwCHMra5BXntgUa469lgs_a9JQ,622
35
35
  langgraph_api/js/remote_new.py,sha256=T_Vr8459bax1C9xxqz_ZYmGivq5Vhspg2Iu9TL0Qc-Q,22707
36
36
  langgraph_api/js/remote_old.py,sha256=2a-3ooAYUZs8aPsfnXafbBd4pP7lRmokoU7TiO7P9Js,22546
@@ -45,10 +45,10 @@ 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=2EpDEs888pJGdZnsyc76GdMp7uuRxM_SNlHBwITU-5I,55668
48
+ langgraph_api/js/tests/api.test.mts,sha256=qJdISmuKo7AWZmR4thY9eq8h4EL5bWKlhI5x7LCJ8dQ,56919
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=fFHm9vW04UN_2mGcHVHqtFIvPhjyFurBg62DAW-GJd4,3889
51
+ langgraph_api/js/tests/graphs/agent.mts,sha256=E9WMv0alMv0njUEECqEsqoRk9NXJUgXW7SyQJ3GOZ8k,5396
52
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
@@ -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=JtRgt5AXlsD4qBm1Nrtzxg-TLtZkFWtRKCvyS-V8CLg,103690
61
+ langgraph_api/js/yarn.lock,sha256=4cFZXkAYtsdCO7nPBDNHehQ1kv3hYMRXwwXutupMHs8,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=qBN_w8mUpSbAZNVL1683_DfZHQ0zvYMNyqGg-g1sz6Y,9625
66
+ langgraph_api/models/run.py,sha256=1TzTmagDXFQD_LhIMRdguZHmrPSzztq1wiMjF63d2fc,9843
67
67
  langgraph_api/patch.py,sha256=94ddcTSZJe22JcpjxiSNjFZdYVnmeoWjk4IX4iBSoyk,1249
68
- langgraph_api/queue.py,sha256=nYtcjbqjwvELK5OXxD2aw5BWAlSJ-VPyCXSODMMXIj0,13353
68
+ langgraph_api/queue.py,sha256=qRuM09mz8o7CkrBIwg-9lV3SW0TehdVGgeXvHc_adYk,13647
69
69
  langgraph_api/route.py,sha256=fM4qYCGbmH0a3_cV8uKocb1sLklehxO6HhdRXqLK6OM,4421
70
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=Uygx6zcY5Wi9lBhRjtxqVDyLZSF1bsaqxg6mYoYVYcY,11900
76
- langgraph_api/utils.py,sha256=fMl3DHOQEwAqkFtrnP0Alfbrqw1UvwZ_JVLm-WTSQJk,2654
75
+ langgraph_api/stream.py,sha256=MUYYNgwtLs1Mhq1dm12zda7j8uFYir49umigK6CnuXU,11944
76
+ langgraph_api/utils.py,sha256=aIHPp_yu-NFUzs0jzNRm6mcqoZmtbLMZl1ugFmwWTss,2692
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
@@ -88,8 +88,8 @@ langgraph_storage/store.py,sha256=D-p3cWc_umamkKp-6Cz3cAriSACpvM5nxUIvND6PuxE,27
88
88
  langgraph_storage/ttl_dict.py,sha256=FlpEY8EANeXWKo_G5nmIotPquABZGyIJyk6HD9u6vqY,1533
89
89
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
90
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,,
91
+ langgraph_api-0.0.17.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
92
+ langgraph_api-0.0.17.dist-info/METADATA,sha256=skjkW8xJ9LfwIyCYABDSreC2ZCXUqktWWekfxrK8AZY,4041
93
+ langgraph_api-0.0.17.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
94
+ langgraph_api-0.0.17.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
95
+ langgraph_api-0.0.17.dist-info/RECORD,,