showcase/shell-docs/src/content/docs/integrations/langgraph/advanced/emit-api-hook-mapping.mdx
CopilotKit's Python SDK provides emit functions that your LangGraph nodes can call to push tool calls and control streaming behavior. On the frontend, React v2 provides hooks that register handlers and renderers for those emitted events.
This page shows exactly how the two sides connect so you can build predictable, end-to-end integrations without guessing.
| Backend (Python SDK) | Frontend (React v2) | What it does |
|---|---|---|
copilotkit_emit_tool_call(config, name, args) | useFrontendTool | Agent manually fires a tool call; the registered handler runs in the browser |
LLM calls a frontend tool normally (via bind_tools) | useFrontendTool | Agent lets the LLM decide when to call the tool; same handler, same result |
LLM calls a frontend tool (via bind_tools) | useComponent | Agent calls a tool and the registered component is rendered inline in the chat |
copilotkit_customize_config(config, emit_tool_calls=["ToolName"]) | useFrontendTool / useComponent | Controls which tool calls are streamed to the frontend; use this to filter noise |
copilotkit_customize_config(config, emit_tool_calls=False) | — | Suppresses all tool call streaming for that LLM invocation |
useComponent and useFrontendTool both respond to persisted tool calls triggered by the LLM. copilotkit_emit_tool_call lets you fire the same frontend mechanism imperatively, without waiting for the LLM.
</Callout>
copilotkit_emit_tool_call → useFrontendToolThis pattern lets a LangGraph node directly invoke a frontend function — for example, to update React state, show a toast, or trigger a browser API — without the LLM needing to decide when to call it.
import { z } from "zod";
import { useFrontendTool } from "@copilotkit/react-core/v2"; // [!code highlight]
export function Page() {
const [status, setStatus] = React.useState("");
// [!code highlight:10]
useFrontendTool({
name: "updateStatus",
description: "Update the status displayed in the UI.",
parameters: z.object({
message: z.string().describe("The status message to display"),
}),
handler: async ({ message }) => {
setStatus(message);
return `Status updated to: ${message}`;
},
});
return <div>Agent says: {status}</div>;
}
from langchain_core.runnables import RunnableConfig
from copilotkit import CopilotKitState
from copilotkit.langgraph import copilotkit_emit_tool_call # [!code highlight]
class AgentState(CopilotKitState):
pass
async def my_node(state: AgentState, config: RunnableConfig):
# Imperatively fires the frontend tool handler without the LLM
await copilotkit_emit_tool_call( # [!code highlight]
config, # [!code highlight]
name="updateStatus", # [!code highlight]
args={"message": "Processing your request..."}, # [!code highlight]
) # [!code highlight]
# ... do other work ...
await copilotkit_emit_tool_call(
config,
name="updateStatus",
args={"message": "Done!"},
)
return state
When copilotkit_emit_tool_call is called:
useFrontendTool handler matching "updateStatus" runs in the browser immediately.state["messages"] unless you add it yourself.useComponentThis pattern lets the LLM decide when to render a custom React component in the chat, passing its parameters as props.
import { useComponent } from "@copilotkit/react-core/v2"; // [!code highlight]
import { z } from "zod";
function WeatherCard({ city, temperature, condition }: {
city: string;
temperature: number;
condition: string;
}) {
return (
<div className="rounded-lg border p-4">
<h3 className="font-semibold">{city}</h3>
<p className="text-2xl">{temperature}°F</p>
<p className="text-sm text-gray-500">{condition}</p>
</div>
);
}
export function Page() {
// [!code highlight:10]
useComponent({
name: "showWeather",
description: "Display a weather card for a city.",
parameters: z.object({
city: z.string(),
temperature: z.number(),
condition: z.string(),
}),
render: WeatherCard,
});
return <div></div>;
}
bind_toolsfrom langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnableConfig
from copilotkit import CopilotKitState
class AgentState(CopilotKitState):
pass
async def chat_node(state: AgentState, config: RunnableConfig):
model = ChatOpenAI(model="gpt-4o").bind_tools(
state["copilotkit"]["actions"] # includes all registered frontend tools/components # [!code highlight]
)
response = await model.ainvoke(state["messages"], config)
return {"messages": response}
When the LLM calls showWeather:
state["messages"] (persisted).useComponent renders WeatherCard inline in the chat with the tool arguments as props.copilotkit_customize_configBy default, CopilotKit streams all LLM tool calls to the frontend. Use copilotkit_customize_config to filter them.
from copilotkit.langgraph import copilotkit_customize_config # [!code highlight]
async def chat_node(state: AgentState, config: RunnableConfig):
# Only stream "showWeather" to the frontend; suppress all others
streaming_config = copilotkit_customize_config( # [!code highlight]
config, # [!code highlight]
emit_tool_calls=["showWeather"], # [!code highlight]
) # [!code highlight]
model = ChatOpenAI(model="gpt-4o").bind_tools(
state["copilotkit"]["actions"]
)
response = await model.ainvoke(state["messages"], streaming_config) # [!code highlight]
return {"messages": response}
emit_tool_calls options at a glance| Value | Effect |
|---|---|
True (default) | Stream all tool calls to the frontend |
False | Suppress all tool call streaming for this LLM call |
"ToolName" | Stream only the named tool call |
["Tool1", "Tool2"] | Stream only the listed tool calls |
| Goal | Recommended pattern |
|---|---|
| Agent imperatively triggers a browser action (toast, state update) | copilotkit_emit_tool_call → useFrontendTool |
| LLM decides when to trigger a browser action | bind_tools(state["copilotkit"]["actions"]) → useFrontendTool |
| LLM renders a rich component in chat (persisted in history) | bind_tools(state["copilotkit"]["actions"]) → useComponent |
| Hide certain LLM tool calls from the frontend | copilotkit_customize_config(emit_tool_calls=[...]) |
| Hide all tool calls from the frontend for a sensitive LLM call | copilotkit_customize_config(emit_tool_calls=False) |
useFrontendTooluseComponentcopilotkit_customize_config reference