Skip to content
Merged
5 changes: 5 additions & 0 deletions src/benchflow/acp/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,11 @@ def _format_acp_model(model: str, agent: str) -> str:
models.dev provider prefix when the agent requires it.
"""
bare = strip_provider_prefix(model)
# Gemini CLI accepts bare Google model IDs. ``google/`` is a models.dev
# provider prefix rather than a registered BenchFlow provider, so the
# generic normalizer intentionally leaves it alone.
if agent == "gemini" and model.startswith(("google/gemini-", "google/gemma-")):
bare = model.removeprefix("google/")
agent_cfg = AGENTS.get(agent)
if agent_cfg and agent_cfg.acp_model_format == "registered-provider/model":
# Proxy mode: BenchFlow's LiteLLM proxy serves the model under the alias
Expand Down
3 changes: 3 additions & 0 deletions src/benchflow/providers/litellm_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,9 @@ def resolve_litellm_route(model: str, env: dict[str, str]) -> LiteLLMRoute:
elif lower.startswith("gemini/"):
upstream = model
required = ("GEMINI_API_KEY",)
elif lower.startswith(("google/gemini-", "google/gemma-")):
upstream = f"gemini/{model.split('/', 1)[1]}"
required = ("GEMINI_API_KEY",)
elif "gemini" in lower:
upstream = f"gemini/{bare}"
required = ("GEMINI_API_KEY",)
Expand Down
35 changes: 26 additions & 9 deletions tests/test_acp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1176,22 +1176,39 @@ def _make_mocks():

@pytest.mark.asyncio
@pytest.mark.parametrize(
"model_in, expected_model",
"agent, model_in, expected_model",
[
# Registered vllm/ prefix stripped; HF org/model intact — this is
# what pi-acp and other ACP agents need for downstream routing.
("vllm/Qwen/Qwen3.5-35B-A3B", "Qwen/Qwen3.5-35B-A3B"),
("zai/glm-5", "glm-5"),
("test-agent", "vllm/Qwen/Qwen3.5-35B-A3B", "Qwen/Qwen3.5-35B-A3B"),
("test-agent", "zai/glm-5", "glm-5"),
# Bare HF ID (no registered prefix) passes through unchanged.
("Qwen/Qwen3-Coder", "Qwen/Qwen3-Coder"),
("test-agent", "Qwen/Qwen3-Coder", "Qwen/Qwen3-Coder"),
# Vertex ADC provider — prefix stripped like any other registered one.
("anthropic-vertex/claude-sonnet-4-6", "claude-sonnet-4-6"),
("test-agent", "anthropic-vertex/claude-sonnet-4-6", "claude-sonnet-4-6"),
# No prefix at all — unchanged.
("claude-sonnet-4-6", "claude-sonnet-4-6"),
("test-agent", "claude-sonnet-4-6", "claude-sonnet-4-6"),
# Gemini CLI expects bare Google Gemini and Gemma model IDs.
(
"gemini",
"google/gemini-3.1-flash-lite-preview",
"gemini-3.1-flash-lite-preview",
),
("gemini", "google/gemma-3-27b-it", "gemma-3-27b-it"),
("gemini", "google/text-bison", "google/text-bison"),
],
ids=[
"vllm-hf",
"zai",
"bare-hf",
"vertex",
"no-prefix",
"gemini-google",
"gemma-google",
"unrelated-google",
],
ids=["vllm-hf", "zai", "bare-hf", "vertex", "no-prefix"],
)
async def test_model_id_selection(self, model_in, expected_model, tmp_path):
async def test_model_id_selection(self, agent, model_in, expected_model, tmp_path):
from benchflow.acp.runtime import connect_acp

mock_acp = self._make_mocks()
Expand All @@ -1203,7 +1220,7 @@ async def test_model_id_selection(self, model_in, expected_model, tmp_path):
):
await connect_acp(
env=mock_env,
agent="test-agent",
agent=agent,
agent_launch="test-agent",
agent_env={},
sandbox_user=None,
Expand Down
21 changes: 21 additions & 0 deletions tests/test_litellm_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,24 @@ def test_proxy_config_no_responses_bridge_for_non_openai_upstream():
config = litellm_proxy_config(route, master_key="sk-local")
names = [entry["model_name"] for entry in config["model_list"]]
assert not any(n.endswith("-responses-bridge") for n in names)


@pytest.mark.parametrize(
"model,expected",
[
(
"google/gemini-3.1-flash-lite-preview",
"gemini/gemini-3.1-flash-lite-preview",
),
("google/gemma-3-27b-it", "gemini/gemma-3-27b-it"),
("google/geminix-1", "gemini/google/geminix-1"),
],
)
def test_google_model_normalizes_only_gemini_families(model, expected):
"""Google Gemini/Gemma IDs get one LiteLLM provider prefix."""
route = resolve_litellm_route(
model,
{"GEMINI_API_KEY": "key"},
)
assert route.upstream_model == expected
assert route.litellm_params["model"] == expected
10 changes: 10 additions & 0 deletions tests/test_litellm_hardening.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ def test_format_acp_model_passes_through_existing_provider_prefix():
)


def test_gemini_acp_model_strips_google_prefix():
"""Guards Gemini model-resource 404s for current Google model IDs."""
from benchflow.acp.runtime import _format_acp_model

assert (
_format_acp_model("google/gemini-3.5-flash-lite", "gemini")
== "gemini-3.5-flash-lite"
)


def test_mimo_registered_xiaomi_model_keeps_provider_prefix():
from benchflow.acp.runtime import _format_acp_model

Expand Down
Loading