fix: add fallback providers to handoff subagents#9332
Conversation
There was a problem hiding this comment.
Code Review
This pull request adds support for parsing and passing fallback chat models and maximum request retries during agent handoff execution, along with a corresponding unit test. The review feedback suggests refactoring the fallback provider resolution logic to reuse an existing helper function from astr_main_agent to avoid code duplication, and updating the unit test's monkeypatching accordingly.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| fallback_ids = prov_settings.get("fallback_chat_models", []) | ||
| fallback_providers: list[Provider] = [] | ||
| if not isinstance(fallback_ids, list): | ||
| logger.warning( | ||
| "fallback_chat_models setting is not a list, skip handoff fallback providers." | ||
| ) | ||
| else: | ||
| seen_provider_ids = {prov_id} if prov_id else set() | ||
| for fallback_id in fallback_ids: | ||
| if not isinstance(fallback_id, str) or not fallback_id: | ||
| continue | ||
| if fallback_id in seen_provider_ids: | ||
| continue | ||
| fallback_provider = ctx.get_provider_by_id(fallback_id) | ||
| if fallback_provider is None: | ||
| logger.warning( | ||
| "Handoff fallback chat provider `%s` not found, skip.", | ||
| fallback_id, | ||
| ) | ||
| continue | ||
| if not isinstance(fallback_provider, Provider): | ||
| logger.warning( | ||
| "Handoff fallback chat provider `%s` is invalid type: %s, skip.", | ||
| fallback_id, | ||
| type(fallback_provider), | ||
| ) | ||
| continue | ||
| fallback_providers.append(fallback_provider) | ||
| seen_provider_ids.add(fallback_id) | ||
|
|
There was a problem hiding this comment.
This block of code is almost identical to the _get_fallback_chat_providers helper function defined in astrbot/core/astr_main_agent.py. To avoid code duplication and adhere to the general rules, we should reuse that helper function.
We can import _get_fallback_chat_providers inside _execute_handoff to avoid circular dependencies, and pass the resolved Provider object directly.
provider = ctx.get_provider_by_id(prov_id) if prov_id else None
if provider:
from astrbot.core.astr_main_agent import _get_fallback_chat_providers
fallback_providers = _get_fallback_chat_providers(provider, ctx, prov_settings)
else:
fallback_providers = []References
- When implementing similar functionality for different cases, refactor the logic into a shared helper function to avoid code duplication.
There was a problem hiding this comment.
Thanks for the suggestion. I am keeping the resolution inline: importing a private helper from astr_main_agent would couple the tool executor to the main-agent module, which already imports FunctionToolExecutor, creating a circular import risk. This is currently the second use site, so extracting a shared provider utility would add broader scope without improving the handoff fix.
| run_hooks=None, | ||
| ), | ||
| ) | ||
| monkeypatch.setattr("astrbot.core.astr_agent_tool_exec.Provider", _ChatProvider) |
There was a problem hiding this comment.
Since we are refactoring the fallback provider resolution to use _get_fallback_chat_providers from astrbot.core.astr_main_agent, we need to monkeypatch Provider in astrbot.core.astr_main_agent instead of astrbot.core.astr_agent_tool_exec so that the isinstance check inside the helper function works correctly with our mock provider class.
| monkeypatch.setattr("astrbot.core.astr_agent_tool_exec.Provider", _ChatProvider) | |
| monkeypatch.setattr('astrbot.core.astr_main_agent.Provider', _ChatProvider) |
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="astrbot/core/astr_agent_tool_exec.py" line_range="401-402" />
<code_context>
max_steps=agent_max_step,
tool_call_timeout=run_context.tool_call_timeout,
stream=stream,
+ fallback_providers=fallback_providers,
+ request_max_retries=prov_settings.get("request_max_retries", 5),
)
yield mcp.types.CallToolResult(
</code_context>
<issue_to_address>
**issue (bug_risk):** Normalize and validate `request_max_retries` from config before passing it through.
Other provider settings (e.g. `max_agent_step`) are cast to `int`, but `request_max_retries` is passed through unmodified from `prov_settings`. If config comes from env vars or user input, this may be a string or other unexpected type and cause failures in `tool_loop_agent`. Please normalize, e.g. `int(prov_settings.get("request_max_retries", 5))`, and consider clamping to a sensible minimum (like `>= 0` or `>= 1`).
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Motivation
Handoff subagents did not receive the configured fallback chat providers or request retry limit. When an upstream streaming connection was interrupted, a subagent could fail after its primary provider without using the same retry and fallback behavior as the main agent.
Modifications
Resolve configured fallback chat providers for handoff subagents.
Skip the primary provider, duplicates, missing providers, and non-chat providers.
Forward
request_max_retriesto the handoff tool-loop runner.Add a regression test covering fallback filtering and retry forwarding.
This is NOT a breaking change.
Test Results
python -m pytest -q tests/unit/test_astr_agent_tool_exec.py-> 13 passedruff format --check astrbot/core/astr_agent_tool_exec.py tests/unit/test_astr_agent_tool_exec.pyruff check astrbot/core/astr_agent_tool_exec.py tests/unit/test_astr_agent_tool_exec.pyChecklist
Summary by Sourcery
Ensure handoff subagents inherit appropriate fallback chat providers and retry settings from the main agent when executing tool-loop handoffs.
New Features:
Bug Fixes:
Enhancements:
Tests: