【AgentScope Java新手村系列】(14)人机交互

📅 2026/7/17 9:45:05 👁️ 阅读次数 📝 资讯
【AgentScope Java新手村系列】(14)人机交互

第十四章 人机交互:Permission 系统 ALLOW/DENY/ASK 规则,运行时 HITL 自动拦截

"用户让 agent 删数据库——我们不希望它'问都不问就执行'。1.x 时代用Hook.stopAgent()onActing阶段抛异常中断;2.0 推荐用Permission系统——为每个 tool 配 ALLOW / DENY / ASK 规则,运行时自动拦截、提示用户、收集决策。这比 hook 优雅得多。"

本章你将学到:PermissionMode5 种模式、PermissionRule4 个字段、ASK模式下如何给前端推送确认请求、以及Middleware在 HITL 中的辅助角色。

14.1 1.x 时代怎么拦截工具?

1.x 时代要在工具调用前拦下来,业务方在Hook.onActingthrow new StopAgentException()。问题:

  • 异常语义不直观

  • 不能区分"想确认" vs "直接拒绝"

  • 没法让前端弹"是否放行"对话框,只能字符串错误

    2.0 用Permission系统替代了这套写法:

决策含义
ALLOW直接执行
DENY直接拒绝 + 错误反馈给 LLM
ASK暂停工具执行,推送确认请求给用户;用户回 ALLOW/DENY 后继续
PASSTHROUGH跳过本条规则,看下一条

14.2 第一个 Permission 例子

这个例子在演示什么?

你有一个 DB 管理员 agent,它有drop_table这种危险工具。你不希望它"问都不问就删表"——所以给drop_table配了一条ASK规则:agent 想删表时,Permission 引擎会暂停执行,把确认请求推给前端,等用户点了"允许"才继续。

import io.agentscope.core.agent.RuntimeContext; import io.agentscope.core.message.UserMessage; import io.agentscope.core.model.DashScopeChatModel; import io.agentscope.core.permission.*; import io.agentscope.harness.HarnessAgent; import java.util.List; public class Chapter14_Permission { public static void main(String[] args) { // 1. 准备 PermissionContext:ACCEPT_EDITS 模式,大部分操作放行 // 但 drop_table 必须人工确认(ASK) PermissionContextState perms = PermissionContextState.builder() .mode(PermissionMode.ACCEPT_EDITS) .addAskRule("drop_table", new PermissionRule( "drop_table", null, // null = 匹配所有 drop_table 调用 PermissionBehavior.ASK, "userSettings")) .build(); // 2. 构造 agent HarnessAgent agent = HarnessAgent.builder() .name("db_admin") .sysPrompt("你是一个 DB 管理员;可以查表,但删表必须先问。") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen-plus") .build()) .workspace(Path.of("./workspace")) .permissionContext(perms) .build(); agent.call( List.of(new UserMessage("user", "把 orders_2024 表 drop 掉。")), RuntimeContext.empty()) .block(); // 跑到 drop_table 时,Permission 会发出 ConfirmRequest,前端需要回应 } }

PermissionRule四个字段:

  • toolName— 工具名
  • ruleContent— 匹配模式,null 表示对所有调用匹配
  • behaviorALLOW/DENY/ASK/PASSTHROUGH
  • source— 规则来源,便于审计("userSettings"/"projectSettings"/"session"/"suggested"

14.3 5 种 PermissionMode

Mode行为适用场景
DEFAULT所有未命中规则都ASK最安全,推荐默认值
ACCEPT_EDITS自动放行工作目录内的文件操作用户在场的活跃开发
EXPLORE只读:放行读、拒绝所有写与命令代码探索、规划
BYPASS放行一切(deny / ask 规则仍生效)完全可信的沙箱
DONT_ASK把所有 ASK 转为 DENY无人值守 / 计划任务

注意EXPLORE模式下 deny 是不可绕过的——即使在BYPASS模式下也照常生效。这是 2.0 的"危险工具不可绕过"原则。

14.3.1PermissionContextState——整个权限系统的"配置单"

前面一直用.builder().mode(...).addAskRule(...).build(),这个构建出来的对象就是PermissionContextState。它是 agent 权限的全部配置,一张对象里包了三样东西:

PermissionContextState ├── mode ← 一个 PermissionMode(DEFAULT / ACCEPT_EDITS / EXPLORE / BYPASS / DONT_ASK) ├── allowRules ← Map<工具名, List<PermissionRule>> — 哪些工具直接放行 ├── denyRules ← Map<工具名, List<PermissionRule>> — 哪些工具直接拒绝 └── askRules ← Map<工具名, List<PermissionRule>> — 哪些工具暂停问用户

核心地位PermissionContextState是 agent 能做什么、不能做什么的唯一配置文件。它不属于某个 session,不属于某个工具——它是整个 agent 的权限配置,在HarnessAgent.builder().permissionContext(perms)时注入,之后不可变。

你可以把它理解为"agent 的权限清单"——清单上写了哪些工具要问、哪些直接放、哪些打死不能用。PermissionEngine(权限引擎)在每次工具调用前照着清单判定。

14.4 ASK 的完整流程

  1. LLM 想调drop_table
  2. Permission 引擎查规则 →ASK
  3. 引擎生成"建议规则"——基于本次调用入参(drop_table('orders_2024')
  4. 引擎把ConfirmRequest推给前端(通过streamEvents()PermissionAskEvent
  5. 前端弹"是否放行"对话框
  6. 用户回 ALLOW + 接受建议规则
  7. 后端调agent.call(...)时把ConfirmResult注入回 session
  8. 工具执行 + 自动把"建议规则"加入PermissionContextState
import io.agentscope.core.event.ConfirmResult; import io.agentscope.core.event.ConfirmRequest; public Map<String, Object> handleAsk(ConfirmRequest req) { // 1. 推送给前端 sendToFrontend(req); // 2. 等用户回 ALLOW boolean userAllowed = waitForUserDecision(); // 3. 构造 ConfirmResult ConfirmResult result = new ConfirmResult( userAllowed, req.getToolCall(), userAllowed ? req.getSuggestedRules() : List.of() // 接受建议 ); return Map.of("decision", result); }

14.5 Middleware 在 HITL 中的辅助角色

Permission只解决"能不能跑",Middleware解决"跑之前 / 之后还要做什么"。HITL 场景里常见的 Middleware 用法:

class HitlAuditMiddleware extends MiddlewareBase { @Override public Mono<HookEvent> onActing(MiddlewareContext ctx, HookEvent event) { event.getToolCalls().forEach(tc -> { auditLog.info("user={} tool={} input={}", ctx.runtime().getUserId(), tc.getName(), tc.getInput()); }); return Mono.just(event); } }

Permission给"能不能跑"的答案;Middleware记"谁、什么时候、跑了什么"。

14.6 与前端 SSE 的协作

streamEvents()会发出PermissionAskEvent

event: permission-ask data: {"toolCallId":"tc-1","toolName":"drop_table","input":{"table":"orders_2024"},"suggestedRules":[...]}

前端订阅后弹窗;用户决策通过另一个 HTTP 端点回传(POST /api/agent/permission/respond),后端用agent.resume(sessionId, confirmResult)继续。

agent.resume(...)2.0 新增——专门用于 ASK 暂停后的恢复。

14.7 1.xHook.stopAgent还能用吗?

能,但不推荐。io.agentscope.core.hook.Hook在 2.0 标@Deprecated,但语义保留:

class LegacyHitlHook implements Hook { @Override public void onActing(HookEvent event) { if (event.getToolCalls().stream().anyMatch(t -> "drop_table".equals(t.getName()))) { throw new StopAgentException("drop_table is forbidden"); } } }

新代码请统一用Permission系统;Hook只用来"写日志/埋点"等不需要 ASK 的场景。

14.8 完整可运行示例

这个例子在演示什么?

你有一个客服 agent,配了 3 个工具:query_order(查订单)、refund_order(退款)、drop_table(删表——危险操作)。我们希望:

  • 查订单:直接放行,不打扰用户(ALLOW)
  • 退款:弹窗问用户"是否确认退款?"(ASK)
  • 删表:直接拒绝,agent 根本没机会删(DENY)

下面用一个 agent 配 3 条 PermissionRule,连续问 3 次,每次触发不同行为:

public class Chapter14_FullHitl { public static void main(String[] args) { PermissionContextState perms = PermissionContextState.builder() .mode(PermissionMode.DEFAULT) .addAllowRule("query_order", new PermissionRule( "query_order", null, PermissionBehavior.ALLOW, "userSettings")) .addAskRule("refund_order", new PermissionRule( "refund_order", null, PermissionBehavior.ASK, "userSettings")) .addDenyRule("drop_table", new PermissionRule( "drop_table", null, PermissionBehavior.DENY, "userSettings")) .build(); HarnessAgent agent = HarnessAgent.builder() .name("customer_service") .sysPrompt("你是客服,可以查订单,但退款需要用户确认,删表绝对禁止。") .model(model()) .workspace(Path.of("./workspace")) .permissionContext(perms) .build(); // 查订单 → ALLOW:工具直接执行,无任何中断 agent.call(List.of(new UserMessage("user", "查订单 123")), RuntimeContext.empty()).block(); // 退款 → ASK:Permission 引擎暂停,发出 ConfirmRequest 等前端回应 agent.call(List.of(new UserMessage("user", "给订单 123 退款 100 元")), RuntimeContext.empty()).block(); // 删表 → DENY:工具调用被直接拒绝,agent 收到错误反馈 agent.call(List.of(new UserMessage("user", "删掉 orders 表")), RuntimeContext.empty()).block(); } }

注意addAllowRule/addAskRule/addDenyRule的第一个参数是精确工具名,不支持通配符。必须写"query_order"而非"query_*"

14.9 本章小结

  • 2.0 用Permission系统做工具调用级 HITL,比 1.xHook.stopAgent优雅。
  • 5 种PermissionMode适配不同部署场景;4 种行为ALLOW/DENY/ASK/PASSTHROUGH
  • ASK模式:PermissionAskEvent推前端 + 用户回ConfirmResult
  • Deny规则和危险路径检查不可绕过——BYPASS模式也拦不住。
  • Middleware留给"日志/埋点"等不需要决策的副作用。