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

人在回路

在运行中途暂停你的 CrewAI 智能体以收集用户决策,然后用用户的答案恢复智能体运行。

将用户纳入回路

有些步骤必须得到人的确认才能发生。人在回路会在运行中途暂停智能体,在前端渲染一个交互式组件,然后等待。用户做出选择;智能体带着该选择恢复运行并继续。

其机制是前端注册的一个工具。当模型调用它时,运行会在该工具调用处停下,直到用户响应。不会有任何事情自动发生:智能体会一直停在那里,直到 respond() 把控制权交还。

在下面的示例中,智能体提出一组任务步骤。用户启用或禁用每个步骤并确认。随后智能体继续运行,严格遵照用户所批准的内容。

NOTE

该模式适用于 Flow。它依赖 Flow 的 chat 循环在 respond() 之后重新进入:返回的值作为工具结果传回,智能体的下一轮据此行动。

动手构建

Bind the frontend actions into the model's tools

在你的 Flow 中,用 *self.state.copilotkit.actions 把前端注册的 actions 加入模型的工具列表。这些 action 就是前端(通过 useHumanInTheLoop)注册的工具。绑定它们,模型才能调用它们;运行会在该工具调用处暂停,直到用户响应。

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

class HumanInTheLoopFlow(Flow[CopilotKitState]):
    @start()
    @listen("route_follow_up")
    async def start_flow(self):
        pass

    @router(start_flow)
    async def chat(self):
        system_prompt = (
            "You perform tasks for the user. When asked to do a task, call the "
            "tool the frontend provides so the user can approve or adjust the steps "
            "before you continue."
        )

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

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

    @listen("route_end")
    async def end(self):
        pass

CopilotKitState 在 self.state.copilotkit.actions 上承载前端注册的 actions。当模型调用其中之一时,运行会在那里暂停。用户响应后,返回的值会作为工具结果写入 self.state.messages,Flow 随后回到 chat 循环,使模型能够根据该决策行动。

Serve the Flow over AG-UI

用 add_crewai_flow_fastapi_endpoint 从 FastAPI 服务器暴露该 Flow,与其他任何智能体的方式相同。完整的服务器、运行时和服务商配置参见前端概览。

python
# server.py
from fastapi import FastAPI
from ag_ui_crewai.endpoint import add_crewai_flow_fastapi_endpoint
from my_agents.human_in_the_loop_flow import HumanInTheLoopFlow

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

add_crewai_flow_fastapi_endpoint(
    app=app,
    flow=HumanInTheLoopFlow(),
    path="/human_in_the_loop",
)

Register the interactive tool on the frontend

useHumanInTheLoop 注册智能体暂停等待的那个工具,并提供一个 render 函数来绘制交互式 UI。当智能体调用该工具时,你的组件出现;当用户做出操作时,你调用 respond() 来恢复智能体。

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

useHumanInTheLoop({
  agentId: "human_in_the_loop",
  name: "generate_task_steps",
  parameters: z.object({
    steps: z.array(
      z.object({
        description: z.string(),
        status: z.enum(["enabled", "disabled", "executing"]),
      })
    ),
  }),
  render: ({ args, respond, status }) => (
    <StepReview
      steps={args.steps ?? []}
      // `status === "executing"` means the agent is waiting for the user
      waiting={status === "executing"}
      onConfirm={(chosen) => respond?.(chosen)}
    />
  ),
});

render 函数接收:

  • args —— 模型生成的工具参数(这里是提出的 steps)。它们会随着模型生成而流式输出。
  • status —— 工具调用的生命周期。当它为 "executing" 时,智能体已暂停,正在等待人。
  • respond(value) —— 用用户的决策恢复智能体。智能体的下一轮会看到返回的值并据此行动。

Let the user decide, then respond

你的组件读取 args.steps,让用户逐个切换,并用最终选择调用 respond()。智能体正是带着这个值继续运行。

tsx
function StepReview({ steps, waiting, onConfirm }) {
  const [choices, setChoices] = useState(steps);

  const toggle = (i) =>
    setChoices((prev) =>
      prev.map((s, idx) =>
        idx === i
          ? { ...s, status: s.status === "enabled" ? "disabled" : "enabled" }
          : s
      )
    );

  return (
    <div>
      {choices.map((step, i) => (
        <label key={i}>
          <input
            type="checkbox"
            checked={step.status === "enabled"}
            disabled={!waiting}
            onChange={() => toggle(i)}
          />
          {step.description}
        </label>
      ))}
      <button disabled={!waiting} onClick={() => onConfirm(choices)}>
        Confirm
      </button>
    </div>
  );
}

用户点击确认后,respond() 被触发,运行恢复,Flow 的 chat 步骤带着消息历史中的用户选择再次执行。

相关

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