主题
共享状态
让 CrewAI 智能体的状态与你应用的 UI 双向同步,任一侧的编辑都会流向另一侧。
一份状态,双向同步
共享状态是一个单一的状态对象,智能体和 UI 都可读写。智能体在工作过程中更新它,你的 React 组件实时渲染它。当用户在 UI 中编辑同一份状态时,改动会流回去,智能体在下一轮就能看到。
经典例子是食谱:智能体起草它,用户调整某个配料或某条步骤,然后智能体从编辑后的版本继续。双方都不拥有这份状态;他们共享它。
NOTE
共享状态依赖于带自定义状态的 Flow。定义一个继承 CopilotKitState 的 AgentState,并把你的 Flow 类型标注为 Flow[AgentState]。Crews 不携带自定义状态,因此此模式仅适用于 Flow。
工作原理
Define the shared state on your Flow
继承 CopilotKitState,使智能体保留 CopilotKit 的消息管线,然后添加你自己的字段。这里的共享字段是 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] = NoneRead and write the state from the agent
智能体通过把当前状态转储到系统提示词中来读取它,并通过给 self.state.recipe 赋值来写回。generate_recipe 工具让模型以结构化参数的形式返回更新后的食谱。
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有两点让它成为共享而非单向:把 self.state 转储进提示词意味着智能体总是基于最新的食谱工作(包括用户在 UI 中所做的编辑),而给 self.state.recipe 赋值会把新值放入该步骤结束时发送给已连接客户端的状态快照中。若要在长步骤进行期间更新,请用 copilotkit_emit_state 显式发送(参见 智能体化生成式 UI)。
Serve the Flow over AG-UI
用 add_crewai_flow_fastapi_endpoint 从你的 FastAPI 应用中暴露该 Flow,然后在 CopilotKit 运行时中注册它。完整的服务端与运行时设置参见 前端概览。
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",
)Read and write the state from the UI
useAgent 用一个 hook 同时提供两个方向。从 agent.state 读取共享状态,用 agent.setState(...) 写回。订阅 OnStateChanged,这样每当智能体更新状态时你的组件都会重新渲染。
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 读取共享状态,agent.setState(...) 把它写回,使智能体在下一轮看到改动,agent.isRunning 反映智能体当前是否在工作。
NOTE
setState 会替换整个状态对象,而不是合并。始终展开当前状态({ ...agent.state, ... }),只覆盖你要改的字段,否则你会丢掉对话以及智能体依赖的其他运行时字段。
双向循环
把这些部分组合起来,一个食谱对象就在两个方向上保持同步:
- 智能体编辑,UI 更新。 Flow 给
self.state.recipe赋值,新值随该步骤的状态快照发出,OnStateChanged重新渲染你的输入项。 - 用户编辑,智能体看见。 UI 中的改动调用
agent.setState(...),而由于 Flow 会把self.state转储进其提示词,智能体在下一轮就基于编辑后的食谱工作。