跳到主要内容

AG-UI: Human-in-the-loop

Human-in-the-loop(HITL)用于在 Agent 执行敏感操作前暂停流程,把决定权交给用户,再根据用户的选择继续执行。

本文使用在线购物示例说明完整流程:Agent 生成购物清单和随机价格,在模拟下单前必须得到用户确认。

完整流程

本示例不会访问真实商店,也不会调用支付接口。

后端:随机价格工具

后端工具为每个 item 随机生成 1.5012.00 之间的单价,并计算总价:

@tool
def get_item_prices(items: list[str], currency: str = "USD") -> str:
priced_items = []
total = 0.0
for item in items:
unit_price = round(random.uniform(1.50, 12.00), 2)
total += unit_price
priced_items.append({"item": item, "unit_price": unit_price, "currency": currency})
return json.dumps({"items": priced_items, "total": round(total, 2), "currency": currency})

将它和 CopilotKitMiddleware() 加入 create_agent。System prompt 应明确要求:购物请求必须先调用 get_item_prices,再调用前端的 purchase_online,不能只返回文字确认。

前端:确认界面

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

useHumanInTheLoop({
agentId: "default",
name: "purchase_online",
description: "Show a shopping list and ask the user to approve it.",
parameters: z.object({
items: z.array(z.object({
item: z.string(),
unit_price: z.number(),
currency: z.string(),
})),
total: z.number(),
currency: z.string(),
}),
render: ({ args, status, respond }) => (
<PurchaseCard
items={args.items}
total={args.total}
currency={args.currency}
disabled={status !== "executing"}
onConfirm={() => respond?.({ approved: true })}
onCancel={() => respond?.({ approved: false })}
/>
),
});

respond 是恢复流程的关键。确认和取消两个分支都必须调用它,否则 Agent run 会一直等待。

if approval.get("approved") is not True:
return "Purchase cancelled. No order was placed."

return "Purchase confirmed. Simulated order SIM-ORDER-2026-001 was placed."

真实项目中,付款、下单、删除数据等不可逆操作都应放在用户确认之后。确认之前可以计算价格、校验库存和准备清单,但不应产生外部副作用。

Runtime 配置

CopilotKit v2 的 Next.js App Router 使用 catch-all route,以便处理 /info 和 Agent run 等子路径:

src/app/api/copilotkit/[[...slug]]/route.ts
import { CopilotRuntime, createCopilotRuntimeHandler } from "@copilotkit/runtime/v2";
import { LangGraphHttpAgent } from "@copilotkit/runtime/langgraph";

const runtime = new CopilotRuntime({
agents: {
default: new LangGraphHttpAgent({
url: process.env.AGENT_URL || "http://localhost:8300/agent",
}),
},
});

const handler = createCopilotRuntimeHandler({ runtime, basePath: "/api/copilotkit" });
export const GET = handler;
export const POST = handler;

Provider 使用 multi-route transport:

<CopilotKit runtimeUrl="/api/copilotkit" useSingleEndpoint={false}>
{children}
</CopilotKit>

如果确认卡片没有出现,请检查工具名、agentIdCopilotKitMiddleware() 和 system prompt 是否一致,并重启前后端服务。不要使用 deprecated 的 EmptyAdaptercopilotRuntimeNextJSAppRouterEndpoint