Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interrupt is not working inside tool definitions but python version is working. #970

Open
kturung opened this issue Mar 8, 2025 · 0 comments

Comments

@kturung
Copy link

kturung commented Mar 8, 2025

I'm trying to gather some sensitive data inside tool call.

It is working on python version.
https://github.com/kturung/Langgraph-Multi-Agent-HITL-Form/blob/main/backend/src/backend/super.py#L78-L82

But on js version. I couldn't make it work. Im working on a fullstack nextjs langgraph chat demo and I need to gather some confidential data from user with hitl. I also tried to add a seperate human_review node with custom agent flow to modify tool arguments, That works but in that case I'm sending sensitive data to LLM. (It remembers the modified tool arguments)

If there are any other solutions I'm willing to try.

This is my graph file. Interrupt is in bookHotel tool.

Dependency versions:
"@langchain/core": "^0.3.42",
"@langchain/langgraph": "^0.2.53",
"@langchain/langgraph-swarm": "^0.0.2",

import * as dotenv from 'dotenv';
dotenv.config();

import { z } from "zod";
import { ChatOpenAI } from "@langchain/openai";
import { RunnableConfig } from "@langchain/core/runnables";
import { tool } from "@langchain/core/tools";
import { MemorySaver } from "@langchain/langgraph";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { createSwarm, createHandoffTool } from "@langchain/langgraph-swarm";
import { SystemMessage } from "@langchain/core/messages";
import { interrupt } from "@langchain/langgraph";
// Initialize OpenAI model with API key from environment variables
const model = new ChatOpenAI({
    model: 'gpt-4o',
    temperature: 0,
    apiKey: process.env.OPENAI_API_KEY,
    streaming: true,
  })

/// Mock data for tools
const RESERVATIONS: Record<string, { 
  flight_info: Record<string, unknown>; 
  hotel_info: Record<string, unknown> 
}> = {};

// Helper function to get default value for reservations
const getReservation = (userId: string) => {
  if (!RESERVATIONS[userId]) {
    RESERVATIONS[userId] = { flight_info: {}, hotel_info: {} };
  }
  return RESERVATIONS[userId];
};

// Get tomorrow's date in ISO format
const TOMORROW = new Date();
TOMORROW.setDate(TOMORROW.getDate() + 1);
const TOMORROW_ISO = TOMORROW.toISOString().split("T")[0];

const FLIGHTS = [
  {
    departure_airport: "BOS",
    arrival_airport: "JFK",
    airline: "Jet Blue",
    date: TOMORROW_ISO,
    id: "1",
  },
];

const HOTELS = [
  {
    location: "New York",
    name: "McKittrick Hotel",
    neighborhood: "Chelsea",
    id: "1",
  },
];

// Flight tools
const searchFlights = tool(
  // Use an underscore prefix for unused parameters to satisfy the linter
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  async (_args) => {
    // Return all flights for simplicity
    return JSON.stringify(FLIGHTS);
  },
  {
    name: "search_flights",
    description:
      "Search flights. If unsure about airport codes, use the biggest airport in the area.",
    schema: z.object({
      departure_airport: z
        .string()
        .describe("3-letter airport code for the departure airport"),
      arrival_airport: z
        .string()
        .describe("3-letter airport code for the arrival airport"),
      date: z.string().describe("YYYY-MM-DD date"),
    }),
  }
);

const bookFlight = tool(
  async (args, runnable) => {
    const config = runnable as RunnableConfig;
    const userId = config.configurable?.user_id as string;
    const flight = FLIGHTS.find((flight) => flight.id === args.flight_id);

    if (flight) {
      getReservation(userId).flight_info = flight;
      return "Successfully booked flight";
    }
    return "Flight not found";
  },
  {
    name: "book_flight",
    description: "Book a flight",
    schema: z.object({
      flight_id: z.string(),
    }),
  }
);

// Hotel tools
const searchHotels = tool(
  // Use an underscore prefix for unused parameters to satisfy the linter
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  async (_args) => {
    // Return all hotels for simplicity
    return JSON.stringify(HOTELS);
  },
  {
    name: "search_hotels",
    description: "Search hotels",
    schema: z.object({
      location: z.string().describe("official, legal city name (proper noun)"),
    }),
  }
);

const bookHotel = tool(
  async (args, runnable) => {
    const config = runnable as RunnableConfig;
    const userId = config.configurable?.user_id as string;
    const hotel = HOTELS.find((hotel) => hotel.id === args.hotel_id);

    if (hotel) {
      const humanReview = interrupt<
        {
          action: string;
          data: unknown;
        }>({
          action: "",
          data: null
        });
      getReservation(userId).hotel_info = hotel;
      return "Successfully booked hotel" + humanReview;
    }
    return "Hotel not found";
  },
  {
    name: "book_hotel",
    description: "Book a hotel",
    schema: z.object({
      hotel_id: z.string(),
    }),
  }
);

// Define handoff tools
const transferToHotelAssistant = createHandoffTool({
  agentName: "hotel_assistant",
  description:
    "Transfer user to the hotel-booking assistant that can search for and book hotels.",
});

const transferToFlightAssistant = createHandoffTool({
  agentName: "flight_assistant",
  description:
    "Transfer user to the flight-booking assistant that can search for and book flights.",
});

// Define agent prompt function
const makePrompt = (baseSystemPrompt: string) => {
  // The eslint-disable is needed because we need to match the expected function signature
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return (state: any, config: RunnableConfig) => {
    const userId = config.configurable?.user_id as string;
    const currentReservation = getReservation(userId);
    const systemPrompt = `${baseSystemPrompt}\n\nUser's active reservation: ${JSON.stringify(
      currentReservation
    )}\nToday is: ${new Date().toString()}`;

    return [new SystemMessage({ content: systemPrompt }), ...state.messages];
  };
};

// Define agents
const flightAssistant = createReactAgent({
  llm: model,
  tools: [searchFlights, bookFlight, transferToHotelAssistant],
  prompt: makePrompt("You are a flight booking assistant"),
  name: "flight_assistant",
});

const hotelAssistant = createReactAgent({
  llm: model,
  tools: [searchHotels, bookHotel, transferToFlightAssistant],
  prompt: makePrompt("You are a hotel booking assistant"),
  name: "hotel_assistant",
});

// Compile and run!
const checkpointer = new MemorySaver();
const builder = createSwarm({
  agents: [flightAssistant, hotelAssistant],
  defaultActiveAgent: "flight_assistant",
});

// Important: compile the swarm with a checkpointer to remember
// previous interactions and last active agent
export const app = builder.compile({
  checkpointer,
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant