create-langgraph-app 1.3.0 → 1.5.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/README.md +1 -1
- package/dist/create.d.ts +12 -1
- package/dist/create.js +248 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npx create-langgraph-app
|
|
|
11
11
|
## What it does
|
|
12
12
|
|
|
13
13
|
1. Asks for your project name
|
|
14
|
-
2. Lets you pick an LLM provider (OpenAI, Anthropic, Google, Groq, Ollama)
|
|
14
|
+
2. Lets you pick an LLM provider (OpenAI, Anthropic, Google, Groq, DeepSeek, Ollama)
|
|
15
15
|
3. Lets you select which agent patterns to include:
|
|
16
16
|
- **Supervisor** — central coordinator + worker agents
|
|
17
17
|
- **Swarm** — peer-to-peer agent handoffs
|
package/dist/create.d.ts
CHANGED
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export {
|
|
2
|
+
export interface Config {
|
|
3
|
+
name: string;
|
|
4
|
+
provider: string;
|
|
5
|
+
patterns: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function generateEnv(config: Config): string;
|
|
8
|
+
export declare function generatePackageJson(config: Config): string;
|
|
9
|
+
export declare function generateTsConfig(): string;
|
|
10
|
+
export declare function getPatternFiles(config: Config): {
|
|
11
|
+
path: string;
|
|
12
|
+
content: string;
|
|
13
|
+
}[];
|
package/dist/create.js
CHANGED
|
@@ -4,6 +4,7 @@ import { stdin, stdout } from "node:process";
|
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { execSync } from "node:child_process";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
7
8
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
8
9
|
const BOLD = "\x1b[1m";
|
|
9
10
|
const DIM = "\x1b[2m";
|
|
@@ -52,8 +53,27 @@ const PROVIDERS = {
|
|
|
52
53
|
google: { envKey: "GOOGLE_API_KEY", defaultModel: "gemini-2.0-flash" },
|
|
53
54
|
groq: { envKey: "GROQ_API_KEY", defaultModel: "llama-3.3-70b-versatile" },
|
|
54
55
|
ollama: { envKey: "", defaultModel: "llama3.2" },
|
|
56
|
+
deepseek: { envKey: "DEEPSEEK_API_KEY", defaultModel: "deepseek-v4-flash" },
|
|
55
57
|
};
|
|
56
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Extra .env lines when RAG borrows another provider's embeddings — that key is
|
|
60
|
+
* required too, which is not obvious from the chat provider alone.
|
|
61
|
+
*/
|
|
62
|
+
function embeddingsEnvLines(config) {
|
|
63
|
+
if (!config.patterns.includes("rag"))
|
|
64
|
+
return [];
|
|
65
|
+
const e = embeddingsFor(config.provider);
|
|
66
|
+
if (!e.borrowed)
|
|
67
|
+
return [];
|
|
68
|
+
return [
|
|
69
|
+
`# RAG embeddings: ${config.provider} has no embeddings API, so RAG uses ${e.id}`,
|
|
70
|
+
`# and needs this key in addition to your chat provider's.`,
|
|
71
|
+
`${e.envKey}=`,
|
|
72
|
+
`# EMBEDDINGS_MODEL=${e.defaultModel}`,
|
|
73
|
+
``,
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
export function generateEnv(config) {
|
|
57
77
|
const prov = PROVIDERS[config.provider];
|
|
58
78
|
const lines = [
|
|
59
79
|
`# LLM Provider`,
|
|
@@ -67,10 +87,12 @@ function generateEnv(config) {
|
|
|
67
87
|
else {
|
|
68
88
|
lines.push(`# No API key needed for ${config.provider}`);
|
|
69
89
|
}
|
|
70
|
-
lines.push(``, `# Model (optional — defaults to ${prov.defaultModel})`, `# LLM_MODEL=${prov.defaultModel}`, `LLM_TEMPERATURE=0`, ``,
|
|
90
|
+
lines.push(``, `# Model (optional — defaults to ${prov.defaultModel})`, `# LLM_MODEL=${prov.defaultModel}`, `LLM_TEMPERATURE=0`, ``, ...embeddingsEnvLines(config), ...(config.provider === "deepseek"
|
|
91
|
+
? [`# DeepSeek reasoning mode (optional — "disabled" | "enabled")`, `# DEEPSEEK_THINKING=disabled`, ``]
|
|
92
|
+
: []), `PORT=3000`, ``, `# LangSmith tracing (optional)`, `# LANGCHAIN_TRACING_V2=true`, `# LANGSMITH_API_KEY=`, `# LANGSMITH_PROJECT=${config.name}`);
|
|
71
93
|
return lines.join("\n") + "\n";
|
|
72
94
|
}
|
|
73
|
-
function generatePackageJson(config) {
|
|
95
|
+
export function generatePackageJson(config) {
|
|
74
96
|
const deps = {
|
|
75
97
|
"@langchain/core": "^1.2.3",
|
|
76
98
|
"@langchain/langgraph": "^1.4.8",
|
|
@@ -90,8 +112,14 @@ function generatePackageJson(config) {
|
|
|
90
112
|
google: "@langchain/google-genai",
|
|
91
113
|
groq: "@langchain/groq",
|
|
92
114
|
ollama: "@langchain/ollama",
|
|
115
|
+
deepseek: "@langchain/deepseek",
|
|
93
116
|
};
|
|
94
117
|
deps[provPkg[config.provider]] = "latest";
|
|
118
|
+
// RAG needs an embeddings SDK, which is a different package when the chat
|
|
119
|
+
// provider has no embeddings API of its own (anthropic, groq, deepseek).
|
|
120
|
+
if (config.patterns.includes("rag")) {
|
|
121
|
+
deps[embeddingsFor(config.provider).pkg] ??= "latest";
|
|
122
|
+
}
|
|
95
123
|
const pkg = {
|
|
96
124
|
name: config.name,
|
|
97
125
|
version: "0.1.0",
|
|
@@ -114,7 +142,7 @@ function generatePackageJson(config) {
|
|
|
114
142
|
};
|
|
115
143
|
return JSON.stringify(pkg, null, 2) + "\n";
|
|
116
144
|
}
|
|
117
|
-
function generateTsConfig() {
|
|
145
|
+
export function generateTsConfig() {
|
|
118
146
|
return JSON.stringify({
|
|
119
147
|
compilerOptions: {
|
|
120
148
|
target: "ES2022",
|
|
@@ -133,7 +161,7 @@ function generateTsConfig() {
|
|
|
133
161
|
function generateEnvConfig(config) {
|
|
134
162
|
return `import "dotenv/config";
|
|
135
163
|
|
|
136
|
-
const VALID_PROVIDERS = ["openai", "anthropic", "google", "groq", "ollama"] as const;
|
|
164
|
+
const VALID_PROVIDERS = ["openai", "anthropic", "google", "groq", "ollama", "deepseek"] as const;
|
|
137
165
|
export type LlmProvider = (typeof VALID_PROVIDERS)[number];
|
|
138
166
|
|
|
139
167
|
function resolveProvider(): LlmProvider {
|
|
@@ -150,6 +178,7 @@ const API_KEY_MAP: Record<LlmProvider, string> = {
|
|
|
150
178
|
google: "GOOGLE_API_KEY",
|
|
151
179
|
groq: "GROQ_API_KEY",
|
|
152
180
|
ollama: "",
|
|
181
|
+
deepseek: "DEEPSEEK_API_KEY",
|
|
153
182
|
};
|
|
154
183
|
|
|
155
184
|
export const LLM_PROVIDER = resolveProvider();
|
|
@@ -162,6 +191,9 @@ if (requiredKey && !process.env[requiredKey]) {
|
|
|
162
191
|
|
|
163
192
|
export const LLM_MODEL = process.env.LLM_MODEL || undefined;
|
|
164
193
|
export const LLM_TEMPERATURE = Number(process.env.LLM_TEMPERATURE ?? 0);
|
|
194
|
+
// Uses || rather than ?? so an empty DEEPSEEK_THINKING= in .env falls back
|
|
195
|
+
// to "disabled" instead of sending an invalid thinking type to the API.
|
|
196
|
+
export const DEEPSEEK_THINKING = process.env.DEEPSEEK_THINKING || "disabled";
|
|
165
197
|
export const PORT = Number(process.env.PORT ?? 3000);
|
|
166
198
|
`;
|
|
167
199
|
}
|
|
@@ -173,11 +205,72 @@ const PROVIDER_LLM = {
|
|
|
173
205
|
google: { pkg: "@langchain/google-genai", className: "ChatGoogleGenerativeAI", modelArg: "model", defaultModel: "gemini-2.0-flash" },
|
|
174
206
|
groq: { pkg: "@langchain/groq", className: "ChatGroq", modelArg: "model", defaultModel: "llama-3.3-70b-versatile" },
|
|
175
207
|
ollama: { pkg: "@langchain/ollama", className: "ChatOllama", modelArg: "model", defaultModel: "llama3.2" },
|
|
208
|
+
deepseek: { pkg: "@langchain/deepseek", className: "ChatDeepSeek", modelArg: "model", defaultModel: "deepseek-v4-flash", extraArgs: 'modelKwargs: { thinking: { type: DEEPSEEK_THINKING } }' },
|
|
209
|
+
};
|
|
210
|
+
// Only these three have a native embeddings API. Anthropic, Groq and DeepSeek
|
|
211
|
+
// have none, so RAG on those providers borrows OpenAI's.
|
|
212
|
+
const PROVIDER_EMBEDDINGS = {
|
|
213
|
+
openai: { pkg: "@langchain/openai", className: "OpenAIEmbeddings", defaultModel: "text-embedding-3-small", envKey: "OPENAI_API_KEY" },
|
|
214
|
+
google: { pkg: "@langchain/google-genai", className: "GoogleGenerativeAIEmbeddings", defaultModel: "text-embedding-004", envKey: "GOOGLE_API_KEY" },
|
|
215
|
+
ollama: { pkg: "@langchain/ollama", className: "OllamaEmbeddings", defaultModel: "nomic-embed-text", envKey: "" },
|
|
176
216
|
};
|
|
217
|
+
/** Resolves which provider supplies embeddings for a given chat provider. */
|
|
218
|
+
function embeddingsFor(chatProvider) {
|
|
219
|
+
const id = chatProvider in PROVIDER_EMBEDDINGS ? chatProvider : "openai";
|
|
220
|
+
return { id, borrowed: id !== chatProvider, ...PROVIDER_EMBEDDINGS[id] };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The scaffold installs one embeddings SDK — the one for the provider resolved
|
|
224
|
+
* at generation time — so the generated config imports only that SDK. Switching
|
|
225
|
+
* EMBEDDINGS_PROVIDER later means installing the matching package and adding a
|
|
226
|
+
* branch here.
|
|
227
|
+
*/
|
|
228
|
+
function generateEmbeddingsConfig(config) {
|
|
229
|
+
const e = embeddingsFor(config.provider);
|
|
230
|
+
const borrowedNote = e.borrowed
|
|
231
|
+
? `// ${config.provider} has no embeddings API, so RAG uses ${e.id} — it needs\n` +
|
|
232
|
+
`// ${e.envKey} in addition to your chat provider's key.\n`
|
|
233
|
+
: "";
|
|
234
|
+
return `import type { Embeddings } from "@langchain/core/embeddings";
|
|
235
|
+
import { ${e.className} } from "${e.pkg}";
|
|
236
|
+
|
|
237
|
+
${borrowedNote}// Set EMBEDDINGS_MODEL in .env to override the default model.
|
|
238
|
+
const PROVIDER = "${e.id}";
|
|
239
|
+
const DEFAULT_MODEL = "${e.defaultModel}";
|
|
240
|
+
const REQUIRED_KEY = "${e.envKey}";
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Builds the embeddings client for RAG.
|
|
244
|
+
*
|
|
245
|
+
* Validation happens here rather than at import time: a project that never uses
|
|
246
|
+
* RAG should not need an embeddings key, and an eager throw would take down
|
|
247
|
+
* every route rather than just the RAG one.
|
|
248
|
+
*/
|
|
249
|
+
export async function createEmbeddings(modelOverride?: string): Promise<Embeddings> {
|
|
250
|
+
const requested = process.env.EMBEDDINGS_PROVIDER?.trim().toLowerCase();
|
|
251
|
+
if (requested && requested !== PROVIDER) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
\`EMBEDDINGS_PROVIDER="\${requested}" but this project was scaffolded with "\${PROVIDER}" embeddings. \` +
|
|
254
|
+
\`Install the matching SDK and add a branch in src/config/embeddings.ts.\`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (REQUIRED_KEY && !process.env[REQUIRED_KEY]) {
|
|
259
|
+
throw new Error(\`\${REQUIRED_KEY} is required for "\${PROVIDER}" embeddings but is not set in .env\`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const model = modelOverride ?? process.env.EMBEDDINGS_MODEL?.trim() ?? DEFAULT_MODEL;
|
|
263
|
+
return new ${e.className}({ model });
|
|
264
|
+
}
|
|
265
|
+
`;
|
|
266
|
+
}
|
|
177
267
|
function generateLlmConfig(config) {
|
|
178
268
|
const p = PROVIDER_LLM[config.provider] ?? PROVIDER_LLM.openai;
|
|
269
|
+
const envImports = config.provider === "deepseek"
|
|
270
|
+
? "LLM_MODEL, LLM_TEMPERATURE, DEEPSEEK_THINKING"
|
|
271
|
+
: "LLM_MODEL, LLM_TEMPERATURE";
|
|
179
272
|
return `import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
180
|
-
import {
|
|
273
|
+
import { ${envImports} } from "./env";
|
|
181
274
|
|
|
182
275
|
const DEFAULT_MODEL = "${p.defaultModel}";
|
|
183
276
|
|
|
@@ -191,7 +284,7 @@ async function createLlm(opts: LlmOptions = {}): Promise<BaseChatModel> {
|
|
|
191
284
|
const temperature = opts.temperature ?? LLM_TEMPERATURE;
|
|
192
285
|
|
|
193
286
|
const { ${p.className} } = await import("${p.pkg}");
|
|
194
|
-
return new ${p.className}({ ${p.modelArg}: model, temperature });
|
|
287
|
+
return new ${p.className}({ ${p.modelArg}: model, temperature${p.extraArgs ? `, ${p.extraArgs}` : ""} });
|
|
195
288
|
}
|
|
196
289
|
|
|
197
290
|
let _default: Promise<BaseChatModel> | undefined;
|
|
@@ -351,7 +444,27 @@ function selectedApps(config) {
|
|
|
351
444
|
function generateServer(config) {
|
|
352
445
|
const apps = selectedApps(config);
|
|
353
446
|
const imports = apps.map((a) => `import { ${a.fn} } from "${a.path}";`).join("\n");
|
|
354
|
-
|
|
447
|
+
// RAG is the only app that can fail at startup — it needs an embeddings
|
|
448
|
+
// provider, and providers without one borrow another provider's key. Build it
|
|
449
|
+
// in a try/catch so a missing key costs /rag rather than every route.
|
|
450
|
+
const hasRag = apps.some((a) => a.route === "rag");
|
|
451
|
+
const entries = apps
|
|
452
|
+
.filter((a) => a.route !== "rag")
|
|
453
|
+
.map((a) => ` "${a.route}": asApp(await ${a.fn}()),`)
|
|
454
|
+
.join("\n");
|
|
455
|
+
const ragEntry = hasRag ? `\n ...(ragApp ? { rag: ragApp } : {}),` : "";
|
|
456
|
+
const ragInit = hasRag
|
|
457
|
+
? ` let ragApp: AppGraph | undefined;
|
|
458
|
+
try {
|
|
459
|
+
ragApp = asApp(await createRagApp());
|
|
460
|
+
} catch (err) {
|
|
461
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
462
|
+
console.warn(\`RAG app unavailable (embeddings failed): \${msg}\`);
|
|
463
|
+
console.warn(" /rag routes are disabled; every other app still works.");
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
`
|
|
467
|
+
: "";
|
|
355
468
|
return `import "./config/env";
|
|
356
469
|
import { fastify, type FastifyError, type FastifyReply, type FastifyRequest } from "fastify";
|
|
357
470
|
import { Command } from "@langchain/langgraph";
|
|
@@ -393,8 +506,8 @@ server.setErrorHandler(async (error: FastifyError, _req: FastifyRequest, reply:
|
|
|
393
506
|
});
|
|
394
507
|
|
|
395
508
|
async function start() {
|
|
396
|
-
const apps: Record<string, AppGraph> = {
|
|
397
|
-
${entries}
|
|
509
|
+
${ragInit} const apps: Record<string, AppGraph> = {
|
|
510
|
+
${entries}${ragEntry}
|
|
398
511
|
};
|
|
399
512
|
|
|
400
513
|
const getApp = (name: string) => {
|
|
@@ -583,7 +696,7 @@ ${supervisorTest}});
|
|
|
583
696
|
`;
|
|
584
697
|
}
|
|
585
698
|
// Pattern-specific: only files for selected patterns are generated
|
|
586
|
-
function getPatternFiles(config) {
|
|
699
|
+
export function getPatternFiles(config) {
|
|
587
700
|
const files = [];
|
|
588
701
|
// Always include core files
|
|
589
702
|
files.push({ path: "src/config/env.ts", content: generateEnvConfig(config) });
|
|
@@ -924,21 +1037,129 @@ export async function createAnalystApp() {
|
|
|
924
1037
|
console.log("Result:", JSON.stringify(structured ?? analysis.messages.at(-1)?.content));`);
|
|
925
1038
|
}
|
|
926
1039
|
if (config.patterns.includes("rag")) {
|
|
1040
|
+
files.push({ path: "src/config/embeddings.ts", content: generateEmbeddingsConfig(config) });
|
|
1041
|
+
files.push({
|
|
1042
|
+
path: "src/tools/rag.ts",
|
|
1043
|
+
content: `import { z } from "zod";
|
|
1044
|
+
import { tool } from "@langchain/core/tools";
|
|
1045
|
+
import type { Embeddings } from "@langchain/core/embeddings";
|
|
1046
|
+
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
|
|
1047
|
+
|
|
1048
|
+
/** In-memory vector store — no external database required. */
|
|
1049
|
+
export interface InMemoryVectorStore {
|
|
1050
|
+
size: number;
|
|
1051
|
+
search: (query: string, k?: number) => Promise<string[]>;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
interface Chunk {
|
|
1055
|
+
content: string;
|
|
1056
|
+
embedding: number[];
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function cosineSimilarity(a: number[], b: number[]): number {
|
|
1060
|
+
let dot = 0, normA = 0, normB = 0;
|
|
1061
|
+
for (let i = 0; i < a.length; i++) {
|
|
1062
|
+
dot += a[i] * b[i];
|
|
1063
|
+
normA += a[i] * a[i];
|
|
1064
|
+
normB += b[i] * b[i];
|
|
1065
|
+
}
|
|
1066
|
+
// Guard the zero vector: without this an empty embedding yields NaN and
|
|
1067
|
+
// silently corrupts the ranking.
|
|
1068
|
+
return normA === 0 || normB === 0 ? 0 : dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
export async function buildVectorStore(
|
|
1072
|
+
embeddings: Embeddings,
|
|
1073
|
+
documents: string[],
|
|
1074
|
+
chunkSize = 500,
|
|
1075
|
+
chunkOverlap = 100,
|
|
1076
|
+
): Promise<InMemoryVectorStore> {
|
|
1077
|
+
const splitter = new RecursiveCharacterTextSplitter({ chunkSize, chunkOverlap });
|
|
1078
|
+
const docs = await splitter.createDocuments(documents);
|
|
1079
|
+
const texts = docs.map((d) => d.pageContent);
|
|
1080
|
+
const vectors = await embeddings.embedDocuments(texts);
|
|
1081
|
+
const chunks: Chunk[] = texts.map((content, i) => ({ content, embedding: vectors[i] }));
|
|
1082
|
+
|
|
1083
|
+
return {
|
|
1084
|
+
size: chunks.length,
|
|
1085
|
+
search: async (query: string, k = 4) => {
|
|
1086
|
+
if (chunks.length === 0) return [];
|
|
1087
|
+
const queryVector = await embeddings.embedQuery(query);
|
|
1088
|
+
return chunks
|
|
1089
|
+
.map((c) => ({ content: c.content, score: cosineSimilarity(queryVector, c.embedding) }))
|
|
1090
|
+
.sort((a, b) => b.score - a.score)
|
|
1091
|
+
.slice(0, k)
|
|
1092
|
+
.map((c) => c.content);
|
|
1093
|
+
},
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
export function createRetrievalTool(vectorStore: InMemoryVectorStore) {
|
|
1098
|
+
return tool(
|
|
1099
|
+
async ({ query, k }) => {
|
|
1100
|
+
const results = await vectorStore.search(query, k);
|
|
1101
|
+
if (results.length === 0) return "No relevant documents found. Try rephrasing.";
|
|
1102
|
+
return results.map((r, i) => "[" + (i + 1) + "] " + r).join("\\n\\n");
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
name: "search_knowledge_base",
|
|
1106
|
+
description:
|
|
1107
|
+
"Search the knowledge base for relevant information. Use this to find context before answering.",
|
|
1108
|
+
schema: z.object({
|
|
1109
|
+
query: z.string().describe("The search query"),
|
|
1110
|
+
k: z.number().optional().default(4).describe("Number of results to return"),
|
|
1111
|
+
}),
|
|
1112
|
+
}
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/** Replace these with your own documents. */
|
|
1117
|
+
export const SAMPLE_DOCS = [
|
|
1118
|
+
"LangGraph is a framework for building stateful, multi-actor applications with LLMs. It uses a graph architecture where nodes are computational steps and edges define the flow between them. Key features include persistence via checkpointing, streaming, and human-in-the-loop workflows.",
|
|
1119
|
+
"The supervisor pattern uses a central coordinator that routes tasks to specialized worker agents and aggregates their results. Workers are stateless and run in isolated context windows, so the supervisor only sees each worker's final answer.",
|
|
1120
|
+
"The swarm pattern lets peer agents hand off control to each other with transfer tools. Each agent is a graph node, and the active agent persists across turns, so a conversation resumes with whichever agent last held it.",
|
|
1121
|
+
];
|
|
1122
|
+
`,
|
|
1123
|
+
});
|
|
927
1124
|
files.push({
|
|
928
1125
|
path: "src/apps/rag.ts",
|
|
929
1126
|
content: `import { MemorySaver } from "@langchain/langgraph";
|
|
930
1127
|
import { getLlm } from "../config/llm";
|
|
1128
|
+
import { createEmbeddings } from "../config/embeddings";
|
|
931
1129
|
import { makeAgent } from "../agents/factory";
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1130
|
+
import {
|
|
1131
|
+
buildVectorStore,
|
|
1132
|
+
createRetrievalTool,
|
|
1133
|
+
SAMPLE_DOCS,
|
|
1134
|
+
type InMemoryVectorStore,
|
|
1135
|
+
} from "../tools/rag";
|
|
1136
|
+
|
|
1137
|
+
let _vectorStore: InMemoryVectorStore | null = null;
|
|
1138
|
+
|
|
1139
|
+
/** Indexes the documents once; later calls reuse the same store. */
|
|
1140
|
+
export async function initRagStore(documents: string[] = SAMPLE_DOCS): Promise<InMemoryVectorStore> {
|
|
1141
|
+
if (!_vectorStore) {
|
|
1142
|
+
const embeddings = await createEmbeddings();
|
|
1143
|
+
_vectorStore = await buildVectorStore(embeddings, documents);
|
|
1144
|
+
console.log("RAG: indexed " + _vectorStore.size + " chunks");
|
|
1145
|
+
}
|
|
1146
|
+
return _vectorStore;
|
|
1147
|
+
}
|
|
935
1148
|
|
|
936
|
-
export async function createRagApp() {
|
|
1149
|
+
export async function createRagApp(vectorStore?: InMemoryVectorStore) {
|
|
937
1150
|
const llm = await getLlm();
|
|
1151
|
+
const retrievalTool = createRetrievalTool(vectorStore ?? (await initRagStore()));
|
|
938
1152
|
|
|
939
1153
|
return makeAgent({
|
|
940
|
-
name: "rag_agent",
|
|
941
|
-
|
|
1154
|
+
name: "rag_agent",
|
|
1155
|
+
llm,
|
|
1156
|
+
tools: [retrievalTool],
|
|
1157
|
+
system: [
|
|
1158
|
+
"You are a knowledgeable assistant with access to a knowledge base.",
|
|
1159
|
+
"ALWAYS search the knowledge base before answering questions.",
|
|
1160
|
+
"Base your answers on the retrieved documents. If they do not contain",
|
|
1161
|
+
"the answer, say so clearly rather than guessing.",
|
|
1162
|
+
].join("\\n"),
|
|
942
1163
|
checkpointer: new MemorySaver(),
|
|
943
1164
|
});
|
|
944
1165
|
}
|
|
@@ -948,7 +1169,7 @@ export async function createRagApp() {
|
|
|
948
1169
|
demos.push(` console.log("\\n=== RAG Demo ===");
|
|
949
1170
|
const ragApp = await createRagApp();
|
|
950
1171
|
const rag = await ragApp.invoke(
|
|
951
|
-
{ messages: [{ role: "user", content: "What is
|
|
1172
|
+
{ messages: [{ role: "user", content: "What is the supervisor pattern?" }] },
|
|
952
1173
|
{ configurable: { thread_id: "rag-demo" } }
|
|
953
1174
|
);
|
|
954
1175
|
console.log("Result:", rag.messages.at(-1)?.content);`);
|
|
@@ -984,6 +1205,7 @@ async function main() {
|
|
|
984
1205
|
{ value: "anthropic", label: "Anthropic (Claude Sonnet)" },
|
|
985
1206
|
{ value: "google", label: "Google (Gemini 2.0 Flash)" },
|
|
986
1207
|
{ value: "groq", label: "Groq (Llama 3.3 70B)" },
|
|
1208
|
+
{ value: "deepseek", label: "DeepSeek (deepseek-v4-flash)" },
|
|
987
1209
|
{ value: "ollama", label: "Ollama (local, no API key)" },
|
|
988
1210
|
]);
|
|
989
1211
|
const patterns = await choose(rl, "Which patterns? (select multiple)", [
|
|
@@ -1035,7 +1257,11 @@ Provider: ${provider}${RESET}
|
|
|
1035
1257
|
rl.close();
|
|
1036
1258
|
}
|
|
1037
1259
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1260
|
+
// Only run the interactive CLI when executed directly. Importing this module
|
|
1261
|
+
// (e.g. from the scaffolder tests) must not prompt.
|
|
1262
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
1263
|
+
main().catch((err) => {
|
|
1264
|
+
console.error("Error:", err);
|
|
1265
|
+
process.exit(1);
|
|
1266
|
+
});
|
|
1267
|
+
}
|