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.
- package/README.md +50 -12
- package/dist/create.js +24 -7
- 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
|
|
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
|
|
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
|
|
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
|
|
59
|
+
npm run dev # run the CLI locally
|
|
60
|
+
npm run build # typecheck and emit dist/
|
|
30
61
|
```
|
|
31
62
|
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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?:
|
|
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
|
|
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?:
|
|
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
|
|
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?:
|
|
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
|
|
586
|
+
configurable: { thread_id },
|
|
570
587
|
});
|
|
571
588
|
const last = result.messages.at(-1);
|
|
572
589
|
return reply.send({
|