mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-07 02:51:50 +00:00
fix(delegation): honor provider override for subagents
Clear inherited provider preference filters when delegation.provider is set so delegated children do not route back to the parent provider. Add a regression test for cross-provider delegation with parent OpenRouter filters. Closes #10653
This commit is contained in:
parent
7a8ee8b29d
commit
83080772f2
2 changed files with 63 additions and 4 deletions
|
|
@ -980,6 +980,48 @@ class TestDelegationProviderIntegration(unittest.TestCase):
|
||||||
self.assertNotEqual(kwargs["base_url"], parent.base_url)
|
self.assertNotEqual(kwargs["base_url"], parent.base_url)
|
||||||
self.assertNotEqual(kwargs["api_key"], parent.api_key)
|
self.assertNotEqual(kwargs["api_key"], parent.api_key)
|
||||||
|
|
||||||
|
@patch("tools.delegate_tool._load_config")
|
||||||
|
@patch("tools.delegate_tool._resolve_delegation_credentials")
|
||||||
|
def test_provider_override_clears_parent_openrouter_filters(
|
||||||
|
self, mock_creds, mock_cfg
|
||||||
|
):
|
||||||
|
"""Delegated provider should not inherit parent provider-preference filters."""
|
||||||
|
mock_cfg.return_value = {
|
||||||
|
"max_iterations": 45,
|
||||||
|
"model": "google/gemini-3-flash-preview",
|
||||||
|
"provider": "openrouter",
|
||||||
|
}
|
||||||
|
mock_creds.return_value = {
|
||||||
|
"model": "google/gemini-3-flash-preview",
|
||||||
|
"provider": "openrouter",
|
||||||
|
"base_url": "https://openrouter.ai/api/v1",
|
||||||
|
"api_key": "sk-or-key",
|
||||||
|
"api_mode": "chat_completions",
|
||||||
|
}
|
||||||
|
parent = _make_mock_parent(depth=0)
|
||||||
|
parent.providers_allowed = ["anthropic/claude-3.5-sonnet"]
|
||||||
|
parent.providers_ignored = ["openai/gpt-4o-mini"]
|
||||||
|
parent.providers_order = ["google/gemini-2.5-pro"]
|
||||||
|
parent.provider_sort = "price"
|
||||||
|
|
||||||
|
with patch("run_agent.AIAgent") as MockAgent:
|
||||||
|
mock_child = MagicMock()
|
||||||
|
mock_child.run_conversation.return_value = {
|
||||||
|
"final_response": "done",
|
||||||
|
"completed": True,
|
||||||
|
"api_calls": 1,
|
||||||
|
}
|
||||||
|
MockAgent.return_value = mock_child
|
||||||
|
|
||||||
|
delegate_task(goal="Cross-provider test", parent_agent=parent)
|
||||||
|
|
||||||
|
_, kwargs = MockAgent.call_args
|
||||||
|
self.assertEqual(kwargs["provider"], "openrouter")
|
||||||
|
self.assertIsNone(kwargs["providers_allowed"])
|
||||||
|
self.assertIsNone(kwargs["providers_ignored"])
|
||||||
|
self.assertIsNone(kwargs["providers_order"])
|
||||||
|
self.assertIsNone(kwargs["provider_sort"])
|
||||||
|
|
||||||
@patch("tools.delegate_tool._load_config")
|
@patch("tools.delegate_tool._load_config")
|
||||||
@patch("tools.delegate_tool._resolve_delegation_credentials")
|
@patch("tools.delegate_tool._resolve_delegation_credentials")
|
||||||
def test_direct_endpoint_credentials_reach_child_agent(self, mock_creds, mock_cfg):
|
def test_direct_endpoint_credentials_reach_child_agent(self, mock_creds, mock_cfg):
|
||||||
|
|
|
||||||
|
|
@ -1032,6 +1032,23 @@ def _build_child_agent(
|
||||||
# fallback_model parameter (which handles both list and dict forms).
|
# fallback_model parameter (which handles both list and dict forms).
|
||||||
parent_fallback = getattr(parent_agent, "_fallback_chain", None) or None
|
parent_fallback = getattr(parent_agent, "_fallback_chain", None) or None
|
||||||
|
|
||||||
|
# Inherit the parent's OpenRouter provider-preference filters by default
|
||||||
|
# (so subagents routed to the same provider honour the same routing
|
||||||
|
# constraints). BUT: when `delegation.provider` is set the user is
|
||||||
|
# explicitly asking the child to run on a different provider, and
|
||||||
|
# parent-level OpenRouter filters (e.g. `only=["Anthropic"]`) would
|
||||||
|
# silently force the child back onto the parent's provider. Clear the
|
||||||
|
# filters in that case so the delegated provider is honoured.
|
||||||
|
child_providers_allowed = getattr(parent_agent, "providers_allowed", None)
|
||||||
|
child_providers_ignored = getattr(parent_agent, "providers_ignored", None)
|
||||||
|
child_providers_order = getattr(parent_agent, "providers_order", None)
|
||||||
|
child_provider_sort = getattr(parent_agent, "provider_sort", None)
|
||||||
|
if override_provider:
|
||||||
|
child_providers_allowed = None
|
||||||
|
child_providers_ignored = None
|
||||||
|
child_providers_order = None
|
||||||
|
child_provider_sort = None
|
||||||
|
|
||||||
child = AIAgent(
|
child = AIAgent(
|
||||||
base_url=effective_base_url,
|
base_url=effective_base_url,
|
||||||
api_key=effective_api_key,
|
api_key=effective_api_key,
|
||||||
|
|
@ -1056,10 +1073,10 @@ def _build_child_agent(
|
||||||
thinking_callback=child_thinking_cb,
|
thinking_callback=child_thinking_cb,
|
||||||
session_db=getattr(parent_agent, "_session_db", None),
|
session_db=getattr(parent_agent, "_session_db", None),
|
||||||
parent_session_id=getattr(parent_agent, "session_id", None),
|
parent_session_id=getattr(parent_agent, "session_id", None),
|
||||||
providers_allowed=parent_agent.providers_allowed,
|
providers_allowed=child_providers_allowed,
|
||||||
providers_ignored=parent_agent.providers_ignored,
|
providers_ignored=child_providers_ignored,
|
||||||
providers_order=parent_agent.providers_order,
|
providers_order=child_providers_order,
|
||||||
provider_sort=parent_agent.provider_sort,
|
provider_sort=child_provider_sort,
|
||||||
tool_progress_callback=child_progress_cb,
|
tool_progress_callback=child_progress_cb,
|
||||||
iteration_budget=None, # fresh budget per subagent
|
iteration_budget=None, # fresh budget per subagent
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue