Выбор мозгов: Ollama или веб-версия DeepSeek

Разбор свободных фраз, теги и подсказку по фотографии теперь может делать
не своя модель, а веб-версия DeepSeek. Своего клиента DeepSeek здесь нет
и не нужно: рядом живёт отдельный сервис deepseek-api, который держит
вкладку браузера и отдаёт наружу протокол Ollama. Для hlamingo вся разница
сводится к тому, по какому адресу спрашивать.

  HLAMINGO_ENGINE=ollama|deepseek
  HLAMINGO_DEEPSEEK_HOST=http://localhost:11500

Эмбеддинги в выбор не входят и всегда идут на OLLAMA_HOST: веб-чат векторов
не отдаёт, а подделать их нечем. Поэтому адрес Ollama нужен и в режиме
DeepSeek, иначе молча умирает поиск по смыслу.

Предохранитель в ollama.py теперь считается по каждому адресу отдельно.
Иначе лежачая Ollama гасила бы живой DeepSeek: неудача по одному адресу
на полминуты объявляла мёртвым и второй.

Проба адреса больше не подменяет config.OLLAMA_HOST на время проверки —
адрес передаётся явным параметром. Запросы идут в несколько потоков,
и подмена задевала чужие.

В настройках (вкладка "шестерёнка") появился выбор "Кто думает", поле
адреса DeepSeek и вторая кнопка проверки связи. Списка моделей теперь два:
для разбора фраз — у выбранного движка, для поиска по смыслу — у Ollama.

Замер на разборе фразы и генерации тегов: Ollama qwen3:8b 2-3 с,
DeepSeek 8-12 с плюс 15 с на подъём браузера при первом запросе.

Тесты: 115 успешно, 0 провалено.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Alex Cube
2026-08-26 12:48:30 +03:00
co-authored by Claude Opus 5
parent 1ae98b0b6f
commit 4d8175931b
7 changed files with 359 additions and 117 deletions
+53 -1
View File
@@ -263,7 +263,7 @@ try:
r.check("поиск работает без ИИ", d["items"] and _time.time() - t0 < 3, d)
r.check("смена адреса сбрасывает паузу",
(ollama.reset_breaker() or True) and ollama._down_until == 0)
(ollama.reset_breaker() or True) and ollama._down_until == {})
finally:
config.OLLAMA_HOST = _was_host
ollama.reset_breaker()
@@ -393,6 +393,58 @@ r.check("инъекция в имя модели — 400",
c.post("/api/config",
json={"ollama_host": "http://127.0.0.1:11434",
"chat_model": "модель\nOLLAMA_HOST=http://зло"}).status_code == 400)
# ---- выбор мозгов: Ollama или веб-DeepSeek ----
# Проверяем не сам DeepSeek (для него нужен браузер с логином), а развилку:
# куда уходит разбор фраз и куда — эмбеддинги. Перепутать их значит тихо
# сломать поиск по смыслу, и это не видно, пока не начнёшь искать.
was_engine = config.ENGINE
DS = "http://127.0.0.1:11500"
try:
d = j("POST", "/api/config", engine="deepseek", deepseek_host=DS)
r.check("движок переключился", d.get("ok") and config.ENGINE == "deepseek", d)
r.check("думает DeepSeek", config.chat_host() == DS, config.chat_host())
r.check("здоровье докладывает движок",
c.get("/api/health").get_json()["engine"] == "deepseek")
r.check("ждём DeepSeek дольше", config.chat_timeout() > config.CHAT_TIMEOUT,
config.chat_timeout())
# Куда реально уходят запросы. Предохранитель и сеть отключаем: важно не
# то, ответил ли кто-то, а по какому адресу постучались.
tried = []
def _spy(req, **kw):
tried.append(req.full_url)
raise OSError("в тесте наружу не ходим")
real_urlopen = ollama.urllib.request.urlopen
real_available = ollama.available
ollama.urllib.request.urlopen = _spy
ollama.available = lambda host=None: True
try:
ollama.embed("гвозди")
ollama.chat(config.CHAT_MODEL, [{"role": "user", "content": "привет"}])
finally:
ollama.urllib.request.urlopen = real_urlopen
ollama.available = real_available
ollama.reset_breaker()
r.check("эмбеддинги идут в Ollama",
any(u.startswith(config.OLLAMA_HOST) and "embed" in u for u in tried), tried)
r.check("разбор фраз идёт в DeepSeek",
any(u.startswith(DS) and u.endswith("/api/chat") for u in tried), tried)
r.check("чужой движок — 400",
c.post("/api/config", json={"engine": "chatgpt"}).status_code == 400)
r.check("мусорный адрес DeepSeek — 400",
c.post("/api/config", json={"deepseek_host": "не адрес"}).status_code == 400)
r.check("проба берёт адрес из запроса, а не из настроек",
c.post("/api/config/probe",
json={"host": "http://192.0.2.1:11434"}).get_json()["ok"] is False
and config.OLLAMA_HOST != "http://192.0.2.1:11434")
finally:
j("POST", "/api/config", engine=was_engine)
ollama.reset_breaker()
r.check("движок вернулся", config.ENGINE == was_engine, config.ENGINE)
# Отказ должен быть полным: адрес из того же запроса применяться не должен,
# иначе половина настроек принята, а половина отвергнута.
r.check("при отказе НИЧЕГО не применилось",