CopilotKit icon indicating copy to clipboard operation
CopilotKit copied to clipboard

🐛 Bug: Copilot Actions does NOT work with Langgraph which has Tool Node

Open Hiraveb opened this issue 7 months ago • 2 comments

♻️ Reproduction Steps

  1. Create langgraph graph with ToolNode
  2. Define copilot actions on frontend
  3. run the agent
  4. frontend action wont get triggered ...

in the graph node, to make fronend actions, we need to attach copilot actions like

        inference_model_with_tools = llm.bind_tools(
            [*state["copilotkit"]["actions"], *tools]
        )

ToolNode dont know know anything about state["copilotkit"]["actions"]

here toolNode is from langgraph.prebuilt import ToolNode

✅ Expected Behavior

Langgraph agent will handle copilot actions and ToolNode actions on backend using ToolNode of graph

❌ Actual Behavior

copilot actions wont get triggered

𝌚 CopilotKit Version

"copilotkit>=0.1.46",

📄 Logs (Optional)


Hiraveb avatar May 18 '25 04:05 Hiraveb

We appreciate you notifying us about this. Our team will investigate. For urgent production-related matters, please schedule a 15-minute discussion: https://cal.com/nathan-tarbert-copilotkit/15min

copilotkit-support avatar May 19 '25 07:05 copilotkit-support

Hi @Hiraveb, you actually don't need to go to a tool node when a CopilotKit action gets called. Effectively CopilotKit will handle it on your behalf. In this situation you just need to see if the LLM called a CopilotKit action and not go to the tool node if it did.

For example, give this code a look.

https://github.com/CopilotKit/CopilotKit/blob/8c72eb24b54796264f7f4f0b30b825bbf3215221/examples/coagents-starter/agent-py/sample_agent/agent.py#L85-L96

tylerslaton avatar May 19 '25 19:05 tylerslaton

Hi @tylerslaton , we have existing tools in the backend defined.

expectation is that both tools defined - Frotnend and Backend will be handled through langgraph agent

Is this supported

Hiraveb avatar May 19 '25 19:05 Hiraveb

There are three ways that tools can be used in CopilotKit.

  1. Frontend Actions via useCopilotAction in the frontend.
  2. Backend Actions defined in your Copilot Runtime.
  3. Agent tools defined inside of agent itself (such as LangGraph here).

CopilotKit supports all three. For both frontend actions and backend actions just make sure you do not go to the tool node. For everything else, go to the tool node. The code I linked does exactly that.

tylerslaton avatar May 19 '25 19:05 tylerslaton

How does LangGraph await the completion of a frontend/backend action and transition to different nodes based on the action's tool message?

rogerkuo1689 avatar Jul 15 '25 14:07 rogerkuo1689

CopilotKit Actions and LangGraph ToolNode are architecturally different and should not be combined directly. CopilotKit Actions are handled by the CopilotKit runtime/frontend system, while ToolNode is only designed for regular LangChain tools. Instead of attaching CopilotKit Actions to ToolNode, you should use conditional routing to ensure CopilotKit Actions are handled by the frontend and only regular tools are routed to ToolNode.

Why CopilotKit Actions Don't Work with ToolNode

CopilotKit Actions and LangGraph ToolNode are architecturally different and should not be combined directly:

Different execution models:

  • CopilotKit Actions are handled by the CopilotKit runtime/frontend system
  • ToolNode is only designed for regular LangChain tools
  • CopilotKit Actions bypass ToolNode entirely and are processed by frontend UI components

Runtime warning: The CopilotKit runtime explicitly warns that "Local 'actions' defined in CopilotRuntime might not be available to remote agents (LangGraph, MCP)"

Frontend integration: CopilotKit Actions trigger frontend UI components (like renderAndWaitForResponse) rather than executing in the graph

Proper Integration Approach

Instead of attaching Actions to ToolNode, use conditional routing:

1. Bind Both Action Types to Model

// JavaScript example
const model = new ChatOpenAI().bindTools([
  ...convertActionsToDynamicStructuredTools(actions), // CopilotKit actions
  ...regularTools // Regular LangChain tools
]);

2. Implement Conditional Routing Logic

JavaScript Implementation:

function shouldContinue(state: AgentState): "tool_node" | "__end__" {
  const messages = state.messages;
  const lastMessage = messages[messages.length - 1] as AIMessage;
  
  if (lastMessage.tool_calls?.length) {
    const actions = copilotkit?.actions;
    const toolCallName = lastMessage.tool_calls![0].name;
    
    // Only route to tool node if NOT a CopilotKit action
    if (!actions || actions.every((action) => action.name !== toolCallName)) {
      return "tool_node"; // Regular tool - execute in ToolNode
    }
  }
  return "__end__"; // CopilotKit action - handled by frontend
}

Python Implementation:

def chat_node(state: AgentState):
    response = model.invoke(state["messages"])
    
    if response.tool_calls:
        # Check if tool call is NOT a CopilotKit action
        if not any(
            action.get("name") == response.tool_calls[0].get("name") 
            for action in actions
        ):
            # Regular tool - route to ToolNode
            return Command(goto="tool_node", update={"messages": response})
    
    # CopilotKit action or no tool call - continue/end
    return {"messages": [response]}

3. Graph Structure

const workflow = new StateGraph(AgentState)
  .addNode("agent", callModel)
  .addNode("tool_node", toolNode) // Only for regular tools
  .addEdge(START, "agent")
  .addConditionalEdges("agent", shouldContinue)
  .addEdge("tool_node", "agent");

Key Takeaways

Don't combine them directly: CopilotKit Actions should never be added to ToolNode • Use conditional routing: Check if a tool call is a CopilotKit Action before routing to ToolNode
Frontend handles Actions: CopilotKit Actions are processed by the UI components, not the graph • Separate but coordinated: Both action types can coexist through proper routing logic

Documentation References:

Examples:


Was this helpful?

If this solution worked for you, please click on the appropriate option below to help us improve:

✅ Issue Solved | ❌ Need more help