CopilotKit 与 CrewAI Conversational Flows:Shared State 只读模式的完整 QA 验证指南
2026/9/12 19:11:22 网站建设 项目流程

CopilotKit 与 CrewAI Conversational Flows:Shared State 只读模式的完整 QA 验证指南

【免费下载链接】CopilotKitThe Frontend Stack for Agents & Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit

导读

本文围绕 CopilotKit 集成示例crewai-conversational-flows中的Shared State(Reading)演示(shared-state-read),系统讲解如何对「前端发布、Agent 只读」的共享状态功能进行端到端 QA 验证。读完本文,你将掌握该 demo 的功能验收清单、UI 测试断言(data-testid)、关键源码实现(agent.setState与后端 Flow 的状态注入),以及如何用 Playwright 自动化复现测试步骤。

什么是 Shared State(Reading)Demo

在 CopilotKit 中,useAgent暴露的agent.state是前端与 Agent 之间共享的单一事实来源(single source of truth)。shared-state-read演示的核心设计非常明确:

  • UI 拥有状态:前端通过agent.setState将菜谱数据写入 agent 状态;
  • Agent 只读:后端 Flow 每一轮对话都会读取该菜谱,但不提供任何修改工具,因此 UI 始终是唯一的数据权威;
  • 双向感知:用户既可以在表单中编辑状态,也可以直接向 Agent 提问“我在做什么菜谱”,Agent 的回答会实时引用当前状态。

该 demo 的源码位于 showcase/integrations/crewai-conversational-flows/src/app/demos/shared-state-read/,对应清单条目shared-state-read在 manifest.yaml 中被标记为shared-state-read特性,与同目录下的shared-state-read-write(双向读写)形成对照。

验证前置条件

开始 QA 前,需满足以下两个条件(详见 QA 文档):

  1. Demo 已部署且可访问:本地开发或 Docker Compose 环境均可,生产环境通过 Railway 部署;
  2. Agent 后端健康:访问/api/health接口应返回正常状态。

本地 D6 验证可通过crewai-conversational-flows的 compose 服务在端口3120上运行(见 PARITY_NOTES.md)。该集成包基于 CrewAI 官方 Conversational Flows API:每个 Agent 路由以conversational=True注册,Flow 通过stream_turn(message, session_id=...)处理对话,并将 AG-UI 的threadId作为 CrewAI 会话的session_id

一、基础功能验证

QA 文档的第一组检查点覆盖页面加载与基本对话链路:

  • 导航至shared-state-readdemo 页面;
  • 验证菜谱卡片表单加载成功(data-testid="recipe-card");
  • 验证CopilotSidebar默认打开,标题为"AI Recipe Assistant"
  • 通过侧边栏发送一条消息;
  • 验证 Agent 正常响应。

在源码中,这些要求逐一对应。页面主体由 page.tsx 构成:

<CopilotKit runtimeUrl="/api/copilotkit" agent="shared-state-read"> <CopilotSidebar defaultOpen labels={{ modalHeaderTitle: "AI Recipe Assistant" }} /> </CopilotKit>

defaultOpen保证了侧边栏默认展开,labels.modalHeaderTitle控制标题文案。而data-testid="recipe-card"由表单组件 recipe-card.tsx 暴露:

<form>await expect(page.locator('[data-testid="recipe-card"]')).toBeVisible({ timeout: 15000, }); await expect(page.getByText("AI Recipe Assistant")).toBeVisible({ timeout: 10000, });

二、初始菜谱状态验证

QA 文档要求核对表单的默认值。这些默认值并非硬编码在 JSX 中,而是集中定义在 types.ts 的INITIAL_RECIPE常量里:

export const INITIAL_RECIPE: RecipeData = { title: "Make Your Recipe", skill_level: SkillLevel.INTERMEDIATE, cooking_time: CookingTime.FortyFiveMin, special_preferences: [], ingredients: [ { icon: "🥕", name: "Carrots", amount: "3 large, grated" }, { icon: "🌾", name: "All-Purpose Flour", amount: "2 cups" }, ], instructions: ["Preheat oven to 350°F (175°C)"], };

对应 QA 检查点:

QA 检查点源码位置
菜谱标题默认为 "Make Your Recipe"INITIAL_RECIPE.title
烹饪时间下拉默认 "45 min"CookingTime.FortyFiveMin,枚举值见types.ts
技能等级下拉默认 "Intermediate"SkillLevel.INTERMEDIATE
默认食材:胡萝卜(3 large, grated,🥕)、通用面粉(2 cups,🌾)INITIAL_RECIPE.ingredients
默认步骤:"Preheat oven to 350 F"INITIAL_RECIPE.instructions

烹饪时间下拉的默认值在recipe-card.tsx中通过索引映射实现:cookingTimeValues.find((t) => t.label === recipe.cooking_time)?.value ?? 3,其中value: 3恰好对应45 min,兜底值保证了异常情况下的默认行为。

初始状态的注入时机

一个容易忽略的实现细节是:初始菜谱并非在组件渲染时就写入状态,而是通过useEffect在首次渲染后一次性播种(见 page.tsx):

useEffect(() => { if (!(agent.state as RecipeAgentState | undefined)?.recipe) { agent.setState({ recipe: INITIAL_RECIPE } satisfies RecipeAgentState); } }, []);

这段代码的含义是:只有当agent.state.recipe尚不存在时才写入初始值,确保 Agent 在第一轮对话时就能读到内容;此后所有编辑都走agent.setState通道。QA 中验证“初始菜谱正确显示”,本质上就是在验证这次播种是否成功。

三、建议(Suggestions)验证

QA 要求确认三条起步建议可见:

  • Create Italian recipe
  • Make it healthier
  • Suggest variations

这三条建议由useConfigureSuggestions配置,位于 page.tsx:

useConfigureSuggestions({ suggestions: [ { title: "Create Italian recipe", message: "Create a delicious Italian pasta recipe." }, { title: "Make it healthier", message: "Make the recipe healthier with more vegetables." }, { title: "Suggest variations", message: "Suggest some creative variations of this recipe." }, ], available: "always", });

available: "always"表示建议在对话过程中始终可用(而非仅在首屏出现)。Playwright 测试对这三条建议做了逐条可见性断言(page.getByRole("button", { name: title })),可直接用于自动化回归。

四、本地状态编辑验证(Local State)

QA 文档中篇幅最大的一组检查点是不经过 AI、纯前端驱动的表单编辑。核心设计是:表单是一个完全受控组件(fully controlled component),每一次编辑都立即通过agent.setState同步进共享状态(见 page.tsx):

const handleChange = (next: RecipeData) => { agent.setState({ recipe: next } satisfies RecipeAgentState); };

受控组件的更新链在 recipe-card.tsx 中实现:update(partial)做浅合并,updateIngredientupdateInstruction分别做不可变数组更新。QA 步骤与实现对应关系如下:

QA 步骤实现要点
编辑标题update({ title: e.target.value })aria-label="Recipe title"
切换技能等级下拉update({ skill_level: value as SkillLevel })
切换烹饪时间下拉通过cookingTimeValues[Number(value)].label写入
切换饮食偏好(如 "Vegetarian")recipe.special_preferences数组的增删,Badge组件以aria-pressed标记选中态
点击 "+ Add Ingredient"data-testid="add-ingredient-button",追加{ icon: "🍴", name: "", amount: "" }空行
编辑食材名称与数量updateIngredient(index, field, value)
点击 "x" 删除食材过滤recipe.ingredients数组
点击 "+ Add Step"追加空字符串到instructions
编辑步骤文案updateInstruction(index, value)
点击 "x" 删除步骤过滤instructions数组

偏好选项来自 types.ts 的SpecialPreferences枚举:High Protein、Low Carb、Spicy、Budget-Friendly、One-Pot Meal、Vegetarian、Vegan,共 7 个可多选标签。

自动化方面,Playwright 测试验证了 "Add Ingredient" 的行为(shared-state-read.spec.ts):

const ingredientCards = page.locator('[data-testid="ingredient-card"]'); const initialCount = await ingredientCards.count(); await page.locator('[data-testid="add-ingredient-button"]').click(); await expect(ingredientCards).toHaveCount(initialCount + 1, { timeout: 5000 });

五、AI 驱动的菜谱更新(useAgent + 共享状态)

这一节验证 Agent 在读取共享状态后给出文本建议,但不会真的改写 UI 数据。QA 步骤为:

  1. 点击 "Create Italian recipe" 建议;
  2. 验证 Agent 根据当前菜谱输出关于标题、食材、步骤的建议;
  3. 验证ping indicator在变更区域出现;
  4. 验证 "Improve with AI" 按钮(data-testid="improve-button")在加载中变为 "Please Wait...";
  5. 点击 "Improve with AI" 并验证菜谱被增强。

其中handleImprove的实现展示了「手动触发生成」的完整调用链(page.tsx):

const handleImprove = () => { if (agent.isRunning) return; agent.addMessage({ id: crypto.randomUUID(), role: "user", content: "Improve the recipe", }); void copilotkit .runAgent({ agent }) .catch((err) => console.error("[shared-state-read] runAgent failed", err)); };

按钮的加载态由isLoading(即agent.isRunning)驱动(recipe-card.tsx):

<Button>SYSTEM_PROMPT = ( "You are a concise recipe assistant. The frontend-owned recipe state is " "included below. Read it when answering, but never claim to have edited " "the recipe because this demo intentionally gives the agent read-only " "access.\n\nCurrent recipe state:\n{recipe}" ) class SharedStateReadState(CopilotKitState): recipe: dict[str, Any] | None = None class SharedStateReadFlow(Flow[SharedStateReadState]): @start() async def chat(self) -> None: recipe = json.dumps(self.state.recipe or {}, indent=2, ensure_ascii=False) response = await copilotkit_stream( await acompletion( model="openai/gpt-5.4", messages=[ {"role": "system", "content": SYSTEM_PROMPT.format(recipe=recipe)}, *self.state.messages, ], tools=self.state.copilotkit.actions, stream=True, ) ) self.state.messages.append(response.choices[0].message)

三个关键点值得深入理解:

  1. 状态注入:每个对话轮次都会把self.state.recipe序列化为 JSON,拼进 system prompt。这意味着 Agent 每次回答都基于最新的前端状态,用户中途的任何编辑都会立即生效;
  2. 只读约束:后端不注册任何写入工具(tools为空列表),system prompt 还显式要求 Agent “不要声称编辑过菜谱”——这是该 demo 与shared-state-read-write的本质区别;
  3. 无状态上下文传递:前端无需把菜谱内容当作消息上下文发送,Agent 直接从runtime.state读取,这正是共享状态模式相比“手动拼上下文”的优势。

该 Flow 在 conversational_flows.py 中注册为_conversational_type(SharedStateReadFlow),对应shared-state-readagent 名称,与前端agent="shared-state-read"useAgent({ agentId: "shared-state-read" })一一对应。

自动化回归中,发送侧边栏消息的断言方式为(shared-state-read.spec.ts):

const input = page.getByPlaceholder("Type a message"); await input.fill("What recipe am I making?"); await input.press("Enter"); await expect( page.locator('[data-testid="copilot-assistant-message"]').first(), ).toBeVisible({ timeout: 30000 });

七、错误处理验证

QA 文档的错误处理检查点有三项:

检查点实现/验证方式
发送空消息应被优雅处理侧边栏消息发送前对空内容进行过滤/拦截,不触发 Agent 调用
正常使用过程无 console 错误handleImproverunAgent的 Promise 做了.catch兜底,异常只会console.error而不会抛出未捕获错误
"Improve with AI" 在加载期间禁用disabled={isLoading}+if (agent.isRunning) return;双重防护

recipe-card.tsx中的isLoadingagent.isRunning派生,因此 Agent 执行期间的任何并发触发都会被拦截,避免状态竞争。

八、预期结果与验收标准

QA 文档给出的最终验收标准如下:

  • 菜谱卡片与侧边栏 3 秒内加载完成
  • Agent 10 秒内响应
  • 菜谱状态在 UI 与 Agent 之间双向同步(UI 编辑 → Agent 读取到新值;Agent 建议 → UI 呈现);
  • ping indicator 高亮被修改的区域
  • 无 UI 错误、无布局破坏

Playwright 配置中对应的超时阈值(15s 定位、30s 响应等待)为线上环境留出了余量,实际验收以文档中的 3s/10s 为目标基准。自动化测试覆盖了“页面加载 + 侧边栏挂载”“建议渲染”“添加食材”“发送消息并收到响应”四条核心链路,其余编辑类操作可参照 QA 清单在手工测试中逐项核对。

结语:从验证清单反推架构

shared-state-read的 QA 文档表面上是一份功能清单,但其背后是 CopilotKit 共享状态模式的一种最小可信实现:前端agent.setState负责写、后端 Flow 通过 system prompt 注入负责读、类型定义RecipeAgentState在前后端之间维持契约。对照同目录的shared-state-read-writedemo(Agent 侧可写回 notes),可以清晰看到 CopilotKit 在「只读上下文」与「双向共享状态」之间的设计取舍。若要在自己的项目中复刻该模式,只需三件事:定义一个共享状态类型、在 UI 中调用agent.setState、在后端 Flow 中把对应字段注入每次模型调用。

【免费下载链接】CopilotKitThe Frontend Stack for Agents & Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询