langgraph-api 0.0.26__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.

Files changed (53) hide show
  1. langgraph_api/api/__init__.py +2 -0
  2. langgraph_api/api/assistants.py +43 -13
  3. langgraph_api/api/meta.py +2 -1
  4. langgraph_api/api/runs.py +14 -1
  5. langgraph_api/api/ui.py +68 -0
  6. langgraph_api/asyncio.py +43 -4
  7. langgraph_api/auth/middleware.py +2 -2
  8. langgraph_api/cli.py +72 -57
  9. langgraph_api/config.py +23 -1
  10. langgraph_api/cron_scheduler.py +1 -1
  11. langgraph_api/graph.py +5 -0
  12. langgraph_api/http.py +24 -7
  13. langgraph_api/js/.gitignore +2 -0
  14. langgraph_api/js/build.mts +49 -3
  15. langgraph_api/js/client.mts +84 -40
  16. langgraph_api/js/global.d.ts +1 -0
  17. langgraph_api/js/package.json +15 -7
  18. langgraph_api/js/remote.py +662 -16
  19. langgraph_api/js/src/graph.mts +5 -4
  20. langgraph_api/js/sse.py +138 -0
  21. langgraph_api/js/tests/api.test.mts +28 -0
  22. langgraph_api/js/tests/compose-postgres.yml +2 -2
  23. langgraph_api/js/tests/graphs/agent.css +1 -0
  24. langgraph_api/js/tests/graphs/agent.ui.tsx +10 -0
  25. langgraph_api/js/tests/graphs/package.json +2 -2
  26. langgraph_api/js/tests/graphs/yarn.lock +13 -13
  27. langgraph_api/js/yarn.lock +710 -1187
  28. langgraph_api/lifespan.py +15 -5
  29. langgraph_api/logging.py +9 -0
  30. langgraph_api/metadata.py +5 -1
  31. langgraph_api/middleware/http_logger.py +1 -1
  32. langgraph_api/patch.py +2 -0
  33. langgraph_api/queue_entrypoint.py +63 -0
  34. langgraph_api/schema.py +2 -0
  35. langgraph_api/stream.py +1 -0
  36. langgraph_api/webhook.py +42 -0
  37. langgraph_api/{queue.py → worker.py} +52 -166
  38. {langgraph_api-0.0.26.dist-info → langgraph_api-0.0.28.dist-info}/METADATA +8 -8
  39. {langgraph_api-0.0.26.dist-info → langgraph_api-0.0.28.dist-info}/RECORD +49 -46
  40. langgraph_storage/database.py +8 -22
  41. langgraph_storage/inmem_stream.py +108 -0
  42. langgraph_storage/ops.py +80 -57
  43. langgraph_storage/queue.py +126 -103
  44. langgraph_storage/retry.py +5 -1
  45. langgraph_storage/store.py +5 -1
  46. openapi.json +3 -3
  47. langgraph_api/js/client.new.mts +0 -861
  48. langgraph_api/js/remote_new.py +0 -694
  49. langgraph_api/js/remote_old.py +0 -667
  50. langgraph_api/js/server_sent_events.py +0 -126
  51. {langgraph_api-0.0.26.dist-info → langgraph_api-0.0.28.dist-info}/LICENSE +0 -0
  52. {langgraph_api-0.0.26.dist-info → langgraph_api-0.0.28.dist-info}/WHEEL +0 -0
  53. {langgraph_api-0.0.26.dist-info → langgraph_api-0.0.28.dist-info}/entry_points.txt +0 -0
@@ -1,861 +0,0 @@
1
- /// <reference types="./global.d.ts" />
2
-
3
- import { z } from "zod";
4
- import zeromq from "zeromq";
5
- import PQueue from "p-queue";
6
- import { v4 as uuid4 } from "uuid";
7
- import {
8
- BaseStore,
9
- Item,
10
- Operation,
11
- Command,
12
- OperationResults,
13
- type Checkpoint,
14
- type CheckpointMetadata,
15
- type CheckpointTuple,
16
- type CompiledGraph,
17
- } from "@langchain/langgraph";
18
- import {
19
- BaseCheckpointSaver,
20
- type ChannelVersions,
21
- type ChannelProtocol,
22
- } from "@langchain/langgraph-checkpoint";
23
- import { createHash } from "node:crypto";
24
- import * as fs from "node:fs/promises";
25
- import * as path from "node:path";
26
- import { serialiseAsDict, serializeError } from "./src/utils/serde.mjs";
27
- import * as importMap from "./src/utils/importMap.mjs";
28
-
29
- import { createLogger, format, transports } from "winston";
30
-
31
- import { load } from "@langchain/core/load";
32
- import { BaseMessageChunk, isBaseMessage } from "@langchain/core/messages";
33
- import type { PyItem, PyResult } from "./src/utils/pythonSchemas.mts";
34
- import type { RunnableConfig } from "@langchain/core/runnables";
35
- import {
36
- runGraphSchemaWorker,
37
- GraphSchema,
38
- resolveGraph,
39
- GraphSpec,
40
- filterValidGraphSpecs,
41
- } from "./src/graph.mts";
42
-
43
- const logger = createLogger({
44
- level: "debug",
45
- format: format.combine(
46
- format.errors({ stack: true }),
47
- format.timestamp(),
48
- format.json(),
49
- format.printf((info) => {
50
- const { timestamp, level, message, ...rest } = info;
51
-
52
- let event;
53
- if (typeof message === "string") {
54
- event = message;
55
- } else {
56
- event = JSON.stringify(message);
57
- }
58
-
59
- if (rest.stack) {
60
- rest.message = event;
61
- event = rest.stack;
62
- }
63
-
64
- return JSON.stringify({ timestamp, level, event, ...rest });
65
- })
66
- ),
67
- transports: [
68
- new transports.Console({
69
- handleExceptions: true,
70
- handleRejections: true,
71
- }),
72
- ],
73
- });
74
-
75
- let GRAPH_SCHEMA: Record<string, Record<string, GraphSchema>> = {};
76
- const GRAPH_RESOLVED: Record<string, CompiledGraph<string>> = {};
77
- const GRAPH_SPEC: Record<string, GraphSpec> = {};
78
-
79
- function getGraph(graphId: string) {
80
- if (!GRAPH_RESOLVED[graphId]) throw new Error(`Graph "${graphId}" not found`);
81
- return GRAPH_RESOLVED[graphId];
82
- }
83
-
84
- async function getOrExtractSchema(graphId: string) {
85
- if (!(graphId in GRAPH_SPEC)) {
86
- throw new Error(`Spec for ${graphId} not found`);
87
- }
88
-
89
- if (!GRAPH_SCHEMA[graphId]) {
90
- try {
91
- const timer = logger.startTimer();
92
- GRAPH_SCHEMA[graphId] = await runGraphSchemaWorker(GRAPH_SPEC[graphId]);
93
- timer.done({ message: `Extracting schema for ${graphId} finished` });
94
- } catch (error) {
95
- throw new Error(`Failed to extract schema for "${graphId}": ${error}`);
96
- }
97
- }
98
-
99
- return GRAPH_SCHEMA[graphId];
100
- }
101
-
102
- const CLIENT_ADDR = "tcp://*:5556";
103
- const REMOTE_ADDR = "tcp://0.0.0.0:5555";
104
-
105
- const CLIENT_HEARTBEAT_INTERVAL_MS = 5_000;
106
-
107
- const clientRouter = new zeromq.Router();
108
- const remoteDealer = new zeromq.Dealer();
109
-
110
- const RunnableConfigSchema = z.object({
111
- tags: z.array(z.string()).optional(),
112
- metadata: z.record(z.unknown()).optional(),
113
- run_name: z.string().optional(),
114
- max_concurrency: z.number().optional(),
115
- recursion_limit: z.number().optional(),
116
- configurable: z.record(z.unknown()).optional(),
117
- run_id: z.string().uuid().optional(),
118
- });
119
-
120
- const getRunnableConfig = (
121
- userConfig: z.infer<typeof RunnableConfigSchema> | null | undefined
122
- ) => {
123
- if (!userConfig) return {};
124
- return {
125
- configurable: userConfig.configurable,
126
- tags: userConfig.tags,
127
- metadata: userConfig.metadata,
128
- runName: userConfig.run_name,
129
- maxConcurrency: userConfig.max_concurrency,
130
- recursionLimit: userConfig.recursion_limit,
131
- runId: userConfig.run_id,
132
- };
133
- };
134
-
135
- const textEncoder = new TextEncoder();
136
- const textDecoder = new TextDecoder();
137
-
138
- // TODO: consider swapping to msgpackr
139
- const packPlain = (value: unknown) => textEncoder.encode(JSON.stringify(value));
140
- const pack = (value: unknown) => textEncoder.encode(serialiseAsDict(value));
141
-
142
- function unpackPlain<T>(value: AllowSharedBufferSource) {
143
- return JSON.parse(textDecoder.decode(value)) as T;
144
- }
145
-
146
- function unpack<T>(value: AllowSharedBufferSource) {
147
- return load<T>(textDecoder.decode(value), {
148
- importMap,
149
- optionalImportEntrypoints: [],
150
- optionalImportsMap: {},
151
- secretsMap: {},
152
- });
153
- }
154
-
155
- interface Future<T> {
156
- resolve: (value: T) => void;
157
- reject: (reason?: any) => void;
158
- promise: Promise<T>;
159
- }
160
-
161
- const remoteTasks: Record<string, Future<unknown>> = {};
162
-
163
- const createFuture = (id: string) => {
164
- const newPromise = new Promise<unknown>((resolve, reject) => {
165
- remoteTasks[id] = { resolve, reject, promise: null! };
166
- });
167
- remoteTasks[id].promise = newPromise;
168
- };
169
-
170
- // Only a singular read is allowed at a time
171
- const queue = new PQueue({ concurrency: 1 });
172
- const scheduleRead = async (): Promise<void> => {
173
- type ResponsePayload =
174
- | { method: string; id: string; success: true; data: unknown }
175
- | {
176
- method: string;
177
- id: string;
178
- success: false;
179
- data: { error: string; message: string };
180
- };
181
-
182
- const [buf] = await remoteDealer.receive();
183
- const response = await unpack<ResponsePayload>(buf);
184
-
185
- const future = remoteTasks[response.id];
186
- if (!future) throw new Error(`No future for ${response.id}`);
187
-
188
- if (response.success) {
189
- future.resolve(response.data);
190
- } else {
191
- future.reject(new Error(response.data.message || response.data.error));
192
- }
193
- };
194
-
195
- interface RouterPacket {
196
- header: Buffer;
197
- input: {
198
- method: string;
199
- id: string;
200
- data: Record<string, any>;
201
- };
202
- }
203
-
204
- async function* getRouterPackets(): AsyncGenerator<RouterPacket> {
205
- for await (const [header, binary] of clientRouter) {
206
- const data = unpackPlain<RouterPacket["input"]>(binary);
207
- yield { header, input: data };
208
- }
209
- }
210
-
211
- async function sendRecv<T = any>(
212
- method: `${"checkpointer" | "store"}_${string}`,
213
- data: unknown
214
- ): Promise<T> {
215
- const id = uuid4();
216
- createFuture(id);
217
-
218
- try {
219
- await remoteDealer.send(packPlain({ method, id, data }));
220
- queue.add(scheduleRead, { timeout: 10_000, throwOnTimeout: true });
221
-
222
- return (await remoteTasks[id].promise) as T;
223
- } finally {
224
- delete remoteTasks[id];
225
- }
226
- }
227
-
228
- const createSendWithTTL = (packet: RouterPacket) => {
229
- const { header, input } = packet;
230
- const { method, id } = input;
231
-
232
- let timer: NodeJS.Timeout | undefined = undefined;
233
- const sendData = async (result?: { success: boolean; data: unknown }) => {
234
- clearTimeout(timer);
235
- await clientRouter.send([header, pack({ method, id, ...result })]);
236
- timer = setTimeout(() => sendData(), CLIENT_HEARTBEAT_INTERVAL_MS);
237
- };
238
-
239
- return { sendData, clear: () => clearTimeout(timer) };
240
- };
241
-
242
- const handleInvoke = async <T extends z.ZodType<any>>(
243
- packet: RouterPacket,
244
- schema: T,
245
- request: (rawPayload: z.infer<T>) => Promise<any>
246
- ) => {
247
- const { sendData, clear } = createSendWithTTL(packet);
248
- try {
249
- const data = await request(schema.parse(packet.input.data));
250
- await sendData({ success: true, data });
251
- } catch (error) {
252
- logger.error(error);
253
- const data = serializeError(error);
254
- await sendData({ success: false, data });
255
- } finally {
256
- clear();
257
- }
258
- };
259
-
260
- const handleStream = async <T extends z.ZodType<any>>(
261
- packet: RouterPacket,
262
- schema: T,
263
- request: (rawPayload: z.infer<T>) => AsyncGenerator<any>
264
- ) => {
265
- const { sendData, clear } = createSendWithTTL(packet);
266
-
267
- let done = false;
268
- try {
269
- const generator = request(schema.parse(packet.input.data));
270
- while (!done) {
271
- const data = await generator.next();
272
- done = data.done ?? false;
273
- await sendData({ success: true, data });
274
- }
275
- } catch (error) {
276
- logger.error(error);
277
- const data = serializeError(error);
278
- await sendData({ success: false, data });
279
- } finally {
280
- clear();
281
- }
282
- };
283
-
284
- class RemoteCheckpointer extends BaseCheckpointSaver<number | string> {
285
- async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {
286
- const result = await sendRecv("checkpointer_get_tuple", { config });
287
-
288
- if (!result) return undefined;
289
- return {
290
- checkpoint: result.checkpoint,
291
- config: result.config,
292
- metadata: result.metadata,
293
- parentConfig: result.parent_config,
294
- pendingWrites: result.pending_writes,
295
- };
296
- }
297
-
298
- async *list(
299
- config: RunnableConfig,
300
- options?: {
301
- limit?: number;
302
- before?: RunnableConfig;
303
- filter?: Record<string, any>;
304
- }
305
- ): AsyncGenerator<CheckpointTuple> {
306
- const result = await sendRecv("checkpointer_list", { config, ...options });
307
-
308
- for (const item of result) {
309
- yield {
310
- checkpoint: item.checkpoint,
311
- config: item.config,
312
- metadata: item.metadata,
313
- parentConfig: item.parent_config,
314
- pendingWrites: item.pending_writes,
315
- };
316
- }
317
- }
318
-
319
- async put(
320
- config: RunnableConfig,
321
- checkpoint: Checkpoint,
322
- metadata: CheckpointMetadata,
323
- newVersions: ChannelVersions
324
- ): Promise<RunnableConfig> {
325
- return await sendRecv<RunnableConfig>("checkpointer_put", {
326
- config,
327
- checkpoint,
328
- metadata,
329
- new_versions: newVersions,
330
- });
331
- }
332
-
333
- async putWrites(
334
- config: RunnableConfig,
335
- writes: [string, unknown][],
336
- taskId: string
337
- ): Promise<void> {
338
- await sendRecv("checkpointer_put_writes", { config, writes, taskId });
339
- }
340
-
341
- getNextVersion(
342
- current: number | string | undefined,
343
- _channel: ChannelProtocol
344
- ): string {
345
- let currentVersion = 0;
346
-
347
- if (current == null) {
348
- currentVersion = 0;
349
- } else if (typeof current === "number") {
350
- currentVersion = current;
351
- } else if (typeof current === "string") {
352
- currentVersion = Number.parseInt(current.split(".")[0], 10);
353
- }
354
-
355
- const nextVersion = String(currentVersion + 1).padStart(32, "0");
356
- try {
357
- const hash = createHash("md5")
358
- .update(serialiseAsDict(_channel.checkpoint()))
359
- .digest("hex");
360
- return `${nextVersion}.${hash}`;
361
- } catch {}
362
-
363
- return nextVersion;
364
- }
365
- }
366
-
367
- function camelToSnake(operation: Operation) {
368
- const snakeCaseKeys = (obj: Record<string, any>): Record<string, any> => {
369
- return Object.fromEntries(
370
- Object.entries(obj).map(([key, value]) => {
371
- const snakeKey = key.replace(
372
- /[A-Z]/g,
373
- (letter) => `_${letter.toLowerCase()}`
374
- );
375
- if (
376
- typeof value === "object" &&
377
- value !== null &&
378
- !Array.isArray(value)
379
- ) {
380
- return [snakeKey, snakeCaseKeys(value)];
381
- }
382
- return [snakeKey, value];
383
- })
384
- );
385
- };
386
-
387
- if ("namespace" in operation && "key" in operation) {
388
- return {
389
- namespace: operation.namespace,
390
- key: operation.key,
391
- ...("value" in operation ? { value: operation.value } : {}),
392
- };
393
- } else if ("namespacePrefix" in operation) {
394
- return {
395
- namespace_prefix: operation.namespacePrefix,
396
- filter: operation.filter,
397
- limit: operation.limit,
398
- offset: operation.offset,
399
- };
400
- } else if ("matchConditions" in operation) {
401
- return {
402
- match_conditions: operation.matchConditions?.map((condition) => ({
403
- match_type: condition.matchType,
404
- path: condition.path,
405
- })),
406
- max_depth: operation.maxDepth,
407
- limit: operation.limit,
408
- offset: operation.offset,
409
- };
410
- }
411
-
412
- return snakeCaseKeys(operation) as Operation;
413
- }
414
-
415
- function pyItemToJs(item?: PyItem): Item | undefined {
416
- if (!item) {
417
- return undefined;
418
- }
419
- return {
420
- namespace: item.namespace,
421
- key: item.key,
422
- value: item.value,
423
- createdAt: item.created_at,
424
- updatedAt: item.updated_at,
425
- };
426
- }
427
-
428
- export class RemoteStore extends BaseStore {
429
- async batch<Op extends Operation[]>(
430
- operations: Op
431
- ): Promise<OperationResults<Op>> {
432
- const results = await sendRecv<PyResult[]>("store_batch", {
433
- operations: operations.map(camelToSnake),
434
- });
435
-
436
- return results.map((result) => {
437
- if (Array.isArray(result)) {
438
- return result.map((item) => pyItemToJs(item));
439
- } else if (
440
- result &&
441
- typeof result === "object" &&
442
- "value" in result &&
443
- "key" in result
444
- ) {
445
- return pyItemToJs(result);
446
- }
447
- return result;
448
- }) as OperationResults<Op>;
449
- }
450
-
451
- async get(namespace: string[], key: string): Promise<Item | null> {
452
- return await sendRecv<Item | null>("store_get", {
453
- namespace: namespace.join("."),
454
- key,
455
- });
456
- }
457
-
458
- async search(
459
- namespacePrefix: string[],
460
- options?: {
461
- filter?: Record<string, any>;
462
- limit?: number;
463
- offset?: number;
464
- }
465
- ): Promise<Item[]> {
466
- return await sendRecv<Item[]>("store_search", {
467
- namespace_prefix: namespacePrefix,
468
- ...options,
469
- });
470
- }
471
-
472
- async put(
473
- namespace: string[],
474
- key: string,
475
- value: Record<string, any>
476
- ): Promise<void> {
477
- await sendRecv("store_put", { namespace, key, value });
478
- }
479
-
480
- async delete(namespace: string[], key: string): Promise<void> {
481
- await sendRecv("store_delete", { namespace, key });
482
- }
483
-
484
- async listNamespaces(options: {
485
- prefix?: string[];
486
- suffix?: string[];
487
- maxDepth?: number;
488
- limit?: number;
489
- offset?: number;
490
- }): Promise<string[][]> {
491
- const data = await sendRecv<{ namespaces: string[][] }>(
492
- "store_list_namespaces",
493
- { max_depth: options?.maxDepth, ...options }
494
- );
495
- return data.namespaces;
496
- }
497
- }
498
-
499
- const StreamModeSchema = z.union([
500
- z.literal("updates"),
501
- z.literal("debug"),
502
- z.literal("values"),
503
- ]);
504
-
505
- const ExtraStreamModeSchema = z.union([
506
- StreamModeSchema,
507
- z.literal("messages"),
508
- z.literal("messages-tuple"),
509
- ]);
510
-
511
- const StreamEventsPayload = z.object({
512
- graph_id: z.string(),
513
- input: z.unknown(),
514
- command: z.object({ resume: z.unknown() }).nullish(),
515
- stream_mode: z
516
- .union([ExtraStreamModeSchema, z.array(ExtraStreamModeSchema)])
517
- .optional(),
518
- config: RunnableConfigSchema.nullish(),
519
- interrupt_before: z.union([z.array(z.string()), z.literal("*")]).nullish(),
520
- interrupt_after: z.union([z.array(z.string()), z.literal("*")]).nullish(),
521
- subgraphs: z.boolean().optional(),
522
- });
523
-
524
- async function* streamEventsRequest(
525
- rawPayload: z.infer<typeof StreamEventsPayload>
526
- ) {
527
- const { graph_id: graphId, ...payload } = rawPayload;
528
- const graph = getGraph(graphId);
529
-
530
- const input = payload.command ? new Command(payload.command) : payload.input;
531
-
532
- const userStreamMode =
533
- payload.stream_mode == null
534
- ? []
535
- : Array.isArray(payload.stream_mode)
536
- ? payload.stream_mode
537
- : [payload.stream_mode];
538
-
539
- const graphStreamMode: Set<"updates" | "debug" | "values" | "messages"> =
540
- new Set();
541
- if (payload.stream_mode) {
542
- for (const mode of userStreamMode) {
543
- if (mode === "messages") {
544
- graphStreamMode.add("values");
545
- } else if (mode === "messages-tuple") {
546
- graphStreamMode.add("messages");
547
- } else {
548
- graphStreamMode.add(mode);
549
- }
550
- }
551
- }
552
-
553
- const config = getRunnableConfig(payload.config);
554
-
555
- const messages: Record<string, BaseMessageChunk> = {};
556
- const completedIds = new Set<string>();
557
-
558
- let interruptBefore: typeof payload.interrupt_before =
559
- payload.interrupt_before ?? undefined;
560
-
561
- if (Array.isArray(interruptBefore) && interruptBefore.length === 0)
562
- interruptBefore = undefined;
563
-
564
- let interruptAfter: typeof payload.interrupt_after =
565
- payload.interrupt_after ?? undefined;
566
-
567
- if (Array.isArray(interruptAfter) && interruptAfter.length === 0)
568
- interruptAfter = undefined;
569
-
570
- const streamMode = [...graphStreamMode];
571
-
572
- for await (const data of graph.streamEvents(input, {
573
- ...config,
574
- version: "v2",
575
- streamMode,
576
- subgraphs: payload.subgraphs,
577
- interruptBefore,
578
- interruptAfter,
579
- })) {
580
- // TODO: upstream this fix to LangGraphJS
581
- if (streamMode.length === 1 && !Array.isArray(data.data.chunk)) {
582
- data.data.chunk = [streamMode[0], data.data.chunk];
583
- }
584
-
585
- if (payload.subgraphs) {
586
- if (Array.isArray(data.data.chunk) && data.data.chunk.length === 2) {
587
- data.data.chunk = [[], ...data.data.chunk];
588
- }
589
- }
590
-
591
- yield data;
592
-
593
- if (userStreamMode.includes("messages")) {
594
- if (data.event === "on_chain_stream" && data.run_id === config.runId) {
595
- const newMessages: Array<BaseMessageChunk> = [];
596
- const [_, chunk]: [string, any] = data.data.chunk;
597
-
598
- let chunkMessages: Array<BaseMessageChunk> = [];
599
- if (
600
- typeof chunk === "object" &&
601
- chunk != null &&
602
- "messages" in chunk &&
603
- !isBaseMessage(chunk)
604
- ) {
605
- chunkMessages = chunk?.messages;
606
- }
607
-
608
- if (!Array.isArray(chunkMessages)) {
609
- chunkMessages = [chunkMessages];
610
- }
611
-
612
- for (const message of chunkMessages) {
613
- if (!message.id || completedIds.has(message.id)) continue;
614
- completedIds.add(message.id);
615
- newMessages.push(message);
616
- }
617
-
618
- if (newMessages.length > 0) {
619
- yield {
620
- event: "on_custom_event",
621
- name: "messages/complete",
622
- data: newMessages,
623
- };
624
- }
625
- } else if (
626
- data.event === "on_chat_model_stream" &&
627
- !data.tags?.includes("nostream")
628
- ) {
629
- const message: BaseMessageChunk = data.data.chunk;
630
-
631
- if (!message.id) continue;
632
-
633
- if (messages[message.id] == null) {
634
- messages[message.id] = message;
635
- yield {
636
- event: "on_custom_event",
637
- name: "messages/metadata",
638
- data: { [message.id]: { metadata: data.metadata } },
639
- };
640
- } else {
641
- messages[message.id] = messages[message.id].concat(message);
642
- }
643
-
644
- yield {
645
- event: "on_custom_event",
646
- name: "messages/partial",
647
- data: [messages[message.id]],
648
- };
649
- }
650
- }
651
- }
652
- }
653
-
654
- const GetGraphPayload = z.object({
655
- graph_id: z.string(),
656
- config: RunnableConfigSchema.nullish(),
657
- xray: z.union([z.number(), z.boolean()]).nullish(),
658
- });
659
-
660
- async function getGraphRequest(rawPayload: z.infer<typeof GetGraphPayload>) {
661
- const { graph_id: graphId, ...payload } = rawPayload;
662
- const graph = getGraph(graphId);
663
- return graph
664
- .getGraph({
665
- ...getRunnableConfig(payload.config),
666
- xray: payload.xray ?? undefined,
667
- })
668
- .toJSON();
669
- }
670
-
671
- const GetSubgraphsPayload = z.object({
672
- graph_id: z.string(),
673
- namespace: z.string().nullish(),
674
- recurse: z.boolean().nullish(),
675
- });
676
-
677
- async function getSubgraphsRequest(
678
- rawPayload: z.infer<typeof GetSubgraphsPayload>
679
- ) {
680
- const { graph_id: graphId, ...payload } = rawPayload;
681
- const graph = getGraph(graphId);
682
- const result: Array<[name: string, Record<string, any>]> = [];
683
-
684
- const graphSchema = await getOrExtractSchema(graphId);
685
- const rootGraphId = Object.keys(graphSchema).find((i) => !i.includes("|"));
686
-
687
- if (!rootGraphId) throw new Error("Failed to find root graph");
688
-
689
- for (const [name] of graph.getSubgraphs(
690
- payload.namespace ?? undefined,
691
- payload.recurse ?? undefined
692
- )) {
693
- const schema =
694
- graphSchema[`${rootGraphId}|${name}`] || graphSchema[rootGraphId];
695
- result.push([name, schema]);
696
- }
697
-
698
- // TODO: make this a stream
699
- return Object.fromEntries(result);
700
- }
701
-
702
- const GetStatePayload = z.object({
703
- graph_id: z.string(),
704
- config: RunnableConfigSchema,
705
- subgraphs: z.boolean().nullish(),
706
- });
707
-
708
- async function getStateRequest(rawPayload: z.infer<typeof GetStatePayload>) {
709
- const { graph_id: graphId, ...payload } = rawPayload;
710
- const graph = getGraph(graphId);
711
-
712
- const state = await graph.getState(getRunnableConfig(payload.config), {
713
- subgraphs: payload.subgraphs ?? undefined,
714
- });
715
-
716
- return state;
717
- }
718
-
719
- const UpdateStatePayload = z.object({
720
- graph_id: z.string(),
721
- config: RunnableConfigSchema,
722
- values: z.unknown(),
723
- as_node: z.string().nullish(),
724
- });
725
-
726
- async function updateStateRequest(
727
- rawPayload: z.infer<typeof UpdateStatePayload>
728
- ) {
729
- const { graph_id: graphId, ...payload } = rawPayload;
730
- const graph = getGraph(graphId);
731
-
732
- const config = await graph.updateState(
733
- getRunnableConfig(payload.config),
734
- payload.values,
735
- payload.as_node ?? undefined
736
- );
737
-
738
- return config;
739
- }
740
-
741
- const GetSchemaPayload = z.object({ graph_id: z.string() });
742
-
743
- async function getSchemaRequest(payload: z.infer<typeof GetSchemaPayload>) {
744
- const { graph_id: graphId } = payload;
745
- const schemas = await getOrExtractSchema(graphId);
746
- const rootGraphId = Object.keys(schemas).find((i) => !i.includes("|"));
747
- if (!rootGraphId) {
748
- throw new Error("Failed to find root graph");
749
- }
750
- return schemas[rootGraphId];
751
- }
752
-
753
- const GetStateHistoryPayload = z.object({
754
- graph_id: z.string(),
755
- config: RunnableConfigSchema,
756
- limit: z.number().nullish(),
757
- before: RunnableConfigSchema.nullish(),
758
- filter: z.record(z.unknown()).nullish(),
759
- });
760
-
761
- async function* getStateHistoryRequest(
762
- rawPayload: z.infer<typeof GetStateHistoryPayload>
763
- ) {
764
- const { graph_id: graphId, ...payload } = rawPayload;
765
- const graph = getGraph(graphId);
766
-
767
- for await (const item of graph.getStateHistory(
768
- getRunnableConfig(payload.config),
769
- {
770
- limit: payload.limit ?? undefined,
771
- before: payload.before ? getRunnableConfig(payload.before) : undefined,
772
- filter: payload.filter ?? undefined,
773
- }
774
- )) {
775
- yield item;
776
- }
777
- }
778
-
779
- const __dirname = new URL(".", import.meta.url).pathname;
780
-
781
- async function main() {
782
- remoteDealer.connect(REMOTE_ADDR);
783
- await clientRouter.bind(CLIENT_ADDR);
784
-
785
- const checkpointer = new RemoteCheckpointer();
786
- const store = new RemoteStore();
787
-
788
- const specs = filterValidGraphSpecs(
789
- z.record(z.string()).parse(JSON.parse(process.env.LANGSERVE_GRAPHS ?? "{}"))
790
- );
791
-
792
- if (!process.argv.includes("--skip-schema-cache")) {
793
- try {
794
- GRAPH_SCHEMA = JSON.parse(
795
- await fs.readFile(path.resolve(__dirname, "client.schemas.json"), {
796
- encoding: "utf-8",
797
- })
798
- );
799
- } catch {
800
- // pass
801
- }
802
- }
803
-
804
- await Promise.all(
805
- specs.map(async ([graphId, rawSpec]) => {
806
- logger.info(`Resolving graph ${graphId}`);
807
- const { resolved, ...spec } = await resolveGraph(rawSpec);
808
-
809
- // TODO: make sure the types do not need to be upfront
810
- // @ts-expect-error Overriding checkpointer with different value type
811
- resolved.checkpointer = checkpointer;
812
- resolved.store = store;
813
-
814
- // registering the graph runtime
815
- GRAPH_RESOLVED[graphId] = resolved;
816
- GRAPH_SPEC[graphId] = spec;
817
- })
818
- );
819
-
820
- for await (const packet of getRouterPackets()) {
821
- switch (packet.input.method) {
822
- case "streamEvents":
823
- handleStream(packet, StreamEventsPayload, streamEventsRequest);
824
- break;
825
- case "getGraph":
826
- handleInvoke(packet, GetGraphPayload, getGraphRequest);
827
- break;
828
- case "getSubgraphs":
829
- handleInvoke(packet, GetSubgraphsPayload, getSubgraphsRequest);
830
- break;
831
- case "getState":
832
- handleInvoke(packet, GetStatePayload, getStateRequest);
833
- break;
834
- case "updateState":
835
- handleInvoke(packet, UpdateStatePayload, updateStateRequest);
836
- break;
837
- case "getSchema":
838
- handleInvoke(packet, GetSchemaPayload, getSchemaRequest);
839
- break;
840
- case "getStateHistory":
841
- handleStream(packet, GetStateHistoryPayload, getStateHistoryRequest);
842
- break;
843
- case "ok":
844
- handleInvoke(packet, z.any(), () => Promise.resolve("ok"));
845
- break;
846
- default:
847
- logger.error(`Unknown method: ${packet.input.method}`);
848
- handleInvoke(packet, z.any(), () => {
849
- throw new Error(`Unknown method: ${packet.input.method}`);
850
- });
851
- break;
852
- }
853
- }
854
- }
855
-
856
- process.on("uncaughtExceptionMonitor", (error) => {
857
- logger.error(error);
858
- process.exit(1);
859
- });
860
-
861
- main();