
人工智能AI AgentAgent 框架后端工具调用【免费下载链接】eveThe Open Framework for Building Agents项目地址https://gitcode.com/gh_mirrors/eve28/eve点击查看免费下载Human-in-the-loopHITL是 eve 中让智能体在关键节点停下来等人的机制审批Approvals和提问Questions两种触发方式共享同一套持久化暂停—恢复协议运行可以等待数秒乃至数天进程重启也不会丢失挂起状态。读完本文你将掌握如何用approval策略为工具加审批门、用ask_question让模型在回合内主动提问并理解从input.requested到turn.waiting、再到inputResponses恢复的完整事件契约最终在 Slack 等渠道或自建前端中正确渲染与应答这些请求。什么是 Human-in-the-LoopHITL 指智能体运行中任何持久化暂停并等待真人的点。在 eve 中只有两种触发方式且两者都走同一条暂停—恢复协议审批Approvals工具策略tool policy允许、拒绝或暂停某次调用以等待人工审查。智能体决定调用工具而策略决定它是自动执行还是需要人工决策。提问Questions智能体自己在回合中途向用户提出澄清问题或选项然后停在那里等待回答。两者的共同点是回合保持打开流式事件上报turn.waiting运行以持久化方式等待——无论是几秒还是几天——答案到达后从上次停下的地方精确恢复。渠道会把请求渲染成原生 UI 交给你处理。Approvals给工具加一道审批门审批是 工具 的一个属性在工具执行前对其做门控。这也包括 工作流工具等待审批的调用不会启动其工作流被策略或人拒绝的调用永远不会执行。同一模型响应中其他不需要审批的调用可以照常继续不受阻塞。通过eve/tools/approval导出的助手函数来设置approvalimport { defineTool } from eve/tools; import { auto } from eve/tools/approval; import { z } from zod; export default defineTool({ description: Refund a charge., inputSchema: z.object({ tenantId: z.string(), chargeId: z.string(), amount: z.number() }), approval: auto(), // or always() / once() / never() / a policy async execute(input) { return refund(input); }, });四个助手的行为如下HelperBehaviornever()永不要求审批省略approval时的默认行为。once()会话中工具首次运行时要求审批之后自动放行。always()每次调用前都要求审批。auto()让决策模型判断这次具体调用是直接执行还是需要用户审批。默认情况下省略approval等同于never()工具调用可以不经人工审批直接执行。因此对于敏感、不可逆、受监管、涉及财务、医疗、就业、住房、法律、安全影响、影响用户或产生外部副作用的行为应当显式要求人工审批或采取其他保障措施。在 policies.ts 的源码中可以看到always()恒返回user-approval、never()恒返回not-applicable而once()依据context.approvedTools.has(context.toolName)判断——只有显式审批通过才会记录到会话拒绝或未应答都不会记录所以下次调用会再次提示。auto()用决策模型自动分类每次调用auto()使用 AI SDK 决策模型 将每次调用分类为clear清晰、可放行或caution需谨慎。默认模型是typesafe-ai/jev即 TypeSafe AI 的 Jev 决策模型。与decide一样除非应用配置了全局的 AI SDK 默认 provider否则模型字符串走 Vercel AI Gatewayapproval: auto({ model: typesafe-ai/jev });决策模型审查工具名称与输入以判断危险效应。出现caution、审查失败或输入不完整时一律要求用户审批。注意工具输入会被发送到决策模型的 provider。从源码policies.ts可以确认几个关键实现细节输入体积上限序列化后的工具输入超过MAX_ACTION_BYTES64 KiB时直接返回user-approval避免超大输入消耗决策模型。超时保护决策调用使用AbortSignal.any([context.abortSignal, AbortSignal.timeout(30_000)])30 秒内无结果即放弃并转为人工审批maxRetries: 0不做重试。失败降级任何异常决策不可用、无效等都会在检查取消信号后返回user-approval——宁可等人不可放行。默认评审指令内置的DEFAULT_INSTRUCTIONS覆盖破坏性数据丢失、凭证泄露、金融交易、部署或公开变更、外部通信、权限或系统变更、隐蔽执行等危险类别并要求重要效果不明确时返回 caution。针对应用自身的策略可以覆盖分类器的文本approval: auto({ model: typesafe-ai/jev, instructions: Review whether this refund needs finance approval., criteria: { clear: The refund can proceed automatically., caution: A person must review the refund., }, });AutoApprovalOptions的instructions与criteria.clear/caution分别对应决策模型收到的评审指令与两个固定结果的描述见 policies.ts。可复用审批授予的生效时机一次可复用的审批授予如once()的本会话首次后放行只会在每一个匹配且尚未处理的请求都被解决之后生效。如果对once()门控工具的多次调用各自生成了审批提示批准其中一个并不会授权其他提示——每个可见提示仍是一次独立决策。待这些挂起请求全部解决后会话后续调用才会自动放行。自定义策略依赖输入做决策当决策依赖输入内容时不要用助手函数而是传入自己的策略函数。它接收与工具执行相同的会话上下文外加{ toolName, toolInput, approvedTools, callId, abortSignal }并同步或异步Promise返回 AI SDK 7 的审批状态。异步策略工作应使用abortSignal这样回合取消时策略也随之停止。用ctx.session.auth.current守卫当前回合的调用者用ctx.session.auth.initiator守卫创建会话的调用者。返回user-approval表示暂停等人返回not-applicable表示继续且不弹提示。toolInput可能是undefined访问前必须做防护。下面的策略先拒绝跨租户调用再仅当金额超过阈值时要求审批approval: ({ session, toolInput }) { const callerTenant session.auth.current?.attributes.tenantId; if (callerTenant undefined || callerTenant ! toolInput?.tenantId) { return { type: denied, reason: Caller cannot access this tenant. }; } return (toolInput?.amount ?? 0) 1000 ? user-approval : not-applicable; },为兼容旧版谓词形态策略可以返回布尔值true视为user-approvalfalse视为not-applicable布尔型 Promise 同样支持。策略也可以返回approved或denied来自动决策。当希望模型收到具体原因时使用{ type: approved | denied, reason }。Approval、ApprovalContext和ApprovalStatus类型从eve/tools/approval导出其完整定义见 approval/definition.ts其中ApprovalStatus为undefined | boolean | not-applicable | approved | denied | user-approval | { type, reason? }的联合类型而Approval既可以是单个策略函数也可以是{ request, response }配置对象resolveApprovalPolicy()负责从两种形态中提取请求期策略definition.ts。审批与幂等重放安全把副作用门控在审批之后也是让非幂等工作在重放replay中保持安全的方式一条位于always()之后的扣款或邮件不会因为一步重跑就再次触发——每次重跑都需要一次全新的人工决策。授权审批响应谁能批准、谁能取消默认情况下只有发起该调用请求的那个回合的人可以批准或取消它。要改变谁能结算一次调用例如指定一组必须批准某工具的设计审批人可以定义审批响应策略response policy。工具带上response策略后会整体替换默认行为因此要让任意应答者都能通过需显式返回{ status: allowed }import { defineTool } from eve/tools; import { always } from eve/tools/approval; import { z } from zod; export default defineTool({ description: Refund a charge., inputSchema: z.object({ chargeId: z.string() }), approval: { request: always(), response: ({ request, response, session, auth }) { // The Slack channel authenticates the responder and includes the workspace and user IDs. // Larger apps can look up approver membership here instead. const approvers [slack:T012AB3CD:U045EF6GH, slack:T012AB3CD:U078JK9LM]; const canApprove approvers.includes(response.principal.principalId); return canApprove ? { status: allowed } : { status: rejected, reason: This user cannot approve refunds. }; }, }, async execute(input) { return refund(input); }, });response策略收到三部分上下文类型定义见 approval/definition.tsrequest被批准调用的稳定标识requestId、callId、toolName与带类型的toolInput以及principal——发起该调用的已认证主体若该调用者未认证或匿名则为null。eve 在请求审批时捕获request.principal因此即使其他人在共享会话中继续操作它也不会改变。response已提交的decisionapprove或cancel以及提交者的principal含principalId、principalType、authenticator、attributes。该身份由你的路由或渠道提供。策略对两种决策都会执行因此被其拒绝的应答者既不能批准也不能取消该调用。session只读的会话身份与谱系id、initiator、parent、turn。auth绑定到应答者的窄化能力getToken(provider, options?)与requireAuth(provider, options?)。当授权取决于某个 provider 身份或权限时使用它们交互式 provider 流程会持久化暂停然后重试策略。返回{ status: allowed }接受该决策返回{ status: rejected, reason }则保持共享请求挂起让其他有资格的应答者仍可结算。当策略只关心谁批准时对cancel返回{ status: allowed }让任何人都能撤销请求。session.initiator是发起会话的人request.principal是发起这次调用的人在共享线程中两者可以不同。比较response.principal与request.principal的完整身份即可实现仅请求者可结算import { defineTool } from eve/tools; import type { SessionAuthContext } from eve/context; import { always } from eve/tools/approval; import { z } from zod; function samePrincipal(a: SessionAuthContext, b: SessionAuthContext): boolean { return ( a.authenticator b.authenticator a.issuer b.issuer a.principalType b.principalType a.principalId b.principalId ); } export default defineTool({ description: Publish a release., inputSchema: z.object({ version: z.string() }), approval: { request: always(), // request.principal is null for an unauthenticated or anonymous caller, so no one matches it. response: ({ request, response }) request.principal ! null samePrincipal(response.principal, request.principal) ? { status: allowed } : { status: rejected, reason: Only the person who asked for this release can respond. }, }, async execute(input) { return publish(input); }, });响应被拒的事件流当策略拒绝一次响应时审批保持挂起回合保持被持有。流式事件依次为approval.candidateoutcome: pending→approval.candidateoutcome: rejected携带策略的reason→turn.waitingon: input同一turnId。此时不会发出session.waiting因此一个在会话边界停止读取的客户端会在拒绝发生后继续读取下去。客户端完成提交后审批提示仍然可回答——提交答案本身并不等于批准approval.settled或input.resolved才记录服务器的最终决定。事实上策略评估的每一个响应都以同样方式开始不仅是拒绝eve 先把响应记录为approval.candidateoutcome: pending再运行策略这样缓慢的策略工作不会丢失响应。放行后则发出approval.settled与input.resolved。没有response策略的审批直接结算不发出approval.candidate。候选也可能以failed、timed-out或stale结束可检查approval.candidate事件中的原因。跳过定时任务分发的自动回合审批session.auth.current标识当前回合的调用者。Markdown 定时任务schedule自动使用应用主体authenticator: app、principalId: eve:app、principalType: runtimerun定时任务则必须把appAuth传给send(...)子会话才会使用该主体。匹配全部三个字段即可在自动化回合跳过审批同时当真人调用同一工具时仍会提示import { defineTool } from eve/tools; import { z } from zod; export default defineTool({ description: Refund a charge., inputSchema: z.object({ chargeId: z.string(), amount: z.number() }), approval: ({ session }) { const auth session.auth.current; return auth?.authenticator app auth.principalId eve:app auth.principalType runtime ? not-applicable : user-approval; }, async execute(input) { return refund(input); }, });approval中的session与execute中的ctx.session形状相同id、auth、turn和可选的parent。如果之后有人恢复了定时任务启动的会话session.auth.current会变成那个人而session.auth.initiator仍是应用主体。只有当策略应对整个会话生效时才检查initiator。跳过定时回合的审批意味着任何非幂等副作用在步骤重放时会再次触发因此该模式应配合幂等键或once()使用。Questions让模型主动停下来提问ask_question工具让模型可以暂停并问用户一个问题而不是靠猜。模型以{ question, options? }调用它question向用户提出的问题需包含回答所需的上下文。options两到三个互斥选项每个选项含label与一句话description。渠道将其渲染为按钮或选择菜单。省略options即为开放式问题。用户始终可以输入自己的答案而不必选选项因此模型永远不需要其他选项。工具返回{ status: answered, answer }携带所选选项的 label 或用户的原话、{ interrupted: true }有未回答该问题的新消息到达或{ status: unavailable }会话无法请求输入。从 ask-question.ts 的输入 schema 可以看到更多约束选项数量minItems: 2、maxItems: 3label最长 80 字符1–5 个词description最长 200 字符一句话说明该选项的影响或权衡question最长 2000 字符同时因为 label 兼任选项 IDschema 级联校验要求选项 label 必须唯一否则答案会歧义。内置的工具说明还要求选项互斥、把推荐选项放第一位并给 label 加(Recommended)后缀、只问没有用户输入就无法取得进展的问题——能用其他工具查到信息就不要问常规步骤不要请求确认。ask_question是 可选加入的内置工具。通过eve add tool/ask_question添加会生成这个文件import { askQuestion } from eve/tools/ask_question; export default askQuestion();ask_question是一个基于ctx.ask()构建的普通 工作流工具每个调用都是一个持久化工作流通过ctx.ask()提问因此回合等待答案时不占用应用运行时常驻见 ask-question-workflow.ts。当需要不同的 schema、或想在同一个调用中基于答案继续行动时可以用ctx.ask()编写自己的工作流工具。没有任何提问工具时模型会在回复文本中提问用户的下一消息携带答案。在自定义工作流工具中被回答的ctx.ask()在身份可用时返回应答者的authenticator、principalId和principalType不包含渠道属性。ctx.session.auth.current仍是运行时的身份快照要识别谁回答了问题应使用响应中的responder。暂停与恢复机制如何工作审批与提问共享同一协议某次工具调用需要审批或某个工作流工具如ask_question调用ctx.ask()。eve 发出携带挂起请求的input.requested流事件。运行持久化暂停等待多久都行。回合保持打开流发出turn.waiting答案到达后回合在同一turnId下恢复。该turn.waiting携带on: input因为必须有人行动。客户端用inputResponses结构化、按requestId键控或普通的后续message回答。文本与选项 ID、选项 label 或数字选项索引匹配的后续消息会自动结算包括approve、cancel这类审批选项。要告诉模型某次调用为何被拒可在cancel响应中携带text例如{ requestId, optionId: cancel, text: Only the three-pack. }。模型会在拒绝结果中收到带引用的备注标记为拒绝者的原话——拒绝者不一定是用户。该备注也可见于任何能读取会话流的人的input.resolved中。对于工具和从子代理subagent代理而来的提示中的ctx.ask()问题后续消息回答第一个开放的请求见下文同时多个请求。消息必须匹配某个选项或问题允许自由文本否则消息遵循会话的turnPolicy。默认的 steering 消息会中止回合所等待的每个execute工作流工具调用的ctx.abortSignal因此此类调用提出的问题如ask_question的会被撤回并以cancelled结算。这些调用结算后模型才会读到该消息。每个请求都带kind判别器tool-approval、question或session-limit。客户端应依据kind选择行为与呈现方式。requestId标识要回答的请求action.callId标识引发它的工具调用两者都不编码请求的语义。运行会从暂停处精确恢复。因为暂停是持久化的等待期间内存中不保留任何状态——进程可以重启挂起的回合依然存活。当子代理请求输入时eve 在它的父会话上发出相同的input.requested事件。通过父会话回答会把响应直接路由给被阻塞的子代理而不会唤起父模型。如果某工具被另一个用户批准只有该次被批准的调用以那个用户的身份运行回合中的后续工具调用仍归属原始所有者。对审批请求而言不匹配选项的后续消息会 steering 回合而非回答它。eve 取消回合挂起的审批调用不会执行input.resolved报告outcome: ignored模型随后读到该消息。即使消息以turnPolicy: queue发送也会如此因为被真人持有的回合在行动之前无法结束。同批中已被批准、且已被那人确认的调用仍会执行。来自回合服务对象以外的人的消息会等待回合结束。取消回合会撤回其审批调用不执行input.resolved报告outcome: cancelled之后对其的任何回答都不会批准任何东西。在 harness 层面这一逻辑由 hitl/approvals.ts 的parkOnApprovals实现模型响应中被判定需要审批的调用进入挂起步骤suspended step回合被持有直到答案到达若该步骤还有运行时任务工作流工具调用input.requested事件会推迟到任务完成后才发出——因为被审批的调用在任务完成前本来也无法执行。同时多个请求一个回合可以同时等待多个请求例如工作流工具用Promise.all提出的两个ctx.ask()问题或模型一步中发出的两个工具调用审批。一条后续消息只回答第一个开放的请求优先是 运行时限制 的续接提示若存在否则是 eve 最先提出的那个请求按input.requested事件顺序。源码中的firstOpenInput()open-input-request.ts正体现了这一优先级先取kind session-limit的开放输入否则取第一个未回答的开放输入。消息在匹配某选项、或该请求允许自由文本时才算回答该请求。否则消息不是答案按上面的规则处理。要用文本回答多个请求需逐条发送消息。与工作流工具调用如ask_question同一步发出的工具调用审批要等工作流工具调用结束后才会被请求因为被审批的调用在那之前无法运行。用户先回答问题然后才看到审批。只显示文本的渠道一次只显示一个请求按上述顺序当前一个被回答后才发布下一个因此回复总是回答用户当前看到的请求。Twilio、GitHub、Linear 和 Chat SDK 渠道就是这样工作。Chat SDK 无法判断某个适配器是否显示按钮所以每个 Chat SDK 渠道包括 Linq、Photon 及带按钮的适配器都逐个显示请求。而 Slack 等带按钮的原生渠道会显示所有开放请求用户可以按任意一个此时输入文本回复仍然只回答第一个。渠道与客户端应答渠道把请求变成原生 UISlack 适配器把审批渲染成按钮、把问题渲染成选择菜单并把用户的选择写回为答案。在每个 渠道 上你都免费获得此能力。自建前端时扫描所有消息中的挂起请求并通过同一会话回答——参见 构建前端 中的客户端 reducer 与inputResponses结构。回合运行期间也可以回答例如在一批审批中第一个答案仍在结算时就提交第二个。默认消息 reducer 会等待服务器确认后才把输入请求标记为已回答提交的答案会把请求标记为data.inputs中的responded直到approval.settled或input.resolved到达单独提交响应并不会结算审批、问题或会话限制提示。回答一个已不再开放的请求会在不发起服务器请求的情况下被拒绝。client.input.responded事件只是给自定义 reducer 的提交通知并不代表服务器已接受该答案。源码参考与延伸阅读想深入实现细节可以直接阅读这些仓库源码审批策略助手与auto()决策逻辑packages/eve/src/tools/approval/policies.ts审批相关类型Approval、ApprovalContext、ApprovalStatus、response policy 上下文与resolveApprovalPolicypackages/eve/src/approval/definition.tsask_question工具定义与输入/输出 schemapackages/eve/src/tools/provided/ask-question.tsctx.ask()工作流实现与结果映射packages/eve/src/tools/provided/ask-question-workflow.tsHITL 会话机状态转换parkOnApprovals等packages/eve/src/harness/hitl/approvals.ts第一个开放请求的选取顺序packages/eve/src/harness/open-input-request.ts继续阅读Tools定义审批所门控的类型化操作内置工具默认工具与ask_question等可选加入工具会话、运行与流式暂停背后的事件与恢复契约构建前端在自有 UI 中渲染并回答请求多租户审批为作者工具与连接工具解析按租户的审批策略赞分享人工智能AI AgentAgent 框架后端工具调用【免费下载链接】eveThe Open Framework for Building Agents项目地址https://gitcode.com/gh_mirrors/eve28/eve点击查看免费下载相关推荐openai-agents-python 人工审批Human-in-the-Loop全指南中断、审批与恢复机制详解openai agents python 人工审批Human in the Loop全指南中断、审批与恢复机制详解 Human in the LoopH人工智能AI AgentAgent 框架多智能体工具调用MCP Clientsagno AgentOS 中 Human-in-the-Loop 的实现剖析从瞬时暂停契约到持久审批记录agno AgentOS 中 Human in the Loop 的实现剖析从瞬时暂停契约到持久审批记录 本文围绕 agno 开源仓库中 cookbook/0人工智能大模型AI AgentAgent 框架多智能体工具调用RAGAgent 工作流Agent 记忆openai-agents-python 人机协同Human-in-the-loop审批流完全指南从工具标记到状态序列化恢复openai agents python 人机协同Human in the loop审批流完全指南从工具标记到状态序列化恢复 导读 本文围绕 docs/h人工智能AI AgentAgent 框架多智能体工具调用MCP Clients创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考