总览:https://github.com/agentic-ai-java/argi/issues/180 ## 代码依据 https://github.com/agentic-ai-java/argi/blob/f45b2cf7a2fb301744fa61ef423795f6d30b65da/argi-agent-framework/src/main/java/io/github/agentic/ai/graph/agent/interceptor/modelfallback/ModelFallbackInterceptor.java#L30 ## 问题、范围与验收 现有 ModelFallbackInterceptor 捕获 handler.call 的同步异常,类注释明确不处理订阅后 publisher 的错误。此项是已声明能力限制的补全,不把当前实现直接认定为违约。已有 ModelRetryInterceptor 在发出事件后不重试,可作为语义参考。 ## 验收 - [ ] 明确调用构建失败、订阅后首事件前失败、输出后失败的分类;评审是否支持首事件前切换并给出理由。 - [ ] 若支持,只有尚无任何对外事件/可观察效果时才切换;取消、deadline、租约失效不得触发 fallback。 - [ ] 输出后失败明确终止或采用显式新 attempt 协议,禁止直接追加另一个模型的文本导致混合/重复结果。 - [ ] 重新校验替代模型请求能力,保留 system/messages/tools/attributes,并明确 fallback 与 retry 的组合顺序和总体调用预算。 - [ ] 覆盖首事件前失败、首事件后失败、usage/tool/RESPONSE 事件、所有候选失败、取消及多层中间件测试。 - [ ] 事件和失败信息能说明实际尝试的模型及结果;保持现有非流式行为或说明版本变更。 与 OPT-18 能力预检、#127 执行预算协调。若决定保留当前限制,需给出明确能力声明、调用方策略及验证依据,不能只关单忽略差异。 ## 验证与变更约束 代码基线 Core f45b2cf7a2fb301744fa61ef423795f6d30b65da。本单来自静态审查,尚未运行针对性复现或实现。先补行为验证,复用已有能力;破坏性调整逐项说明收益、兼容替代、消费者影响、迁移成本和版本。局部修复不等待 Agent/Workflow/Loop 全局重构,新增通用契约不反向依赖 Loop。 ## 关联任务 - https://github.com/agentic-ai-java/argi/issues/209
Activity
Aias00 commentedon Oct 23, 2026
新增可执行复现:被包装的取消仍触发非流式 fallback
基线 Core
76d44429fdaa6a69320e21a1cb77119d0889d792。使用实际编译的 ModelFallbackInterceptor,配置一个备用 ChatClient;handler 首次抛new CompletionException(new CancellationException("stop")),若再次调用则返回普通 assistant 消息。结果:handler 被调用 2 次,最终返回备用模型成功消息。
propagateCancellation仅识别最外层 CancellationException/InterruptedException 与线程中断状态,没有识别 cause 链。对照同工程 ModelRetryInterceptor 已遍历 cause 链。本复现不依赖真实模型/网络,确认同步拦截链中被包装取消仍可触发多余调用;不是首事件前/后流式切换的复现。沿用本单既有“取消、deadline、租约失效不得触发 fallback”验收,不另建同目标 issue。实施时还需覆盖多层 CompletionException/ExecutionException、包装中断及已失效执行控制;不能凭任意错误文本判断取消。
代码:
argi/argi-agent-framework/src/main/java/io/github/agentic/ai/graph/agent/interceptor/modelfallback/ModelFallbackInterceptor.java
Line 99 in 76d4442
XianReallyHot-ZZH commentedon Nov 13, 2026
你好,我想认领这个问题 @Aias00
复现计划
propagateCancellation在当前 main(efd12ae)仍只检查最外层异常类型,被包装的取消会触发多余的 fallback 调用。修复方案(含设计选择)
ModelRetryInterceptor.cancellation(Throwable):遍历 cause 链;取消/中断在任何候选位置直接终止,不触发 fallback。ExecutionGuard(存在时assertActive()),deadline/租约失效不消耗剩余候选。ModelRequest.builder(request).chatClient(...)复用,system/messages/tools/attributes 保留);已发出事件后失败 → 错误原样终止,绝不拼接两个模型的输出;每次订阅独立预算(Flux.defer)。兼容边界
ModelFallbackInterceptor公开构造/builder 契约,不改事件契约;ModelRetryInterceptor。测试范围
完成后提交 PR。
XianReallyHot-ZZH commentedon Nov 13, 2026
已基于最新 main(efd12ae)实现:#368。
按认领评论的方案落地,补充三点实现说明:
cancellationAndInterruptionNeverAttemptAnotherClient契约不变);响应式路径只信错误信号本身,不再读写池化线程的中断标志——避免污染 Netty 事件循环线程导致后续无关请求被误判为取消。取消在首事件前后都解包为同一身份,下游按顶层类型判断取消的分支不再受包装影响。callBlocking阻塞送达错误信号的线程。每次切换前检查ExecutionGuard(主调用入口也补了检查,对齐 retry 的每次尝试检查),每次订阅独立切换状态。Flux.just(ChatEvent.response(...))不经过StreamingModelInterceptor策略——这与ModelRetryInterceptor.toStreamingFlux及 AgentLlmNode 同步分支的既有约定一致,如需收紧建议单开 issue 统一处理。测试 30 项(新增 17 项边界用例):包装取消 ×3、取消身份一致性、首事件前切换/后终止(primary 与 fallback 候选两层)、usage/toolCall 门闸、guard 入口/切换点、每订阅预算、多候选推进、同步构建失败分类、fallback×retry 组合顺序;模块全量 1382 项通过。