@langchain/langgraph-api 0.0.40 → 0.0.41

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.
package/dist/api/runs.mjs CHANGED
@@ -13,7 +13,7 @@ import { serialiseAsDict } from "../utils/serde.mjs";
13
13
  const api = new Hono();
14
14
  const createValidRun = async (threadId, payload, kwargs) => {
15
15
  const { assistant_id: assistantId, ...run } = payload;
16
- const { auth, headers } = kwargs;
16
+ const { auth, headers } = kwargs ?? {};
17
17
  const runId = uuid4();
18
18
  const streamMode = Array.isArray(payload.stream_mode)
19
19
  ? payload.stream_mode
@@ -111,18 +111,7 @@ api.get("/threads/:thread_id/history", zValidator("param", z.object({ thread_id:
111
111
  const states = await Threads.State.list({ configurable: { thread_id, checkpoint_ns: "" } }, { limit, before }, c.var.auth);
112
112
  return jsonExtra(c, states.map(stateSnapshotToThreadState));
113
113
  });
114
- api.post("/threads/:thread_id/history", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", z.object({
115
- limit: z.number().optional().default(10),
116
- before: z.string().optional(),
117
- metadata: z.record(z.string(), z.unknown()).optional(),
118
- checkpoint: z
119
- .object({
120
- checkpoint_id: z.string().uuid().optional(),
121
- checkpoint_ns: z.string().optional(),
122
- checkpoint_map: z.record(z.string(), z.unknown()).optional(),
123
- })
124
- .optional(),
125
- })), async (c) => {
114
+ api.post("/threads/:thread_id/history", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.ThreadHistoryRequest), async (c) => {
126
115
  // Get Thread History Post
127
116
  const { thread_id } = c.req.valid("param");
128
117
  const { limit, before, metadata, checkpoint } = c.req.valid("json");
@@ -140,7 +129,7 @@ api.delete("/threads/:thread_id", zValidator("param", z.object({ thread_id: z.st
140
129
  await Threads.delete(thread_id, c.var.auth);
141
130
  return new Response(null, { status: 204 });
142
131
  });
143
- api.patch("/threads/:thread_id", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", z.object({ metadata: z.record(z.string(), z.unknown()) })), async (c) => {
132
+ api.patch("/threads/:thread_id", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.ThreadPatchRequest), async (c) => {
144
133
  // Patch Thread
145
134
  const { thread_id } = c.req.valid("param");
146
135
  const { metadata } = c.req.valid("json");
@@ -0,0 +1,26 @@
1
+ import type { BaseCheckpointSaver, BaseStore, Pregel } from "@langchain/langgraph";
2
+ import { Hono } from "hono";
3
+ import type { Metadata } from "../storage/ops.mjs";
4
+ type AnyPregel = Pregel<any, any, any, any, any>;
5
+ interface Thread {
6
+ thread_id: string;
7
+ metadata: Metadata;
8
+ }
9
+ interface ThreadSaver {
10
+ get: (id: string) => Promise<Thread>;
11
+ put: (id: string, options: {
12
+ metadata?: Metadata;
13
+ }) => Promise<void>;
14
+ delete: (id: string) => Promise<void>;
15
+ }
16
+ /**
17
+ * Attach LangGraph Platform-esque routes to a given Hono instance.
18
+ * @experimental Does not follow semver.
19
+ */
20
+ export declare function createEmbedServer(options: {
21
+ graph: Record<string, AnyPregel>;
22
+ threads: ThreadSaver;
23
+ checkpointer: BaseCheckpointSaver;
24
+ store?: BaseStore;
25
+ }): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
26
+ export {};
@@ -0,0 +1,167 @@
1
+ import { Hono } from "hono";
2
+ import { zValidator } from "@hono/zod-validator";
3
+ import { streamSSE } from "hono/streaming";
4
+ import { v4 as uuidv4 } from "uuid";
5
+ import * as schemas from "../schemas.mjs";
6
+ import { z } from "zod";
7
+ import { streamState } from "../stream.mjs";
8
+ import { serialiseAsDict } from "../utils/serde.mjs";
9
+ import { getDisconnectAbortSignal, jsonExtra } from "../utils/hono.mjs";
10
+ import { stateSnapshotToThreadState } from "../state.mjs";
11
+ import { ensureContentType } from "../http/middleware.mjs";
12
+ function createStubRun(threadId, payload) {
13
+ const now = new Date();
14
+ const runId = uuidv4();
15
+ const streamMode = Array.isArray(payload.stream_mode)
16
+ ? payload.stream_mode
17
+ : payload.stream_mode
18
+ ? [payload.stream_mode]
19
+ : undefined;
20
+ const config = Object.assign({}, payload.config ?? {}, {
21
+ configurable: {
22
+ run_id: runId,
23
+ thread_id: threadId,
24
+ graph_id: payload.assistant_id,
25
+ },
26
+ }, { metadata: payload.metadata ?? {} });
27
+ return {
28
+ run_id: runId,
29
+ thread_id: threadId,
30
+ assistant_id: payload.assistant_id,
31
+ metadata: payload.metadata ?? {},
32
+ status: "running",
33
+ kwargs: {
34
+ input: payload.input,
35
+ command: payload.command,
36
+ config,
37
+ stream_mode: streamMode,
38
+ interrupt_before: payload.interrupt_before,
39
+ interrupt_after: payload.interrupt_after,
40
+ feedback_keys: payload.feedback_keys,
41
+ subgraphs: payload.stream_subgraphs,
42
+ temporary: false,
43
+ },
44
+ multitask_strategy: "reject",
45
+ created_at: now,
46
+ updated_at: now,
47
+ };
48
+ }
49
+ /**
50
+ * Attach LangGraph Platform-esque routes to a given Hono instance.
51
+ * @experimental Does not follow semver.
52
+ */
53
+ export function createEmbedServer(options) {
54
+ async function getGraph(graphId) {
55
+ const targetGraph = options.graph[graphId];
56
+ targetGraph.store = options.store;
57
+ targetGraph.checkpointer = options.checkpointer;
58
+ return targetGraph;
59
+ }
60
+ const api = new Hono();
61
+ api.use(ensureContentType());
62
+ api.post("/threads", zValidator("json", schemas.ThreadCreate), async (c) => {
63
+ // create a new threaad
64
+ const payload = c.req.valid("json");
65
+ const threadId = payload.thread_id || uuidv4();
66
+ await options.threads.put(threadId, payload);
67
+ return jsonExtra(c, { thread_id: threadId });
68
+ });
69
+ api.get("/threads/:thread_id/state", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("query", z.object({ subgraphs: schemas.coercedBoolean.optional() })), async (c) => {
70
+ // Get Latest Thread State
71
+ const { thread_id } = c.req.valid("param");
72
+ const { subgraphs } = c.req.valid("query");
73
+ const thread = await options.threads.get(thread_id);
74
+ const graphId = thread.metadata?.graph_id;
75
+ const graph = graphId ? options.graph[graphId] : undefined;
76
+ if (graph == null) {
77
+ return jsonExtra(c, stateSnapshotToThreadState({
78
+ values: {},
79
+ next: [],
80
+ config: {},
81
+ metadata: undefined,
82
+ createdAt: undefined,
83
+ parentConfig: undefined,
84
+ tasks: [],
85
+ }));
86
+ }
87
+ const config = { configurable: { thread_id } };
88
+ const result = await graph.getState(config, { subgraphs });
89
+ return jsonExtra(c, stateSnapshotToThreadState(result));
90
+ });
91
+ api.post("/threads/:thread_id/history", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.ThreadHistoryRequest), async (c) => {
92
+ // Get Thread History Post
93
+ const { thread_id } = c.req.valid("param");
94
+ const { limit, before, metadata, checkpoint } = c.req.valid("json");
95
+ const thread = await options.threads.get(thread_id);
96
+ const graphId = thread.metadata?.graph_id;
97
+ const graph = graphId ? options.graph[graphId] : undefined;
98
+ if (graph == null)
99
+ return jsonExtra(c, []);
100
+ const config = { configurable: { thread_id, ...checkpoint } };
101
+ const result = [];
102
+ const beforeConfig = typeof before === "string"
103
+ ? { configurable: { checkpoint_id: before } }
104
+ : before;
105
+ for await (const state of graph.getStateHistory(config, {
106
+ limit,
107
+ before: beforeConfig,
108
+ filter: metadata,
109
+ })) {
110
+ result.push(stateSnapshotToThreadState(state));
111
+ }
112
+ return jsonExtra(c, result);
113
+ });
114
+ api.post("/threads/:thread_id/runs/stream", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.RunCreate), async (c) => {
115
+ // Stream Run
116
+ return streamSSE(c, async (stream) => {
117
+ const { thread_id } = c.req.valid("param");
118
+ const payload = c.req.valid("json");
119
+ const signal = getDisconnectAbortSignal(c, stream);
120
+ const run = createStubRun(thread_id, payload);
121
+ // update thread with new graph_id
122
+ const thread = await options.threads.get(thread_id);
123
+ await options.threads.put(thread_id, {
124
+ metadata: {
125
+ ...thread.metadata,
126
+ graph_id: payload.assistant_id,
127
+ assistant_id: payload.assistant_id,
128
+ },
129
+ });
130
+ for await (const { event, data } of streamState(run, {
131
+ attempt: 1,
132
+ getGraph,
133
+ signal,
134
+ })) {
135
+ await stream.writeSSE({ data: serialiseAsDict(data), event });
136
+ }
137
+ });
138
+ });
139
+ api.post("/runs/stream", zValidator("json", schemas.RunCreate), async (c) => {
140
+ // Stream Stateless Run
141
+ return streamSSE(c, async (stream) => {
142
+ const payload = c.req.valid("json");
143
+ const signal = getDisconnectAbortSignal(c, stream);
144
+ const threadId = uuidv4();
145
+ await options.threads.put(threadId, {
146
+ metadata: {
147
+ graph_id: payload.assistant_id,
148
+ assistant_id: payload.assistant_id,
149
+ },
150
+ });
151
+ try {
152
+ const run = createStubRun(threadId, payload);
153
+ for await (const { event, data } of streamState(run, {
154
+ attempt: 1,
155
+ getGraph,
156
+ signal,
157
+ })) {
158
+ await stream.writeSSE({ data: serialiseAsDict(data), event });
159
+ }
160
+ }
161
+ finally {
162
+ await options.threads.delete(threadId);
163
+ }
164
+ });
165
+ });
166
+ return api;
167
+ }
package/dist/queue.mjs CHANGED
@@ -3,6 +3,7 @@ import { streamState, } from "./stream.mjs";
3
3
  import { logError, logger } from "./logging.mjs";
4
4
  import { serializeError } from "./utils/serde.mjs";
5
5
  import { callWebhook } from "./webhook.mjs";
6
+ import { getGraph } from "./graph/load.mjs";
6
7
  const MAX_RETRY_ATTEMPTS = 3;
7
8
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
8
9
  export const queue = async () => {
@@ -14,7 +15,7 @@ export const queue = async () => {
14
15
  await sleep(1000 * Math.random());
15
16
  }
16
17
  };
17
- const worker = async (run, attempt, abortSignal) => {
18
+ const worker = async (run, attempt, signal) => {
18
19
  const startedAt = new Date();
19
20
  let endedAt = undefined;
20
21
  let checkpoint = undefined;
@@ -48,8 +49,10 @@ const worker = async (run, attempt, abortSignal) => {
48
49
  const runId = run.run_id;
49
50
  const resumable = run.kwargs?.resumable ?? false;
50
51
  try {
51
- const stream = streamState(run, attempt, {
52
- signal: abortSignal,
52
+ const stream = streamState(run, {
53
+ getGraph,
54
+ attempt,
55
+ signal,
53
56
  ...(!temporary ? { onCheckpoint, onTaskResult } : undefined),
54
57
  });
55
58
  for await (const { event, data } of stream) {
@@ -1340,6 +1340,49 @@ export declare const ThreadStateUpdate: z.ZodObject<{
1340
1340
  checkpoint_id?: string | undefined;
1341
1341
  as_node?: string | undefined;
1342
1342
  }>;
1343
+ export declare const ThreadHistoryRequest: z.ZodObject<{
1344
+ limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
1345
+ before: z.ZodOptional<z.ZodString>;
1346
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1347
+ checkpoint: z.ZodOptional<z.ZodObject<{
1348
+ checkpoint_id: z.ZodOptional<z.ZodString>;
1349
+ checkpoint_ns: z.ZodOptional<z.ZodString>;
1350
+ checkpoint_map: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1351
+ }, "strip", z.ZodTypeAny, {
1352
+ checkpoint_ns?: string | undefined;
1353
+ checkpoint_id?: string | undefined;
1354
+ checkpoint_map?: Record<string, unknown> | undefined;
1355
+ }, {
1356
+ checkpoint_ns?: string | undefined;
1357
+ checkpoint_id?: string | undefined;
1358
+ checkpoint_map?: Record<string, unknown> | undefined;
1359
+ }>>;
1360
+ }, "strip", z.ZodTypeAny, {
1361
+ limit: number;
1362
+ metadata?: Record<string, unknown> | undefined;
1363
+ checkpoint?: {
1364
+ checkpoint_ns?: string | undefined;
1365
+ checkpoint_id?: string | undefined;
1366
+ checkpoint_map?: Record<string, unknown> | undefined;
1367
+ } | undefined;
1368
+ before?: string | undefined;
1369
+ }, {
1370
+ metadata?: Record<string, unknown> | undefined;
1371
+ checkpoint?: {
1372
+ checkpoint_ns?: string | undefined;
1373
+ checkpoint_id?: string | undefined;
1374
+ checkpoint_map?: Record<string, unknown> | undefined;
1375
+ } | undefined;
1376
+ limit?: number | undefined;
1377
+ before?: string | undefined;
1378
+ }>;
1379
+ export declare const ThreadPatchRequest: z.ZodObject<{
1380
+ metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
1381
+ }, "strip", z.ZodTypeAny, {
1382
+ metadata: Record<string, unknown>;
1383
+ }, {
1384
+ metadata: Record<string, unknown>;
1385
+ }>;
1343
1386
  export declare const AssistantLatestVersion: z.ZodObject<{
1344
1387
  version: z.ZodNumber;
1345
1388
  }, "strip", z.ZodTypeAny, {
package/dist/schemas.mjs CHANGED
@@ -388,6 +388,21 @@ export const ThreadStateUpdate = z
388
388
  as_node: z.string().optional(),
389
389
  })
390
390
  .describe("Payload for adding state to a thread.");
391
+ export const ThreadHistoryRequest = z.object({
392
+ limit: z.number().optional().default(10),
393
+ before: z.string().optional(),
394
+ metadata: z.record(z.string(), z.unknown()).optional(),
395
+ checkpoint: z
396
+ .object({
397
+ checkpoint_id: z.string().uuid().optional(),
398
+ checkpoint_ns: z.string().optional(),
399
+ checkpoint_map: z.record(z.string(), z.unknown()).optional(),
400
+ })
401
+ .optional(),
402
+ });
403
+ export const ThreadPatchRequest = z.object({
404
+ metadata: z.record(z.string(), z.unknown()),
405
+ });
391
406
  export const AssistantLatestVersion = z.object({
392
407
  version: z.number(),
393
408
  });
@@ -744,27 +744,37 @@ export class Runs {
744
744
  static async *next() {
745
745
  yield* conn.withGenerator(async function* (STORE, options) {
746
746
  const now = new Date();
747
- const pendingRuns = Object.values(STORE.runs)
747
+ const pendingRunIds = Object.values(STORE.runs)
748
748
  .filter((run) => run.status === "pending" && run.created_at < now)
749
- .sort((a, b) => a.created_at.getTime() - b.created_at.getTime());
750
- if (!pendingRuns.length) {
749
+ .sort((a, b) => a.created_at.getTime() - b.created_at.getTime())
750
+ .map((run) => run.run_id);
751
+ if (!pendingRunIds.length) {
751
752
  return;
752
753
  }
753
- for (const run of pendingRuns) {
754
- const runId = run.run_id;
755
- const threadId = run.thread_id;
756
- const thread = STORE.threads[threadId];
757
- if (!thread) {
758
- await console.warn(`Unexpected missing thread in Runs.next: ${threadId}`);
759
- continue;
760
- }
754
+ for (const runId of pendingRunIds) {
761
755
  if (StreamManager.isLocked(runId))
762
756
  continue;
763
757
  try {
764
758
  const signal = StreamManager.lock(runId);
759
+ const run = STORE.runs[runId];
760
+ if (!run)
761
+ continue;
762
+ const threadId = run.thread_id;
763
+ const thread = STORE.threads[threadId];
764
+ if (!thread) {
765
+ logger.warn(`Unexpected missing thread in Runs.next: ${threadId}`);
766
+ continue;
767
+ }
768
+ // is the run still valid?
769
+ if (run.status !== "pending")
770
+ continue;
771
+ if (Object.values(STORE.runs).some((run) => run.thread_id === threadId && run.status === "running")) {
772
+ continue;
773
+ }
765
774
  options.schedulePersist();
766
775
  STORE.retry_counter[runId] ??= 0;
767
776
  STORE.retry_counter[runId] += 1;
777
+ STORE.runs[runId].status = "running";
768
778
  yield { run, attempt: STORE.retry_counter[runId], signal };
769
779
  }
770
780
  finally {
@@ -842,7 +852,8 @@ export class Runs {
842
852
  }
843
853
  // if multitask_mode = reject, check for inflight runs
844
854
  // and if there are any, return them to reject putting a new run
845
- const inflightRuns = Object.values(STORE.runs).filter((run) => run.thread_id === threadId && run.status === "pending");
855
+ const inflightRuns = Object.values(STORE.runs).filter((run) => run.thread_id === threadId &&
856
+ (run.status === "pending" || run.status === "running"));
846
857
  if (options?.preventInsertInInflight) {
847
858
  if (inflightRuns.length > 0)
848
859
  return inflightRuns;
@@ -1090,7 +1101,7 @@ export class Runs {
1090
1101
  yield { event: "error", data: "Run not found" };
1091
1102
  break;
1092
1103
  }
1093
- else if (run.status !== "pending") {
1104
+ else if (run.status !== "pending" && run.status !== "running") {
1094
1105
  break;
1095
1106
  }
1096
1107
  }
package/dist/stream.d.mts CHANGED
@@ -1,4 +1,5 @@
1
- import { type CheckpointMetadata, type Interrupt, type StateSnapshot } from "@langchain/langgraph";
1
+ import type { BaseCheckpointSaver, LangGraphRunnableConfig, CheckpointMetadata, Interrupt, StateSnapshot } from "@langchain/langgraph";
2
+ import type { Pregel } from "@langchain/langgraph/pregel";
2
3
  import type { Checkpoint, Run, RunnableConfig } from "./storage/ops.mjs";
3
4
  interface DebugTask {
4
5
  id: string;
@@ -27,7 +28,11 @@ export type StreamTaskResult = Prettify<Omit<DebugTask, "state"> & {
27
28
  state?: StateSnapshot;
28
29
  checkpoint?: Checkpoint;
29
30
  }>;
30
- export declare function streamState(run: Run, attempt?: number, options?: {
31
+ export declare function streamState(run: Run, options: {
32
+ attempt: number;
33
+ getGraph: (graphId: string, config: LangGraphRunnableConfig | undefined, options?: {
34
+ checkpointer?: BaseCheckpointSaver | null;
35
+ }) => Promise<Pregel<any, any, any, any, any>>;
31
36
  onCheckpoint?: (checkpoint: StreamCheckpoint) => void;
32
37
  onTaskResult?: (taskResult: StreamTaskResult) => void;
33
38
  signal?: AbortSignal;
package/dist/stream.mjs CHANGED
@@ -2,7 +2,6 @@ import { isBaseMessage } from "@langchain/core/messages";
2
2
  import { LangChainTracer } from "@langchain/core/tracers/tracer_langchain";
3
3
  import { Client as LangSmithClient, getDefaultProjectName } from "langsmith";
4
4
  import { getLangGraphCommand } from "./command.mjs";
5
- import { getGraph } from "./graph/load.mjs";
6
5
  import { checkLangGraphSemver } from "./semver/index.mjs";
7
6
  import { runnableConfigToCheckpoint, taskRunnableConfigToCheckpoint, } from "./utils/runnableConfig.mjs";
8
7
  const isRunnableConfig = (config) => {
@@ -54,13 +53,13 @@ function preprocessDebugCheckpoint(payload) {
54
53
  return result;
55
54
  }
56
55
  let LANGGRAPH_VERSION;
57
- export async function* streamState(run, attempt = 1, options) {
56
+ export async function* streamState(run, options) {
58
57
  const kwargs = run.kwargs;
59
58
  const graphId = kwargs.config?.configurable?.graph_id;
60
59
  if (!graphId || typeof graphId !== "string") {
61
60
  throw new Error("Invalid or missing graph_id");
62
61
  }
63
- const graph = await getGraph(graphId, kwargs.config, {
62
+ const graph = await options.getGraph(graphId, kwargs.config, {
64
63
  checkpointer: kwargs.temporary ? null : undefined,
65
64
  });
66
65
  const userStreamMode = kwargs.stream_mode ?? [];
@@ -75,7 +74,7 @@ export async function* streamState(run, attempt = 1, options) {
75
74
  libStreamMode.add("debug");
76
75
  yield {
77
76
  event: "metadata",
78
- data: { run_id: run.run_id, attempt },
77
+ data: { run_id: run.run_id, attempt: options.attempt },
79
78
  };
80
79
  if (!LANGGRAPH_VERSION) {
81
80
  const version = await checkLangGraphSemver();
@@ -83,7 +82,7 @@ export async function* streamState(run, attempt = 1, options) {
83
82
  }
84
83
  const metadata = {
85
84
  ...kwargs.config?.metadata,
86
- run_attempt: attempt,
85
+ run_attempt: options.attempt,
87
86
  langgraph_version: LANGGRAPH_VERSION?.version ?? "0.0.0",
88
87
  langgraph_plan: "developer",
89
88
  langgraph_host: "self-hosted",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph-api",
3
- "version": "0.0.40",
3
+ "version": "0.0.41",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": "^18.19.0 || >=20.16.0"
@@ -27,6 +27,10 @@
27
27
  "types": "./dist/graph/parser/index.d.mts",
28
28
  "default": "./dist/graph/parser/index.mjs"
29
29
  },
30
+ "./experimental/embed": {
31
+ "types": "./dist/experimental/embed.d.mts",
32
+ "default": "./dist/experimental/embed.mjs"
33
+ },
30
34
  "./package.json": "./package.json"
31
35
  },
32
36
  "repository": {
@@ -49,7 +53,7 @@
49
53
  "@babel/code-frame": "^7.26.2",
50
54
  "@hono/node-server": "^1.12.0",
51
55
  "@hono/zod-validator": "^0.2.2",
52
- "@langchain/langgraph-ui": "0.0.40",
56
+ "@langchain/langgraph-ui": "0.0.41",
53
57
  "@types/json-schema": "^7.0.15",
54
58
  "@typescript/vfs": "^1.6.0",
55
59
  "dedent": "^1.5.3",