create-langgraph-app 1.0.0 → 1.2.0
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/create.js +308 -183
- package/package.json +1 -1
package/dist/create.js
CHANGED
|
@@ -72,18 +72,14 @@ function generateEnv(config) {
|
|
|
72
72
|
}
|
|
73
73
|
function generatePackageJson(config) {
|
|
74
74
|
const deps = {
|
|
75
|
-
"@langchain/core": "^1.
|
|
76
|
-
"@langchain/langgraph": "^1.
|
|
77
|
-
"@langchain/langgraph-supervisor": "^1.0.1",
|
|
75
|
+
"@langchain/core": "^1.2.3",
|
|
76
|
+
"@langchain/langgraph": "^1.4.8",
|
|
78
77
|
"@langchain/mcp-adapters": "^1.1.3",
|
|
79
|
-
dotenv: "^17.4.
|
|
80
|
-
fastify: "^5.
|
|
81
|
-
langchain: "^1.3
|
|
82
|
-
zod: "^4.3
|
|
78
|
+
dotenv: "^17.4.2",
|
|
79
|
+
fastify: "^5.10.0",
|
|
80
|
+
langchain: "^1.5.3",
|
|
81
|
+
zod: "^4.4.3",
|
|
83
82
|
};
|
|
84
|
-
if (config.patterns.includes("swarm")) {
|
|
85
|
-
deps["@langchain/langgraph-swarm"] = "^1.0.1";
|
|
86
|
-
}
|
|
87
83
|
if (config.patterns.includes("rag")) {
|
|
88
84
|
deps["@langchain/textsplitters"] = "^1.0.1";
|
|
89
85
|
}
|
|
@@ -109,10 +105,10 @@ function generatePackageJson(config) {
|
|
|
109
105
|
},
|
|
110
106
|
dependencies: Object.fromEntries(Object.entries(deps).sort(([a], [b]) => a.localeCompare(b))),
|
|
111
107
|
devDependencies: {
|
|
112
|
-
"@types/node": "^25.
|
|
113
|
-
tsx: "^4.
|
|
114
|
-
typescript: "^6.0.
|
|
115
|
-
vitest: "^4.1.
|
|
108
|
+
"@types/node": "^25.9.1",
|
|
109
|
+
tsx: "^4.22.4",
|
|
110
|
+
typescript: "^6.0.3",
|
|
111
|
+
vitest: "^4.1.8",
|
|
116
112
|
},
|
|
117
113
|
};
|
|
118
114
|
return JSON.stringify(pkg, null, 2) + "\n";
|
|
@@ -157,6 +153,7 @@ const API_KEY_MAP: Record<LlmProvider, string> = {
|
|
|
157
153
|
|
|
158
154
|
export const LLM_PROVIDER = resolveProvider();
|
|
159
155
|
|
|
156
|
+
// Fail fast: validate the selected provider's key at startup.
|
|
160
157
|
const requiredKey = API_KEY_MAP[LLM_PROVIDER];
|
|
161
158
|
if (requiredKey && !process.env[requiredKey]) {
|
|
162
159
|
throw new Error(\`\${requiredKey} is required for provider "\${LLM_PROVIDER}" but not set in .env\`);
|
|
@@ -167,127 +164,173 @@ export const LLM_TEMPERATURE = Number(process.env.LLM_TEMPERATURE ?? 0);
|
|
|
167
164
|
export const PORT = Number(process.env.PORT ?? 3000);
|
|
168
165
|
`;
|
|
169
166
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
groq: "llama-3.3-70b-versatile",
|
|
179
|
-
ollama: "llama3.2",
|
|
167
|
+
// Per-provider class + constructor shape. The generated llm.ts only imports the
|
|
168
|
+
// provider that was selected, so the scaffold depends on a single LLM SDK.
|
|
169
|
+
const PROVIDER_LLM = {
|
|
170
|
+
openai: { pkg: "@langchain/openai", className: "ChatOpenAI", modelArg: "modelName", defaultModel: "gpt-4o-mini" },
|
|
171
|
+
anthropic: { pkg: "@langchain/anthropic", className: "ChatAnthropic", modelArg: "modelName", defaultModel: "claude-sonnet-4-20250514" },
|
|
172
|
+
google: { pkg: "@langchain/google-genai", className: "ChatGoogleGenerativeAI", modelArg: "model", defaultModel: "gemini-2.0-flash" },
|
|
173
|
+
groq: { pkg: "@langchain/groq", className: "ChatGroq", modelArg: "model", defaultModel: "llama-3.3-70b-versatile" },
|
|
174
|
+
ollama: { pkg: "@langchain/ollama", className: "ChatOllama", modelArg: "model", defaultModel: "llama3.2" },
|
|
180
175
|
};
|
|
176
|
+
function generateLlmConfig(config) {
|
|
177
|
+
const p = PROVIDER_LLM[config.provider] ?? PROVIDER_LLM.openai;
|
|
178
|
+
return `import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
179
|
+
import { LLM_MODEL, LLM_TEMPERATURE } from "./env";
|
|
181
180
|
|
|
182
|
-
|
|
183
|
-
const model = LLM_MODEL ?? DEFAULTS[LLM_PROVIDER] ?? DEFAULTS.openai;
|
|
184
|
-
const temperature = LLM_TEMPERATURE;
|
|
181
|
+
const DEFAULT_MODEL = "${p.defaultModel}";
|
|
185
182
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return new ChatGroq({ modelName: model, temperature });
|
|
198
|
-
}
|
|
199
|
-
case "ollama": {
|
|
200
|
-
const { ChatOllama } = require("@langchain/ollama");
|
|
201
|
-
return new ChatOllama({ model, temperature });
|
|
202
|
-
}
|
|
203
|
-
default: {
|
|
204
|
-
const { ChatOpenAI } = require("@langchain/openai");
|
|
205
|
-
return new ChatOpenAI({ modelName: model, temperature });
|
|
206
|
-
}
|
|
207
|
-
}
|
|
183
|
+
export interface LlmOptions {
|
|
184
|
+
model?: string;
|
|
185
|
+
temperature?: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function createLlm(opts: LlmOptions = {}): Promise<BaseChatModel> {
|
|
189
|
+
const model = opts.model ?? LLM_MODEL ?? DEFAULT_MODEL;
|
|
190
|
+
const temperature = opts.temperature ?? LLM_TEMPERATURE;
|
|
191
|
+
|
|
192
|
+
const { ${p.className} } = await import("${p.pkg}");
|
|
193
|
+
return new ${p.className}({ ${p.modelArg}: model, temperature });
|
|
208
194
|
}
|
|
209
195
|
|
|
210
|
-
|
|
196
|
+
let _default: Promise<BaseChatModel> | undefined;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Returns a chat model.
|
|
200
|
+
*
|
|
201
|
+
* Called with no options it returns a memoized, shared instance (nothing is
|
|
202
|
+
* constructed until first use). Pass options to build a distinct model, e.g. a
|
|
203
|
+
* cheap router and a strong worker:
|
|
204
|
+
*
|
|
205
|
+
* const router = await getLlm({ model: "${p.defaultModel}" });
|
|
206
|
+
* const worker = await getLlm({ model: "<a-stronger-model>" });
|
|
207
|
+
*/
|
|
208
|
+
export function getLlm(opts?: LlmOptions): Promise<BaseChatModel> {
|
|
209
|
+
if (!opts || Object.keys(opts).length === 0) {
|
|
210
|
+
_default ??= createLlm();
|
|
211
|
+
return _default;
|
|
212
|
+
}
|
|
213
|
+
return createLlm(opts);
|
|
214
|
+
}
|
|
211
215
|
`;
|
|
212
216
|
}
|
|
213
217
|
function generateAgentFactory() {
|
|
214
|
-
return `import type
|
|
215
|
-
import type {
|
|
216
|
-
import {
|
|
218
|
+
return `import { createAgent, type ResponseFormat, type TypedToolStrategy } from "langchain";
|
|
219
|
+
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
220
|
+
import type { BaseCheckpointSaver } from "@langchain/langgraph-checkpoint";
|
|
221
|
+
|
|
222
|
+
type CreateAgentParams = Parameters<typeof createAgent>[0];
|
|
217
223
|
|
|
218
224
|
export interface MakeAgentParams {
|
|
219
225
|
name: string;
|
|
220
226
|
llm: BaseChatModel;
|
|
221
|
-
tools?:
|
|
227
|
+
tools?: CreateAgentParams["tools"];
|
|
222
228
|
system?: string;
|
|
223
|
-
responseFormat?:
|
|
229
|
+
responseFormat?: ResponseFormat | TypedToolStrategy<Record<string, unknown>>;
|
|
230
|
+
/**
|
|
231
|
+
* Only the outermost agent of a multi-agent setup should get a
|
|
232
|
+
* checkpointer — subagents inherit it at runtime, which is what lets
|
|
233
|
+
* interrupt() calls bubble up to the top-level graph.
|
|
234
|
+
*/
|
|
235
|
+
checkpointer?: BaseCheckpointSaver;
|
|
224
236
|
}
|
|
225
237
|
|
|
226
|
-
|
|
227
|
-
|
|
238
|
+
/** Wraps LangChain's createAgent with a simpler interface. */
|
|
239
|
+
export function makeAgent({
|
|
240
|
+
name,
|
|
241
|
+
llm,
|
|
242
|
+
tools = [],
|
|
243
|
+
system,
|
|
244
|
+
responseFormat,
|
|
245
|
+
checkpointer,
|
|
246
|
+
}: MakeAgentParams) {
|
|
247
|
+
return createAgent({
|
|
228
248
|
name,
|
|
229
|
-
llm,
|
|
249
|
+
model: llm,
|
|
230
250
|
tools,
|
|
231
|
-
...(system ? {
|
|
251
|
+
...(system ? { systemPrompt: system } : {}),
|
|
232
252
|
...(responseFormat ? { responseFormat } : {}),
|
|
253
|
+
...(checkpointer ? { checkpointer } : {}),
|
|
233
254
|
});
|
|
234
255
|
}
|
|
256
|
+
|
|
257
|
+
export type AgentGraph = ReturnType<typeof makeAgent>;
|
|
235
258
|
`;
|
|
236
259
|
}
|
|
237
260
|
function generateSupervisorHelper() {
|
|
238
|
-
return `import
|
|
239
|
-
import
|
|
240
|
-
import {
|
|
241
|
-
import {
|
|
261
|
+
return `import { z } from "zod";
|
|
262
|
+
import { tool } from "@langchain/core/tools";
|
|
263
|
+
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
264
|
+
import type { BaseCheckpointSaver } from "@langchain/langgraph-checkpoint";
|
|
265
|
+
import { MemorySaver } from "@langchain/langgraph";
|
|
266
|
+
import { makeAgent, type AgentGraph } from "./factory";
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Supervisor via the "subagents" pattern: a main agent coordinates workers
|
|
270
|
+
* by calling them as tools. Subagents are stateless and run in isolated
|
|
271
|
+
* context windows; only the supervisor gets a checkpointer, so interrupt()
|
|
272
|
+
* inside a subagent tool pauses the whole graph.
|
|
273
|
+
*/
|
|
274
|
+
|
|
275
|
+
export interface SubagentSpec {
|
|
276
|
+
/** Tool name the supervisor calls, e.g. "math_expert". */
|
|
277
|
+
name: string;
|
|
278
|
+
/** Tells the supervisor's model when to delegate to this agent. */
|
|
279
|
+
description: string;
|
|
280
|
+
agent: AgentGraph;
|
|
281
|
+
}
|
|
242
282
|
|
|
243
|
-
|
|
283
|
+
/** Wraps a compiled agent as a tool the supervisor can call. */
|
|
284
|
+
export function subagentTool({ name, description, agent }: SubagentSpec) {
|
|
285
|
+
return tool(
|
|
286
|
+
async ({ task }) => {
|
|
287
|
+
const result = await agent.invoke({
|
|
288
|
+
messages: [{ role: "user", content: task }],
|
|
289
|
+
});
|
|
290
|
+
const last = result.messages.at(-1);
|
|
291
|
+
if (!last) return "(no response)";
|
|
292
|
+
return typeof last.content === "string"
|
|
293
|
+
? last.content
|
|
294
|
+
: JSON.stringify(last.content);
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name,
|
|
298
|
+
description,
|
|
299
|
+
schema: z.object({
|
|
300
|
+
task: z
|
|
301
|
+
.string()
|
|
302
|
+
.describe("A self-contained task with all context the agent needs"),
|
|
303
|
+
}),
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
}
|
|
244
307
|
|
|
245
|
-
export interface MakeSupervisorParams
|
|
308
|
+
export interface MakeSupervisorParams {
|
|
309
|
+
subagents: SubagentSpec[];
|
|
310
|
+
llm: BaseChatModel;
|
|
311
|
+
supervisorName?: string;
|
|
312
|
+
prompt?: string;
|
|
246
313
|
checkpointer?: BaseCheckpointSaver;
|
|
247
|
-
store?: BaseStore;
|
|
248
314
|
}
|
|
249
315
|
|
|
250
|
-
export function makeSupervisor({
|
|
251
|
-
|
|
316
|
+
export function makeSupervisor({
|
|
317
|
+
subagents,
|
|
318
|
+
llm,
|
|
319
|
+
supervisorName = "supervisor",
|
|
320
|
+
prompt,
|
|
321
|
+
checkpointer,
|
|
322
|
+
}: MakeSupervisorParams) {
|
|
323
|
+
const defaultPrompt =
|
|
324
|
+
"You coordinate a team of specialists. Delegate work to them via " +
|
|
325
|
+
\`your tools (\${subagents.map((s) => s.name).join(", ")}) and answer \` +
|
|
326
|
+
"the user only once the delegated work is done.";
|
|
327
|
+
|
|
328
|
+
return makeAgent({
|
|
329
|
+
name: supervisorName,
|
|
330
|
+
llm,
|
|
331
|
+
tools: subagents.map(subagentTool),
|
|
332
|
+
system: prompt ?? defaultPrompt,
|
|
252
333
|
checkpointer: checkpointer ?? new MemorySaver(),
|
|
253
|
-
store: store ?? new InMemoryStore(),
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
`;
|
|
257
|
-
}
|
|
258
|
-
function generateSupervisorApp() {
|
|
259
|
-
return `import { z } from "zod";
|
|
260
|
-
import { tool } from "@langchain/core/tools";
|
|
261
|
-
import { llm } from "./config/env";
|
|
262
|
-
import { makeAgent } from "./agents/factory";
|
|
263
|
-
import { makeSupervisor } from "./agents/supervisor";
|
|
264
|
-
|
|
265
|
-
const add = tool(async ({ a, b }) => String(a + b), {
|
|
266
|
-
name: "add", description: "Add two numbers",
|
|
267
|
-
schema: z.object({ a: z.number(), b: z.number() }),
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
const multiply = tool(async ({ a, b }) => String(a * b), {
|
|
271
|
-
name: "multiply", description: "Multiply two numbers",
|
|
272
|
-
schema: z.object({ a: z.number(), b: z.number() }),
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
export function createApp() {
|
|
276
|
-
const math = makeAgent({
|
|
277
|
-
name: "math_expert", llm,
|
|
278
|
-
tools: [add, multiply],
|
|
279
|
-
system: "You are a math expert. Use tools to compute answers.",
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
const writer = makeAgent({
|
|
283
|
-
name: "writer", llm, tools: [],
|
|
284
|
-
system: "You write crisp, structured answers.",
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
return makeSupervisor({
|
|
288
|
-
agents: [math, writer], llm,
|
|
289
|
-
outputMode: "last_message",
|
|
290
|
-
supervisorName: "supervisor",
|
|
291
334
|
});
|
|
292
335
|
}
|
|
293
336
|
`;
|
|
@@ -297,7 +340,7 @@ function getPatternFiles(config) {
|
|
|
297
340
|
const files = [];
|
|
298
341
|
// Always include core files
|
|
299
342
|
files.push({ path: "src/config/env.ts", content: generateEnvConfig(config) });
|
|
300
|
-
files.push({ path: "src/config/llm.ts", content: generateLlmConfig() });
|
|
343
|
+
files.push({ path: "src/config/llm.ts", content: generateLlmConfig(config) });
|
|
301
344
|
files.push({ path: "src/agents/factory.ts", content: generateAgentFactory() });
|
|
302
345
|
files.push({ path: "src/agents/supervisor.ts", content: generateSupervisorHelper() });
|
|
303
346
|
// Index file — imports vary by selected patterns
|
|
@@ -306,7 +349,7 @@ function getPatternFiles(config) {
|
|
|
306
349
|
if (config.patterns.includes("supervisor")) {
|
|
307
350
|
files.push({
|
|
308
351
|
path: "src/apps/supervisor.ts",
|
|
309
|
-
content: `import {
|
|
352
|
+
content: `import { getLlm } from "../config/llm";
|
|
310
353
|
import { z } from "zod";
|
|
311
354
|
import { tool } from "@langchain/core/tools";
|
|
312
355
|
import { makeAgent } from "../agents/factory";
|
|
@@ -317,7 +360,9 @@ const add = tool(async ({ a, b }) => String(a + b), {
|
|
|
317
360
|
schema: z.object({ a: z.number(), b: z.number() }),
|
|
318
361
|
});
|
|
319
362
|
|
|
320
|
-
export function createSupervisorApp() {
|
|
363
|
+
export async function createSupervisorApp() {
|
|
364
|
+
const llm = await getLlm();
|
|
365
|
+
|
|
321
366
|
const math = makeAgent({
|
|
322
367
|
name: "math_expert", llm,
|
|
323
368
|
tools: [add],
|
|
@@ -330,8 +375,11 @@ export function createSupervisorApp() {
|
|
|
330
375
|
});
|
|
331
376
|
|
|
332
377
|
return makeSupervisor({
|
|
333
|
-
|
|
334
|
-
|
|
378
|
+
subagents: [
|
|
379
|
+
{ name: "math_expert", description: "Delegate calculations to the math expert.", agent: math },
|
|
380
|
+
{ name: "writer", description: "Delegate writing. Include all facts the text should contain.", agent: writer },
|
|
381
|
+
],
|
|
382
|
+
llm,
|
|
335
383
|
supervisorName: "supervisor",
|
|
336
384
|
});
|
|
337
385
|
}
|
|
@@ -339,7 +387,7 @@ export function createSupervisorApp() {
|
|
|
339
387
|
});
|
|
340
388
|
imports.push(`import { createSupervisorApp } from "./apps/supervisor";`);
|
|
341
389
|
demos.push(` console.log("=== Supervisor Demo ===");
|
|
342
|
-
const supervisorApp = createSupervisorApp();
|
|
390
|
+
const supervisorApp = await createSupervisorApp();
|
|
343
391
|
const sup = await supervisorApp.invoke(
|
|
344
392
|
{ messages: [{ role: "user", content: "What is 10 + 15?" }] },
|
|
345
393
|
{ configurable: { thread_id: "demo" } }
|
|
@@ -349,68 +397,143 @@ export function createSupervisorApp() {
|
|
|
349
397
|
if (config.patterns.includes("swarm")) {
|
|
350
398
|
files.push({
|
|
351
399
|
path: "src/agents/swarm.ts",
|
|
352
|
-
content: `import
|
|
353
|
-
import {
|
|
354
|
-
import {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
400
|
+
content: `import { z } from "zod";
|
|
401
|
+
import { AIMessage } from "@langchain/core/messages";
|
|
402
|
+
import {
|
|
403
|
+
StateGraph,
|
|
404
|
+
StateSchema,
|
|
405
|
+
MessagesValue,
|
|
406
|
+
MemorySaver,
|
|
407
|
+
START,
|
|
408
|
+
END,
|
|
409
|
+
} from "@langchain/langgraph";
|
|
410
|
+
import type { BaseCheckpointSaver } from "@langchain/langgraph-checkpoint";
|
|
411
|
+
import type { AgentGraph } from "./factory";
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Swarm via the "handoffs" pattern: each agent is a graph node, and handoff
|
|
415
|
+
* tools (see ./handoff.ts) jump between nodes while flipping activeAgent.
|
|
416
|
+
* activeAgent is checkpointed, so the conversation resumes with whichever
|
|
417
|
+
* agent last held it.
|
|
418
|
+
*/
|
|
419
|
+
|
|
420
|
+
export const SwarmState = new StateSchema({
|
|
421
|
+
messages: MessagesValue,
|
|
422
|
+
activeAgent: z.string().optional(),
|
|
359
423
|
});
|
|
360
424
|
|
|
361
|
-
type
|
|
425
|
+
export type SwarmStateType = typeof SwarmState.State;
|
|
362
426
|
|
|
363
|
-
export
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
427
|
+
export interface SwarmAgentSpec {
|
|
428
|
+
/** Node name; handoff tools reference it as transfer_to_<name>. */
|
|
429
|
+
name: string;
|
|
430
|
+
agent: AgentGraph;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export interface MakeSwarmParams {
|
|
434
|
+
agents: SwarmAgentSpec[];
|
|
367
435
|
defaultActiveAgent: string;
|
|
368
436
|
checkpointer?: BaseCheckpointSaver;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export function makeSwarm({
|
|
440
|
+
agents,
|
|
441
|
+
defaultActiveAgent,
|
|
442
|
+
checkpointer,
|
|
443
|
+
}: MakeSwarmParams) {
|
|
444
|
+
const names = agents.map((a) => a.name);
|
|
445
|
+
|
|
446
|
+
const routeToActive = (state: SwarmStateType) =>
|
|
447
|
+
state.activeAgent ?? defaultActiveAgent;
|
|
448
|
+
|
|
449
|
+
// After an agent runs: done if it answered (no pending tool calls);
|
|
450
|
+
// otherwise a handoff moved activeAgent, so continue there.
|
|
451
|
+
const routeAfterAgent = (state: SwarmStateType) => {
|
|
452
|
+
const last = state.messages.at(-1);
|
|
453
|
+
if (last && AIMessage.isInstance(last) && !last.tool_calls?.length) {
|
|
454
|
+
return END;
|
|
455
|
+
}
|
|
456
|
+
return state.activeAgent ?? defaultActiveAgent;
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
// Node names are only known at runtime, so the graph is built with
|
|
460
|
+
// string keys and the routers are cast to the node-name union.
|
|
461
|
+
let builder = new StateGraph(SwarmState) as StateGraph<
|
|
462
|
+
typeof SwarmState,
|
|
463
|
+
SwarmStateType
|
|
464
|
+
>;
|
|
465
|
+
for (const { name, agent } of agents) {
|
|
466
|
+
builder = builder.addNode(name, async (state: SwarmStateType) =>
|
|
467
|
+
agent.invoke(state)
|
|
468
|
+
) as typeof builder;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
builder.addConditionalEdges(START, routeToActive as never, names as never);
|
|
472
|
+
for (const name of names) {
|
|
473
|
+
builder.addConditionalEdges(
|
|
474
|
+
name as never,
|
|
475
|
+
routeAfterAgent as never,
|
|
476
|
+
[...names, END] as never
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return builder.compile({
|
|
481
|
+
checkpointer: checkpointer ?? new MemorySaver(),
|
|
482
|
+
});
|
|
373
483
|
}
|
|
374
484
|
`,
|
|
375
485
|
});
|
|
376
486
|
files.push({
|
|
377
487
|
path: "src/agents/handoff.ts",
|
|
378
488
|
content: `import { z } from "zod";
|
|
379
|
-
import {
|
|
380
|
-
import {
|
|
381
|
-
import { Command
|
|
382
|
-
|
|
489
|
+
import { tool, type ToolRuntime } from "@langchain/core/tools";
|
|
490
|
+
import { AIMessage, ToolMessage } from "@langchain/core/messages";
|
|
491
|
+
import { Command } from "@langchain/langgraph";
|
|
492
|
+
import type { SwarmStateType } from "./swarm";
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Handoff tool for the handoffs pattern. Returns a Command targeting the
|
|
496
|
+
* PARENT graph: it flips activeAgent and jumps to the target agent's node.
|
|
497
|
+
* The calling agent's last AI message (the one containing this tool call)
|
|
498
|
+
* plus a ToolMessage are copied into parent state so the conversation
|
|
499
|
+
* history stays well-formed.
|
|
500
|
+
*/
|
|
383
501
|
export function createHandoffTool({ agentName, description }: { agentName: string; description?: string }) {
|
|
384
|
-
const toolName = \`transfer_to_\${agentName.replace(/\\s+/g, "_").toLowerCase()}\`;
|
|
385
|
-
|
|
386
502
|
return tool(
|
|
387
|
-
async (
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
503
|
+
async (_, runtime: ToolRuntime<SwarmStateType>) => {
|
|
504
|
+
const lastAiMessage = [...(runtime.state.messages ?? [])]
|
|
505
|
+
.reverse()
|
|
506
|
+
.find(AIMessage.isInstance);
|
|
507
|
+
const transferMessage = new ToolMessage({
|
|
391
508
|
content: \`Transferred to \${agentName}\`,
|
|
392
|
-
|
|
393
|
-
tool_call_id: cfg.toolCall.id,
|
|
509
|
+
tool_call_id: runtime.toolCallId ?? "",
|
|
394
510
|
});
|
|
395
511
|
return new Command({
|
|
396
512
|
goto: agentName,
|
|
513
|
+
update: {
|
|
514
|
+
activeAgent: agentName,
|
|
515
|
+
messages: [lastAiMessage, transferMessage].filter(Boolean),
|
|
516
|
+
},
|
|
397
517
|
graph: Command.PARENT,
|
|
398
|
-
update: { messages: messages.concat(tm), activeAgent: agentName },
|
|
399
518
|
});
|
|
400
519
|
},
|
|
401
|
-
{
|
|
520
|
+
{
|
|
521
|
+
name: \`transfer_to_\${agentName}\`,
|
|
522
|
+
description: description ?? \`Transfer the conversation to \${agentName}.\`,
|
|
523
|
+
schema: z.object({}),
|
|
524
|
+
}
|
|
402
525
|
);
|
|
403
526
|
}
|
|
404
527
|
`,
|
|
405
528
|
});
|
|
406
529
|
files.push({
|
|
407
530
|
path: "src/apps/swarm.ts",
|
|
408
|
-
content: `import {
|
|
531
|
+
content: `import { getLlm } from "../config/llm";
|
|
409
532
|
import { z } from "zod";
|
|
410
533
|
import { tool } from "@langchain/core/tools";
|
|
411
534
|
import { makeAgent } from "../agents/factory";
|
|
412
535
|
import { createHandoffTool } from "../agents/handoff";
|
|
413
|
-
import { makeSwarm
|
|
536
|
+
import { makeSwarm } from "../agents/swarm";
|
|
414
537
|
|
|
415
538
|
const add = tool(async ({ a, b }) => String(a + b), {
|
|
416
539
|
name: "add", description: "Add two numbers",
|
|
@@ -422,7 +545,9 @@ const multiply = tool(async ({ a, b }) => String(a * b), {
|
|
|
422
545
|
schema: z.object({ a: z.number(), b: z.number() }),
|
|
423
546
|
});
|
|
424
547
|
|
|
425
|
-
export function createSwarmApp() {
|
|
548
|
+
export async function createSwarmApp() {
|
|
549
|
+
const llm = await getLlm();
|
|
550
|
+
|
|
426
551
|
const alice = makeAgent({
|
|
427
552
|
name: "alice", llm,
|
|
428
553
|
tools: [add, createHandoffTool({ agentName: "bob" })],
|
|
@@ -436,7 +561,10 @@ export function createSwarmApp() {
|
|
|
436
561
|
});
|
|
437
562
|
|
|
438
563
|
return makeSwarm({
|
|
439
|
-
agents: [
|
|
564
|
+
agents: [
|
|
565
|
+
{ name: "alice", agent: alice },
|
|
566
|
+
{ name: "bob", agent: bob },
|
|
567
|
+
],
|
|
440
568
|
defaultActiveAgent: "alice",
|
|
441
569
|
});
|
|
442
570
|
}
|
|
@@ -444,7 +572,7 @@ export function createSwarmApp() {
|
|
|
444
572
|
});
|
|
445
573
|
imports.push(`import { createSwarmApp } from "./apps/swarm";`);
|
|
446
574
|
demos.push(` console.log("\\n=== Swarm Demo ===");
|
|
447
|
-
const swarmApp = createSwarmApp();
|
|
575
|
+
const swarmApp = await createSwarmApp();
|
|
448
576
|
const swarm = await swarmApp.invoke(
|
|
449
577
|
{ messages: [{ role: "user", content: "add 5 and 7, then talk to bob and multiply by 3" }] },
|
|
450
578
|
{ configurable: { thread_id: "swarm-demo" } }
|
|
@@ -456,10 +584,9 @@ export function createSwarmApp() {
|
|
|
456
584
|
path: "src/apps/interrupt.ts",
|
|
457
585
|
content: `import { z } from "zod";
|
|
458
586
|
import { tool } from "@langchain/core/tools";
|
|
459
|
-
import { interrupt } from "@langchain/langgraph";
|
|
460
|
-
import {
|
|
587
|
+
import { interrupt, MemorySaver } from "@langchain/langgraph";
|
|
588
|
+
import { getLlm } from "../config/llm";
|
|
461
589
|
import { makeAgent } from "../agents/factory";
|
|
462
|
-
import { makeSupervisor } from "../agents/supervisor";
|
|
463
590
|
|
|
464
591
|
const deleteRecord = tool(
|
|
465
592
|
async (args) => {
|
|
@@ -479,17 +606,16 @@ const deleteRecord = tool(
|
|
|
479
606
|
}
|
|
480
607
|
);
|
|
481
608
|
|
|
482
|
-
export function createInterruptApp() {
|
|
483
|
-
const
|
|
609
|
+
export async function createInterruptApp() {
|
|
610
|
+
const llm = await getLlm();
|
|
611
|
+
|
|
612
|
+
// A single agent with a checkpointer — interrupt() inside delete_record
|
|
613
|
+
// pauses the graph; resume with Command({ resume: "yes" }) on the thread.
|
|
614
|
+
return makeAgent({
|
|
484
615
|
name: "db_admin", llm,
|
|
485
616
|
tools: [deleteRecord],
|
|
486
617
|
system: "You are a database administrator.",
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
return makeSupervisor({
|
|
490
|
-
agents: [dbAdmin], llm,
|
|
491
|
-
outputMode: "last_message",
|
|
492
|
-
supervisorName: "interrupt_supervisor",
|
|
618
|
+
checkpointer: new MemorySaver(),
|
|
493
619
|
});
|
|
494
620
|
}
|
|
495
621
|
`,
|
|
@@ -497,7 +623,7 @@ export function createInterruptApp() {
|
|
|
497
623
|
imports.push(`import { Command } from "@langchain/langgraph";`);
|
|
498
624
|
imports.push(`import { createInterruptApp } from "./apps/interrupt";`);
|
|
499
625
|
demos.push(` console.log("\\n=== Human-in-the-Loop Demo ===");
|
|
500
|
-
const interruptApp = createInterruptApp();
|
|
626
|
+
const interruptApp = await createInterruptApp();
|
|
501
627
|
const hitlCfg = { configurable: { thread_id: "hitl-demo" } };
|
|
502
628
|
await interruptApp.invoke(
|
|
503
629
|
{ messages: [{ role: "user", content: "delete record rec_2" }] },
|
|
@@ -514,9 +640,10 @@ export function createInterruptApp() {
|
|
|
514
640
|
files.push({
|
|
515
641
|
path: "src/apps/analyst.ts",
|
|
516
642
|
content: `import { z } from "zod";
|
|
517
|
-
import {
|
|
643
|
+
import { toolStrategy } from "langchain";
|
|
644
|
+
import { MemorySaver } from "@langchain/langgraph";
|
|
645
|
+
import { getLlm } from "../config/llm";
|
|
518
646
|
import { makeAgent } from "../agents/factory";
|
|
519
|
-
import { makeSupervisor } from "../agents/supervisor";
|
|
520
647
|
|
|
521
648
|
const SummarySchema = z.object({
|
|
522
649
|
title: z.string(),
|
|
@@ -524,57 +651,55 @@ const SummarySchema = z.object({
|
|
|
524
651
|
sentiment: z.enum(["positive", "negative", "neutral"]),
|
|
525
652
|
});
|
|
526
653
|
|
|
527
|
-
export function createAnalystApp() {
|
|
528
|
-
const
|
|
654
|
+
export async function createAnalystApp() {
|
|
655
|
+
const llm = await getLlm();
|
|
656
|
+
|
|
657
|
+
// The structured result is returned on the structuredResponse key of the
|
|
658
|
+
// final state. toolStrategy works with every provider; models with native
|
|
659
|
+
// structured output support could use providerStrategy instead.
|
|
660
|
+
return makeAgent({
|
|
529
661
|
name: "analyst", llm, tools: [],
|
|
530
662
|
system: "Analyze text and produce structured summaries.",
|
|
531
|
-
responseFormat: SummarySchema,
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return makeSupervisor({
|
|
535
|
-
agents: [analyst], llm,
|
|
536
|
-
outputMode: "last_message",
|
|
537
|
-
supervisorName: "analyst_supervisor",
|
|
663
|
+
responseFormat: toolStrategy(SummarySchema),
|
|
664
|
+
checkpointer: new MemorySaver(),
|
|
538
665
|
});
|
|
539
666
|
}
|
|
540
667
|
`,
|
|
541
668
|
});
|
|
542
669
|
imports.push(`import { createAnalystApp } from "./apps/analyst";`);
|
|
543
670
|
demos.push(` console.log("\\n=== Structured Output Demo ===");
|
|
544
|
-
const analystApp = createAnalystApp();
|
|
671
|
+
const analystApp = await createAnalystApp();
|
|
545
672
|
const analysis = await analystApp.invoke(
|
|
546
673
|
{ messages: [{ role: "user", content: "Analyze: Revenue grew 25% but churn increased 8%." }] },
|
|
547
674
|
{ configurable: { thread_id: "analyst-demo" } }
|
|
548
675
|
);
|
|
549
|
-
|
|
676
|
+
const structured = (analysis as Record<string, unknown>).structuredResponse;
|
|
677
|
+
console.log("Result:", JSON.stringify(structured ?? analysis.messages.at(-1)?.content));`);
|
|
550
678
|
}
|
|
551
679
|
if (config.patterns.includes("rag")) {
|
|
552
680
|
files.push({
|
|
553
681
|
path: "src/apps/rag.ts",
|
|
554
|
-
content: `import {
|
|
682
|
+
content: `import { MemorySaver } from "@langchain/langgraph";
|
|
683
|
+
import { getLlm } from "../config/llm";
|
|
555
684
|
import { makeAgent } from "../agents/factory";
|
|
556
|
-
import { makeSupervisor } from "../agents/supervisor";
|
|
557
685
|
// TODO: Add your vector store, embeddings, and retrieval tool here.
|
|
558
686
|
// See the full starter kit for a complete RAG implementation:
|
|
559
687
|
// https://github.com/ac12644/langgraph-starter-kit
|
|
560
688
|
|
|
561
|
-
export function createRagApp() {
|
|
562
|
-
const
|
|
689
|
+
export async function createRagApp() {
|
|
690
|
+
const llm = await getLlm();
|
|
691
|
+
|
|
692
|
+
return makeAgent({
|
|
563
693
|
name: "rag_agent", llm, tools: [],
|
|
564
694
|
system: "You are a knowledgeable assistant. Answer questions based on your knowledge.",
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
return makeSupervisor({
|
|
568
|
-
agents: [ragAgent], llm,
|
|
569
|
-
outputMode: "last_message",
|
|
570
|
-
supervisorName: "rag_supervisor",
|
|
695
|
+
checkpointer: new MemorySaver(),
|
|
571
696
|
});
|
|
572
697
|
}
|
|
573
698
|
`,
|
|
574
699
|
});
|
|
575
700
|
imports.push(`import { createRagApp } from "./apps/rag";`);
|
|
576
701
|
demos.push(` console.log("\\n=== RAG Demo ===");
|
|
577
|
-
const ragApp = createRagApp();
|
|
702
|
+
const ragApp = await createRagApp();
|
|
578
703
|
const rag = await ragApp.invoke(
|
|
579
704
|
{ messages: [{ role: "user", content: "What is RAG and how does it work?" }] },
|
|
580
705
|
{ configurable: { thread_id: "rag-demo" } }
|