How to use LangGraph with Flutch SDK.
Overview
LangGraph is a framework for building stateful workflows. See LangGraph docs for framework details.
This guide covers:
- Wrapping LangGraph in AbstractGraphBuilder
- Accessing agent configuration from nodes
- Using Flutch services (ModelInitializer, McpRuntimeClient)
- Adding interactive callbacks
Builder Integration
Wrap your LangGraph workflow in AbstractGraphBuilder.
Basic Setup
typescriptimport { Injectable, Inject } from '@nestjs/common'; import { AbstractGraphBuilder, IGraphRequestPayload } from '@flutch/sdk'; import { StateGraph, START, END, Annotation } from '@langchain/langgraph'; import { MongoDBSaver } from '@langchain/langgraph-checkpoint-mongodb'; // Define your state export const MyState = Annotation.Root({ messages: Annotation<BaseMessage[]>({ reducer: (current, update) => [...current, ...update], default: () => [] }) }); @Injectable() export class MyGraphV1Builder extends AbstractGraphBuilder<'1.0.0'> { readonly version = '1.0.0' as const; constructor( @Inject('CHECKPOINTER') private readonly checkpointer: MongoDBSaver, private readonly generateNode: GenerateNode ) { super(); } async buildGraph(payload?: any): Promise<CompiledGraph> { // Build LangGraph workflow const workflow = new StateGraph(MyState) .addNode('generate', this.generateNode.execute.bind(this.generateNode)) .addEdge(START, 'generate') .addEdge('generate', END); // Compile with checkpointer (provided by Flutch) return workflow.compile({ checkpointer: this.checkpointer }); } }
Key points:
- ✅ Checkpointer injected by Flutch - automatic state persistence
- ✅ Use
bind(this)for node methods - ✅ Return compiled graph from
buildGraph()
Configuration Access
Access agent settings from config.configurable.graphSettings in your nodes.
Node with Configuration
typescriptimport { Injectable, Logger } from '@nestjs/common'; import { LangGraphRunnableConfig } from '@langchain/langgraph'; @Injectable() export class GenerateNode { private readonly logger = new Logger(GenerateNode.name); async execute( state: MyStateValues, config?: LangGraphRunnableConfig ): Promise<Partial<MyStateValues>> { // Get agent-specific settings const graphSettings = config?.configurable?.graphSettings; const systemPrompt = graphSettings?.systemPrompt || 'You are a helpful assistant'; const modelId = graphSettings?.modelSettings?.modelId || 'gpt-4o'; const temperature = graphSettings?.modelSettings?.temperature || 0.7; this.logger.debug(`Using model: ${modelId}, temp: ${temperature}`); // Use settings in your logic const messages = [ new SystemMessage(systemPrompt), ...state.messages ]; return { messages }; } }
Agent config flows automatically:
Agent Config → payload.graphSettings → config.configurable.graphSettings → Your Node
Using Models
Inject ModelInitializer to create models from agent config.
Model Initialization
typescriptimport { Injectable } from '@nestjs/common'; import { ModelInitializer, McpRuntimeClient } from '@flutch/sdk'; import { LangGraphRunnableConfig } from '@langchain/langgraph'; @Injectable() export class GenerateNode { constructor( private readonly modelInitializer: ModelInitializer, private readonly mcpClient: McpRuntimeClient ) {} async execute( state: MyStateValues, config?: LangGraphRunnableConfig ): Promise<Partial<MyStateValues>> { const graphSettings = config?.configurable?.graphSettings; const modelId = graphSettings?.modelSettings?.modelId; const enabledTools = graphSettings?.availableTools || []; // Initialize model const model = await this.modelInitializer.initializeChatModel({ modelId, temperature: graphSettings?.modelSettings?.temperature }); // Add tools if configured let modelWithTools = model; if (enabledTools.length > 0) { const tools = await this.mcpClient.getTools(enabledTools); modelWithTools = model.bindTools(tools); } // Use model const result = await modelWithTools.invoke(state.messages, config); return { messages: [result] }; } }
Benefits:
- ✅ Models from catalog - no API keys in code
- ✅ Tools automatically filtered and converted
- ✅ Each agent has different model/tools
Interactive Callbacks
Add callback buttons from LangGraph nodes.
Callback in Node
typescriptimport { Injectable } from '@nestjs/common'; import { Callback, CallbackResult, ExtendedCallbackContext } from '@flutch/sdk'; // 1. Create callback handler @Injectable() export class ApprovalCallbacks { @Callback('approve-action') async handleApproval(context: ExtendedCallbackContext): Promise<CallbackResult> { const { action } = context.params; // Execute action await this.executeAction(action); return { success: true, message: 'Action approved!', patch: { text: '✅ Action completed', disableButtons: true } }; } private async executeAction(action: string) { // Action logic } } // 2. Register in builder @Injectable() @WithCallbacks(ApprovalCallbacks) export class MyGraphV1Builder extends AbstractGraphBuilder<'1.0.0'> { // ... } // 3. Issue callback from node @Injectable() export class PlanNode { constructor( private readonly callbackService: CallbackService ) {} async execute(state: MyStateValues): Promise<Partial<MyStateValues>> { const plan = await createPlan(state); // Create callback button const token = await this.callbackService.issue({ handler: 'approve-action', params: { action: plan } }); return { output: { text: 'Please approve this plan:', buttons: [ { text: 'Approve', callbackToken: token } ] } }; } }
Flow:
Node → issue callback → User clicks → Handler executes → Workflow continues
Streaming
Enable streaming with metadata.
typescriptimport { StreamChannel } from '@amelie/graph-service-core'; async buildGraph() { const workflow = new StateGraph(MyState) .addNode( 'generate', this.generateNode.execute.bind(this.generateNode), { metadata: { stream_channel: StreamChannel.TEXT // Enable streaming } } ); return workflow.compile({ checkpointer: this.checkpointer }); }
Stream channels:
StreamChannel.TEXT- Text tokensStreamChannel.REASONING- Reasoning
Module Setup
Register everything in NestJS module.
Graph Module
typescriptimport { Module } from '@nestjs/common'; import { UniversalGraphModule } from '@flutch/sdk'; import { MyGraphV1Builder } from './versions/v1.0.0/builder'; import { GenerateNode } from './nodes/generate.node'; import { ApprovalCallbacks } from './callbacks/approval.callbacks'; @Module({ imports: [ UniversalGraphModule.forRoot({ builders: [MyGraphV1Builder] }) ], providers: [ GenerateNode, ApprovalCallbacks ] }) export class MyGraphModule {}
Bootstrap
typescriptimport { bootstrap } from '@flutch/sdk'; import { MyGraphModule } from './my-graph.module'; async function main() { await bootstrap(MyGraphModule); } main();
Complete Example
Full integration with tool calling.
State Definition
typescript// src/state.model.ts import { Annotation } from '@langchain/langgraph'; import { BaseMessage, AIMessage } from '@langchain/core/messages'; export const MyState = Annotation.Root({ messages: Annotation<BaseMessage[]>({ reducer: (current, update) => [...current, ...update], default: () => [] }), output: Annotation<{ text: string; metadata?: any; }>({ reducer: (current, update) => update ?? current, default: () => ({ text: '' }) }) }); export type MyStateValues = typeof MyState.State;
Node Implementation
typescript// src/nodes/generate.node.ts import { Injectable, Logger } from '@nestjs/common'; import { ModelInitializer, McpRuntimeClient } from '@flutch/sdk'; import { MyStateValues } from '../state.model'; import { LangGraphRunnableConfig } from '@langchain/langgraph'; import { SystemMessage } from '@langchain/core/messages'; @Injectable() export class GenerateNode { private readonly logger = new Logger(GenerateNode.name); constructor( private readonly modelInitializer: ModelInitializer, private readonly mcpClient: McpRuntimeClient ) {} async execute( state: MyStateValues, config?: LangGraphRunnableConfig ): Promise<Partial<MyStateValues>> { const graphSettings = config?.configurable?.graphSettings; const systemPrompt = graphSettings?.systemPrompt || ''; const modelId = graphSettings?.modelSettings?.modelId || 'gpt-4o'; const enabledTools = graphSettings?.availableTools || []; // Initialize model with tools const model = await this.modelInitializer.initializeChatModel({ modelId }); let modelWithTools = model; if (enabledTools.length > 0) { const tools = await this.mcpClient.getTools(enabledTools); modelWithTools = model.bindTools(tools); this.logger.debug(`Configured ${tools.length} tools`); } // Prepare messages const messages = [ new SystemMessage(systemPrompt), ...state.messages ]; // Invoke model const result = await modelWithTools.invoke(messages, config); return { messages: [result], output: { text: result.content as string, metadata: { modelId, toolsUsed: (result as any).tool_calls?.length || 0 } } }; } async executeTools( state: MyStateValues, config?: LangGraphRunnableConfig ): Promise<Partial<MyStateValues>> { const lastMessage = state.messages[state.messages.length - 1]; const toolCalls = (lastMessage as any)?.tool_calls || []; const toolMessages = []; for (const toolCall of toolCalls) { const result = await this.mcpClient.executeTool( toolCall.name, toolCall.args ); toolMessages.push({ type: 'tool', tool_call_id: toolCall.id, content: result.success ? JSON.stringify(result.data) : result.error, name: toolCall.name }); } return { messages: toolMessages }; } }
Builder
typescript// src/versions/v1.0.0/builder.ts import { Injectable, Inject } from '@nestjs/common'; import { AbstractGraphBuilder } from '@flutch/sdk'; import { StateGraph, START, END } from '@langchain/langgraph'; import { MongoDBSaver } from '@langchain/langgraph-checkpoint-mongodb'; import { MyState, MyStateValues } from '../../state.model'; import { GenerateNode } from '../../nodes/generate.node'; import { StreamChannel } from '@amelie/graph-service-core'; @Injectable() export class MyGraphV1Builder extends AbstractGraphBuilder<'1.0.0'> { readonly version = '1.0.0' as const; constructor( @Inject('CHECKPOINTER') private readonly checkpointer: MongoDBSaver, private readonly generateNode: GenerateNode ) { super(); } async buildGraph(): Promise<any> { const workflow = new StateGraph(MyState) // Generate node with streaming .addNode( 'generate', this.generateNode.execute.bind(this.generateNode), { metadata: { stream_channel: StreamChannel.TEXT } } ) // Tools node .addNode( 'tools', this.generateNode.executeTools.bind(this.generateNode) ); // Flow workflow.addEdge(START, 'generate'); // Conditional: check if tools needed workflow.addConditionalEdges( 'generate', (state: MyStateValues) => { const lastMessage = state.messages[state.messages.length - 1]; return (lastMessage as any)?.tool_calls?.length > 0 ? 'tools' : END; }, { tools: 'tools', [END]: END } ); // Loop back after tools workflow.addEdge('tools', 'generate'); return workflow.compile({ checkpointer: this.checkpointer }); } }
Related Documentation
Flutch SDK:
- SDK Reference - TypeScript - Full SDK API
- Callbacks - Interactive buttons guide
- Configuration Schema - Define agent settings
LangGraph:
- LangGraph Docs - Official LangGraph documentation
- LangGraph - Python - Python integration
Other frameworks:
- LlamaIndex Workflows - Event-driven RAG