create-langgraph-app 1.5.0 → 1.5.1

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.
Files changed (3) hide show
  1. package/README.md +50 -12
  2. package/dist/create.js +24 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  Interactive CLI to scaffold a new LangGraph multi-agent project in seconds.
4
4
 
5
- ## Usage
6
-
7
5
  ```bash
8
6
  npx create-langgraph-app
9
7
  ```
@@ -11,28 +9,68 @@ npx create-langgraph-app
11
9
  ## What it does
12
10
 
13
11
  1. Asks for your project name
14
- 2. Lets you pick an LLM provider (OpenAI, Anthropic, Google, Groq, DeepSeek, Ollama)
12
+ 2. Lets you pick an LLM provider — OpenAI, Anthropic, Google, Groq, DeepSeek, or Ollama
15
13
  3. Lets you select which agent patterns to include:
16
- - **Supervisor** — central coordinator + worker agents
14
+ - **Supervisor** — central coordinator delegating to worker agents
17
15
  - **Swarm** — peer-to-peer agent handoffs
18
16
  - **Human-in-the-Loop** — approval before dangerous actions
19
- - **Structured Output** — typed JSON responses
20
- - **RAG** — retrieval-augmented generation
21
- 4. Generates a ready-to-run project with only the patterns you selected
17
+ - **Structured Output** — typed JSON responses validated by Zod
18
+ - **RAG** — retrieval-augmented generation over an in-memory vector store
19
+ 4. Generates a ready-to-run project containing only the patterns you picked
22
20
  5. Installs dependencies
23
21
 
22
+ ## What you get
23
+
24
+ ```
25
+ src/
26
+ ├── config/ env validation, LLM factory, embeddings (with RAG)
27
+ ├── agents/ makeAgent() and the helpers your patterns need
28
+ ├── apps/ one file per selected pattern
29
+ ├── tools/ retrieval tool + vector store (with RAG)
30
+ ├── server.ts Fastify server — invoke, stream, resume, threads
31
+ └── index.ts CLI demo running each pattern
32
+ tests/ offline tests that need no API key
33
+ ```
34
+
35
+ Everything is built on LangChain's `createAgent` — the **subagents** pattern for
36
+ supervisors, **handoffs** for swarms. No deprecated packages.
37
+
38
+ ## RAG and embeddings
39
+
40
+ Selecting RAG generates a working implementation: chunking, embedding,
41
+ cosine-similarity search, and a retrieval tool the agent calls before answering.
42
+ Swap `SAMPLE_DOCS` in `src/tools/rag.ts` for your own documents.
43
+
44
+ Embeddings are a separate capability from chat. OpenAI, Google and Ollama have
45
+ their own; **Anthropic, Groq and DeepSeek do not**, so those projects use OpenAI
46
+ embeddings and need `OPENAI_API_KEY` in addition to the chat provider's key. The
47
+ generated `.env` says so, and `EMBEDDINGS_MODEL` overrides the model.
48
+
49
+ To run RAG with no API key at all, scaffold with Ollama.
50
+
51
+ If embeddings are unavailable, only RAG is affected — the other patterns and
52
+ routes keep working, and the server logs why `/rag` is disabled.
53
+
24
54
  ## Development
25
55
 
26
56
  ```bash
27
57
  cd create-langgraph-app
28
58
  npm install
29
- npm run dev # Run locally
59
+ npm run dev # run the CLI locally
60
+ npm run build # typecheck and emit dist/
30
61
  ```
31
62
 
32
- ## Publishing
63
+ Generated projects are compiled in CI, so a change that breaks the output fails
64
+ the build. See `tests/scaffolder/` in the [starter kit](https://github.com/ac12644/langgraph-starter-kit).
65
+
66
+ ## Releasing
67
+
68
+ Publishing is automated. Bump the version in `package.json`, then push a
69
+ matching `cli-v*` tag:
33
70
 
34
71
  ```bash
35
- cd create-langgraph-app
36
- npm run build
37
- npm publish
72
+ git tag -a cli-v1.5.0 -m "create-langgraph-app v1.5.0"
73
+ git push origin cli-v1.5.0
38
74
  ```
75
+
76
+ The `Publish CLI to npm` workflow builds and publishes with provenance.
package/dist/create.js CHANGED
@@ -485,19 +485,35 @@ function httpError(status: number, message: string) {
485
485
  return Object.assign(new Error(message), { statusCode: status });
486
486
  }
487
487
 
488
+ // \`system\` is excluded: the app owns its system prompt, and accepting one
489
+ // from the request lets any caller override it.
490
+ const CLIENT_ROLES = ["user", "assistant"];
491
+
488
492
  /** Reject malformed bodies before they reach the agent. */
489
493
  function validateMessages(raw: unknown): { role: string; content: string }[] {
490
- if (raw === undefined) return [];
491
494
  if (!Array.isArray(raw)) throw httpError(400, '"messages" must be an array');
495
+ if (raw.length === 0) throw httpError(400, '"messages" must not be empty');
492
496
  return raw.map((m, i) => {
493
497
  const { role, content } = (m ?? {}) as Record<string, unknown>;
494
498
  if (typeof role !== "string" || typeof content !== "string") {
495
499
  throw httpError(400, \`messages[\${i}] needs string "role" and "content"\`);
496
500
  }
501
+ if (!CLIENT_ROLES.includes(role)) {
502
+ throw httpError(400, \`messages[\${i}].role must be one of: \${CLIENT_ROLES.join(", ")}\`);
503
+ }
497
504
  return { role, content };
498
505
  });
499
506
  }
500
507
 
508
+ /** Defaults to "default"; anything but a non-empty string is a 400, not a 500. */
509
+ function validateThreadId(raw: unknown): string {
510
+ if (raw === undefined) return "default";
511
+ if (typeof raw !== "string" || raw.trim() === "") {
512
+ throw httpError(400, '"thread_id" must be a non-empty string');
513
+ }
514
+ return raw;
515
+ }
516
+
501
517
  const server = fastify({ logger: false });
502
518
 
503
519
  server.setErrorHandler(async (error: FastifyError, _req: FastifyRequest, reply: FastifyReply) => {
@@ -518,9 +534,9 @@ ${entries}${ragEntry}
518
534
 
519
535
  server.post<{ Params: { app: string } }>("/:app/invoke", async (req, reply) => {
520
536
  const app = getApp(req.params.app);
521
- const body = (req.body ?? {}) as { messages?: unknown; thread_id?: string };
537
+ const body = (req.body ?? {}) as { messages?: unknown; thread_id?: unknown };
522
538
  const messages = validateMessages(body.messages);
523
- const thread_id = body.thread_id ?? "default";
539
+ const thread_id = validateThreadId(body.thread_id);
524
540
 
525
541
  const result = await app.invoke({ messages }, { configurable: { thread_id } });
526
542
  const last = result.messages.at(-1);
@@ -533,9 +549,9 @@ ${entries}${ragEntry}
533
549
 
534
550
  server.post<{ Params: { app: string } }>("/:app/stream", async (req, reply) => {
535
551
  const app = getApp(req.params.app);
536
- const body = (req.body ?? {}) as { messages?: unknown; thread_id?: string };
552
+ const body = (req.body ?? {}) as { messages?: unknown; thread_id?: unknown };
537
553
  const messages = validateMessages(body.messages);
538
- const thread_id = body.thread_id ?? "default";
554
+ const thread_id = validateThreadId(body.thread_id);
539
555
 
540
556
  reply.raw.writeHead(200, {
541
557
  "Content-Type": "text/event-stream",
@@ -561,12 +577,13 @@ ${entries}${ragEntry}
561
577
 
562
578
  server.post<{ Params: { app: string } }>("/:app/resume", async (req, reply) => {
563
579
  const app = getApp(req.params.app);
564
- const body = (req.body ?? {}) as { thread_id?: string; decision?: string };
580
+ const body = (req.body ?? {}) as { thread_id?: unknown; decision?: string };
581
+ const thread_id = validateThreadId(body.thread_id);
565
582
  if (body.decision === undefined) {
566
583
  return reply.status(400).send({ error: '"decision" field is required' });
567
584
  }
568
585
  const result = await app.invoke(new Command({ resume: body.decision }), {
569
- configurable: { thread_id: body.thread_id ?? "default" },
586
+ configurable: { thread_id },
570
587
  });
571
588
  const last = result.messages.at(-1);
572
589
  return reply.send({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-langgraph-app",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "Scaffold a new LangGraph multi-agent project in seconds",
5
5
  "type": "module",
6
6
  "bin": {