## 已确认问题 `ModelRequest` 已区分“没有指定筛选”与“显式空集合”,并提供 `dynamicToolRegistry`。独立 ReAct 的 `invokeModel()` 直接使用 `request.getToolRegistry().definitions()`,没有读取 `hasToolSelection()/getTools()` 或动态注册表。拦截器明确关闭工具后,模型仍收到原工具声明。 代码:https://github.com/agentic-ai-java/argi/blob/b34835354b27f1f320ea8438edf2e9d170e69c84/argi-agent/argi-agent-core/src/main/java/io/github/agentic/ai/agent/react/ReActAgent.java#L379 ## 最小复现场景 1. 创建包含 `secret` 工具的 ReActAgent,用记录 ChatRequest 的假 ChatClient 返回正常终态。 2. 增加 ModelInterceptor,调用 `next.call(ModelRequest.builder(request).tools(List.of()).build())`。 3. 调用 Agent,检查假客户端收到的 `ChatRequest.tools()`。 实际结果:`explicit empty tool selection disclosed=[secret]`。期望为空,且模型即使返回未允许的调用,也不能执行该工具。 同一路径还会忽略通过 `ModelRequest.dynamicToolRegistry(...)` 提供的调用级工具。 ## 交付与验收 - [ ] 明确并执行静态工具、动态工具和显式选择的组合规则;对重复名称采用明确、可测试的策略。 - [ ] 未指定筛选和显式空集合语义不同;显式空集合必须不暴露、不执行任何工具。 - [ ] 选择子集时,只声明和执行该次选择中的工具;动态工具可按相同规则参与筛选。 - [ ] 增加无筛选、空筛选、子集、动态工具、重复名称及模型请求未授权名称的行为测试。 - [ ] 非流式和流式调用、重复订阅与并发调用保持一致,调用间工具集合不能串用。 ## 关联与范围 关联 #196、#195。实际执行应使用哪份调用级绑定另行建单;本单负责筛选和动态披露语义,不新建授权体系,也不将工具未披露等同于已完成所有业务授权。 ## 验证基线与变更约束 审阅基线:Core `b34835354b27f1f320ea8438edf2e9d170e69c84` / Extensions `cb2635ab2fc338e8aade28a132a15f48b4e655fc`。以下为离线 stub、独立 Java 探针或候选工件编译验证的结果,不代表真实模型、外部服务或生产环境验收。 修复保持独立 ReAct/Workflow 不依赖 Graph/Loop,不向 Core 引入供应商 SDK。优先修正现有契约的执行行为;确需改变公开 API、配置或定义格式时,说明较小替代方案、收益、消费者影响、目标版本与迁移方式。不得以跳过测试或删除能力换取通过。 ## 配套缺陷 https://github.com/agentic-ai-java/argi/issues/269
Activity
konlue commentedon Oct 25, 2026
你好,我想认领 #268。
基于 main
b34835354确认:1. 缺口一行级
ReActAgent.invokeModel组装ChatRequest时直接用request.getToolRegistry().definitions()暴露全量工具,ModelRequest上现成的工具选择语义完全没被消费——hasToolSelection()(区分“未指定”与“显式空集”)、getTools()(显式名单)、dynamicToolRegistry(调用级动态披露)都在,但声明侧一个都没接。所以拦截器tools(List.of())之后模型仍然收到全部工具声明,和 issue 里的 repro 一致。2. 声明与执行共享同一 registry
invokeModel的声明来源与 ToolCall 执行(executeTool里的 find/execute)用的是同一个availableTools(每 turn 由resolveTools静态∪动态合并产出)。这意味着修复不能只筛声明侧——筛选结果必须同步到执行侧,否则“declared and executed”只完成一半(执行侧的一致性正是 #269 的主题,两票在这一点上耦合)。3. 重名现状
ToolRegistry构造器对重名直接抛异常,而ModelRequest.getTools()的 distinct 是按 name 保序(静态优先)——组合规则可以顺势定为静态优先、动态同名视为被遮蔽,不需要引入新的去重协议。方案(单 PR,零新协议)
invokeModel在拦截器链尾计算「生效工具集」:静态 registry ∪dynamicToolRegistry(静态优先去重)∩ 显式筛选(hasToolSelection()时按getTools()名单收窄;显式空集 → 生效集为空)。用生效集生成模型声明。trustedExecutionContext语义不动;不新增 API、不建新的授权体系(工具不披露 ≠ 业务授权,与 issue 非目标一致)。测试
照
ReActAgentTest的ScriptedClient模式,全离线:无筛选 / 显式空集 / 子集筛选 / 动态工具参与筛选 / 静态动态重名 / 流式与非流式一致 / 并发调用无工具集泄漏;含 issue 的最小 repro(拦截器空选集 → fake client 观察到的声明为空)。组合规则如果需要更复杂的策略(比如动态优先、按 tag 匹配)后续 PR 再演进。
两个实现决策
modelTurn内的局部引用回传(invokeModel在拦截器链尾算好生效集,同 turn 的executeTool直接使用)——零 API 变化;让ModelResponse携带生效 registry 更显式但要动公共契约,留给后续有跨 turn 需求时再议。ModelRequest.getTools()现有 distinct 语义一致,不引入新的去重协议;ToolRegistry构造器的重名快速失败继续作为合并入口的兜底。fix(agent): honor interceptor tool selection and dynamic registries i…
Merge pull request #333 from konlue/feat/t268-interceptor-tool-filtering