langgraph-api 0.1.9__tar.gz → 0.1.13__tar.gz

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.

Files changed (107) hide show
  1. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/PKG-INFO +2 -2
  2. langgraph_api-0.1.13/langgraph_api/__init__.py +1 -0
  3. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/api/threads.py +6 -2
  4. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/cli.py +33 -2
  5. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/package.json +2 -2
  6. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/api.test.mts +45 -3
  7. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/agent_simple.mts +23 -2
  8. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/yarn.lock +15 -5
  9. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/metadata.py +15 -11
  10. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/server.py +0 -2
  11. langgraph_api-0.1.13/langgraph_api/tunneling/cloudflare.py +119 -0
  12. langgraph_api-0.1.13/langgraph_license/validation.py +22 -0
  13. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/openapi.json +20 -0
  14. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/pyproject.toml +2 -2
  15. langgraph_api-0.1.9/langgraph_api/__init__.py +0 -1
  16. langgraph_api-0.1.9/langgraph_license/middleware.py +0 -21
  17. langgraph_api-0.1.9/langgraph_license/validation.py +0 -11
  18. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/LICENSE +0 -0
  19. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/README.md +0 -0
  20. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/api/__init__.py +0 -0
  21. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/api/assistants.py +0 -0
  22. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/api/mcp.py +0 -0
  23. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/api/meta.py +0 -0
  24. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/api/openapi.py +0 -0
  25. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/api/runs.py +0 -0
  26. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/api/store.py +0 -0
  27. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/api/ui.py +0 -0
  28. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/asyncio.py +0 -0
  29. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/auth/__init__.py +0 -0
  30. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/auth/custom.py +0 -0
  31. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/auth/langsmith/__init__.py +0 -0
  32. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/auth/langsmith/backend.py +0 -0
  33. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/auth/langsmith/client.py +0 -0
  34. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/auth/middleware.py +0 -0
  35. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/auth/noop.py +0 -0
  36. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/auth/studio_user.py +0 -0
  37. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/command.py +0 -0
  38. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/config.py +0 -0
  39. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/cron_scheduler.py +0 -0
  40. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/errors.py +0 -0
  41. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/graph.py +0 -0
  42. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/http.py +0 -0
  43. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/.gitignore +0 -0
  44. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/.prettierrc +0 -0
  45. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/__init__.py +0 -0
  46. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/base.py +0 -0
  47. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/build.mts +0 -0
  48. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/client.http.mts +0 -0
  49. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/client.mts +0 -0
  50. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/errors.py +0 -0
  51. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/global.d.ts +0 -0
  52. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/remote.py +0 -0
  53. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/schema.py +0 -0
  54. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/src/graph.mts +0 -0
  55. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/src/hooks.mjs +0 -0
  56. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/src/parser/parser.mts +0 -0
  57. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/src/parser/parser.worker.mjs +0 -0
  58. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/src/schema/types.mts +0 -0
  59. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/src/schema/types.template.mts +0 -0
  60. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/src/utils/files.mts +0 -0
  61. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/src/utils/importMap.mts +0 -0
  62. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  63. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/src/utils/serde.mts +0 -0
  64. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/sse.py +0 -0
  65. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/auth.test.mts +0 -0
  66. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/compose-postgres.auth.yml +0 -0
  67. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/compose-postgres.yml +0 -0
  68. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/.gitignore +0 -0
  69. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/agent.css +0 -0
  70. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/agent.mts +0 -0
  71. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/agent.ui.tsx +0 -0
  72. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/auth.mts +0 -0
  73. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/command.mts +0 -0
  74. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/delay.mts +0 -0
  75. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/dynamic.mts +0 -0
  76. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/error.mts +0 -0
  77. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/http.mts +0 -0
  78. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/langgraph.json +0 -0
  79. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/nested.mts +0 -0
  80. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/package.json +0 -0
  81. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/weather.mts +0 -0
  82. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/graphs/yarn.lock +0 -0
  83. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/parser.test.mts +0 -0
  84. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/tests/utils.mts +0 -0
  85. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/js/ui.py +0 -0
  86. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/logging.py +0 -0
  87. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/middleware/__init__.py +0 -0
  88. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/middleware/http_logger.py +0 -0
  89. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/middleware/private_network.py +0 -0
  90. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/models/__init__.py +0 -0
  91. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/models/run.py +0 -0
  92. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/patch.py +0 -0
  93. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/queue_entrypoint.py +0 -0
  94. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/route.py +0 -0
  95. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/schema.py +0 -0
  96. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/serde.py +0 -0
  97. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/sse.py +0 -0
  98. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/state.py +0 -0
  99. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/stream.py +0 -0
  100. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/thread_ttl.py +0 -0
  101. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/utils.py +0 -0
  102. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/validation.py +0 -0
  103. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/webhook.py +0 -0
  104. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_api/worker.py +0 -0
  105. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_license/__init__.py +0 -0
  106. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/langgraph_runtime/__init__.py +0 -0
  107. {langgraph_api-0.1.9 → langgraph_api-0.1.13}/logging.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: langgraph-api
3
- Version: 0.1.9
3
+ Version: 0.1.13
4
4
  Summary:
5
5
  License: Elastic-2.0
6
6
  Author: Nuno Campos
@@ -18,7 +18,7 @@ Requires-Dist: jsonschema-rs (>=0.20.0,<0.30)
18
18
  Requires-Dist: langchain-core (>=0.2.38,<0.4.0)
19
19
  Requires-Dist: langgraph (>=0.2.56,<0.4.0)
20
20
  Requires-Dist: langgraph-checkpoint (>=2.0.23,<3.0)
21
- Requires-Dist: langgraph-sdk (>=0.1.61,<0.2.0)
21
+ Requires-Dist: langgraph-sdk (>=0.1.63,<0.2.0)
22
22
  Requires-Dist: langsmith (>=0.1.63,<0.4.0)
23
23
  Requires-Dist: orjson (>=3.9.7)
24
24
  Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
@@ -0,0 +1 @@
1
+ __version__ = "0.1.12"
@@ -55,15 +55,19 @@ async def search_threads(
55
55
  """List threads."""
56
56
  payload = await request.json(ThreadSearchRequest)
57
57
  async with connect() as conn:
58
- iter = await Threads.search(
58
+ iter, total = await Threads.search(
59
59
  conn,
60
60
  status=payload.get("status"),
61
61
  values=payload.get("values"),
62
62
  metadata=payload.get("metadata"),
63
63
  limit=payload.get("limit") or 10,
64
64
  offset=payload.get("offset") or 0,
65
+ sort_by=payload.get("sort_by"),
66
+ sort_order=payload.get("sort_order"),
65
67
  )
66
- return ApiResponse([thread async for thread in iter])
68
+ return ApiResponse(
69
+ [thread async for thread in iter], headers={"X-Pagination-Total": str(total)}
70
+ )
67
71
 
68
72
 
69
73
  @retry_db
@@ -122,6 +122,7 @@ def run_server(
122
122
  n_jobs_per_worker: int | None = None,
123
123
  env_file: str | None = None,
124
124
  open_browser: bool = False,
125
+ tunnel: bool = False,
125
126
  debug_port: int | None = None,
126
127
  wait_for_client: bool = False,
127
128
  env: str | pathlib.Path | Mapping[str, str] | None = None,
@@ -189,9 +190,33 @@ def run_server(
189
190
  debugpy.wait_for_client()
190
191
  logger.info("Debugger attached. Starting server...")
191
192
 
192
- local_url = f"http://{host}:{port}"
193
+ # Determine local or tunneled URL
194
+ upstream_url = f"http://{host}:{port}"
193
195
  if mount_prefix:
194
- local_url += mount_prefix
196
+ upstream_url += mount_prefix
197
+ if tunnel:
198
+ logger.info("Starting Cloudflare Tunnel...")
199
+ from concurrent.futures import TimeoutError as FutureTimeoutError
200
+
201
+ from langgraph_api.tunneling.cloudflare import start_tunnel
202
+
203
+ tunnel_obj = start_tunnel(port)
204
+ try:
205
+ public_url = tunnel_obj.url.result(timeout=30)
206
+ except FutureTimeoutError:
207
+ logger.warning(
208
+ "Timed out waiting for Cloudflare Tunnel URL; using local URL %s",
209
+ upstream_url,
210
+ )
211
+ public_url = upstream_url
212
+ except Exception as e:
213
+ tunnel_obj.process.kill()
214
+ raise RuntimeError("Failed to start Cloudflare Tunnel") from e
215
+ local_url = public_url
216
+ if mount_prefix:
217
+ local_url += mount_prefix
218
+ else:
219
+ local_url = upstream_url
195
220
  to_patch = dict(
196
221
  MIGRATIONS_PATH="__inmem",
197
222
  DATABASE_URI=":memory:",
@@ -361,6 +386,11 @@ def main():
361
386
  action="store_true",
362
387
  help="Whether to break and wait for a debugger to attach",
363
388
  )
389
+ parser.add_argument(
390
+ "--tunnel",
391
+ action="store_true",
392
+ help="Expose the server via Cloudflare Tunnel",
393
+ )
364
394
 
365
395
  args = parser.parse_args()
366
396
 
@@ -378,6 +408,7 @@ def main():
378
408
  graphs,
379
409
  n_jobs_per_worker=args.n_jobs_per_worker,
380
410
  open_browser=not args.no_browser,
411
+ tunnel=args.tunnel,
381
412
  debug_port=args.debug_port,
382
413
  wait_for_client=args.wait_for_client,
383
414
  env=config_data.get("env", None),
@@ -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.44",
13
- "@langchain/langgraph": "^0.2.64",
13
+ "@langchain/langgraph": "^0.2.65",
14
14
  "@langchain/langgraph-checkpoint": "^0.0.17",
15
15
  "@types/json-schema": "^7.0.15",
16
16
  "@typescript/vfs": "^1.6.0",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "jose": "^6.0.10",
36
- "@langchain/langgraph-sdk": "^0.0.67",
36
+ "@langchain/langgraph-sdk": "^0.0.70",
37
37
  "@types/react": "^19.0.8",
38
38
  "@types/react-dom": "^19.0.3",
39
39
  "@types/node": "^22.2.0",
@@ -1,14 +1,14 @@
1
- import { describe, expect, it, beforeEach, beforeAll } from "vitest";
2
1
  import { Client, FeedbackStreamEvent } from "@langchain/langgraph-sdk";
2
+ import { beforeAll, beforeEach, describe, expect, it } from "vitest";
3
3
 
4
- import postgres from "postgres";
5
- import { findLast, gatherIterator } from "./utils.mts";
6
4
  import {
7
5
  BaseMessageFields,
8
6
  BaseMessageLike,
9
7
  MessageType,
10
8
  } from "@langchain/core/messages";
11
9
  import { randomUUID } from "crypto";
10
+ import postgres from "postgres";
11
+ import { findLast, gatherIterator } from "./utils.mts";
12
12
 
13
13
  const sql = postgres(
14
14
  process.env.POSTGRES_URI ??
@@ -238,6 +238,19 @@ describe("threads crud", () => {
238
238
  search = await client.threads.search({ offset: 1, limit: 1 });
239
239
  expect(search.length).toBe(1);
240
240
  expect(createThreadResponse.thread_id).toBe(search[0].thread_id);
241
+
242
+ // test sorting
243
+ search = await client.threads.search({
244
+ sortBy: "created_at",
245
+ sortOrder: "asc",
246
+ });
247
+ expect(search[0].thread_id).toBe(createThreadResponse.thread_id);
248
+
249
+ search = await client.threads.search({
250
+ sortBy: "created_at",
251
+ sortOrder: "desc",
252
+ });
253
+ expect(search[1].thread_id).toBe(createThreadResponse.thread_id);
241
254
  });
242
255
  });
243
256
 
@@ -2078,6 +2091,7 @@ it("custom routes - mutate request body", async () => {
2078
2091
  messages: expect.arrayContaining([
2079
2092
  expect.objectContaining({ content: "end: extra-client" }),
2080
2093
  ]),
2094
+ prompts: [],
2081
2095
  });
2082
2096
  });
2083
2097
 
@@ -2094,6 +2108,34 @@ it("custom routes - langgraph", async () => {
2094
2108
  messages: expect.arrayContaining([
2095
2109
  expect.objectContaining({ content: "input" }),
2096
2110
  ]),
2111
+ prompts: [],
2112
+ },
2113
+ });
2114
+ });
2115
+
2116
+ it("send map-reduce", async () => {
2117
+ const assistant = await client.assistants.create({ graphId: "agent_simple" });
2118
+ const thread = await client.threads.create();
2119
+
2120
+ const chunks = await gatherIterator(
2121
+ client.runs.stream(thread.thread_id, assistant.assistant_id, {
2122
+ input: { messages: [{ role: "human", content: "input" }] },
2123
+ config: { configurable: { "map-reduce": true } },
2124
+ }),
2125
+ );
2126
+
2127
+ expect(chunks.filter((i) => i.event === "error")).toEqual([]);
2128
+ expect(findLast(chunks, (i) => i.event === "values")).toMatchObject({
2129
+ data: {
2130
+ messages: [
2131
+ { type: "human", content: "input" },
2132
+ { type: "human", content: "map-reduce" },
2133
+ ],
2134
+ prompts: [
2135
+ { type: "ai", content: "first" },
2136
+ { type: "ai", content: "second" },
2137
+ { type: "ai", content: "third" },
2138
+ ],
2097
2139
  },
2098
2140
  });
2099
2141
  });
@@ -7,7 +7,7 @@ import {
7
7
  START,
8
8
  LangGraphRunnableConfig,
9
9
  } from "@langchain/langgraph";
10
- import { BaseMessage, ToolMessage } from "@langchain/core/messages";
10
+ import { AIMessage, BaseMessage, ToolMessage } from "@langchain/core/messages";
11
11
  import { FakeListChatModel } from "@langchain/core/utils/testing";
12
12
 
13
13
  const getStableModel = (() => {
@@ -25,6 +25,7 @@ const AgentState = Annotation.Root({
25
25
  key_two: Annotation<string>(),
26
26
  sleep: Annotation<number>(),
27
27
  messages: MessagesAnnotation.spec.messages,
28
+ prompts: MessagesAnnotation.spec.messages,
28
29
  });
29
30
 
30
31
  async function callModel(
@@ -47,6 +48,10 @@ async function callModel(
47
48
  };
48
49
  }
49
50
 
51
+ if (config.configurable?.["map-reduce"] != null) {
52
+ return { messages: ["map-reduce"] };
53
+ }
54
+
50
55
  const model = getStableModel(config.configurable?.thread_id ?? "$");
51
56
  const existing = await config.store?.get([userId ?? "ALL"], "key_one");
52
57
  if (!existing) {
@@ -68,15 +73,31 @@ async function callTool(
68
73
  return { messages: [response] };
69
74
  }
70
75
 
71
- function shouldContinue(state: typeof AgentState.State): typeof END | Send {
76
+ function shouldContinue(
77
+ state: typeof AgentState.State,
78
+ ): typeof END | Send | Send[] {
72
79
  const lastMessage = state.messages.at(-1);
73
80
  if ((lastMessage?.content as string).startsWith("end")) return END;
81
+ if ((lastMessage?.content as string).includes("map-reduce")) {
82
+ return [
83
+ new Send("map-reduce", { messages: [new AIMessage("first")] }),
84
+ new Send("map-reduce", { messages: [new AIMessage("second")] }),
85
+ new Send("map-reduce", { messages: [new AIMessage("third")] }),
86
+ ];
87
+ }
74
88
  return new Send("tool", lastMessage);
75
89
  }
76
90
 
91
+ function callMapReduce(state: {
92
+ messages: BaseMessage[];
93
+ }): typeof AgentState.Update {
94
+ return { prompts: state.messages.slice(-1) };
95
+ }
96
+
77
97
  const workflow = new StateGraph(AgentState)
78
98
  .addNode("agent", callModel)
79
99
  .addNode("tool", callTool)
100
+ .addNode("map-reduce", callMapReduce)
80
101
  .addEdge(START, "agent")
81
102
  .addConditionalEdges("agent", shouldContinue)
82
103
  .addEdge("tool", "agent");
@@ -235,7 +235,17 @@
235
235
  dependencies:
236
236
  uuid "^10.0.0"
237
237
 
238
- "@langchain/langgraph-sdk@^0.0.67", "@langchain/langgraph-sdk@~0.0.32":
238
+ "@langchain/langgraph-sdk@^0.0.70":
239
+ version "0.0.70"
240
+ resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.70.tgz#9589f984b47de5e4a669b6008cbf01427a3d41d0"
241
+ integrity sha512-O8I12bfeMVz5fOrXnIcK4IdRf50IqyJTO458V56wAIHLNoi4H8/JHM+2M+Y4H2PtslXIGnvomWqlBd0eY5z/Og==
242
+ dependencies:
243
+ "@types/json-schema" "^7.0.15"
244
+ p-queue "^6.6.2"
245
+ p-retry "4"
246
+ uuid "^9.0.0"
247
+
248
+ "@langchain/langgraph-sdk@~0.0.32":
239
249
  version "0.0.67"
240
250
  resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.67.tgz#d856110727669f59cf9367cea99d88479de164d4"
241
251
  integrity sha512-JWa0OuiXPoFztmBleUj6N00snuZt80Df6BSMUEjBfMSRNVPn7yiyyz/QTkzuW1LqqVZKPI4O8Bpimnw2xIg3BA==
@@ -256,10 +266,10 @@
256
266
  esbuild-plugin-tailwindcss "^2.0.1"
257
267
  zod "^3.23.8"
258
268
 
259
- "@langchain/langgraph@^0.2.64":
260
- version "0.2.64"
261
- resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.64.tgz#90f8c32bc34fbc3f7c1b04595f1e5165dd5a5fb4"
262
- integrity sha512-M6lh8ekDoZVCLdA10jeqIsU58LODDzXpP38aeXil5A5pg31IJp5L8O4yBfbp8mRobVX+Bbga5R5ZRyQBQl6NTg==
269
+ "@langchain/langgraph@^0.2.65":
270
+ version "0.2.65"
271
+ resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.65.tgz#f080add1c26a3eb3744af2ef23b8b53eddbf5cb2"
272
+ integrity sha512-g/Xap2KSEaEBXMJXGZTh31fd0qrdfaWA1l8NJzweJg6AkvVSf+d6DmMk9DtzGW8W1H1qQ2I6FWZ3AdP61Kkaig==
263
273
  dependencies:
264
274
  "@langchain/langgraph-checkpoint" "~0.0.17"
265
275
  "@langchain/langgraph-sdk" "~0.0.32"
@@ -40,7 +40,10 @@ RUN_COUNTER = 0
40
40
  NODE_COUNTER = 0
41
41
  FROM_TIMESTAMP = datetime.now(UTC).isoformat()
42
42
 
43
- if "api.smith.langchain.com" in LANGSMITH_AUTH_ENDPOINT:
43
+ if (
44
+ "api.smith.langchain.com" in LANGSMITH_AUTH_ENDPOINT
45
+ and not LANGGRAPH_CLOUD_LICENSE_KEY
46
+ ):
44
47
  METADATA_ENDPOINT = LANGSMITH_AUTH_ENDPOINT.rstrip("/") + "/v1/metadata/submit"
45
48
  else:
46
49
  METADATA_ENDPOINT = "https://api.smith.langchain.com/v1/metadata/submit"
@@ -100,20 +103,21 @@ async def metadata_loop() -> None:
100
103
  "from_timestamp": from_timestamp,
101
104
  "to_timestamp": to_timestamp,
102
105
  "tags": {
106
+ # Tag values must be strings.
103
107
  "langgraph.python.version": langgraph.version.__version__,
104
- "langgraph_api.version": __version__,
105
- "langgraph.platform.revision": REVISION,
106
- "langgraph.platform.variant": VARIANT,
108
+ "langgraph_api.version": __version__ or "",
109
+ "langgraph.platform.revision": REVISION or "",
110
+ "langgraph.platform.variant": VARIANT or "",
107
111
  "langgraph.platform.host": HOST,
108
- "langgraph.platform.tenant_id": TENANT_ID,
109
- "langgraph.platform.project_id": PROJECT_ID,
112
+ "langgraph.platform.tenant_id": TENANT_ID or "",
113
+ "langgraph.platform.project_id": PROJECT_ID or "",
110
114
  "langgraph.platform.plan": PLAN,
111
115
  # user app features
112
- "user_app.uses_indexing": USES_INDEXING,
113
- "user_app.uses_custom_app": USES_CUSTOM_APP,
114
- "user_app.uses_custom_auth": USES_CUSTOM_AUTH,
115
- "user_app.uses_thread_ttl": USES_THREAD_TTL,
116
- "user_app.uses_store_ttl": USES_STORE_TTL,
116
+ "user_app.uses_indexing": str(USES_INDEXING or ""),
117
+ "user_app.uses_custom_app": str(USES_CUSTOM_APP or ""),
118
+ "user_app.uses_custom_auth": str(USES_CUSTOM_AUTH),
119
+ "user_app.uses_thread_ttl": str(USES_THREAD_TTL),
120
+ "user_app.uses_store_ttl": str(USES_STORE_TTL),
117
121
  },
118
122
  "measures": {
119
123
  "langgraph.platform.runs": runs,
@@ -30,7 +30,6 @@ from langgraph_runtime.lifespan import lifespan
30
30
  from langgraph_api.middleware.http_logger import AccessLoggerMiddleware
31
31
  from langgraph_api.middleware.private_network import PrivateNetworkMiddleware
32
32
  from langgraph_api.utils import SchemaGenerator
33
- from langgraph_license.middleware import LicenseValidationMiddleware
34
33
  from langgraph_runtime.retry import OVERLOADED_EXCEPTIONS
35
34
  from langgraph_api.js.base import is_js_path
36
35
  from langgraph_sdk.client import configure_loopback_transports
@@ -68,7 +67,6 @@ middleware.extend(
68
67
  **config.CORS_CONFIG,
69
68
  )
70
69
  ),
71
- Middleware(LicenseValidationMiddleware),
72
70
  Middleware(AccessLoggerMiddleware, logger=logger),
73
71
  ]
74
72
  )
@@ -0,0 +1,119 @@
1
+ import atexit
2
+ import logging
3
+ import os
4
+ import platform as _platform
5
+ import re
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ import tarfile
10
+ import tempfile
11
+ import threading
12
+ import urllib.request
13
+ from concurrent.futures import Future
14
+ from pathlib import Path
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ CLOUDFLARED_VERSION = os.environ.get("LANGGRAPH_CLOUDFLARED_VERSION", "2025.2.1")
19
+ CACHE_DIR = (
20
+ Path(os.path.expanduser("~"))
21
+ / ".langgraph_api"
22
+ / "cloudflared"
23
+ / CLOUDFLARED_VERSION
24
+ )
25
+
26
+
27
+ def get_platform_arch():
28
+ plat = sys.platform
29
+ if plat.startswith("darwin"):
30
+ plat = "darwin"
31
+ elif plat.startswith("linux"):
32
+ plat = "linux"
33
+ elif plat.startswith("win"):
34
+ plat = "windows"
35
+ else:
36
+ raise RuntimeError(f"Unsupported platform: {sys.platform}")
37
+ arch = _platform.machine().lower()
38
+ if arch in ("x86_64", "amd64"):
39
+ arch = "amd64"
40
+ elif arch in ("arm64", "aarch64"):
41
+ arch = "arm64"
42
+ elif arch in ("i386", "i686", "x86"):
43
+ arch = "386"
44
+ else:
45
+ raise RuntimeError(f"Unsupported architecture: {_platform.machine()}")
46
+ return plat, arch
47
+
48
+
49
+ def ensure_cloudflared() -> Path:
50
+ plat, arch = get_platform_arch()
51
+ # determine file names and if archive
52
+ if plat == "windows":
53
+ file_name = f"cloudflared-windows-{arch}.exe"
54
+ is_archive = False
55
+ elif plat == "darwin":
56
+ file_name = f"cloudflared-darwin-{arch}.tgz"
57
+ is_archive = True
58
+ else: # linux
59
+ file_name = f"cloudflared-linux-{arch}"
60
+ is_archive = False
61
+
62
+ CACHE_DIR.mkdir(parents=True, exist_ok=True)
63
+ binary_name = "cloudflared" if is_archive else file_name
64
+ target_bin = CACHE_DIR / binary_name
65
+ if not target_bin.exists():
66
+ url = f"https://github.com/cloudflare/cloudflared/releases/download/{CLOUDFLARED_VERSION}/{file_name}"
67
+ logger.info(f"Downloading cloudflared from {url}")
68
+ with urllib.request.urlopen(url) as resp:
69
+ data = resp.read()
70
+ with tempfile.TemporaryDirectory() as tmpd:
71
+ tmpd = Path(tmpd)
72
+ path = tmpd / file_name
73
+ path.write_bytes(data)
74
+ if is_archive:
75
+ with tarfile.open(path) as tf:
76
+ tf.extractall(tmpd)
77
+ src = tmpd / "cloudflared"
78
+ else:
79
+ src = path
80
+ shutil.move(str(src), str(target_bin))
81
+ target_bin.chmod(0o755)
82
+ return target_bin
83
+
84
+
85
+ class CloudflareTunnel:
86
+ def __init__(self, process: subprocess.Popen, url_future: Future[str]):
87
+ self.process = process
88
+ self.url = url_future
89
+
90
+
91
+ def start_tunnel(port: int) -> CloudflareTunnel:
92
+ bin_path = ensure_cloudflared()
93
+ cmd = [str(bin_path), "tunnel", "--url", f"http://localhost:{port}"]
94
+ proc = subprocess.Popen(
95
+ cmd,
96
+ stdout=subprocess.PIPE,
97
+ stderr=subprocess.PIPE,
98
+ text=True,
99
+ bufsize=1,
100
+ )
101
+ atexit.register(proc.kill)
102
+
103
+ url_future: Future[str] = Future()
104
+
105
+ def _reader(stream):
106
+ for line in stream:
107
+ line = line.strip()
108
+ logger.info(f"[cloudflared] {line}")
109
+ if not url_future.done():
110
+ # match any trycloudflare.com host with optional subdomains
111
+ pattern = re.compile(r"(https://[A-Za-z0-9.-]+\.trycloudflare\.com)")
112
+ m = pattern.search(line)
113
+ if m:
114
+ url_future.set_result(m.group(1))
115
+
116
+ threading.Thread(target=_reader, args=(proc.stdout,), daemon=True).start()
117
+ threading.Thread(target=_reader, args=(proc.stderr,), daemon=True).start()
118
+
119
+ return CloudflareTunnel(proc, url_future)
@@ -0,0 +1,22 @@
1
+ """Noop license middleware"""
2
+
3
+
4
+ async def get_license_status() -> bool:
5
+ """Always return true"""
6
+ return True
7
+
8
+
9
+ def plus_features_enabled() -> bool:
10
+ """Always return false"""
11
+ return False
12
+
13
+
14
+ async def check_license_periodically(_: int = 60):
15
+ """
16
+ Periodically re-verify the license.
17
+ If the license ever fails, you could decide to log,
18
+ raise an exception, or attempt a graceful shutdown.
19
+ """
20
+ raise NotImplementedError(
21
+ "This is a noop license middleware. No license check is performed."
22
+ )
@@ -3962,6 +3962,26 @@
3962
3962
  "description": "Offset to start from.",
3963
3963
  "default": 0,
3964
3964
  "minimum": 0
3965
+ },
3966
+ "sort_by": {
3967
+ "type": "string",
3968
+ "enum": [
3969
+ "thread_id",
3970
+ "status",
3971
+ "created_at",
3972
+ "updated_at"
3973
+ ],
3974
+ "title": "Sort By",
3975
+ "description": "Sort by field."
3976
+ },
3977
+ "sort_order": {
3978
+ "type": "string",
3979
+ "enum": [
3980
+ "asc",
3981
+ "desc"
3982
+ ],
3983
+ "title": "Sort Order",
3984
+ "description": "Sort order."
3965
3985
  }
3966
3986
  },
3967
3987
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langgraph-api"
3
- version = "0.1.9"
3
+ version = "0.1.13"
4
4
  description = ""
5
5
  authors = [
6
6
  "Nuno Campos <nuno@langchain.dev>",
@@ -48,7 +48,7 @@ jsonschema-rs = ">=0.20.0,<0.30"
48
48
  structlog = ">=24.1.0,<26"
49
49
  pyjwt = "^2.9.0"
50
50
  cryptography = ">=42.0.0,<45.0"
51
- langgraph-sdk = "^0.1.61"
51
+ langgraph-sdk = "^0.1.63"
52
52
  cloudpickle = "^3.0.0"
53
53
 
54
54
  [tool.poetry.group.dev.dependencies]
@@ -1 +0,0 @@
1
- __version__ = "0.1.9"
@@ -1,21 +0,0 @@
1
- """Middleware for license validation."""
2
-
3
- from typing import Any
4
-
5
- from starlette.middleware.base import BaseHTTPMiddleware
6
- from starlette.requests import Request
7
- from starlette.responses import Response
8
- from starlette.types import ASGIApp
9
-
10
-
11
- class LicenseValidationMiddleware(BaseHTTPMiddleware):
12
- """Noop license middleware"""
13
-
14
- def __init__(self, app: ASGIApp):
15
- """Initialize middleware."""
16
- super().__init__(app)
17
-
18
- async def dispatch(self, request: Request, call_next: Any) -> Response:
19
- """Noop middleware."""
20
- response = await call_next(request)
21
- return response
@@ -1,11 +0,0 @@
1
- """Noop license middleware"""
2
-
3
-
4
- async def get_license_status() -> bool:
5
- """Always return true"""
6
- return True
7
-
8
-
9
- def plus_features_enabled() -> bool:
10
- """Always return false"""
11
- return False
File without changes
File without changes