AG-UI: Human-in-the-loop
Human-in-the-loop(HITL)用于在 Agent 执行敏感操作前暂停流程,把决定权交给用户,再根据用户的选择继续执行。
本文使用在线购物示例说明完整流程:Agent 生成购物清单和随机价格,在模拟下单前必须得到用户确认。
完整流程
本示例不会访问真实商店,也不会调用支付接口。
后端:随机价格工具
后端工具为每个 item 随机生成 1.50 到 12.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>
如果确认卡片没有出现,请检查工具名、agentId、CopilotKitMiddleware() 和 system prompt 是否一致,并重启前后端服务。不要使用 deprecated 的 EmptyAdapter 或 copilotRuntimeNextJSAppRouterEndpoint。