@langchain/langgraph-api 1.1.8 → 1.1.10
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/README.md +3 -3
- package/dist/api/assistants.d.mts +3 -0
- package/dist/api/assistants.mjs +193 -0
- package/dist/api/meta.d.mts +3 -0
- package/dist/api/meta.mjs +65 -0
- package/dist/api/runs.d.mts +3 -0
- package/dist/api/runs.mjs +324 -0
- package/dist/api/store.d.mts +3 -0
- package/dist/api/store.mjs +111 -0
- package/dist/api/threads.d.mts +3 -0
- package/dist/api/threads.mjs +143 -0
- package/dist/auth/custom.d.mts +9 -0
- package/dist/auth/custom.mjs +32 -0
- package/dist/auth/index.d.mts +43 -0
- package/dist/auth/index.mjs +163 -0
- package/dist/cli/entrypoint.d.mts +1 -0
- package/dist/cli/entrypoint.mjs +41 -0
- package/dist/cli/spawn.d.mts +42 -0
- package/dist/cli/spawn.mjs +47 -0
- package/dist/cli/utils/ipc/client.d.mts +5 -0
- package/dist/cli/utils/ipc/client.mjs +47 -0
- package/dist/cli/utils/ipc/utils/get-pipe-path.d.mts +1 -0
- package/dist/cli/utils/ipc/utils/get-pipe-path.mjs +29 -0
- package/dist/cli/utils/ipc/utils/temporary-directory.d.mts +5 -0
- package/dist/cli/utils/ipc/utils/temporary-directory.mjs +40 -0
- package/dist/command.d.mts +11 -0
- package/dist/command.mjs +15 -0
- package/dist/experimental/embed.d.mts +42 -0
- package/dist/experimental/embed.mjs +299 -0
- package/dist/graph/api.d.mts +1 -0
- package/dist/graph/api.mjs +2 -0
- package/dist/graph/load.d.mts +19 -0
- package/dist/graph/load.hooks.d.mts +2 -0
- package/dist/graph/load.hooks.mjs +52 -0
- package/dist/graph/load.mjs +96 -0
- package/dist/graph/load.utils.d.mts +22 -0
- package/dist/graph/load.utils.mjs +49 -0
- package/dist/graph/parser/index.d.mts +23 -0
- package/dist/graph/parser/index.mjs +58 -0
- package/dist/graph/parser/parser.d.mts +77 -0
- package/dist/graph/parser/parser.mjs +429 -0
- package/dist/graph/parser/parser.worker.d.mts +1 -0
- package/dist/graph/parser/parser.worker.mjs +7 -0
- package/dist/graph/parser/schema/types.d.mts +154 -0
- package/dist/graph/parser/schema/types.mjs +1496 -0
- package/dist/graph/parser/schema/types.template.d.mts +1 -0
- package/dist/graph/parser/schema/types.template.mts +92 -0
- package/dist/http/custom.d.mts +6 -0
- package/dist/http/custom.mjs +10 -0
- package/dist/http/middleware.d.mts +11 -0
- package/dist/http/middleware.mjs +57 -0
- package/dist/logging.d.mts +10 -0
- package/dist/logging.mjs +115 -0
- package/dist/loopback.d.mts +4 -0
- package/dist/loopback.mjs +10 -0
- package/dist/preload.d.mts +1 -0
- package/dist/preload.mjs +29 -0
- package/dist/queue.d.mts +2 -0
- package/dist/queue.mjs +119 -0
- package/dist/schemas.d.mts +1552 -0
- package/dist/schemas.mjs +492 -0
- package/dist/semver/index.d.mts +15 -0
- package/dist/semver/index.mjs +46 -0
- package/dist/server.d.mts +175 -0
- package/dist/server.mjs +181 -0
- package/dist/state.d.mts +3 -0
- package/dist/state.mjs +30 -0
- package/dist/storage/checkpoint.d.mts +19 -0
- package/dist/storage/checkpoint.mjs +127 -0
- package/dist/storage/context.d.mts +3 -0
- package/dist/storage/context.mjs +11 -0
- package/dist/storage/importMap.d.mts +55 -0
- package/dist/storage/importMap.mjs +55 -0
- package/dist/storage/ops.d.mts +169 -0
- package/dist/storage/ops.mjs +1262 -0
- package/dist/storage/persist.d.mts +18 -0
- package/dist/storage/persist.mjs +81 -0
- package/dist/storage/store.d.mts +17 -0
- package/dist/storage/store.mjs +41 -0
- package/dist/storage/types.d.mts +301 -0
- package/dist/storage/types.mjs +1 -0
- package/dist/stream.d.mts +43 -0
- package/dist/stream.mjs +235 -0
- package/dist/ui/load.d.mts +8 -0
- package/dist/ui/load.mjs +53 -0
- package/dist/utils/abort.d.mts +1 -0
- package/dist/utils/abort.mjs +8 -0
- package/dist/utils/hono.d.mts +5 -0
- package/dist/utils/hono.mjs +24 -0
- package/dist/utils/importMap.d.mts +55 -0
- package/dist/utils/importMap.mjs +55 -0
- package/dist/utils/runnableConfig.d.mts +3 -0
- package/dist/utils/runnableConfig.mjs +45 -0
- package/dist/utils/serde.d.mts +5 -0
- package/dist/utils/serde.mjs +20 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +11 -0
- package/dist/webhook.d.mts +11 -0
- package/dist/webhook.mjs +30 -0
- package/package.json +19 -19
package/README.md
CHANGED
|
@@ -4,6 +4,6 @@ In-memory implementation of the LangGraph.js API.
|
|
|
4
4
|
|
|
5
5
|
## Tests
|
|
6
6
|
|
|
7
|
-
1. Build the latest code changes to test: `
|
|
8
|
-
1. Start a local server: `
|
|
9
|
-
1. Run the tests: `
|
|
7
|
+
1. Build the latest code changes to test: `pnpm build`
|
|
8
|
+
1. Start a local server: `pnpm dev`
|
|
9
|
+
1. Run the tests: `pnpm test`
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { zValidator } from "@hono/zod-validator";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { v4 as uuid } from "uuid";
|
|
4
|
+
import { z } from "zod/v3";
|
|
5
|
+
import { getAssistantId, getCachedStaticGraphSchema, getGraph, } from "../graph/load.mjs";
|
|
6
|
+
import { getRuntimeGraphSchema } from "../graph/parser/index.mjs";
|
|
7
|
+
import { HTTPException } from "hono/http-exception";
|
|
8
|
+
import * as schemas from "../schemas.mjs";
|
|
9
|
+
import { assistants } from "../storage/context.mjs";
|
|
10
|
+
const api = new Hono();
|
|
11
|
+
const RunnableConfigSchema = z.object({
|
|
12
|
+
tags: z.array(z.string()).optional(),
|
|
13
|
+
metadata: z.record(z.unknown()).optional(),
|
|
14
|
+
run_name: z.string().optional(),
|
|
15
|
+
max_concurrency: z.number().optional(),
|
|
16
|
+
recursion_limit: z.number().optional(),
|
|
17
|
+
configurable: z.record(z.unknown()).optional(),
|
|
18
|
+
run_id: z.string().uuid().optional(),
|
|
19
|
+
});
|
|
20
|
+
const getRunnableConfig = (userConfig) => {
|
|
21
|
+
if (!userConfig)
|
|
22
|
+
return {};
|
|
23
|
+
return {
|
|
24
|
+
configurable: userConfig.configurable,
|
|
25
|
+
tags: userConfig.tags,
|
|
26
|
+
metadata: userConfig.metadata,
|
|
27
|
+
runName: userConfig.run_name,
|
|
28
|
+
maxConcurrency: userConfig.max_concurrency,
|
|
29
|
+
recursionLimit: userConfig.recursion_limit,
|
|
30
|
+
runId: userConfig.run_id,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
api.post("/assistants", zValidator("json", schemas.AssistantCreate), async (c) => {
|
|
34
|
+
// Create Assistant
|
|
35
|
+
const payload = c.req.valid("json");
|
|
36
|
+
const assistant = await assistants().put(payload.assistant_id ?? uuid(), {
|
|
37
|
+
config: payload.config ?? {},
|
|
38
|
+
context: payload.context ?? {},
|
|
39
|
+
graph_id: payload.graph_id,
|
|
40
|
+
metadata: payload.metadata ?? {},
|
|
41
|
+
if_exists: payload.if_exists ?? "raise",
|
|
42
|
+
name: payload.name ?? "Untitled",
|
|
43
|
+
description: payload.description,
|
|
44
|
+
}, c.var.auth);
|
|
45
|
+
return c.json(assistant);
|
|
46
|
+
});
|
|
47
|
+
api.post("/assistants/search", zValidator("json", schemas.AssistantSearchRequest), async (c) => {
|
|
48
|
+
// Search Assistants
|
|
49
|
+
const payload = c.req.valid("json");
|
|
50
|
+
const result = [];
|
|
51
|
+
let total = 0;
|
|
52
|
+
for await (const item of assistants().search({
|
|
53
|
+
graph_id: payload.graph_id,
|
|
54
|
+
name: payload.name,
|
|
55
|
+
metadata: payload.metadata,
|
|
56
|
+
limit: payload.limit ?? 10,
|
|
57
|
+
offset: payload.offset ?? 0,
|
|
58
|
+
sort_by: payload.sort_by,
|
|
59
|
+
sort_order: payload.sort_order,
|
|
60
|
+
select: payload.select,
|
|
61
|
+
}, c.var.auth)) {
|
|
62
|
+
result.push(Object.fromEntries(Object.entries(item.assistant).filter(([k]) => !payload.select || payload.select.includes(k))));
|
|
63
|
+
if (total === 0) {
|
|
64
|
+
total = item.total;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (total === payload.limit) {
|
|
68
|
+
c.res.headers.set("X-Pagination-Next", ((payload.offset ?? 0) + total).toString());
|
|
69
|
+
c.res.headers.set("X-Pagination-Total", ((payload.offset ?? 0) + total + 1).toString());
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
c.res.headers.set("X-Pagination-Total", ((payload.offset ?? 0) + total).toString());
|
|
73
|
+
}
|
|
74
|
+
return c.json(result);
|
|
75
|
+
});
|
|
76
|
+
api.post("/assistants/count", zValidator("json", schemas.AssistantCountRequest), async (c) => {
|
|
77
|
+
const payload = c.req.valid("json");
|
|
78
|
+
const total = await assistants().count(payload, c.var.auth);
|
|
79
|
+
return c.json(total);
|
|
80
|
+
});
|
|
81
|
+
api.get("/assistants/:assistant_id", async (c) => {
|
|
82
|
+
// Get Assistant
|
|
83
|
+
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
84
|
+
return c.json(await assistants().get(assistantId, c.var.auth));
|
|
85
|
+
});
|
|
86
|
+
api.delete("/assistants/:assistant_id", async (c) => {
|
|
87
|
+
// Delete Assistant
|
|
88
|
+
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
89
|
+
return c.json(await assistants().delete(assistantId, c.var.auth));
|
|
90
|
+
});
|
|
91
|
+
api.patch("/assistants/:assistant_id", zValidator("json", schemas.AssistantPatch), async (c) => {
|
|
92
|
+
// Patch Assistant
|
|
93
|
+
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
94
|
+
const payload = c.req.valid("json");
|
|
95
|
+
return c.json(await assistants().patch(assistantId, payload, c.var.auth));
|
|
96
|
+
});
|
|
97
|
+
api.get("/assistants/:assistant_id/graph", zValidator("query", z.object({ xray: schemas.coercedBoolean.optional() })), async (c) => {
|
|
98
|
+
// Get Assistant Graph
|
|
99
|
+
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
100
|
+
const assistant = await assistants().get(assistantId, c.var.auth);
|
|
101
|
+
const { xray } = c.req.valid("query");
|
|
102
|
+
const config = getRunnableConfig(assistant.config);
|
|
103
|
+
const graph = await getGraph(assistant.graph_id, config);
|
|
104
|
+
const drawable = await graph.getGraphAsync({
|
|
105
|
+
...config,
|
|
106
|
+
xray: xray ?? undefined,
|
|
107
|
+
});
|
|
108
|
+
return c.json(drawable.toJSON());
|
|
109
|
+
});
|
|
110
|
+
api.get("/assistants/:assistant_id/schemas", zValidator("json", z.object({ config: RunnableConfigSchema.optional() })), async (c) => {
|
|
111
|
+
// Get Assistant Schemas
|
|
112
|
+
const json = c.req.valid("json");
|
|
113
|
+
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
114
|
+
const assistant = await assistants().get(assistantId, c.var.auth);
|
|
115
|
+
const config = getRunnableConfig(json.config);
|
|
116
|
+
const graph = await getGraph(assistant.graph_id, config);
|
|
117
|
+
const schema = await (async () => {
|
|
118
|
+
const runtimeSchema = await getRuntimeGraphSchema(graph);
|
|
119
|
+
if (runtimeSchema)
|
|
120
|
+
return runtimeSchema;
|
|
121
|
+
const graphSchema = await getCachedStaticGraphSchema(assistant.graph_id);
|
|
122
|
+
const rootGraphId = Object.keys(graphSchema).find((i) => !i.includes("|"));
|
|
123
|
+
if (!rootGraphId)
|
|
124
|
+
throw new HTTPException(404, { message: "Failed to find root graph" });
|
|
125
|
+
return graphSchema[rootGraphId];
|
|
126
|
+
})();
|
|
127
|
+
return c.json({
|
|
128
|
+
graph_id: assistant.graph_id,
|
|
129
|
+
input_schema: schema.input,
|
|
130
|
+
output_schema: schema.output,
|
|
131
|
+
state_schema: schema.state,
|
|
132
|
+
config_schema: schema.config,
|
|
133
|
+
// From JS PoV `configSchema` and `contextSchema` are indistinguishable,
|
|
134
|
+
// thus we use config_schema for context_schema.
|
|
135
|
+
context_schema: schema.config,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
api.get("/assistants/:assistant_id/subgraphs/:namespace?", zValidator("param", z.object({ assistant_id: z.string(), namespace: z.string().optional() })), zValidator("query", z.object({ recurse: schemas.coercedBoolean.optional() })), async (c) => {
|
|
139
|
+
// Get Assistant Subgraphs
|
|
140
|
+
const { assistant_id, namespace } = c.req.valid("param");
|
|
141
|
+
const { recurse } = c.req.valid("query");
|
|
142
|
+
const assistantId = getAssistantId(assistant_id);
|
|
143
|
+
const assistant = await assistants().get(assistantId, c.var.auth);
|
|
144
|
+
const config = getRunnableConfig(assistant.config);
|
|
145
|
+
const graph = await getGraph(assistant.graph_id, config);
|
|
146
|
+
const result = [];
|
|
147
|
+
const subgraphsGenerator = "getSubgraphsAsync" in graph
|
|
148
|
+
? graph.getSubgraphsAsync.bind(graph)
|
|
149
|
+
: // @ts-expect-error older versions of langgraph don't have getSubgraphsAsync
|
|
150
|
+
graph.getSubgraphs.bind(graph);
|
|
151
|
+
let graphSchemaPromise;
|
|
152
|
+
for await (const [ns, subgraph] of subgraphsGenerator(namespace, recurse)) {
|
|
153
|
+
const schema = await (async () => {
|
|
154
|
+
const runtimeSchema = await getRuntimeGraphSchema(subgraph);
|
|
155
|
+
if (runtimeSchema)
|
|
156
|
+
return runtimeSchema;
|
|
157
|
+
graphSchemaPromise ??= getCachedStaticGraphSchema(assistant.graph_id);
|
|
158
|
+
const graphSchema = await graphSchemaPromise;
|
|
159
|
+
const rootGraphId = Object.keys(graphSchema).find((i) => !i.includes("|"));
|
|
160
|
+
if (!rootGraphId) {
|
|
161
|
+
throw new HTTPException(404, {
|
|
162
|
+
message: "Failed to find root graph",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return graphSchema[`${rootGraphId}|${ns}`] || graphSchema[rootGraphId];
|
|
166
|
+
})();
|
|
167
|
+
result.push([ns, schema]);
|
|
168
|
+
}
|
|
169
|
+
return c.json(Object.fromEntries(result));
|
|
170
|
+
});
|
|
171
|
+
api.post("/assistants/:assistant_id/latest", zValidator("json", schemas.AssistantLatestVersion), async (c) => {
|
|
172
|
+
// Set Latest Assistant Version
|
|
173
|
+
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
174
|
+
const { version } = c.req.valid("json");
|
|
175
|
+
return c.json(await assistants().setLatest(assistantId, version, c.var.auth));
|
|
176
|
+
});
|
|
177
|
+
api.post("/assistants/:assistant_id/versions", zValidator("json", z.object({
|
|
178
|
+
limit: z.number().min(1).max(1000).optional().default(10),
|
|
179
|
+
offset: z.number().min(0).optional().default(0),
|
|
180
|
+
metadata: z.record(z.unknown()).optional(),
|
|
181
|
+
})), async (c) => {
|
|
182
|
+
// Get Assistant Versions
|
|
183
|
+
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
|
184
|
+
const { limit, offset, metadata } = c.req.valid("json");
|
|
185
|
+
const versions = await assistants().getVersions(assistantId, { limit, offset, metadata }, c.var.auth);
|
|
186
|
+
if (!versions?.length) {
|
|
187
|
+
throw new HTTPException(404, {
|
|
188
|
+
message: `Assistant "${assistantId}" not found.`,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return c.json(versions);
|
|
192
|
+
});
|
|
193
|
+
export default api;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import * as url from "node:url";
|
|
5
|
+
const api = new Hono();
|
|
6
|
+
// Get the version using the same pattern as semver/index.mts
|
|
7
|
+
const packageJsonPath = path.resolve(url.fileURLToPath(import.meta.url), "../../../package.json");
|
|
8
|
+
let version;
|
|
9
|
+
let langgraph_js_version;
|
|
10
|
+
let versionInfoLoaded = false;
|
|
11
|
+
const loadVersionInfo = async () => {
|
|
12
|
+
try {
|
|
13
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
|
|
14
|
+
version = packageJson.version;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
console.warn("Could not determine version of langgraph-api");
|
|
18
|
+
}
|
|
19
|
+
// Get the installed version of @langchain/langgraph
|
|
20
|
+
try {
|
|
21
|
+
const langgraphPkg = await import("@langchain/langgraph/package.json");
|
|
22
|
+
if (langgraphPkg?.default?.version) {
|
|
23
|
+
langgraph_js_version = langgraphPkg.default.version;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
console.warn("Could not determine version of @langchain/langgraph");
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
// read env variable
|
|
31
|
+
const env = process.env;
|
|
32
|
+
api.get("/info", async (c) => {
|
|
33
|
+
if (!versionInfoLoaded) {
|
|
34
|
+
await loadVersionInfo();
|
|
35
|
+
versionInfoLoaded = true;
|
|
36
|
+
}
|
|
37
|
+
const langsmithApiKey = env["LANGSMITH_API_KEY"] || env["LANGCHAIN_API_KEY"];
|
|
38
|
+
const langsmithTracing = (() => {
|
|
39
|
+
if (langsmithApiKey) {
|
|
40
|
+
// Check if any tracing variable is explicitly set to "false"
|
|
41
|
+
const tracingVars = [
|
|
42
|
+
env["LANGCHAIN_TRACING_V2"],
|
|
43
|
+
env["LANGCHAIN_TRACING"],
|
|
44
|
+
env["LANGSMITH_TRACING_V2"],
|
|
45
|
+
env["LANGSMITH_TRACING"],
|
|
46
|
+
];
|
|
47
|
+
// Return true unless explicitly disabled
|
|
48
|
+
return !tracingVars.some((val) => val === "false" || val === "False");
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
})();
|
|
52
|
+
return c.json({
|
|
53
|
+
version,
|
|
54
|
+
langgraph_js_version,
|
|
55
|
+
context: "js",
|
|
56
|
+
flags: {
|
|
57
|
+
assistants: true,
|
|
58
|
+
crons: false,
|
|
59
|
+
langsmith: !!langsmithTracing,
|
|
60
|
+
langsmith_tracing_replicas: true,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
api.get("/ok", (c) => c.json({ ok: true }));
|
|
65
|
+
export default api;
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { zValidator } from "@hono/zod-validator";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { HTTPException } from "hono/http-exception";
|
|
4
|
+
import { streamSSE } from "hono/streaming";
|
|
5
|
+
import { v4 as uuid4 } from "uuid";
|
|
6
|
+
import { z } from "zod/v3";
|
|
7
|
+
import { getAssistantId } from "../graph/load.mjs";
|
|
8
|
+
import { logError, logger } from "../logging.mjs";
|
|
9
|
+
import * as schemas from "../schemas.mjs";
|
|
10
|
+
import { runs, threads } from "../storage/context.mjs";
|
|
11
|
+
import { getDisconnectAbortSignal, jsonExtra, waitKeepAlive, } from "../utils/hono.mjs";
|
|
12
|
+
import { serialiseAsDict } from "../utils/serde.mjs";
|
|
13
|
+
const api = new Hono();
|
|
14
|
+
const createValidRun = async (threadId, payload, kwargs) => {
|
|
15
|
+
const { assistant_id: assistantId, ...run } = payload;
|
|
16
|
+
const { auth, headers } = kwargs ?? {};
|
|
17
|
+
const runId = uuid4();
|
|
18
|
+
const streamMode = Array.isArray(payload.stream_mode)
|
|
19
|
+
? payload.stream_mode
|
|
20
|
+
: payload.stream_mode != null
|
|
21
|
+
? [payload.stream_mode]
|
|
22
|
+
: [];
|
|
23
|
+
if (streamMode.length === 0)
|
|
24
|
+
streamMode.push("values");
|
|
25
|
+
const multitaskStrategy = payload.multitask_strategy ?? "reject";
|
|
26
|
+
const preventInsertInInflight = multitaskStrategy === "reject";
|
|
27
|
+
const config = { ...run.config };
|
|
28
|
+
if (run.checkpoint_id) {
|
|
29
|
+
config.configurable ??= {};
|
|
30
|
+
config.configurable.checkpoint_id = run.checkpoint_id;
|
|
31
|
+
}
|
|
32
|
+
if (run.checkpoint) {
|
|
33
|
+
config.configurable ??= {};
|
|
34
|
+
Object.assign(config.configurable, run.checkpoint);
|
|
35
|
+
}
|
|
36
|
+
if (run.langsmith_tracer) {
|
|
37
|
+
config.configurable ??= {};
|
|
38
|
+
Object.assign(config.configurable, {
|
|
39
|
+
langsmith_project: run.langsmith_tracer.project_name,
|
|
40
|
+
langsmith_example_id: run.langsmith_tracer.example_id,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (headers) {
|
|
44
|
+
for (const [rawKey, value] of headers.entries()) {
|
|
45
|
+
const key = rawKey.toLowerCase();
|
|
46
|
+
if (key.startsWith("x-")) {
|
|
47
|
+
if (["x-api-key", "x-tenant-id", "x-service-key"].includes(key)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
config.configurable ??= {};
|
|
51
|
+
config.configurable[key] = value;
|
|
52
|
+
}
|
|
53
|
+
else if (key === "user-agent") {
|
|
54
|
+
config.configurable ??= {};
|
|
55
|
+
config.configurable[key] = value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
let userId;
|
|
60
|
+
if (auth) {
|
|
61
|
+
userId = auth.user.identity ?? auth.user.id;
|
|
62
|
+
config.configurable ??= {};
|
|
63
|
+
config.configurable["langgraph_auth_user"] = auth.user;
|
|
64
|
+
config.configurable["langgraph_auth_user_id"] = userId;
|
|
65
|
+
config.configurable["langgraph_auth_permissions"] = auth.scopes;
|
|
66
|
+
}
|
|
67
|
+
let feedbackKeys = run.feedback_keys != null
|
|
68
|
+
? Array.isArray(run.feedback_keys)
|
|
69
|
+
? run.feedback_keys
|
|
70
|
+
: [run.feedback_keys]
|
|
71
|
+
: undefined;
|
|
72
|
+
if (!feedbackKeys?.length)
|
|
73
|
+
feedbackKeys = undefined;
|
|
74
|
+
const [first, ...inflight] = await runs().put(runId, getAssistantId(assistantId), {
|
|
75
|
+
input: run.input,
|
|
76
|
+
command: run.command,
|
|
77
|
+
config,
|
|
78
|
+
context: run.context,
|
|
79
|
+
stream_mode: streamMode,
|
|
80
|
+
interrupt_before: run.interrupt_before,
|
|
81
|
+
interrupt_after: run.interrupt_after,
|
|
82
|
+
webhook: run.webhook,
|
|
83
|
+
feedback_keys: feedbackKeys,
|
|
84
|
+
temporary: threadId == null && (run.on_completion ?? "delete") === "delete",
|
|
85
|
+
subgraphs: run.stream_subgraphs ?? false,
|
|
86
|
+
resumable: run.stream_resumable ?? false,
|
|
87
|
+
}, {
|
|
88
|
+
threadId,
|
|
89
|
+
userId,
|
|
90
|
+
metadata: run.metadata,
|
|
91
|
+
status: "pending",
|
|
92
|
+
multitaskStrategy,
|
|
93
|
+
preventInsertInInflight,
|
|
94
|
+
afterSeconds: payload.after_seconds,
|
|
95
|
+
ifNotExists: payload.if_not_exists,
|
|
96
|
+
}, auth);
|
|
97
|
+
if (first?.run_id === runId) {
|
|
98
|
+
logger.info("Created run", { run_id: runId, thread_id: threadId });
|
|
99
|
+
if ((multitaskStrategy === "interrupt" || multitaskStrategy === "rollback") &&
|
|
100
|
+
inflight.length > 0) {
|
|
101
|
+
try {
|
|
102
|
+
await runs().cancel(threadId, inflight.map((run) => run.run_id), { action: multitaskStrategy }, auth);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
logger.warn("Failed to cancel inflight runs, might be already cancelled", {
|
|
106
|
+
error,
|
|
107
|
+
run_ids: inflight.map((run) => run.run_id),
|
|
108
|
+
thread_id: threadId,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return first;
|
|
113
|
+
}
|
|
114
|
+
else if (multitaskStrategy === "reject") {
|
|
115
|
+
throw new HTTPException(422, {
|
|
116
|
+
message: "Thread is already running a task. Wait for it to finish or choose a different multitask strategy.",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
throw new HTTPException(500, {
|
|
120
|
+
message: "Unreachable state when creating run",
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
api.post("/runs/crons", zValidator("json", schemas.CronCreate), async () => {
|
|
124
|
+
// Create Thread Cron
|
|
125
|
+
throw new HTTPException(500, { message: "Not implemented" });
|
|
126
|
+
});
|
|
127
|
+
api.post("/runs/crons/search", zValidator("json", schemas.CronSearch), async () => {
|
|
128
|
+
// Search Crons
|
|
129
|
+
throw new HTTPException(500, { message: "Not implemented" });
|
|
130
|
+
});
|
|
131
|
+
api.delete("/runs/crons/:cron_id", zValidator("param", z.object({ cron_id: z.string().uuid() })), async () => {
|
|
132
|
+
// Delete Cron
|
|
133
|
+
throw new HTTPException(500, { message: "Not implemented" });
|
|
134
|
+
});
|
|
135
|
+
api.post("/threads/:thread_id/runs/crons", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.CronCreate), async () => {
|
|
136
|
+
// Create Thread Cron
|
|
137
|
+
throw new HTTPException(500, { message: "Not implemented" });
|
|
138
|
+
});
|
|
139
|
+
api.post("/runs/stream", zValidator("json", schemas.RunCreate), async (c) => {
|
|
140
|
+
// Stream Stateless Run
|
|
141
|
+
const payload = c.req.valid("json");
|
|
142
|
+
const run = await createValidRun(undefined, payload, {
|
|
143
|
+
auth: c.var.auth,
|
|
144
|
+
headers: c.req.raw.headers,
|
|
145
|
+
});
|
|
146
|
+
c.header("Content-Location", `/runs/${run.run_id}`);
|
|
147
|
+
return streamSSE(c, async (stream) => {
|
|
148
|
+
const cancelOnDisconnect = payload.on_disconnect === "cancel"
|
|
149
|
+
? getDisconnectAbortSignal(c, stream)
|
|
150
|
+
: undefined;
|
|
151
|
+
try {
|
|
152
|
+
for await (const { event, data } of runs().stream.join(run.run_id, undefined, {
|
|
153
|
+
cancelOnDisconnect,
|
|
154
|
+
lastEventId: run.kwargs.resumable ? "-1" : undefined,
|
|
155
|
+
ignore404: true,
|
|
156
|
+
}, c.var.auth)) {
|
|
157
|
+
await stream.writeSSE({ data: serialiseAsDict(data), event });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
logError(error, { prefix: "Error streaming run" });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
// TODO: port to Python API
|
|
166
|
+
api.get("/runs/:run_id/stream", zValidator("param", z.object({ run_id: z.string().uuid() })), zValidator("query", z.object({ cancel_on_disconnect: schemas.coercedBoolean.optional() })), async (c) => {
|
|
167
|
+
const { run_id } = c.req.valid("param");
|
|
168
|
+
const query = c.req.valid("query");
|
|
169
|
+
const lastEventId = c.req.header("Last-Event-ID") || undefined;
|
|
170
|
+
c.header("Content-Location", `/runs/${run_id}`);
|
|
171
|
+
return streamSSE(c, async (stream) => {
|
|
172
|
+
const cancelOnDisconnect = query.cancel_on_disconnect
|
|
173
|
+
? getDisconnectAbortSignal(c, stream)
|
|
174
|
+
: undefined;
|
|
175
|
+
try {
|
|
176
|
+
for await (const { id, event, data } of runs().stream.join(run_id, undefined, { cancelOnDisconnect, lastEventId, ignore404: true }, c.var.auth)) {
|
|
177
|
+
await stream.writeSSE({ id, data: serialiseAsDict(data), event });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
logError(error, { prefix: "Error streaming run" });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
api.post("/runs/wait", zValidator("json", schemas.RunCreate), async (c) => {
|
|
186
|
+
// Wait Stateless Run
|
|
187
|
+
const payload = c.req.valid("json");
|
|
188
|
+
const run = await createValidRun(undefined, payload, {
|
|
189
|
+
auth: c.var.auth,
|
|
190
|
+
headers: c.req.raw.headers,
|
|
191
|
+
});
|
|
192
|
+
c.header("Content-Location", `/runs/${run.run_id}`);
|
|
193
|
+
return waitKeepAlive(c, runs().wait(run.run_id, undefined, c.var.auth));
|
|
194
|
+
});
|
|
195
|
+
api.post("/runs", zValidator("json", schemas.RunCreate), async (c) => {
|
|
196
|
+
// Create Stateless Run
|
|
197
|
+
const payload = c.req.valid("json");
|
|
198
|
+
const run = await createValidRun(undefined, payload, {
|
|
199
|
+
auth: c.var.auth,
|
|
200
|
+
headers: c.req.raw.headers,
|
|
201
|
+
});
|
|
202
|
+
c.header("Content-Location", `/runs/${run.run_id}`);
|
|
203
|
+
return jsonExtra(c, run);
|
|
204
|
+
});
|
|
205
|
+
api.post("/runs/batch", zValidator("json", schemas.RunBatchCreate), async (c) => {
|
|
206
|
+
// Batch Runs
|
|
207
|
+
const payload = c.req.valid("json");
|
|
208
|
+
const runs = await Promise.all(payload.map((run) => createValidRun(undefined, run, {
|
|
209
|
+
auth: c.var.auth,
|
|
210
|
+
headers: c.req.raw.headers,
|
|
211
|
+
})));
|
|
212
|
+
return jsonExtra(c, runs);
|
|
213
|
+
});
|
|
214
|
+
api.get("/threads/:thread_id/runs", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("query", z.object({
|
|
215
|
+
limit: z.coerce.number().nullish(),
|
|
216
|
+
offset: z.coerce.number().nullish(),
|
|
217
|
+
status: z.string().nullish(),
|
|
218
|
+
metadata: z.record(z.string(), z.unknown()).nullish(),
|
|
219
|
+
})), async (c) => {
|
|
220
|
+
// List runs
|
|
221
|
+
const { thread_id } = c.req.valid("param");
|
|
222
|
+
const { limit, offset, status, metadata } = c.req.valid("query");
|
|
223
|
+
const [runsResponse] = await Promise.all([
|
|
224
|
+
runs().search(thread_id, { limit, offset, status, metadata }, c.var.auth),
|
|
225
|
+
threads().get(thread_id, c.var.auth),
|
|
226
|
+
]);
|
|
227
|
+
return jsonExtra(c, runsResponse);
|
|
228
|
+
});
|
|
229
|
+
api.post("/threads/:thread_id/runs", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.RunCreate), async (c) => {
|
|
230
|
+
// Create Run
|
|
231
|
+
const { thread_id } = c.req.valid("param");
|
|
232
|
+
const payload = c.req.valid("json");
|
|
233
|
+
const run = await createValidRun(thread_id, payload, {
|
|
234
|
+
auth: c.var.auth,
|
|
235
|
+
headers: c.req.raw.headers,
|
|
236
|
+
});
|
|
237
|
+
c.header("Content-Location", `/threads/${thread_id}/runs/${run.run_id}`);
|
|
238
|
+
return jsonExtra(c, run);
|
|
239
|
+
});
|
|
240
|
+
api.post("/threads/:thread_id/runs/stream", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.RunCreate), async (c) => {
|
|
241
|
+
// Stream Run
|
|
242
|
+
const { thread_id } = c.req.valid("param");
|
|
243
|
+
const payload = c.req.valid("json");
|
|
244
|
+
const run = await createValidRun(thread_id, payload, {
|
|
245
|
+
auth: c.var.auth,
|
|
246
|
+
headers: c.req.raw.headers,
|
|
247
|
+
});
|
|
248
|
+
c.header("Content-Location", `/threads/${thread_id}/runs/${run.run_id}`);
|
|
249
|
+
return streamSSE(c, async (stream) => {
|
|
250
|
+
const cancelOnDisconnect = payload.on_disconnect === "cancel"
|
|
251
|
+
? getDisconnectAbortSignal(c, stream)
|
|
252
|
+
: undefined;
|
|
253
|
+
try {
|
|
254
|
+
for await (const { id, event, data } of runs().stream.join(run.run_id, thread_id, {
|
|
255
|
+
cancelOnDisconnect,
|
|
256
|
+
lastEventId: run.kwargs.resumable ? "-1" : undefined,
|
|
257
|
+
}, c.var.auth)) {
|
|
258
|
+
await stream.writeSSE({ id, data: serialiseAsDict(data), event });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
logError(error, { prefix: "Error streaming run" });
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
api.post("/threads/:thread_id/runs/wait", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.RunCreate), async (c) => {
|
|
267
|
+
// Wait Run
|
|
268
|
+
const { thread_id } = c.req.valid("param");
|
|
269
|
+
const payload = c.req.valid("json");
|
|
270
|
+
const run = await createValidRun(thread_id, payload, {
|
|
271
|
+
auth: c.var.auth,
|
|
272
|
+
headers: c.req.raw.headers,
|
|
273
|
+
});
|
|
274
|
+
c.header("Content-Location", `/threads/${thread_id}/runs/${run.run_id}`);
|
|
275
|
+
return waitKeepAlive(c, runs().join(run.run_id, thread_id, c.var.auth));
|
|
276
|
+
});
|
|
277
|
+
api.get("/threads/:thread_id/runs/:run_id", zValidator("param", z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })), async (c) => {
|
|
278
|
+
const { thread_id, run_id } = c.req.valid("param");
|
|
279
|
+
const [run] = await Promise.all([
|
|
280
|
+
runs().get(run_id, thread_id, c.var.auth),
|
|
281
|
+
threads().get(thread_id, c.var.auth),
|
|
282
|
+
]);
|
|
283
|
+
if (run == null)
|
|
284
|
+
throw new HTTPException(404, { message: "Run not found" });
|
|
285
|
+
return jsonExtra(c, run);
|
|
286
|
+
});
|
|
287
|
+
api.delete("/threads/:thread_id/runs/:run_id", zValidator("param", z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })), async (c) => {
|
|
288
|
+
// Delete Run
|
|
289
|
+
const { thread_id, run_id } = c.req.valid("param");
|
|
290
|
+
await runs().delete(run_id, thread_id, c.var.auth);
|
|
291
|
+
return c.body(null, 204);
|
|
292
|
+
});
|
|
293
|
+
api.get("/threads/:thread_id/runs/:run_id/join", zValidator("param", z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })), async (c) => {
|
|
294
|
+
// Join Run Http
|
|
295
|
+
const { thread_id, run_id } = c.req.valid("param");
|
|
296
|
+
return jsonExtra(c, await runs().join(run_id, thread_id, c.var.auth));
|
|
297
|
+
});
|
|
298
|
+
api.get("/threads/:thread_id/runs/:run_id/stream", zValidator("param", z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })), zValidator("query", z.object({ cancel_on_disconnect: schemas.coercedBoolean.optional() })), async (c) => {
|
|
299
|
+
// Stream Run Http
|
|
300
|
+
const { thread_id, run_id } = c.req.valid("param");
|
|
301
|
+
const { cancel_on_disconnect } = c.req.valid("query");
|
|
302
|
+
const lastEventId = c.req.header("Last-Event-ID") || undefined;
|
|
303
|
+
return streamSSE(c, async (stream) => {
|
|
304
|
+
const signal = cancel_on_disconnect
|
|
305
|
+
? getDisconnectAbortSignal(c, stream)
|
|
306
|
+
: undefined;
|
|
307
|
+
for await (const { id, event, data } of runs().stream.join(run_id, thread_id, { cancelOnDisconnect: signal, lastEventId }, c.var.auth)) {
|
|
308
|
+
await stream.writeSSE({ id, data: serialiseAsDict(data), event });
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
api.post("/threads/:thread_id/runs/:run_id/cancel", zValidator("param", z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })), zValidator("query", z.object({
|
|
313
|
+
wait: z.coerce.boolean().optional().default(false),
|
|
314
|
+
action: z.enum(["interrupt", "rollback"]).optional().default("interrupt"),
|
|
315
|
+
})), async (c) => {
|
|
316
|
+
// Cancel Run Http
|
|
317
|
+
const { thread_id, run_id } = c.req.valid("param");
|
|
318
|
+
const { wait, action } = c.req.valid("query");
|
|
319
|
+
await runs().cancel(thread_id, [run_id], { action }, c.var.auth);
|
|
320
|
+
if (wait)
|
|
321
|
+
await runs().join(run_id, thread_id, c.var.auth);
|
|
322
|
+
return c.body(null, wait ? 204 : 202);
|
|
323
|
+
});
|
|
324
|
+
export default api;
|