Install any skill in seconds. Free to start, no credit card required.
Get Started Free →CRITICAL: LangGraph workflows are NestJS applications under apps/langgraph/. They receive the same parameters as n8n workflows and use the same webhook status pattern. Wrap them as API agents.
.claude/skills/majiayu000-langgraph-development-skill/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 45% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 56% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 100% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 134% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 59% | 0% |
name: LangGraph Development description: Create LangGraph workflows as NestJS applications under apps/langgraph/. Use same webhook pattern as n8n, receive same parameters (taskId, conversationId, userId, provider, model, statusWebhook). Wrap as API agents with request/response transforms. CRITICAL: Status webhook URL must read from environment variables. All endpoints follow A2A protocol. allowed-tools: Read, Write, Edit, Bash, Grep, Glob
CRITICAL: LangGraph workflows are NestJS applications under apps/langgraph/. They receive the same parameters as n8n workflows and use the same webhook status pattern. Wrap them as API agents.
Use this skill when:
LangGraph applications follow the same pattern as n8n:
apps/
├── api/ # Main NestJS API
├── n8n/ # N8N workflows (existing)
├── langgraph/ # LangGraph workflows (NEW)
│ ├── src/
│ │ ├── main.ts
│ │ ├── app.module.ts
│ │ ├── workflows/
│ │ │ └── example-workflow.ts
│ │ └── controllers/
│ │ └── langgraph.controller.ts
│ ├── package.json
│ └── tsconfig.json
├── crewai/ # CrewAI workflows (FUTURE)
└── openai/ # OpenAI workflows (FUTURE)Each LangGraph application is a standalone NestJS app. Example structure:
typescript// apps/langgraph/src/main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // Port from environment or default const port = process.env.PORT || 8000; await app.listen(port); console.log(`LangGraph service running on port ${port}`); } bootstrap();
typescript// apps/langgraph/src/app.module.ts import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { LangGraphController } from './controllers/langgraph.controller'; import { LangGraphService } from './services/langgraph.service'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: ['.env'], }), ], controllers: [LangGraphController], providers: [LangGraphService], }) export class AppModule {}
LangGraph endpoints receive the same parameters as n8n workflows:
typescriptinterface LangGraphRequest { taskId: string; conversationId: string; userId: string; userMessage: string; // Or "prompt" or "announcement" statusWebhook: string; // MUST read from env: API_BASE_URL/webhooks/status provider?: string; // "openai" | "anthropic" | "ollama" model?: string; // Model name stepName?: string; // For status tracking sequence?: number; // Step sequence number totalSteps?: number; // Total steps in workflow }
typescript// apps/langgraph/src/controllers/langgraph.controller.ts import { Controller, Post, Body, Logger } from '@nestjs/common'; import { LangGraphService } from '../services/langgraph.service'; interface LangGraphWorkflowRequest { taskId: string; conversationId: string; userId: string; userMessage: string; statusWebhook: string; provider?: string; model?: string; stepName?: string; sequence?: number; totalSteps?: number; } @Controller('api/orchestrate') export class LangGraphController { private readonly logger = new Logger(LangGraphController.name); constructor(private readonly langGraphService: LangGraphService) {} @Post() async executeWorkflow(@Body() request: LangGraphWorkflowRequest) { this.logger.log(`Executing LangGraph workflow for task ${request.taskId}`); // Send start status if statusWebhook provided if (request.statusWebhook) { await this.sendStatus(request.statusWebhook, { taskId: request.taskId, status: 'started', stepName: request.stepName || 'workflow-start', sequence: request.sequence || 0, totalSteps: request.totalSteps || 1, }); } // Execute LangGraph workflow const result = await this.langGraphService.execute(request); // Send completion status if (request.statusWebhook) { await this.sendStatus(request.statusWebhook, { taskId: request.taskId, status: 'completed', stepName: request.stepName || 'workflow-complete', sequence: request.sequence || (request.totalSteps || 1), totalSteps: request.totalSteps || 1, }); } return { status: 'completed', payload: { content: result.content, metadata: result.metadata, }, }; } private async sendStatus(webhookUrl: string, status: any) { try { await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(status), }); } catch (error) { this.logger.warn(`Failed to send status to ${webhookUrl}:`, error); } } }
typescript// ❌ WRONG const statusWebhook = 'http://host.docker.internal:7100/webhooks/status';
typescript// ✅ CORRECT const apiBaseUrl = process.env.API_BASE_URL || process.env.VITE_API_BASE_URL || 'http://host.docker.internal:7100'; const statusWebhook = `${apiBaseUrl}/webhooks/status`;
In API Agent Configuration:
yamlapi_configuration: request_transform: format: "custom" template: | { "taskId": "{{taskId}}", "conversationId": "{{conversationId}}", "userId": "{{userId}}", "userMessage": "{{userMessage}}", "statusWebhook": "{{env.API_BASE_URL}}/webhooks/status", "provider": "{{payload.provider}}", "model": "{{payload.model}}" }
yamlmetadata: name: "langgraph-example" displayName: "LangGraph Example Workflow" description: "Example LangGraph workflow wrapped as API agent" version: "0.1.0" type: "api" api_configuration: endpoint: "http://localhost:8000/api/orchestrate" method: "POST" timeout: 120000 headers: Content-Type: "application/json" request_transform: format: "custom" template: | { "taskId": "{{taskId}}", "conversationId": "{{conversationId}}", "userId": "{{userId}}", "userMessage": "{{userMessage}}", "statusWebhook": "{{env.API_BASE_URL}}/webhooks/status", "provider": "{{payload.provider}}", "model": "{{payload.model}}" } response_transform: format: "field_extraction" field: "payload.content" configuration: execution_capabilities: supports_converse: false supports_plan: false supports_build: true
LangGraph endpoints must follow A2A protocol:
GET /healthGET /.well-known/agent.jsonPOST /api/orchestratetypescript@Controller() export class LangGraphController { @Get('health') health() { return { status: 'ok', service: 'langgraph' }; } }
typescript@Get('.well-known/agent.json') agentCard() { return { name: 'langgraph-example', displayName: 'LangGraph Example Workflow', description: 'Example LangGraph workflow', type: 'api', version: '0.1.0', capabilities: { modes: ['build'], inputModes: ['application/json'], outputModes: ['application/json'], }, }; }
LangGraph workflows support both real-time (SSE) and polling:
typescript@Post('stream') async streamWorkflow(@Body() request: LangGraphWorkflowRequest, @Res() res: Response) { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); // Stream workflow steps for await (const step of this.langGraphService.streamExecute(request)) { res.write(`data: ${JSON.stringify(step)}\n\n`); } res.end(); }
typescript@Post('async') async executeAsync(@Body() request: LangGraphWorkflowRequest) { const taskId = request.taskId; // Start workflow in background this.langGraphService.executeAsync(request); return { taskId, status: 'processing', statusUrl: `/api/orchestrate/status/${taskId}`, }; } @Get('status/:taskId') async getStatus(@Param('taskId') taskId: string) { return this.langGraphService.getStatus(taskId); }
LangGraph workflows return standardized responses:
typescriptinterface LangGraphResponse { status: 'completed' | 'error' | 'processing'; payload: { content: string; // Main content metadata?: { steps?: number; duration?: number; [key: string]: unknown; }; }; error?: { message: string; code?: string; }; }
typescript// apps/langgraph/src/services/langgraph.service.ts import { Injectable, Logger } from '@nestjs/common'; import { StateGraph } from '@langchain/langgraph'; @Injectable() export class LangGraphService { private readonly logger = new Logger(LangGraphService.name); async execute(request: LangGraphWorkflowRequest) { // Build LangGraph state machine const workflow = this.buildWorkflow(request); // Execute workflow const result = await workflow.invoke({ messages: [{ role: 'user', content: request.userMessage }], provider: request.provider || 'openai', model: request.model || 'gpt-4', }); return { content: result.output, metadata: { steps: result.steps, duration: result.duration, }, }; } private buildWorkflow(request: LangGraphWorkflowRequest) { // Create LangGraph state machine const workflow = new StateGraph({ // Define workflow nodes and edges }); return workflow.compile(); } }
When creating LangGraph workflows:
apps/langgraph/package.json configured with NestJS dependencies| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 29,931 | 11,553 | -61% | 1 | 1 | 0% | 5,700 | 3,449 | -39% | 0 | 0 | — |
case-02 | fail→pass | 21,280 | 23,099 | +9% | 1 | 1 | 0% | 4,491 | 6,507 | +45% | 0 | 0 | — |
case-03 | fail→fail | 50,310 | 11,364 | -77% | 1 | 1 | 0% | 5,853 | 3,541 | -40% | 0 | 0 | — |
case-04 | pass→fail | 12,726 | 15,622 | +23% | 1 | 1 | 0% | 2,646 | 3,288 | +24% | 0 | 0 | — |
case-05 | pass→fail | 8,170 | 5,015 | -39% | 1 | 1 | 0% | 1,853 | 3,239 | +75% | 0 | 0 | — |
case-06 | pass→fail | 11,834 | 4,582 | -61% | 1 | 1 | 0% | 2,417 | 3,214 | +33% | 0 | 0 | — |
case-07 | fail→pass | 12,024 | 2,796 | -77% | 1 | 1 | 0% | 2,333 | 3,637 | +56% | 0 | 0 | — |
case-08 | fail→pass | 14,552 | 15,466 | +6% | 1 | 1 | 0% | 2,798 | 5,604 | +100% | 0 | 0 | — |
case-09 | fail→fail | 27,437 | 4,877 | -82% | 1 | 1 | 0% | 6,071 | 3,246 | -47% | 0 | 0 | — |
case-10 | fail→pass | 9,538 | 4,953 | -48% | 1 | 1 | 0% | 1,710 | 3,994 | +134% | 0 | 0 | — |
case-11 | fail→pass | 15,817 | 16,973 | +7% | 1 | 1 | 0% | 2,796 | 4,458 | +59% | 0 | 0 | — |
case-12 | fail→pass | 14,820 | 13,319 | -10% | 1 | 1 | 0% | 2,952 | 5,963 | +102% | 0 | 0 | — |
case-13 | fail→pass | 11,069 | 2,650 | -76% | 1 | 1 | 0% | 2,227 | 3,567 | +60% | 0 | 0 | — |
case-14 | fail→pass | 21,192 | 22,673 | +7% | 1 | 1 | 0% | 4,114 | 7,967 | +94% | 0 | 0 | — |
case-15 | fail→pass | 17,300 | 19,858 | +15% | 1 | 1 | 0% | 3,352 | 7,554 | +125% | 0 | 0 | — |
case-16 | fail→pass | 16,462 | 10,134 | -38% | 1 | 1 | 0% | 3,279 | 5,335 | +63% | 0 | 0 | — |
case-17 | fail→pass | 13,645 | 5,254 | -61% | 1 | 1 | 0% | 2,694 | 4,053 | +50% | 0 | 0 | — |
case-18 | fail→pass | 13,175 | 6,351 | -52% | 1 | 1 | 0% | 2,607 | 4,252 | +63% | 0 | 0 | — |
case-19 | fail→fail | 4,872 | 5,821 | +19% | 1 | 1 | 0% | 882 | 3,347 | +279% | 0 | 0 | — |
case-20 | pass→pass | 14,054 | 8,478 | -40% | 1 | 1 | 0% | 2,493 | 3,959 | +59% | 0 | 0 | — |
case-21 | fail→fail | 15,026 | 15,395 | +2% | 1 | 1 | 0% | 3,063 | 6,390 | +109% | 0 | 0 | — |
case-22 | fail→fail | 4,871 | 4,430 | -9% | 1 | 1 | 0% | 904 | 3,173 | +251% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 14 counted toward the lift figure. The other 8 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +41 percentage points is the difference between those two pass rates over the 14 comparable cases. 6 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.