[feat] plugin: AI summary of search results via local Ollama server

Add an optional, disabled-by-default plugin that shows an AI generated
summary at the top of the result page, generated by a (local) Ollama
server:

- async: the result page is never delayed; a client plugin streams the
  answer (NDJSON over a new /ai_summary endpoint) into a placeholder
  answer with a typing indicator, collapsed behind a More button, with
  an inline follow-up chat
- trigger: first page of general searches only, skipped when an infobox
  or instant answer already answers the query
- grounding (per-user preference): send the top result snippets as
  context, the model answers from them instead of its own knowledge
- configuration: new AI Summary preferences tab (server URL, model,
  grounding) with instance defaults in a new ai_summary: settings
  section; all three preferences can be locked for public instances

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jasonwitty
2026-07-28 10:47:18 -07:00
co-authored by Claude Fable 5
parent b060c780d0
commit b47fd08cb7
22 changed files with 1227 additions and 3 deletions
+10
View File
@@ -93,8 +93,10 @@ from searx.preferences import (
ClientPref,
ValidationException,
)
import searx.ai_summary
import searx.answerers
import searx.plugins
import searx.plugins.ai_summary
from searx.metrics import get_engines_stats, get_engine_errors, get_reliabilities, histogram, counter, openmetrics
@@ -328,6 +330,8 @@ def get_translations():
'Source': gettext('Source'),
# infinite scroll
'error_loading_next_page': gettext('Error loading the next page'),
# AI summary
'error_loading_ai_summary': gettext('Error loading the AI summary'),
}
@@ -981,11 +985,17 @@ def preferences():
preferences_url_params = sxng_request.preferences.get_as_url_params(),
locked_preferences = get_setting("preferences").lock,
doi_resolvers = get_setting("doi_resolvers", {}),
ai_summary_models = searx.ai_summary.model_choices(),
ai_summary_default_model = get_setting("ai_summary").model,
ai_summary_default_server = get_setting("ai_summary").base_url,
# fmt: on
)
app.add_url_rule('/favicon_proxy', methods=['GET'], endpoint="favicon_proxy", view_func=favicons.favicon_proxy)
app.add_url_rule(
'/ai_summary', methods=['POST'], endpoint="ai_summary", view_func=searx.plugins.ai_summary.ai_summary_view
)
@app.route('/image_proxy', methods=['GET'])