[fix] plugin: accept version pinned model names

The pattern that validates the model name rejected "@", so names like
gemini-1.5-pro@001 were refused with "no valid model configured".  Those
names are ordinary on the gateways this plugin can be pointed at, which
made the api_key support less useful than it looks.

Verified against a LiteLLM gateway serving an alias named gemma4-e4b@001:
the model list, the request and the streamed answer all carry the pinned
name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
jasonwitty
2026-08-14 11:00:13 -07:00
parent 37d7d11959
commit 1fa83635c0
2 changed files with 28 additions and 4 deletions
+5 -1
View File
@@ -45,7 +45,11 @@ if t.TYPE_CHECKING:
from . import PluginCfg from . import PluginCfg
VALID_ROLES = ("user", "assistant") VALID_ROLES = ("user", "assistant")
MODEL_NAME_REGEXP = re.compile(r"[A-Za-z0-9._:/-]{1,128}") # Model names differ per provider: "gemma3:4b" (Ollama), "bedrock/anthropic.
# claude-3-5-sonnet" (a gateway's routing prefix), "gemini-1.5-pro@001" (a
# pinned version). The pattern accepts those and rejects anything that could
# change the meaning of the request body it is placed into.
MODEL_NAME_REGEXP = re.compile(r"[A-Za-z0-9._:/@-]{1,128}")
log = logging.getLogger("searx.plugins.ai_summary") log = logging.getLogger("searx.plugins.ai_summary")
+23 -3
View File
@@ -324,9 +324,29 @@ class PluginAISummary(SearxTestCase):
self.assertEqual(res.status_code, 400) self.assertEqual(res.status_code, 400)
def test_endpoint_invalid_model_pref(self): def test_endpoint_invalid_model_pref(self):
self.client.set_cookie("ai_summary_model", "bad model name!") # an empty preference is not invalid -- it falls back to the
res = self.client.post("/ai_summary", json={"messages": [{"role": "user", "content": "hi"}]}) # administrator's default, which is covered by the tests above
self.assertEqual(res.status_code, 400) for model in ["bad model name!", "model;rm -rf", 'model"quoted', "model\nname", "x" * 129]:
self.client.set_cookie("ai_summary_model", model)
res = self.client.post("/ai_summary", json={"messages": [{"role": "user", "content": "hi"}]})
self.assertEqual(res.status_code, 400, model)
def test_endpoint_accepts_provider_model_names(self):
# model names differ per provider: a version pin (@), a gateway's
# routing prefix (/) and an Ollama tag (:) are all valid names
for model in [
"gemma3:4b",
"llama3.2:3b",
"gemini-1.5-pro@001",
"bedrock/anthropic.claude-3-5-sonnet",
"azure/my-deployment",
]:
self.mock_upstream(sse_stream_mock([]))
self.client.set_cookie("ai_summary_model", model)
res = self.client.post("/ai_summary", json={"messages": [{"role": "user", "content": "hi"}]})
self.assertEqual(res.status_code, 200, model)
lines = [json.loads(line) for line in res.data.decode().splitlines() if line]
self.assertEqual(lines[-1], {"done": True, "model": model})
def test_endpoint_no_server_configured(self): def test_endpoint_no_server_configured(self):
self.setattr4test(searx.get_setting("ai_summary"), "base_url", "") self.setattr4test(searx.get_setting("ai_summary"), "base_url", "")