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