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

基于工具的生成式 UI

把 CrewAI 智能体的工具调用映射到 React 组件,并在参数到达时将其流式传入。

将工具调用渲染为组件

当你的 Crew 或 Flow 调用工具时,你通常不希望把原始参数直接倒进聊天里。基于工具的生成式 UI 会把智能体调用的每个工具映射到一个你自己拥有的 React 组件。智能体决定何时调用工具;你决定用户看到什么。

由于 CopilotKit 会在模型生成工具调用的同时把它流式传输到前端,参数会逐步填充。你的组件可以在第一个字段到达时就绘制出来,并在其余内容流入时更新。

本指南构建一个俳句生成器:智能体调用 generate_haiku 工具,前端把每首俳句渲染为一张卡片。它假定你已经有一个与 Next.js 应用通信的 Crew 或 Flow。如果没有,请从 前端概览 开始,了解完整的服务端、运行时与服务商设置。

NOTE

工具渲染对 Crews 和 Flows 都适用。下面的示例使用 Flow,但无论哪种方式,前端接线完全相同。

分步讲解

Define the tool on the backend

用 JSON schema 声明该工具并传给模型。copilotkit_stream 包装器配合 stream=True,才能在工具调用生成时逐个参数分块地将其流式传输到前端。

python
# haiku_flow.py
from crewai.flow.flow import Flow, start
from litellm import acompletion
from ag_ui_crewai.sdk import copilotkit_stream, CopilotKitState

GENERATE_HAIKU_TOOL = {
    "type": "function",
    "function": {
        "name": "generate_haiku",
        "description": "Generate a haiku in Japanese and its English translation",
        "parameters": {
            "type": "object",
            "properties": {
                "japanese": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Three lines in Japanese",
                },
                "english": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Three lines in English",
                },
            },
            "required": ["japanese", "english"],
        },
    },
}

class HaikuFlow(Flow[CopilotKitState]):
    @start()
    async def chat(self):
        system_prompt = "You help the user write haikus. Use the generate_haiku tool."

        response = await copilotkit_stream(
            await acompletion(
                model="openai/gpt-4o",
                messages=[
                    {"role": "system", "content": system_prompt},
                    *self.state.messages,
                ],
                tools=[GENERATE_HAIKU_TOOL],
                parallel_tool_calls=False,
                stream=True,
            )
        )

        message = response.choices[0].message
        self.state.messages.append(message)

        if message.tool_calls:
            self.state.messages.append({
                "tool_call_id": message.tool_calls[0].id,
                "role": "tool",
                "content": "Haiku generated.",
            })

该工具没有 Python 实现。它存在只是为了让模型发出一个前端可渲染的结构化调用。调用之后,追加一条简短的工工具结果,使对话在下一轮保持格式良好。

Serve the Flow over AG-UI

在 FastAPI 应用中用独立路径暴露该 Flow:

python
# server.py
from fastapi import FastAPI
from ag_ui_crewai.endpoint import add_crewai_flow_fastapi_endpoint
from haiku_flow import HaikuFlow

app = FastAPI(title="CrewAI Agent Server")

add_crewai_flow_fastapi_endpoint(
    app=app,
    flow=HaikuFlow(),
    path="/haiku",
)

把智能体注册到 CopilotKit 运行时,并完全按 前端概览 所示将 <CopilotKit> 指向它。本指南其余部分假定该智能体以 id haiku 注册。

Register the rendering component

在前端,用与后端声明的相同 name 调用 useRenderTool。useRenderTool 是用于渲染工具调用的 hook:它接收一个 render 函数,没有要执行的东西,因为这个工具纯粹用于展示。

NOTE

当工具只绘制 UI 时使用 useRenderTool。如果该工具还需要在浏览器中运行某些东西,请改用 useFrontendTool,它把 handler 与可选的 render 配对。

tsx
"use client";
import { useRenderTool } from "@copilotkit/react-core/v2";
import { z } from "zod";

useRenderTool({
  name: "generate_haiku",
  parameters: z.object({
    japanese: z.array(z.string()),
    english: z.array(z.string()),
  }),
  render: ({ args, status }) => {
    if (!args.japanese) return <></>; // still streaming
    return <HaikuCard japanese={args.japanese} english={args.english} />;
  },
});

该工具由 <CopilotKit agent="haiku"> provider 限定到活动智能体,因此这里不需要 agentId。有几点需要注意:

  • name 必须与后端工具名完全一致(generate_haiku)。正是这种匹配让 CopilotKit 把调用路由到这个组件。
  • render 接收 { args, status }。 随着模型流式传输调用,args 会逐步填充;早期它可能为空或不完整。如果你想在参数流式传输期间显示加载状态,status 会经历 "inProgress" / "executing" 到 "complete"。
  • 对不完整参数做防护。 在你需要的字段存在之前返回空片段。这里我们先等待 args.japanese,再渲染卡片。

Render the haiku

render 函数委托给一个普通的 React 组件。它没有任何 CopilotKit 特有的东西:接收 props 并返回标记。

tsx
function HaikuCard({
  japanese,
  english,
}: {
  japanese: string[];
  english: string[];
}) {
  return (
    <div className="haiku-card">
      {japanese.map((line, i) => (
        <div key={i} className="haiku-line">
          <span className="jp">{line}</span>
          <span className="en">{english?.[i]}</span>
        </div>
      ))}
    </div>
  );
}

由于 english 与 japanese 一同流式传入,请使用可选访问(english?.[i]),这样在译文仍在到达时卡片也能干净地渲染。

Run it

启动两个进程,向助手要一首俳句。随着参数流式传入,卡片会逐行填充渲染。

bash
uvicorn server:app --port 8000   # terminal 1
npm run dev                       # terminal 2

渐进式渲染的工作原理

模型不会一次性发出工具调用。它流式输出 token,CopilotKit 会在每一块新参数到达时重新调用你的 render 函数:

  1. 调用开始。args 为空,因此你的防护返回空片段。
  2. args.japanese 逐行填充。卡片出现并增长。
  3. args.english 填充。译文各就其位。
  4. 调用完成。args 持有最终且完全校验过的对象。

这就是不完整参数防护很重要的原因:render 按设计就是针对不完整数据运行的。只读取你已有的字段,让其余部分在到达时渲染出来。

后端工具

上面的 generate_haiku 工具没有 Python 实现——它存在只是为了让模型发出一个前端可渲染的结构化调用。但由你的 Crew 或 Flow 在服务端运行的真实工具的渲染方式相同。

当智能体或 Crew 在运行期间执行工具时,桥接层会呈现该工具调用及其结果。为该工具名注册一个 useRenderTool,并在 render 中读取 result:

tsx
useRenderTool({
  name: "get_weather",
  parameters: z.object({ location: z.string() }),
  render: ({ args, result, status }) => {
    if (status !== "complete") return <WeatherSkeleton location={args.location} />;
    return <WeatherCard data={JSON.parse(result)} />;
  },
});

NOTE

后端工具必须返回 JSON 字符串,而不是 Python dict。桥接层会把工具输出字符串化,因此原始 dict 会以 Python repr 形式到达,浏览器无法 JSON.parse。请从工具中返回 json.dumps(...)。

相关

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