Back to Copilotkit

State Rendering

showcase/shell-docs/src/content/docs/integrations/deepagents/generative-ui/state-rendering.mdx

1.68.27.2 KB
Original Source

<IframeSwitcher id="agent-state-example" exampleUrl="https://feature-viewer.copilotkit.ai/langgraph/feature/agentic_generative_ui?sidebar=false&chatDefaultOpen=false" codeUrl="https://feature-viewer.copilotkit.ai/langgraph/feature/agentic_generative_ui?view=code&sidebar=false&codeLayout=tabs" exampleLabel="Demo" codeLabel="Code" height="700px" />

What is this?

State rendering lets you build UI that reflects your agent's state in real-time. As your agent progresses through nodes and emits state updates, your frontend renders those changes — showing progress, drafts, or intermediate results.

When should I use this?

Use state rendering when you want to:

  • Show real-time progress (e.g. "Researching... 2/5 complete")
  • Display drafts that update as the agent works
  • Build dashboards that reflect agent state
  • Render structured output outside of the chat

Implementation

<Steps> <Step> ### Run and connect your agent <RunAndConnect /> </Step> <Step> ### Build an agent that produces state
Define the `searches` state, then add a tool that returns each completed update.

<Tabs groupId="agent_language" items={['Python', 'TypeScript']} persist>
  <Tab value="Python">
    ```python title="agent.py"
    from typing import Any, TypedDict

    from copilotkit import (
        CopilotKitMiddleware,
        CopilotKitState,
        StateItem,
        StateStreamingMiddleware,
    )
    from deepagents import create_deep_agent
    from langchain.agents.middleware import AgentMiddleware
    from langchain.messages import ToolMessage
    from langchain.tools import ToolRuntime, tool
    from langgraph.types import Command

    class Search(TypedDict):
        query: str
        done: bool

    class AgentState(CopilotKitState):
        searches: list[Search]

    class SearchesStateMiddleware(AgentMiddleware[AgentState, Any, Any]):
        state_schema = AgentState

    @tool
    def report_research_progress(
        searches: list[Search],
        runtime: ToolRuntime[None, AgentState],
    ) -> Command:
        """Report the current research tasks and completion status."""
        return Command(
            update={
                "searches": searches,
                "messages": [
                    ToolMessage(
                        content="Research progress saved.",
                        tool_call_id=runtime.tool_call_id,
                    )
                ],
            }
        )

    agent = create_deep_agent(
        model="openai:gpt-5.4",
        tools=[report_research_progress],
        middleware=[
            SearchesStateMiddleware(),
            CopilotKitMiddleware(),
            StateStreamingMiddleware(
                StateItem(
                    state_key="searches",
                    tool="report_research_progress",
                    tool_argument="searches",
                )
            ),
        ],
        system_prompt=(
            "You are a research assistant. Use report_research_progress "
            "to show each task and mark it done when complete."
        ),
    )
    ```
  </Tab>
  <Tab value="TypeScript">
    ```ts title="agent.ts"
    import { ToolMessage } from "@langchain/core/messages";
    import { tool, type ToolRuntime } from "@langchain/core/tools";
    import { Command } from "@langchain/langgraph";
    import {
      copilotkitMiddleware,
      zodState,
    } from "@copilotkit/sdk-js/langgraph";
    import {
      stateItem,
      stateStreamingMiddleware,
    } from "@copilotkit/sdk-js/langgraph-middlewares";
    import { createDeepAgent } from "deepagents";
    import { createMiddleware } from "langchain";
    import { z } from "zod";

    const SearchSchema = z.object({
      query: z.string(),
      done: z.boolean(),
    });
    type Search = z.infer<typeof SearchSchema>;

    const SearchesStateSchema = z.object({
      searches: z.array(SearchSchema),
    });

    const searchesStateMiddleware = createMiddleware({
      name: "SearchesState",
      stateSchema: z.object({
        searches: zodState(z.array(SearchSchema).default(() => [])),
      }),
    });

    const reportResearchProgress = tool(
      (
        input: { searches: Search[] },
        runtime: ToolRuntime<typeof SearchesStateSchema>,
      ) =>
        new Command({
          update: {
            searches: input.searches,
            messages: [
              new ToolMessage({
                content: "Research progress saved.",
                tool_call_id: runtime.toolCallId,
              }),
            ],
          },
        }),
      {
        name: "report_research_progress",
        description:
          "Report the current research tasks and completion status.",
        schema: z.object({ searches: z.array(SearchSchema) }),
      },
    );

    export const agent = createDeepAgent({
      model: "openai:gpt-5.4",
      tools: [reportResearchProgress],
      middleware: [
        searchesStateMiddleware,
        copilotkitMiddleware,
        stateStreamingMiddleware(
          stateItem({
            stateKey: "searches",
            tool: "report_research_progress",
            toolArgument: "searches",
          }),
        ),
      ],
      systemPrompt:
        "You are a research assistant. Use report_research_progress " +
        "to show each task and mark it done when complete.",
    });
    ```
  </Tab>
</Tabs>
</Step> <Step> ### Understand the two update phases
The state-streaming middleware sends partial `searches` arguments while the model creates them. The frontend can show each partial value immediately.

The tool then returns a `Command` that saves the completed list. Its `ToolMessage` closes the active tool call.

Keep the state key, tool name, and tool argument identical. A mismatch sends updates to the wrong state field.
</Step> <Step> ### Render state in the UI
Use the `useAgent` hook to access agent state anywhere in your app. You can render it in the chat, in dashboards, sidebars, or custom layouts.

```tsx title="app/page.tsx"
import { useAgent } from "@copilotkit/react-core/v2"; // [!code highlight]

function YourMainContent() {
  // [!code highlight:3]
  const { agent } = useAgent({
    agentId: "sample_agent",
  });

  const state = (agent.state ?? {}) as {
    searches?: { query: string; done: boolean }[];
  };
  const searches = state.searches ?? [];

  return (
    <div>
      {searches.map((search, index) => (
        <div key={index}>
          {search.done ? "✅" : "⏳"} {search.query}
        </div>
      ))}
    </div>
  );
}
```
</Step> <Step> ### Give it a try!
Ask the agent to research a topic. The search items appear and update while the agent works.
</Step> </Steps>