diff --git a/searx/plugins/ai_summary.py b/searx/plugins/ai_summary.py index d99386629..c919fd604 100644 --- a/searx/plugins/ai_summary.py +++ b/searx/plugins/ai_summary.py @@ -45,7 +45,11 @@ if t.TYPE_CHECKING: from . import PluginCfg 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") diff --git a/tests/unit/test_plugin_ai_summary.py b/tests/unit/test_plugin_ai_summary.py index dec1807c5..9143c95b0 100644 --- a/tests/unit/test_plugin_ai_summary.py +++ b/tests/unit/test_plugin_ai_summary.py @@ -324,9 +324,29 @@ class PluginAISummary(SearxTestCase): self.assertEqual(res.status_code, 400) def test_endpoint_invalid_model_pref(self): - self.client.set_cookie("ai_summary_model", "bad model name!") - res = self.client.post("/ai_summary", json={"messages": [{"role": "user", "content": "hi"}]}) - self.assertEqual(res.status_code, 400) + # an empty preference is not invalid -- it falls back to the + # administrator's default, which is covered by the tests above + 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): self.setattr4test(searx.get_setting("ai_summary"), "base_url", "")