Langgraph agent being stuck midway, without any errors in development server of Next JS #49

Closed
opened 2026-02-15 17:15:04 -05:00 by yindo · 5 comments
Owner

Originally created by @DevDeepakBhattarai on GitHub (Jul 17, 2024).

I am using Next JS 14, with vercel AI SDK to build a LLM app with Generative UI being inspired from here.

Here is the code of my graph

import {
  AIMessage,
  type BaseMessage,
  ToolMessage,
  HumanMessage,
  type AIMessageChunk,
  FunctionMessage,
} from "@langchain/core/messages";
import { type RunnableConfig } from "@langchain/core/runnables";
import { StateGraph, START, END } from "@langchain/langgraph";
import {
  ChatPromptTemplate,
  MessagesPlaceholder,
} from "@langchain/core/prompts";
import { OpenAI } from "@langchain/openai";
import { type StructuredToolInterface } from "@langchain/core/tools";
import { search_tool } from "./tools/search";
import { type AvailableModels, modelPicker } from "@/lib/modelPicker";
import { suggestion } from "./tools/suggestion";
import { env } from "@/env";
import { ConversationSummaryBufferMemory } from "langchain/memory";
import { redis } from "@/lib/redis";
import { UpstashRedisChatMessageHistory } from "@langchain/community/stores/message/upstash_redis";
import { weatherTool } from "./tools/weather";
import { OUTPUT_MODEL } from "../utils/server";
import { safeJsonParse } from "@/lib/utils";
import { crypto_tool } from "./tools/crypto";
const intermediateTools = [search_tool.name];
const SystemMessage = `You are a ALLWEONE, a helpful assistant that is capable of doing anything. You are provided with a list of functions, and an input from the user.\nYour job is to determine whether to chat with the user with plain text in markdown format, or call a function help the user.\nWhen using markdown format, you will use it wisely, only using it when absolutely necessary\n\n\nBelow is the summary of the conversation that has happened between you(AI) and the user, written by a third person\n----------\n{existingSummary}\n----------\n`;
const promptWithImage = ChatPromptTemplate.fromMessages([
  ["system", SystemMessage],
  ["placeholder", "{input}"],
  new MessagesPlaceholder({
    variableName: "chat_history",
    optional: true,
  }),
]);

const promptWithoutImages = ChatPromptTemplate.fromMessages([
  ["system", SystemMessage],
  ["user", "{objective}"],
  new MessagesPlaceholder({
    variableName: "chat_history",
    optional: true,
  }),
]);

const memory = new ConversationSummaryBufferMemory({
  memoryKey: "history",
  maxTokenLimit: 300,
  llm: new OpenAI({
    model: "gpt-3.5-turbo",
    temperature: 0,
    apiKey: env.OPENAI_API_KEY,
  }),
});

export interface AgentExecutorState {
  input: BaseMessage[];
  model: AvailableModels;
  chat_history: BaseMessage[];
  objective: string;
  chatId: string;
  userId: string;
  /**
   * The plain text result of the LLM if
   * no tool was used.
   */
  result?: string;
  /**
   * The parsed tool result that was called.
   */
  toolCall?: {
    name: string;
    parameters: Record<string, unknown>;
    id: string;
  };
  /**
   * The result of a tool.
   */
  toolResult?: Record<string, unknown>;
  existingSummary: string;
}

const invokeModel = async (
  state: AgentExecutorState,
  config?: RunnableConfig,
): Promise<Partial<AgentExecutorState>> => {
  const initialPrompt =
    state.model !== "groq" ? promptWithImage : promptWithoutImages;
  const MessageHistoryStore = new UpstashRedisChatMessageHistory({
    sessionId: `${state.userId}-chat-${state.chatId}`, // Or some other unique identifier for the conversation
    client: redis,
  });

  const tools = [search_tool, weatherTool, crypto_tool];
  const llm = modelPicker(state.model, true).bindTools(tools, {
    tool_choice: "auto",
  });
  const chain = initialPrompt.pipe(llm);

  let result: AIMessageChunk | undefined = undefined;
  result = await chain
    .withConfig({ runName: OUTPUT_MODEL })
    .invoke(state, config);

  if (!result) {
    return {
      result: "Sorry there was an error",
    };
  }

  if (
    state.model === "gemini" &&
    result.additional_kwargs.tool_calls &&
    result.additional_kwargs.tool_calls.length > 0
  ) {
    const tool_call = result.additional_kwargs.tool_calls[0]!;
    const toolCall = {
      name: tool_call.function.name,
      parameters: safeJsonParse(tool_call.function.arguments)!,
      id: tool_call.id ?? "",
    };
    return {
      toolCall,
      chat_history: [result],
    };
  }

  if (result.tool_calls && result.tool_calls.length > 0) {
    const toolCall = {
      name: result.tool_calls[0]!.name,
      parameters: result.tool_calls[0]!.args,
      id: result.tool_calls[0]!.id ?? "",
    };
    return {
      toolCall,
      chat_history: [result],
    };
  }

  result.content &&
    void MessageHistoryStore.addAIMessage(result.content as string);

  const newSummary = await memory.predictNewSummary(
    [
      new HumanMessage(state.objective),
      new AIMessage(result.content as string),
    ],
    state.existingSummary,
  );
  await redis.set(`${state.userId}-summary-${state.chatId}`, newSummary);

  return {
    result: result.content as string,
    chat_history: [result],
    toolCall: undefined,
  };
};

const invokeToolsOrReturn = (state: AgentExecutorState) => {
  if (state.toolCall && intermediateTools.includes(state.toolCall.name)) {
    return "invokeIntermediateTools";
  } else if (state.toolCall) {
    return "invokeResponseTools";
  } else if (state.result) {
    return "generate_suggestion";
  } else {
    console.error("No tool call or result found.");
    return END;
  }
};

const invokeTools = async (
  state: AgentExecutorState,
  config?: RunnableConfig,
): Promise<Partial<AgentExecutorState>> => {
  if (!state.toolCall) {
    throw new Error("No tool call found.");
  }
  const MessageHistoryStore = new UpstashRedisChatMessageHistory({
    sessionId: `${state.userId}-chat-${state.chatId}`, // Or some other unique identifier for the conversation
    client: redis,
  });
  const toolMap: Record<string, StructuredToolInterface> = {
    [search_tool.name]: search_tool,
    [weatherTool.name]: weatherTool,
    [crypto_tool.name]: crypto_tool,
  };
  const selectedTool = toolMap[state.toolCall.name];

  if (!selectedTool) {
    throw new Error("No tool found in tool map.");
  }
  const toolResult = await selectedTool.invoke(
    state.toolCall.parameters,
    config,
  );

  void MessageHistoryStore.addMessage(
    new FunctionMessage({ name: state.toolCall.name, content: toolResult }),
  );

  return {
    toolResult: JSON.parse(toolResult),
    chat_history: [
      new ToolMessage({ tool_call_id: state.toolCall.id, content: toolResult }),
    ],
  };
};

export function agentExecutor() {
  const workflow = new StateGraph<AgentExecutorState>({
    channels: {
      input: null,
      chat_history: {
        value: (a, b) => a.concat(b),
      },
      result: null,
      toolCall: null,
      toolResult: null,

      objective: null,
      model: null,
      chatId: null,
      userId: null,
      existingSummary: null,
    },
  })
    .addNode("invokeModel", invokeModel)
    .addNode("invokeIntermediateTools", invokeTools)
    .addNode("invokeResponseTools", invokeTools)
    .addNode("generate_suggestion", suggestion)
    .addEdge(START, "invokeModel")
    .addConditionalEdges("invokeModel", invokeToolsOrReturn)
    .addEdge("invokeIntermediateTools", "invokeModel")
    .addEdge("invokeResponseTools", END)
    .addEdge("generate_suggestion", END);

  const graph = workflow.compile();
  return graph;
}

The code is quite messy : ) I will fix it.

But when ever I am in the development server "pnpm dev" my agent does not complete the full task. It gets stuck after invokeModel for some reason.

Here is the langsmith trace from the development sever,
image

Here is the langsmith trace from the production server, after I do pnpm build and pnpm start and run the same query
image

Can any body help me here ?

System Information and Package Information

pnpm 9.4
windows 10
node 18.17.0
next js 14.2.5

Originally created by @DevDeepakBhattarai on GitHub (Jul 17, 2024). I am using Next JS 14, with vercel AI SDK to build a LLM app with Generative UI being inspired from [here](https://github.com/bracesproul/gen-ui/). Here is the code of my graph ```typescript import { AIMessage, type BaseMessage, ToolMessage, HumanMessage, type AIMessageChunk, FunctionMessage, } from "@langchain/core/messages"; import { type RunnableConfig } from "@langchain/core/runnables"; import { StateGraph, START, END } from "@langchain/langgraph"; import { ChatPromptTemplate, MessagesPlaceholder, } from "@langchain/core/prompts"; import { OpenAI } from "@langchain/openai"; import { type StructuredToolInterface } from "@langchain/core/tools"; import { search_tool } from "./tools/search"; import { type AvailableModels, modelPicker } from "@/lib/modelPicker"; import { suggestion } from "./tools/suggestion"; import { env } from "@/env"; import { ConversationSummaryBufferMemory } from "langchain/memory"; import { redis } from "@/lib/redis"; import { UpstashRedisChatMessageHistory } from "@langchain/community/stores/message/upstash_redis"; import { weatherTool } from "./tools/weather"; import { OUTPUT_MODEL } from "../utils/server"; import { safeJsonParse } from "@/lib/utils"; import { crypto_tool } from "./tools/crypto"; const intermediateTools = [search_tool.name]; const SystemMessage = `You are a ALLWEONE, a helpful assistant that is capable of doing anything. You are provided with a list of functions, and an input from the user.\nYour job is to determine whether to chat with the user with plain text in markdown format, or call a function help the user.\nWhen using markdown format, you will use it wisely, only using it when absolutely necessary\n\n\nBelow is the summary of the conversation that has happened between you(AI) and the user, written by a third person\n----------\n{existingSummary}\n----------\n`; const promptWithImage = ChatPromptTemplate.fromMessages([ ["system", SystemMessage], ["placeholder", "{input}"], new MessagesPlaceholder({ variableName: "chat_history", optional: true, }), ]); const promptWithoutImages = ChatPromptTemplate.fromMessages([ ["system", SystemMessage], ["user", "{objective}"], new MessagesPlaceholder({ variableName: "chat_history", optional: true, }), ]); const memory = new ConversationSummaryBufferMemory({ memoryKey: "history", maxTokenLimit: 300, llm: new OpenAI({ model: "gpt-3.5-turbo", temperature: 0, apiKey: env.OPENAI_API_KEY, }), }); export interface AgentExecutorState { input: BaseMessage[]; model: AvailableModels; chat_history: BaseMessage[]; objective: string; chatId: string; userId: string; /** * The plain text result of the LLM if * no tool was used. */ result?: string; /** * The parsed tool result that was called. */ toolCall?: { name: string; parameters: Record<string, unknown>; id: string; }; /** * The result of a tool. */ toolResult?: Record<string, unknown>; existingSummary: string; } const invokeModel = async ( state: AgentExecutorState, config?: RunnableConfig, ): Promise<Partial<AgentExecutorState>> => { const initialPrompt = state.model !== "groq" ? promptWithImage : promptWithoutImages; const MessageHistoryStore = new UpstashRedisChatMessageHistory({ sessionId: `${state.userId}-chat-${state.chatId}`, // Or some other unique identifier for the conversation client: redis, }); const tools = [search_tool, weatherTool, crypto_tool]; const llm = modelPicker(state.model, true).bindTools(tools, { tool_choice: "auto", }); const chain = initialPrompt.pipe(llm); let result: AIMessageChunk | undefined = undefined; result = await chain .withConfig({ runName: OUTPUT_MODEL }) .invoke(state, config); if (!result) { return { result: "Sorry there was an error", }; } if ( state.model === "gemini" && result.additional_kwargs.tool_calls && result.additional_kwargs.tool_calls.length > 0 ) { const tool_call = result.additional_kwargs.tool_calls[0]!; const toolCall = { name: tool_call.function.name, parameters: safeJsonParse(tool_call.function.arguments)!, id: tool_call.id ?? "", }; return { toolCall, chat_history: [result], }; } if (result.tool_calls && result.tool_calls.length > 0) { const toolCall = { name: result.tool_calls[0]!.name, parameters: result.tool_calls[0]!.args, id: result.tool_calls[0]!.id ?? "", }; return { toolCall, chat_history: [result], }; } result.content && void MessageHistoryStore.addAIMessage(result.content as string); const newSummary = await memory.predictNewSummary( [ new HumanMessage(state.objective), new AIMessage(result.content as string), ], state.existingSummary, ); await redis.set(`${state.userId}-summary-${state.chatId}`, newSummary); return { result: result.content as string, chat_history: [result], toolCall: undefined, }; }; const invokeToolsOrReturn = (state: AgentExecutorState) => { if (state.toolCall && intermediateTools.includes(state.toolCall.name)) { return "invokeIntermediateTools"; } else if (state.toolCall) { return "invokeResponseTools"; } else if (state.result) { return "generate_suggestion"; } else { console.error("No tool call or result found."); return END; } }; const invokeTools = async ( state: AgentExecutorState, config?: RunnableConfig, ): Promise<Partial<AgentExecutorState>> => { if (!state.toolCall) { throw new Error("No tool call found."); } const MessageHistoryStore = new UpstashRedisChatMessageHistory({ sessionId: `${state.userId}-chat-${state.chatId}`, // Or some other unique identifier for the conversation client: redis, }); const toolMap: Record<string, StructuredToolInterface> = { [search_tool.name]: search_tool, [weatherTool.name]: weatherTool, [crypto_tool.name]: crypto_tool, }; const selectedTool = toolMap[state.toolCall.name]; if (!selectedTool) { throw new Error("No tool found in tool map."); } const toolResult = await selectedTool.invoke( state.toolCall.parameters, config, ); void MessageHistoryStore.addMessage( new FunctionMessage({ name: state.toolCall.name, content: toolResult }), ); return { toolResult: JSON.parse(toolResult), chat_history: [ new ToolMessage({ tool_call_id: state.toolCall.id, content: toolResult }), ], }; }; export function agentExecutor() { const workflow = new StateGraph<AgentExecutorState>({ channels: { input: null, chat_history: { value: (a, b) => a.concat(b), }, result: null, toolCall: null, toolResult: null, objective: null, model: null, chatId: null, userId: null, existingSummary: null, }, }) .addNode("invokeModel", invokeModel) .addNode("invokeIntermediateTools", invokeTools) .addNode("invokeResponseTools", invokeTools) .addNode("generate_suggestion", suggestion) .addEdge(START, "invokeModel") .addConditionalEdges("invokeModel", invokeToolsOrReturn) .addEdge("invokeIntermediateTools", "invokeModel") .addEdge("invokeResponseTools", END) .addEdge("generate_suggestion", END); const graph = workflow.compile(); return graph; } ``` The code is quite messy : ) I will fix it. But when ever I am in the development server "pnpm dev" my agent does not complete the full task. It gets stuck after `invokeModel` for some reason. Here is the langsmith trace from the development sever, ![image](https://github.com/user-attachments/assets/9215cf78-f216-4f04-8ab3-858d09d9e301) Here is the langsmith trace from the production server, after I do `pnpm build` and `pnpm start` and run the same query ![image](https://github.com/user-attachments/assets/08efd293-2efa-4133-84b7-b960e0e262c6) Can any body help me here ? # System Information and Package Information pnpm 9.4 windows 10 node 18.17.0 next js 14.2.5
yindo closed this issue 2026-02-15 17:15:04 -05:00
Author
Owner

@DevDeepakBhattarai commented on GitHub (Jul 17, 2024):

 const newSummary = await memory.predictNewSummary(
   [
     new HumanMessage(state.objective),
     new AIMessage(result.content as string),
   ],
   state.existingSummary,
 );
 await redis.set(`${state.userId}-summary-${state.chatId}`, newSummary);

When I removed this part from the invokeModel it worked. It seems ConversationSummaryBufferMemory is blocking langgraph execution somehow.

@DevDeepakBhattarai commented on GitHub (Jul 17, 2024): ```typescript const newSummary = await memory.predictNewSummary( [ new HumanMessage(state.objective), new AIMessage(result.content as string), ], state.existingSummary, ); await redis.set(`${state.userId}-summary-${state.chatId}`, newSummary); ``` When I removed this part from the `invokeModel` it worked. It seems `ConversationSummaryBufferMemory` is blocking langgraph execution somehow.
Author
Owner

@hwchase17 commented on GitHub (Jul 17, 2024):

hmm asking our team to look into it.
Separately, I would likely not use ConversationSummaryBufferMemory here. I would use the built in persistence in LangGraph to manage the list of messages, and then just calculate the summary using a prompt rather than relying on the one in there

@hwchase17 commented on GitHub (Jul 17, 2024): hmm asking our team to look into it. Separately, I would likely not use ConversationSummaryBufferMemory here. I would use the built in persistence in LangGraph to manage the list of messages, and then just calculate the summary using a prompt rather than relying on the one in there
Author
Owner

@jacoblee93 commented on GitHub (Jul 17, 2024):

Yeah echoing @hwchase17 above, would suggest using a prompt to just manually summarize the messages.

I also see this in your code:

new OpenAI({
  model: "gpt-3.5-turbo",
  temperature: 0,
  apiKey: env.OPENAI_API_KEY,
}),

gpt-3.5-turbo is a chat model - can you try importing and using ChatOpenAI instead? You may also want to switch to gpt-4o.

https://js.langchain.com/v0.2/docs/integrations/chat/openai

@jacoblee93 commented on GitHub (Jul 17, 2024): Yeah echoing @hwchase17 above, would suggest using a prompt to just manually summarize the messages. I also see this in your code: ```js new OpenAI({ model: "gpt-3.5-turbo", temperature: 0, apiKey: env.OPENAI_API_KEY, }), ``` `gpt-3.5-turbo` is a chat model - can you try importing and using `ChatOpenAI` instead? You may also want to switch to `gpt-4o`. https://js.langchain.com/v0.2/docs/integrations/chat/openai
Author
Owner

@DevDeepakBhattarai commented on GitHub (Jul 17, 2024):

Yeah echoing @hwchase17 above, would suggest using a prompt to just manually summarize the messages.

I also see this in your code:

new OpenAI({
  model: "gpt-3.5-turbo",
  temperature: 0,
  apiKey: env.OPENAI_API_KEY,
}),

gpt-3.5-turbo is a chat model - can you try importing and using ChatOpenAI instead? You may also want to switch to gpt-4o.

https://js.langchain.com/v0.2/docs/integrations/chat/openai

Thanks for you response @hwchase17 and @jacoblee93.
I will go ahead and implement the memory part my self rather then relying on ConversationSummaryBufferMemory.

And yes changing OpenAI to ChatOpenAI as suggested by @jacoblee93 does work fix the issue I was having.

@DevDeepakBhattarai commented on GitHub (Jul 17, 2024): > Yeah echoing @hwchase17 above, would suggest using a prompt to just manually summarize the messages. > > I also see this in your code: > > ```js > new OpenAI({ > model: "gpt-3.5-turbo", > temperature: 0, > apiKey: env.OPENAI_API_KEY, > }), > ``` > > `gpt-3.5-turbo` is a chat model - can you try importing and using `ChatOpenAI` instead? You may also want to switch to `gpt-4o`. > > https://js.langchain.com/v0.2/docs/integrations/chat/openai Thanks for you response @hwchase17 and @jacoblee93. I will go ahead and implement the memory part my self rather then relying on `ConversationSummaryBufferMemory`. And yes changing `OpenAI` to `ChatOpenAI` as suggested by @jacoblee93 does work fix the issue I was having.
Author
Owner

@DevDeepakBhattarai commented on GitHub (Jul 17, 2024):

hmm asking our team to look into it. Separately, I would likely not use ConversationSummaryBufferMemory here. I would use the built in persistence in LangGraph to manage the list of messages, and then just calculate the summary using a prompt rather than relying on the one in there

And could I ask you, What would be the most cost effective and efficient way for me to manage memory in a chat app , where this langgraph agent is fired on every user quesiton.

Where it has to remember all the previous messages from the chat and also the history during current (single) execution of the agent.

@DevDeepakBhattarai commented on GitHub (Jul 17, 2024): > hmm asking our team to look into it. Separately, I would likely not use ConversationSummaryBufferMemory here. I would use the built in persistence in LangGraph to manage the list of messages, and then just calculate the summary using a prompt rather than relying on the one in there And could I ask you, What would be the most cost effective and efficient way for me to manage memory in a chat app , where this langgraph agent is fired on every user quesiton. Where it has to remember all the previous messages from the chat and also the history during current (single) execution of the agent.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraphjs#49