mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-15 09:21:36 +00:00
fix(gemini): strip native self prefixes before generateContent (#36141)
Strip `google/` and `gemini/` self-prefixes before native Gemini generateContent calls, and keep provider-normalization expectations aligned.
This commit is contained in:
parent
7d11fa4e9e
commit
c8e5f34f24
5 changed files with 58 additions and 3 deletions
|
|
@ -41,6 +41,16 @@ DEFAULT_GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"
|
|||
GEMINI_DEFAULT_MAX_OUTPUT_TOKENS = 65535
|
||||
|
||||
|
||||
def bare_gemini_model_id(model: str) -> str:
|
||||
"""Strip Gemini's own provider prefix from an aggregator-style model id."""
|
||||
name = (model or "").strip()
|
||||
lowered = name.lower()
|
||||
for prefix in ("google/", "gemini/"):
|
||||
if lowered.startswith(prefix):
|
||||
return name[len(prefix):].strip() or name
|
||||
return name
|
||||
|
||||
|
||||
def is_native_gemini_base_url(base_url: str) -> bool:
|
||||
"""Return True when the endpoint speaks Gemini's native REST API."""
|
||||
normalized = str(base_url or "").strip().rstrip("/").lower()
|
||||
|
|
@ -914,6 +924,7 @@ class GeminiNativeClient:
|
|||
thinking_config=thinking_config,
|
||||
)
|
||||
|
||||
model = bare_gemini_model_id(model)
|
||||
if stream:
|
||||
return self._stream_completion(model=model, request=request, timeout=timeout)
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@ _STRIP_VENDOR_ONLY_PROVIDERS: frozenset[str] = frozenset({
|
|||
|
||||
# Providers whose native naming is authoritative -- pass through unchanged.
|
||||
_AUTHORITATIVE_NATIVE_PROVIDERS: frozenset[str] = frozenset({
|
||||
"gemini",
|
||||
"huggingface",
|
||||
})
|
||||
|
||||
|
|
@ -103,6 +102,8 @@ _MATCHING_PREFIX_STRIP_PROVIDERS: frozenset[str] = frozenset({
|
|||
"arcee",
|
||||
"ollama-cloud",
|
||||
"custom",
|
||||
"gemini",
|
||||
"xai",
|
||||
})
|
||||
|
||||
# Providers whose APIs require lowercase model IDs. Xiaomi's
|
||||
|
|
|
|||
|
|
@ -198,6 +198,45 @@ def test_native_client_uses_x_goog_api_key_and_native_models_endpoint(monkeypatc
|
|||
assert response.choices[0].message.content == "hello"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model, expected", [
|
||||
("google/gemini-2.0-flash", "gemini-2.0-flash"),
|
||||
("gemini/gemini-3-pro-preview", "gemini-3-pro-preview"),
|
||||
("Google/Gemini-2.5-Pro", "Gemini-2.5-Pro"),
|
||||
("models/gemini-x", "models/gemini-x"),
|
||||
("tunedModels/my-tune", "tunedModels/my-tune"),
|
||||
])
|
||||
def test_bare_gemini_model_id_strips_only_self_prefix(model, expected):
|
||||
from agent.gemini_native_adapter import bare_gemini_model_id
|
||||
|
||||
assert bare_gemini_model_id(model) == expected
|
||||
|
||||
|
||||
def test_native_client_strips_self_prefix_from_model_url(monkeypatch):
|
||||
from agent.gemini_native_adapter import GeminiNativeClient
|
||||
|
||||
recorded = {}
|
||||
|
||||
class DummyHTTP:
|
||||
def post(self, url, json=None, headers=None, timeout=None):
|
||||
recorded["url"] = url
|
||||
return DummyResponse(payload={
|
||||
"candidates": [{"content": {"parts": [{"text": "ok"}]}, "finishReason": "STOP"}],
|
||||
"usageMetadata": {"promptTokenCount": 1, "candidatesTokenCount": 1, "totalTokenCount": 2},
|
||||
})
|
||||
|
||||
def close(self):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr("agent.gemini_native_adapter.httpx.Client", lambda *a, **k: DummyHTTP())
|
||||
client = GeminiNativeClient(api_key="AIza-test", base_url="https://generativelanguage.googleapis.com/v1beta")
|
||||
client.chat.completions.create(
|
||||
model="google/gemini-2.0-flash",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
)
|
||||
|
||||
assert recorded["url"] == "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
|
||||
|
||||
|
||||
def test_native_http_error_keeps_status_and_retry_after():
|
||||
from agent.gemini_native_adapter import gemini_http_error
|
||||
|
||||
|
|
|
|||
|
|
@ -143,7 +143,8 @@ class TestGeminiModelNormalization:
|
|||
assert normalize_model_for_provider("gemini-2.5-flash", "gemini") == "gemini-2.5-flash"
|
||||
|
||||
def test_strip_vendor_prefix(self):
|
||||
assert normalize_model_for_provider("google/gemini-2.5-flash", "gemini") == "google/gemini-2.5-flash"
|
||||
assert normalize_model_for_provider("google/gemini-2.5-flash", "gemini") == "gemini-2.5-flash"
|
||||
assert normalize_model_for_provider("gemini/gemini-2.5-flash", "gemini") == "gemini-2.5-flash"
|
||||
|
||||
def test_gemma_vendor_detection(self):
|
||||
assert detect_vendor("gemma-4-31b-it") == "google"
|
||||
|
|
|
|||
|
|
@ -167,10 +167,13 @@ class TestAggregatorProviders:
|
|||
class TestIssue6211NativeProviderPrefixNormalization:
|
||||
@pytest.mark.parametrize("model,target_provider,expected", [
|
||||
("zai/glm-5.1", "zai", "glm-5.1"),
|
||||
("google/gemini-2.5-pro", "gemini", "google/gemini-2.5-pro"),
|
||||
("google/gemini-2.5-pro", "gemini", "gemini-2.5-pro"),
|
||||
("gemini/gemini-2.5-pro", "gemini", "gemini-2.5-pro"),
|
||||
("moonshot/kimi-k2.5", "kimi-coding", "kimi-k2.5"),
|
||||
("anthropic/claude-sonnet-4.6", "openrouter", "anthropic/claude-sonnet-4.6"),
|
||||
("x-ai/grok-4-fast-reasoning", "xai", "grok-4-fast-reasoning"),
|
||||
("Qwen/Qwen3.5-397B-A17B", "huggingface", "Qwen/Qwen3.5-397B-A17B"),
|
||||
("openai/gpt-5.4", "xai", "openai/gpt-5.4"),
|
||||
("modal/zai-org/GLM-5-FP8", "custom", "modal/zai-org/GLM-5-FP8"),
|
||||
])
|
||||
def test_native_provider_prefixes_are_only_stripped_on_matching_provider(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue