CopilotKit × Agno 实战:用前端工具 + 应用级模态框实现 In-App HITL 人工审批

发布时间:2026/9/12 6:55:01

CopilotKit × Agno 实战:用前端工具 + 应用级模态框实现 In-App HITL 人工审批 CopilotKit × Agno 实战用前端工具 应用级模态框实现 In-App HITL 人工审批【免费下载链接】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 开源仓库中 Agno 集成的hitl-in-app演示为骨架完整讲解如何在聊天界面之外、应用页面层级实现 Human-in-the-LoopHITL人工审批Agent 在执行退款、降级套餐、升级工单等影响客户的操作前会通过前端工具挂起执行并在应用顶层弹出审批对话框由操作员点击「Approve / Reject」后把结果回传给 Agent 继续执行。读完本文你将掌握useFrontendTool挂起 Promise、createPortal应用级弹窗、Agno 侧external_execution工具的完整实现链路以及对应的 QA 验证清单与 E2E 断言策略。一、什么是 In-App HITL审批弹窗位于聊天之外hitl-in-app演示的核心特征是「审批发生在应用页面层级而非聊天气泡内部」。原 QA 文档qa/hitl-in-app.md开篇即点明其定位frontend-tool app-level modal。与「在聊天流内呈现按钮」的 in-chat HITL 方案相比In-App HITL 的关键差异在于审批对话框通过 React Portal 渲染为body的直接子节点覆盖整个页面聊天面板右侧的CopilotPopup与工单面板左侧的 Support Inbox同时保持可见操作员可以在不打断对话上下文的前提下完成高风险操作的授权。因此 QA 文档将验证重点放在「approval-dialog是否出现在聊天区域之外并覆盖页面」这一条上这正是该模式与聊天内嵌 HITL 的分水岭。二、整体架构与一次审批的完整链路整个演示由前端 Next.js 应用与 Agno Python Agent 后端两部分组成。运行时通过 CopilotKit 路由 以 AG-UI 协议代理到 Agno 后端// showcase/integrations/agno/src/app/api/copilotkit/route.ts const AGENT_URL process.env.AGENT_URL || http://localhost:8000; function createMainAgent() { return new HttpAgent({ url: ${AGENT_URL}/agui }); }hitl-in-app这一 agent 名称被映射到主 Agentroute.ts 中的mainAgentNames数组前端通过agenthitl-in-app指定路由page.tsx。一次完整审批的调用链为用户点击建议 pill如 Approve refund for #12345消息发送给 Agno AgentAgent 依据 instructions 判断该操作影响客户调用request_user_approval工具该工具在前后端均被声明为「由前端执行」Agno 后端发出工具调用事件后暂停本轮运行前端useFrontendTool的 handler 收到调用参数返回一个挂起的 Promise其resolve被存入 React state页面据此渲染应用级审批弹窗Portal 到body操作员点击 Approve/Reject弹窗调用resolve({ approved, reason? })Promise 完成工具结果Tool Result返回给 AgentAgent 恢复运行按结果输出确认或拒绝的话术。三、前端实现useFrontendTool 挂起 Promise前端核心在 page.tsx 中。首先通过useFrontendTool注册与后端同名的工具并用zod描述参数useFrontendTool({ name: request_user_approval, description: Ask the operator to approve or reject an action before you take it. The operator will respond via an in-app modal dialog that appears OUTSIDE the chat surface. The tool returns an object of the shape { approved: boolean, reason?: string }., parameters: z.object({ message: z.string().describe( Short summary of the action needing approval (include concrete numbers / IDs)., ), context: z.string().optional().describe( Optional extra context — e.g. the ticket ID or policy rule., ), }), handler: async ({ message, context }) { return await new Promise{ approved: boolean; reason?: string }( (resolve) { setDialog({ open: true, pending: { message, context }, resolve }); }, ); }, });这是整个模式的精髓所在handler 返回的 Promise 不立即完成而是把resolve函数劫持进 React state。只要弹窗未操作该 Promise 就保持 pendingAgent 的运行就被前端工具机制挂起操作员点击按钮时resolve被调用Promise 完成工具结果自动回传 Agentpage.tsx。为此页面维护了一个带判别联合discriminated union的 state把resolve与待审批内容绑定在一起type ResolveFn (value: { approved: boolean; reason?: string }) void; type DialogState | { open: false } | { open: true; pending: PendingApproval; resolve: ResolveFn };当用户点击按钮时handleResolve先调用存储的resolve(result)完成 Promise再关闭弹窗const handleResolve (result: { approved: boolean; reason?: string }) { if (dialog.open) { dialog.resolve(result); setDialog({ open: false }); } };页面布局同时渲染三部分左侧工单面板TicketsPanel、右侧CopilotPopup聊天、以及条件渲染的ApprovalDialogpage.tsx。四、应用级模态框ApprovalDialog 与 createPortalApprovalDialogapproval-dialog.tsx负责把审批 UI 提升到应用层级。其注释明确说明模态框被portal 到body而非渲染在聊天气泡树内。关键实现通过createPortal(content, document.body)完成并设置fixed inset-0 z-50全屏遮罩与roledialog、aria-modaltrue无障碍语义。同时为 QA 提供了三个稳定的测试锚点data-testidapproval-dialog-overlay遮罩层验证 Portal 位置与开闭状态data-testidapproval-dialog对话框主体QA 文档验证其出现在聊天之外data-testidapproval-dialog-reason可选备注输入框data-testidapproval-dialog-approve/approval-dialog-reject审批 / 拒绝按钮。组件还包含一个可选备注reason文本框——这对应前端工具返回结构中的reason?: string字段onClick{() onResolve({ approved: true, reason: reason.trim() || undefined, }) }useEffect中先setMounted(true)再返回内容是为了避免 SSR 时document未定义导致的报错——这是 Next.js 中使用createPortal的标准做法。E2E 测试通过body [data-testidapproval-dialog-overlay]这一选择器断言弹窗确实挂载在body下hitl-in-app.spec.ts从测试层面锁定了「应用级弹窗」这一契约。五、后端实现Agno 的 external_execution 工具在 Agno 侧同名工具定义于 main.py其签名与前端的 zod schema 一一对应tool(external_executionTrue, external_execution_silentTrue) def request_user_approval(message: str, context: str ): Ask the operator to approve or reject an action before you take it. The operator will respond via an in-app modal dialog that appears OUTSIDE the chat surface. The tool returns an object of the shape { approved: boolean, reason?: string }. 两个装饰器参数是关键external_executionTrue声明工具由外部前端执行Agno 只负责发出工具调用事件并等待结果而不是自己执行external_execution_silentTrue工具调用过程中不产生多余的事件输出保证前端只收到一次干净的工具调用。该工具被注册进主 Agent 的tools列表main.py并在 Agent 的 instructions 中明确约束了使用时机main.pyUSER APPROVAL (HITL): When asked to take any action that affects a customer — for example issuing a refund, updating a plan, cancelling a subscription, escalating a ticket, or sending a credit — call request_user_approval FIRST with a short summary and optional context. Follow the tool result: if approved, confirm in one short sentence; if rejected, acknowledge and do not retry.此外Agent 配置中db_create_session_db()与tool_call_limit15也与 HITL 相关审批期间 Agent 运行会被挂起等待前端响应需要可写会话存储以支持运行恢复见 main.py 的注释说明。六、运行前提执行 QA 测试前需满足原文档列出的两个前置条件前提说明Demo 已部署在/demos/hitl-in-app对应源码位于 hitl-in-app/page.tsx通过next dev或next start启动Agent 后端健康Agno 后端监听AGENT_URL默认http://localhost:8000路由的GET /api/copilotkit健康检查会返回agent_status字段见 route.ts七、QA 测试步骤In-App HITL 验收清单以下为原 QA 文档hitl-in-app.md的完整验收步骤并补充了可对照的测试锚点与断言细节。7.1 基础功能导航到/demos/hitl-in-app页面加载CopilotKitagenthitl-in-app并默认展开CopilotPopup。验证工单面板与三张工单渲染左侧 Support Inbox 应显示#12345Jordan Rivera退款争议金额 $50.00、#12346Priya Shah降级到 Starter、#12347Morgan Lee升级到支付团队三张工单。工单数据硬编码于 tickets-panel.tsxE2E 通过getByTestId(ticket-12345)等锚点断言可见性。验证右侧聊天正常渲染聊天输入框占位符为 Type a messageCopilotPopup的labels.chatInputPlaceholder配置。同时初始状态下不应存在任何审批弹窗approval-dialog-overlay计数为 0。7.2 功能专项检查批准路径refund #12345点击建议 pillApprove refund for #12345。三个建议 pill 由 suggestions.ts 通过useConfigureSuggestions注册available: always并带有完整消息模板例如Please approve a $50 refund to Jordan Rivera on ticket #12345 for the duplicate charge.。验证审批对话框出现在聊天之外并覆盖页面断言data-testidapproval-dialog可见且其挂载位置为body [data-testidapproval-dialog-overlay]Portal 契约。点击Approveapproval-dialog-approve。验证弹窗关闭且 Agent 继续跟进approval-dialog-overlay数量在数秒内降为 0随后聊天区出现 Agent 的确认消息E2E 断言开头短语 I am processing the $50 refund见 hitl-in-app.spec.ts。拒绝路径downgrade #12346点击建议 pillDowngrade plan for #12346消息模板要求降级到 Starter 计划、下一账单周期生效。对话框出现后点击Rejectapproval-dialog-reject。验证 Agent 确认收到拒绝拒绝结果{ approved: false, reason? }回传后Agent 应acknowledge and do not retry按 instructions 约定不再重试。E2E 中对应的拒绝分支断言短语如 refund request was not approved / Not escalatedhitl-in-app.spec.ts。7.3 错误处理无未捕获的 console 错误全程页面加载、弹窗开合、审批与拒绝控制台不得出现未捕获异常。这一点在 E2E 测试中由test.describe.configure({ mode: serial })与严格断言组合保障。八、E2E 测试要点如何真实地验证审批结果仓库为该 demo 配套了完整的 Playwright 测试 hitl-in-app.spec.ts其设计对理解本模式极有价值Serial 模式是载重结构load-bearingaimock 确定性夹具按sequenceIndex0 approve 分支、1 reject 分支匹配因此批准测试必须在拒绝测试之前运行依赖测试顺序保证分支正确。Portal 契约断言所有弹窗可见性断言都使用body [data-testidapproval-dialog-overlay]直接验证弹窗是body的子节点而非聊天树内部。多轮审批回归测试「先批准 #12345 再升级 #12347」确保每个 pill 都触发自己独立的request_user_approval调用并挂载独立弹窗——这对应一个曾修复的 aimock 多 pill bug夹具需通过toolCallId串联后续分支避免首个审批后第二个 pill 直接跳过弹窗hitl-in-app.spec.ts 的注释详述了该回归案例。已知暂缓项downgrade #12346的审批/拒绝流程测试被显式跳过TODO注释标明上游 bug 于 2026-05-07 存在超出本次改写范围QA 执行时应知悉此限制。九、常见问题与排查建议现象排查方向弹窗未出现在页面顶层而是嵌在聊天内检查是否使用createPortal(content, document.body)E2E 用body [data-testidapproval-dialog-overlay]选择器可直接定位点击 Approve/Reject 后 Agent 无后续响应确认resolve是否被正确调用、handleResolve是否先resolve再关闭弹窗后端检查request_user_approval是否带external_executionTrue审批后对话无工具结果、Agent 卡住检查 Agent 的会话存储db是否可写HITL 挂起依赖运行恢复以及tool_call_limit是否过低导致运行被截断页面首个审批完成后后续 pill 不弹窗参考多轮审批回归确保夹具按toolCallId串联后续分支而不是依赖hasToolResult布尔切换SSR 下document未定义报错使用mountedstate useEffect延迟渲染 Portal 内容approval-dialog.tsx 的标准做法十、小结In-App HITL 的本质是把「需要人工裁决的暂停点」从模型内部转移到前端应用层Agno 侧用external_execution工具声明这段代码由前端跑前端用useFrontendTool 挂起 Promise 制造暂停再用createPortal把裁决 UI 提升到页面层级。三者结合既保证了操作员拥有全页面上下文工单、金额、政策都在视野内也让 Agent 的每次高风险操作都有据可查、可控可拒。配合仓库内的 QA 清单 与 E2E 测试你可以在自己的业务中快速复刻这一模式。【免费下载链接】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),仅供参考
延伸阅读

更多相关文章

2026/9/12 6:50:01

macOS 应用精选集 awesome-macOS:告别盲目找软件的烦恼

macOS 应用精选集 awesome-macOS:告别盲目找软件的烦恼 【免费下载链接】awesome-macOS  A curated list of awesome applications, softwares, tools and shiny things for macOS. 项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-macOS 装个…

2026/9/12 7:35:05

AI烧钱失控?从Token计费原理到全员AI成本治理实战指南

1. 一笔3亿美元的账单:全员AI从效率神话到成本失控 先看一笔让人后背发凉的账:Salesforce在公司里全员推广Claude之后,半年时间光是API调用就烧掉了3亿美元。这不是哪个创业公司被薅了羊毛,是一家老牌软件巨头的真实账目。按这个规…

2026/9/12 7:35:05

COMSOL多物理场仿真在激光加工熔池分析中的应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/12 7:35:05

Cursor辅助编码实战:让AI真正读懂项目的上下文与规则体系

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/12 7:35:05

货币双重属性解析:保障与资本的动态平衡

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/12 2:05:33

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/12 3:55:12

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/12 0:04:17

MATLAB仿生优化框架:长鼻浣熊算法多策略融合实现

简介:本资源是一份面向智能优化算法研究者与MATLAB初学者的仿生智能算法实践代码包,聚焦于长鼻浣熊优化算法(COA)的多策略改进与性能验证。针对传统COA易陷局部最优、收敛精度不足等问题,作者融合Circle映射初始化提升…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 JavaWeb 的校园一卡通管理系统的设计与实现 基于 JavaWeb 的校园卡业务管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 Java 的图书馆借阅管理平台的搭建与实现 基于 Java 的图书馆综合管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/12 6:29:36

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/10 15:19:50

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/12 6:37:43

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码