[mod] ai_summary plugin: switch to the OpenAI chat completions API

Talk to the LLM server via GET /v1/models and POST /v1/chat/completions
(SSE) instead of Ollama's native API.  Any OpenAI compatible server now
works (Ollama, vLLM, llama.cpp, LM Studio, Hugging Face TGI, ...);
Ollama serves this API natively, existing setups keep working unchanged.

The Ollama specific keep_alive option is dropped, the ai_summary.grounding
setting is added as instance wide default of the grounding preference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jasonwitty
2026-07-28 13:01:24 -07:00
parent 112541db28
commit 4abb7dba67
6 changed files with 70 additions and 57 deletions
+10 -10
View File
@@ -23,12 +23,12 @@ BASE_URL = "http://127.0.0.1:11434"
MODEL = "test-model"
def ollama_stream_mock(lines: list[dict], status_code: int = 200) -> Mock:
def sse_stream_mock(lines: list[dict], status_code: int = 200) -> Mock:
"""A mock httpx client whose ``stream()`` context manager yields the given
(Ollama) NDJSON lines."""
objects as a SSE stream (OpenAI chat completions format)."""
upstream = Mock(status_code=status_code)
upstream.iter_lines.return_value = iter([json.dumps(line) for line in lines])
upstream.iter_lines.return_value = iter([f"data: {json.dumps(line)}" for line in lines] + ["data: [DONE]"])
@contextmanager
def stream(*_args, **_kwargs):
@@ -218,11 +218,11 @@ class PluginAISummary(SearxTestCase):
def test_endpoint_streams_ndjson(self):
self.mock_upstream(
ollama_stream_mock(
sse_stream_mock(
[
{"message": {"content": "Hello "}, "done": False},
{"message": {"content": "world"}, "done": False},
{"done": True},
{"choices": [{"delta": {"content": "Hello "}}]},
{"choices": [{"delta": {"content": "world"}}]},
{"choices": [{"delta": {}, "finish_reason": "stop"}]},
]
)
)
@@ -237,7 +237,7 @@ class PluginAISummary(SearxTestCase):
self.assertEqual(lines[2], {"done": True, "model": MODEL})
def test_endpoint_model_pref_wins(self):
self.mock_upstream(ollama_stream_mock([{"done": True}]))
self.mock_upstream(sse_stream_mock([]))
self.client.set_cookie("ai_summary_model", "my-own-model:7b")
res = self.client.post("/ai_summary", json={"messages": [{"role": "user", "content": "hi"}]})
@@ -245,7 +245,7 @@ class PluginAISummary(SearxTestCase):
self.assertEqual(lines[-1], {"done": True, "model": "my-own-model:7b"})
def test_endpoint_upstream_error(self):
self.mock_upstream(ollama_stream_mock([], status_code=500))
self.mock_upstream(sse_stream_mock([], status_code=500))
res = self.client.post("/ai_summary", json={"messages": [{"role": "user", "content": "hi"}]})
self.assertEqual(res.status_code, 502)
@@ -253,7 +253,7 @@ class PluginAISummary(SearxTestCase):
def test_endpoint_error_while_streaming(self):
upstream = Mock(status_code=200)
upstream.iter_lines.return_value = iter(
[json.dumps({"message": {"content": "Hello"}, "done": False}), "this is not json"]
['data: {"choices": [{"delta": {"content": "Hello"}}]}', "data: this is not json"]
)
@contextmanager