Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions agent/auxiliary_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3479,6 +3479,7 @@ def _retry_same_provider_sync(
extra_body=effective_extra_body,
reasoning_config=reasoning_config,
base_url=retry_base or resolved_base_url,
task=task,
)
if _is_anthropic_compat_endpoint(resolved_provider, retry_base):
retry_kwargs["messages"] = _convert_openai_images_to_anthropic(retry_kwargs["messages"])
Expand Down Expand Up @@ -3538,6 +3539,7 @@ async def _retry_same_provider_async(
extra_body=effective_extra_body,
reasoning_config=reasoning_config,
base_url=retry_base or resolved_base_url,
task=task,
)
if _is_anthropic_compat_endpoint(resolved_provider, retry_base):
retry_kwargs["messages"] = _convert_openai_images_to_anthropic(retry_kwargs["messages"])
Expand Down Expand Up @@ -3679,7 +3681,7 @@ def _call_fallback_candidate_sync(
temperature=temperature, max_tokens=max_tokens,
tools=tools, timeout=effective_timeout,
extra_body=effective_extra_body, reasoning_config=reasoning_config,
base_url=fb_base)
base_url=fb_base, task=task)
try:
return _validate_llm_response(
fb_client.chat.completions.create(**fb_kwargs), task)
Expand All @@ -3696,7 +3698,7 @@ def _call_fallback_candidate_sync(
tools=tools, timeout=effective_timeout,
extra_body=effective_extra_body,
reasoning_config=reasoning_config,
base_url=str(getattr(retry_client, "base_url", "") or fb_base))
base_url=str(getattr(retry_client, "base_url", "") or fb_base), task=task)
try:
return _validate_llm_response(
retry_client.chat.completions.create(**retry_kwargs), task)
Expand Down Expand Up @@ -3737,7 +3739,7 @@ async def _call_fallback_candidate_async(
temperature=temperature, max_tokens=max_tokens,
tools=tools, timeout=effective_timeout,
extra_body=effective_extra_body, reasoning_config=reasoning_config,
base_url=fb_base)
base_url=fb_base, task=task)
try:
return _validate_llm_response(
await fb_client.chat.completions.create(**fb_kwargs), task)
Expand All @@ -3755,7 +3757,7 @@ async def _call_fallback_candidate_async(
tools=tools, timeout=effective_timeout,
extra_body=effective_extra_body,
reasoning_config=reasoning_config,
base_url=str(getattr(retry_client, "base_url", "") or fb_base))
base_url=str(getattr(retry_client, "base_url", "") or fb_base), task=task)
try:
return _validate_llm_response(
await retry_client.chat.completions.create(**retry_kwargs), task)
Expand Down Expand Up @@ -6353,6 +6355,7 @@ def _build_call_kwargs(
extra_body: Optional[dict] = None,
reasoning_config: Optional[dict] = None,
base_url: Optional[str] = None,
task: Optional[str] = None,
) -> dict:
"""Build kwargs for .chat.completions.create() with model/provider adjustments."""
kwargs: Dict[str, Any] = {
Expand Down Expand Up @@ -6407,11 +6410,16 @@ def _build_call_kwargs(
_provider_norm in {"nvidia", "nvidia-nim", "nim", "build-nvidia", "nemotron"}
or base_url_host_matches(_effective_base, "integrate.api.nvidia.com")
)
_is_moa = bool(task) and str(task) == "moa_reference"
if (
_is_anthropic_compat_endpoint(provider, _effective_base)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This also matches moa_aggregator. Current one-shot MoA passes reference_max_tokens through to that aggregator (agent/conversation_loop.py:892; agent/moa_loop.py:722-727), while the documented contract says this cap applies to advisors only. Restrict this exception to task == "moa_reference".

or _is_nvidia_nim
or _is_moa
):
kwargs["max_tokens"] = max_tokens
# Use auxiliary_max_tokens_param() so models that require
# max_completion_tokens (GPT-5 family, Copilot) get the right
# parameter name instead of a hardcoded max_tokens that 400s.
kwargs.update(auxiliary_max_tokens_param(max_tokens, model=model))

if tools:
# Defensive dedup: providers like Google Vertex, Azure, and Bedrock
Expand Down Expand Up @@ -6758,7 +6766,7 @@ def call_llm(
temperature=temperature, max_tokens=max_tokens,
tools=tools, timeout=effective_timeout, extra_body=effective_extra_body,
reasoning_config=reasoning_config,
base_url=_base_info or resolved_base_url)
base_url=_base_info or resolved_base_url, task=task)

# Convert image blocks for Anthropic-compatible endpoints (e.g. MiniMax)
_client_base = str(getattr(client, "base_url", "") or "")
Expand Down Expand Up @@ -7367,7 +7375,7 @@ async def async_call_llm(
temperature=temperature, max_tokens=max_tokens,
tools=tools, timeout=effective_timeout, extra_body=effective_extra_body,
reasoning_config=reasoning_config,
base_url=_client_base or resolved_base_url)
base_url=_client_base or resolved_base_url, task=task)

# Convert image blocks for Anthropic-compatible endpoints (e.g. MiniMax)
if _is_anthropic_compat_endpoint(resolved_provider, _client_base):
Expand Down
119 changes: 119 additions & 0 deletions tests/agent/test_auxiliary_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,125 @@ def test_keeps_max_tokens_for_nvidia_nim(self):
)
assert kwargs["max_tokens"] == 4096

# ── MoA task should honor max_tokens on ALL providers (#reference_max_tokens) ──

@pytest.mark.parametrize(
"provider,model,base_url,expected_key",
[
("zai", "glm-5.2", "https://api.z.ai/api/coding/paas/v4", "max_tokens"),
("openrouter", "deepseek/deepseek-v4-flash:nitro", "https://openrouter.ai/api/v1", "max_tokens"),
("copilot", "gpt-5.5", "https://api.githubcopilot.com", "max_completion_tokens"),
("nous", "hermes-4", "https://inference-api.nousresearch.com/v1", "max_tokens"),
],
)
def test_moa_task_sends_max_tokens_on_openai_compatible(self, provider, model, base_url, expected_key):
"""MoA reference tasks must honor max_tokens regardless of provider.

The ``reference_max_tokens`` config option (PR #56756) caps advisor output
to reduce turn latency. Before the fix, ``_build_call_kwargs`` silently
dropped the value for OpenAI-compatible providers (PR #34845), so the cap
never reached the API. With the ``task`` parameter threaded through,
``task == "moa_reference"`` includes the output cap in kwargs.

Models that require ``max_completion_tokens`` (GPT-5 family, Copilot)
get the correct parameter name via ``auxiliary_max_tokens_param()``.
"""
from agent.auxiliary_client import _build_call_kwargs

kwargs = _build_call_kwargs(
provider=provider,
model=model,
messages=[{"role": "user", "content": "hi"}],
max_tokens=800,
base_url=base_url,
task="moa_reference",
)
assert kwargs[expected_key] == 800

def test_moa_task_sends_max_tokens_on_anthropic_wire(self):
"""MoA reference tasks on Anthropic-compat endpoints keep max_tokens (unchanged behavior)."""
from agent.auxiliary_client import _build_call_kwargs

kwargs = _build_call_kwargs(
provider="minimax",
model="minimax-m2",
messages=[{"role": "user", "content": "hi"}],
max_tokens=600,
base_url="https://api.minimax.io/v1",
task="moa_reference",
)
assert kwargs["max_tokens"] == 600

def test_moa_aggregator_does_not_get_max_tokens_on_openai_compat(self):
"""``reference_max_tokens`` is an advisors-only contract (#56756).

The aggregator is the acting model — it must NOT be capped by the
reference token budget. Only ``task == "moa_reference"`` triggers
the exception in ``_build_call_kwargs``.
"""
from agent.auxiliary_client import _build_call_kwargs

kwargs = _build_call_kwargs(
provider="zai",
model="glm-5.2",
messages=[{"role": "user", "content": "hi"}],
max_tokens=800,
base_url="https://api.z.ai/api/coding/paas/v4",
task="moa_aggregator",
)
assert "max_tokens" not in kwargs
assert "max_completion_tokens" not in kwargs

def test_non_moa_tasks_still_omit_max_tokens(self):
"""Regression guard: compression/titles/vision keep PR #34845 behavior."""
from agent.auxiliary_client import _build_call_kwargs

for task in ("compression", "vision", "title_generation", None, ""):
kwargs = _build_call_kwargs(
provider="openrouter",
model="deepseek/deepseek-v4-flash:nitro",
messages=[{"role": "user", "content": "hi"}],
max_tokens=800,
base_url="https://openrouter.ai/api/v1",
task=task,
)
assert "max_tokens" not in kwargs, f"max_tokens should be dropped for task={task!r}"

def test_moa_task_exact_match(self):
"""Only task == "moa_reference" triggers the cap — not the aggregator,
not arbitrary 'moa_' prefixed tasks."""
from agent.auxiliary_client import _build_call_kwargs

# 'moa_reference' → honored
kw = _build_call_kwargs(
provider="zai", model="glm-5.2",
messages=[{"role": "user", "content": "hi"}],
max_tokens=500,
base_url="https://api.z.ai/api/coding/paas/v4",
task="moa_reference",
)
assert kw["max_tokens"] == 500

# 'moa_aggregator' → dropped (aggregator is the acting model, not an advisor)
kw2 = _build_call_kwargs(
provider="zai", model="glm-5.2",
messages=[{"role": "user", "content": "hi"}],
max_tokens=500,
base_url="https://api.z.ai/api/coding/paas/v4",
task="moa_aggregator",
)
assert "max_tokens" not in kw2

# 'moa_custom_future' → dropped (only moa_reference is whitelisted)
kw3 = _build_call_kwargs(
provider="zai", model="glm-5.2",
messages=[{"role": "user", "content": "hi"}],
max_tokens=500,
base_url="https://api.z.ai/api/coding/paas/v4",
task="moa_custom_future",
)
assert "max_tokens" not in kw3


class TestNousTagsScoping:
def test_tags_injected_when_provider_is_nous(self, monkeypatch):
Expand Down