Skip to content
CrewAI 文档版本 跟随上游 源快照 4e126a1d 更新于 2026-10-18

共享状态

让 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] = None

Read 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 转储进其提示词,智能体在下一轮就基于编辑后的食谱工作。

相关

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。