本页还没有译文
本页 22 段还没译成한국어,下面显示的是英文原文。Shared State
还没翻译译自英文(docs.crewai.com)本页 22 段都还没译
One state, both directions
Shared state is a single state object that the agent and the UI both read and write. The agent updates it as it works and your React components render it live. When the user edits that same state in the UI, the change flows back so the agent sees it on its next turn.
The classic example is a recipe: the agent drafts it, the user tweaks an ingredient or an instruction, and the agent picks up from the edited version. Neither side owns the state; they share it.
Note
Shared state relies on a Flow with custom state. Define an AgentState that subclasses CopilotKitState and type your Flow as Flow[AgentState]. Crews do not carry custom state, so this pattern is Flow-only.
How it works
-
Define the shared state on your Flow
Subclass
CopilotKitStateso the agent keeps CopilotKit's message plumbing, then add your own fields. Here the shared field isrecipe.# recipe_flow.py import json from typing import List, Optional from pydantic import BaseModel, Field from crewai.flow.flow import Flow, start, router, listen from litellm import acompletion from ag_ui_crewai.sdk import copilotkit_stream, CopilotKitState class Ingredient(BaseModel): name: str amount: str class Recipe(BaseModel): title: str ingredients: List[Ingredient] = Field(default_factory=list) instructions: List[str] = Field(default_factory=list) class AgentState(CopilotKitState): recipe: Optional[Recipe] = None -
Read and write the state from the agent
The agent reads the current state by dumping it into the system prompt, and writes it back by assigning to
self.state.recipe. Agenerate_recipetool lets the model return the updated recipe as structured arguments.GENERATE_RECIPE_TOOL = { "type": "function", "function": { "name": "generate_recipe", "description": "Generate or modify the recipe.", "parameters": { "type": "object", "properties": {"recipe": {"type": "object"}}, "required": ["recipe"], }, }, } class SharedStateFlow(Flow[AgentState]): @start() @listen("route_follow_up") async def start_flow(self): pass @router(start_flow) async def chat(self): # The current shared state is visible to the model. system_prompt = f"""You help the user build a recipe. Current recipe: {self.state.model_dump_json(indent=2)} Modify it by calling generate_recipe.""" response = await copilotkit_stream( await acompletion( model="openai/gpt-4o", messages=[ {"role": "system", "content": system_prompt}, *self.state.messages, ], tools=[*self.state.copilotkit.actions, GENERATE_RECIPE_TOOL], parallel_tool_calls=False, stream=True, ) ) message = response.choices[0].message self.state.messages.append(message) if message.tool_calls: call = message.tool_calls[0] if call.function.name == "generate_recipe": args = json.loads(call.function.arguments) self.state.recipe = Recipe(**args["recipe"]) # write to shared state self.state.messages.append({ "role": "tool", "content": "Recipe updated.", "tool_call_id": call.id, }) return "route_follow_up" return "route_end" @listen("route_end") async def end(self): passTwo things make this shared rather than one-way: dumping
self.stateinto the prompt means the agent always works from the latest recipe (including edits the user made in the UI), and assigningself.state.recipeputs the new value into the state snapshot sent to connected clients at the end of the step. For updates during a long step, emit explicitly withcopilotkit_emit_state(see Agentic Generative UI). -
Serve the Flow over AG-UI
Expose the Flow from your FastAPI app with
add_crewai_flow_fastapi_endpoint, then register it in the CopilotKit runtime. See the Frontend Overview for the full server and runtime setup.# server.py from fastapi import FastAPI from ag_ui_crewai.endpoint import add_crewai_flow_fastapi_endpoint from recipe_flow import SharedStateFlow app = FastAPI(title="CrewAI Agent Server") add_crewai_flow_fastapi_endpoint( app=app, flow=SharedStateFlow(), path="/shared_state", ) -
Read and write the state from the UI
useAgentgives you both directions in one hook. Read the shared state offagent.state, and write it back withagent.setState(...). Subscribe toOnStateChangedso your component re-renders whenever the agent updates the state."use client"; import { useAgent, UseAgentUpdate } from "@copilotkit/react-core/v2"; function RecipeEditor() { const { agent } = useAgent({ agentId: "shared_state", updates: [UseAgentUpdate.OnStateChanged], }); const state = agent?.state as { recipe?: Recipe } | undefined; const isLoading = agent?.isRunning; const recipe = state?.recipe; // setState replaces the whole state object, so spread the current // state and override only the field you changed. Passing just // `{ recipe }` would drop messages and other runtime fields. const updateRecipe = (patch: Partial<Recipe>) => agent?.setState({ ...(agent.state ?? {}), recipe: { ...(recipe ?? {}), ...patch } }); return ( <div> <input value={recipe?.title ?? ""} disabled={isLoading} onChange={(e) => updateRecipe({ title: e.target.value })} /> {/* render inputs for ingredients and instructions the same way */} </div> ); }agent.statereads the shared state,agent.setState(...)writes it back so the agent sees the change on its next turn, andagent.isRunningreflects whether the agent is currently working.Note
setStatereplaces the entire state object rather than merging. Always spread the current state ({ ...agent.state, ... }) and override only the fields you are changing, or you will drop the conversation and other runtime fields the agent depends on.
The two-way loop
Putting the pieces together, a single recipe object is kept in sync in both directions:
- Agent edits, UI updates. The Flow assigns
self.state.recipe, the new value ships in the step's state snapshot, andOnStateChangedre-renders your inputs. - User edits, agent sees it. A change in the UI calls
agent.setState(...), and because the Flow dumpsself.stateinto its prompt, the agent works from the edited recipe on its next turn.
Related
Render live agent state as it changes.
Stream in-progress state to the UI as the agent works.
Pause the agent to collect user approval or input mid-run.