Langgraph Human In The Loop with socket
Through the interruption function of langgraph, I learned that humans can intervene in the middle of Agent execution.
But if you look at the examples, they all just ignore human interaction. What should I do to actually get confirmation from the user? I think there are three main ways.
Using Langgraph API Server
You can run the langgraph API server with docker using langgraph cli, then run the graph, change the state, and restart it with the langgraph SDK.
The items provided by langgraph must be used in the manner provided. There are a lot of settings, and it seems like it might be difficult to integrate it with my code.
Graph management on the server
This is a method of implementing only the necessary parts of the Langgraph API server above on my custom server. For example, when running a graph, the client that executed the graph and the graph checkpoint must be saved, and after the user's confirmation, the graph must be loaded again and the state changed according to the user's response to be restarted.
There may be a lot to think about.
socket connection
When executing the Agent, a socket is connected and the user interacts through the socket. It works by simply adding the step of receiving user confirmation through socket connection and socket communication in the existing example code.
Instead, it may be difficult to implement streaming that is typed like typing.
Implemented with socket connection
First of all, I wanted to implement it in a way that does not increase complexity as much as possible, so I implemented it with a socket connection.
The server uses NestJs and the client uses NextJs.
server
First, create a gateway for Websocket connection. A connection was created at agent/start and the agent was executed immediately.
@WebSocketGateway({ namespace: "/", transport: ["websocket", "polling"], path: "/agent/start", cors: { origin: "*", methods: ["GET", "POST"], credentials: true, }, }) export class AgentGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; protected readonly logger = new Logger(this.constructor.name); constructor( private readonly agentFactory: AgentFactory ) {} private pendingConfirmations = new Map<string, (response: boolean) => void>(); // Handle new connections handleConnection(client: Socket) { console.log(`Client connected: ${client.id}`); // Option 1: Get actionData from query parameters const actionData: { agent: AgentName } = client.handshake.query.actionData ? JSON.parse(client.handshake.query.actionData as string) : null; if (actionData) { this.startAgentProcess(client, actionData); } else { // If no actionData is provided, you can wait for an event client.emit("error", "No action data provided"); client.disconnect(); } } // Handle disconnections handleDisconnect(client: Socket) { console.log(`Client disconnected: ${client.id}`); this.pendingConfirmations.delete(client.id); } // Send confirmation request async sendConfirmationRequest(clientId: string, data: any): Promise<boolean> { return new Promise((resolve) => { this.pendingConfirmations.set(clientId, resolve); this.server.to(clientId).emit("confirmation_request", data); // Optional timeout for response setTimeout(() => { if (this.pendingConfirmations.has(clientId)) { this.pendingConfirmations.delete(clientId); resolve(false); // Default to 'false' if timeout occurs } }, 3000000); // 3000 seconds timeout }); } // Handle client's confirmation response @SubscribeMessage("confirmation_response") handleClientResponse( @MessageBody() data: { confirmed: boolean }, @ConnectedSocket() client: Socket ) { const resolve = this.pendingConfirmations.get(client.id); if (resolve) { resolve(data.confirmed); this.pendingConfirmations.delete(client.id); } } // Start the agent process private async startAgentProcess( client: Socket, actionData: { agent: AgentName } ) { const graph = await this.agentFactory.create({ agentName: actionData.agent, }); const initialInput = { input: "hello world" }; // Thread const graphStateConfig = { configurable: { thread_id: "1" }, streamMode: "values" as const, }; // Run the graph until the first interruption for await (const event of await graph.stream( initialInput, graphStateConfig )) { this.logAndEmit(client, `--- ${event.input} ---`); } // Will log when the graph is interrupted, after step 2. this.logAndEmit(client, "---GRAPH INTERRUPTED---"); const userConfirmed = await this.sendConfirmationRequest(client.id, { message: "Do you want to proceed with this action?", actionData, }); if (userConfirmed) { // If approved, continue the graph execution. We must pass `null` as // the input here, or the graph will for await (const event of await graph.stream(null, graphStateConfig)) { this.logAndEmit(client, `--- ${event.input} ---`); } this.logAndEmit(client, "---ACTION EXECUTED---"); } else { this.logAndEmit(client, "---ACTION CANCELLED---"); } // Optionally disconnect the client client.disconnect(); } private logAndEmit(client: Socket, message: string) { console.log(message); client.emit("message", { message }); } }
The key is simple. When a socket is connected, an agent is immediately created and executed, and when it is interrupted, a confirmation request message is sent to the client and waits. Once the confirmation is resolved, the graph continues.
The agent used in the above code is an agent that sequentially uses steps 1 2 3 below in the langgraph document.
const GraphState = Annotation.Root({ input: Annotation<string>, }); const step1 = (state: typeof GraphState.State) => { console.log("---Step 1---"); return state; }; const step2 = (state: typeof GraphState.State) => { console.log("---Step 2---"); return state; }; const step3 = (state: typeof GraphState.State) => { console.log("---Step 3---"); return state; }; const builder = new StateGraph(GraphState) .addNode("step1", step1) .addNode("step2", step2) .addNode("step3", step3) .addEdge(START, "step1") .addEdge("step1", "step2") .addEdge("step2", "step3") .addEdge("step3", END); // Set up memory const graphStateMemory = new MemorySaver(); const graph = builder.compile({ checkpointer: graphStateMemory, interruptBefore: ["step3"], }); return graph;
client
The client creates a hook to manage agent start and its status.
import { useRef, useState } from "react"; import io, { Socket } from "socket.io-client"; export const useAgentSocket = () => { const socketRef = useRef<Socket | null>(null); const [confirmationRequest, setConfirmationRequest] = useState<any>(null); const [messages, setMessages] = useState<string[]>([]); const connectAndRun = (actionData: any) => { return new Promise((resolve, reject) => { socketRef.current = io("http://localhost:8000", { path: "/agent/start", transports: ["websocket", "polling"], query: { actionData: JSON.stringify(actionData), }, }); socketRef.current.on("connect", () => { console.log("Connected:", socketRef.current?.id); resolve(void 0); }); socketRef.current.on("connect_error", (error) => { console.error("Connection error:", error); reject(error); }); // Listen for confirmation requests socketRef.current.on("confirmation_request", (data) => { setConfirmationRequest(data); }); // Listen for messages socketRef.current.on("message", (data) => { console.log("Received message:", data); setMessages((prevMessages) => [...prevMessages, data.message]); }); socketRef.current.on("disconnect", () => { console.log("Disconnected from server"); }); }); }; const sendConfirmationResponse = (confirmed: boolean) => { if (socketRef.current) { socketRef.current.emit("confirmation_response", { confirmed }); setConfirmationRequest(null); } }; const disconnectSocket = () => { if (socketRef.current) { socketRef.current.disconnect(); } }; const clearMessages = () => { setMessages([]); }; return { confirmationRequest, sendConfirmationResponse, connectAndRun, disconnectSocket, messages, clearMessages, }; };
Establishes a connection and updates the confirmationRequest status when a confirmation request arrives. Just look at the confirmationRequest status in the UI component and pop up a window to the user.
The above is the detailed content of Langgraph Human In The Loop with socket. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



Article discusses creating, publishing, and maintaining JavaScript libraries, focusing on planning, development, testing, documentation, and promotion strategies.

The article discusses strategies for optimizing JavaScript performance in browsers, focusing on reducing execution time and minimizing impact on page load speed.

Frequently Asked Questions and Solutions for Front-end Thermal Paper Ticket Printing In Front-end Development, Ticket Printing is a common requirement. However, many developers are implementing...

The article discusses effective JavaScript debugging using browser developer tools, focusing on setting breakpoints, using the console, and analyzing performance.

The article explains how to use source maps to debug minified JavaScript by mapping it back to the original code. It discusses enabling source maps, setting breakpoints, and using tools like Chrome DevTools and Webpack.

There is no absolute salary for Python and JavaScript developers, depending on skills and industry needs. 1. Python may be paid more in data science and machine learning. 2. JavaScript has great demand in front-end and full-stack development, and its salary is also considerable. 3. Influencing factors include experience, geographical location, company size and specific skills.

This tutorial will explain how to create pie, ring, and bubble charts using Chart.js. Previously, we have learned four chart types of Chart.js: line chart and bar chart (tutorial 2), as well as radar chart and polar region chart (tutorial 3). Create pie and ring charts Pie charts and ring charts are ideal for showing the proportions of a whole that is divided into different parts. For example, a pie chart can be used to show the percentage of male lions, female lions and young lions in a safari, or the percentage of votes that different candidates receive in the election. Pie charts are only suitable for comparing single parameters or datasets. It should be noted that the pie chart cannot draw entities with zero value because the angle of the fan in the pie chart depends on the numerical size of the data point. This means any entity with zero proportion

Once you have mastered the entry-level TypeScript tutorial, you should be able to write your own code in an IDE that supports TypeScript and compile it into JavaScript. This tutorial will dive into various data types in TypeScript. JavaScript has seven data types: Null, Undefined, Boolean, Number, String, Symbol (introduced by ES6) and Object. TypeScript defines more types on this basis, and this tutorial will cover all of them in detail. Null data type Like JavaScript, null in TypeScript
