mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-08-16 13:57:59 +00:00
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Vulnerability Scan / Detect changes (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Nightly Canary Release / Check for new commits (push) Has been cancelled
Nightly Canary Release / Build nightly packages (push) Has been cancelled
Nightly Canary Release / Publish nightly to PyPI (push) Has been cancelled
* docs: add Frontend guides section (CopilotKit + AG-UI) Add a Frontend sub-group under Guides documenting how to build user interfaces for CrewAI Crews and Flows with CopilotKit over the AG-UI protocol. Pages: overview, generative UI, tool-based generative UI, agentic generative UI, human-in-the-loop, shared state, frontend actions, predictive state updates, and channels. * docs: mirror Frontend guides into v1.15.5 (Latest) Also register the Frontend sub-group and pages under the default v1.15.5 version so the section is visible without switching to Edge. * docs(frontend): address audit — correct APIs and claims - Use useRenderTool for display-only tool rendering (was useFrontendTool) - Correct state 'auto-streams' claims: snapshot at step boundaries, document copilotkit_emit_state for mid-step progress - Fix setState usage to spread full state (replace, not merge) - Add tool description to the frontend-action example - Rewrite Channels with the real @copilotkit/channels createBot API (Slack + Discord adapters); drop unsupported platform claims - Note self-hosted vs managed CopilotKit paths and pin package versions * docs(frontend): remove versions callout from overview * docs(frontend): drop package-generation framing from emit_state note * docs(frontend): add generative UI spectrum (A2UI, reasoning) + Conversational Flows Rewrite generative-ui as the controlled/declarative/open-ended spectrum; add A2UI (declarative), Reasoning (controlled), and a Conversational Flows page; add a backend-tools section to tool-based; note the three execution shapes in the overview. * docs(frontend): address review — edge-only, attribute access, safe defaults Remove the docs/v1.15.5 mirror (versioned snapshots are cut from edge by the release tooling; the docs-snapshots CI guard rejects manual docs/v* writes). Use attribute access on the LiteLLM message in shared-state, guard setState against undefined agent/recipe, and use Field(default_factory=list) for the agent-state list.
211 lines
7.5 KiB
Plaintext
211 lines
7.5 KiB
Plaintext
---
|
|
title: Shared State
|
|
description: Keep your CrewAI agent's state and your app's UI in two-way sync, so edits on either side flow to the other.
|
|
icon: arrows-rotate
|
|
mode: "wide"
|
|
---
|
|
|
|
## 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.
|
|
</Note>
|
|
|
|
## How it works
|
|
|
|
<Steps>
|
|
|
|
<Step title="Define the shared state on your Flow">
|
|
|
|
Subclass `CopilotKitState` so the agent keeps CopilotKit's message plumbing, then add your own fields. Here the shared field is `recipe`.
|
|
|
|
```python
|
|
# 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
|
|
```
|
|
|
|
</Step>
|
|
|
|
<Step title="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`. A `generate_recipe` tool lets the model return the updated recipe as structured arguments.
|
|
|
|
```python
|
|
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):
|
|
pass
|
|
```
|
|
|
|
Two things make this shared rather than one-way: dumping `self.state` into the prompt means the agent always works from the latest recipe (including edits the user made in the UI), and assigning `self.state.recipe` puts 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 with `copilotkit_emit_state` (see [Agentic Generative UI](/en/guides/frontend/agentic-generative-ui)).
|
|
|
|
</Step>
|
|
|
|
<Step title="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](/en/guides/frontend/overview) for the full server and runtime setup.
|
|
|
|
```python
|
|
# 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",
|
|
)
|
|
```
|
|
|
|
</Step>
|
|
|
|
<Step title="Read and write the state from the UI">
|
|
|
|
`useAgent` gives you both directions in one hook. Read the shared state off `agent.state`, and write it back with `agent.setState(...)`. Subscribe to `OnStateChanged` so your component re-renders whenever the agent updates the state.
|
|
|
|
```tsx
|
|
"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.state` reads the shared state, `agent.setState(...)` writes it back so the agent sees the change on its next turn, and `agent.isRunning` reflects whether the agent is currently working.
|
|
|
|
<Note>
|
|
`setState` **replaces** 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.
|
|
</Note>
|
|
|
|
</Step>
|
|
|
|
</Steps>
|
|
|
|
## 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, and `OnStateChanged` re-renders your inputs.
|
|
- **User edits, agent sees it.** A change in the UI calls `agent.setState(...)`, and because the Flow dumps `self.state` into its prompt, the agent works from the edited recipe on its next turn.
|
|
|
|
## Related
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="Agentic Generative UI" icon="list-check" href="/en/guides/frontend/agentic-generative-ui">
|
|
Render live agent state as it changes.
|
|
</Card>
|
|
<Card title="Predictive State" icon="gauge-high" href="/en/guides/frontend/predictive-state-updates">
|
|
Stream in-progress state to the UI as the agent works.
|
|
</Card>
|
|
<Card title="Human-in-the-Loop" icon="user-check" href="/en/guides/frontend/human-in-the-loop">
|
|
Pause the agent to collect user approval or input mid-run.
|
|
</Card>
|
|
</CardGroup>
|