Skip to content

fix: add fallback providers to handoff subagents#9332

Open
konodiodaaaaa1 wants to merge 1 commit into
AstrBotDevs:masterfrom
konodiodaaaaa1:fix/handoff-fallback-providers
Open

fix: add fallback providers to handoff subagents#9332
konodiodaaaaa1 wants to merge 1 commit into
AstrBotDevs:masterfrom
konodiodaaaaa1:fix/handoff-fallback-providers

Conversation

@konodiodaaaaa1

@konodiodaaaaa1 konodiodaaaaa1 commented Jul 20, 2026

Copy link
Copy Markdown

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_retries to 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 passed
  • ruff format --check astrbot/core/astr_agent_tool_exec.py tests/unit/test_astr_agent_tool_exec.py
  • ruff check astrbot/core/astr_agent_tool_exec.py tests/unit/test_astr_agent_tool_exec.py

Checklist

  • Changes have been tested and verification results are included above.
  • No new dependencies were introduced.
  • No malicious code was introduced.

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:

  • Support passing filtered fallback chat providers to handoff subagents during tool-loop execution.

Bug Fixes:

  • Prevent handoff subagents from failing without using configured fallback providers and request retry limits when the primary chat provider fails.

Enhancements:

  • Filter and validate configured fallback chat provider IDs to skip the primary provider, duplicates, missing providers, and non-chat providers before handoff.
  • Forward request retry configuration from provider settings to the handoff tool-loop runner to align behavior with the main agent.

Tests:

  • Add an async regression test verifying that execute_handoff forwards valid fallback providers and request_max_retries to the tool-loop agent.

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. area:core The bug / feature is about astrbot's core, backend area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. labels Jul 20, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +358 to +387
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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
  1. When implementing similar functionality for different cases, refactor the logic into a shared helper function to avoid code duplication.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
monkeypatch.setattr("astrbot.core.astr_agent_tool_exec.Provider", _ChatProvider)
monkeypatch.setattr('astrbot.core.astr_main_agent.Provider', _ChatProvider)

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread astrbot/core/astr_agent_tool_exec.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant