fix(ai): request final responses from Qwen3.5

This commit is contained in:
cesnimda
2026-10-20 15:33:07 +02:00
parent f9e1d06bf7
commit 1d34406d1b
3 changed files with 61 additions and 0 deletions
@@ -0,0 +1,9 @@
# Qwen3.5 compatibility preparation
The owner requested `qwen3.5:9b-q4_K_M` after live deployment. The model is downloaded, but the active sidecar still uses the previous model. This change prepares the existing adapter; it does not activate a model or qualify the withheld P5-13 capability.
The existing adapter consumes Ollama's final `response`. A synthetic local probe found an empty response with the Qwen3.5 thinking default; a20October probe with `think:false` returned the requested synthetic JSON and no thinking text. The adapter now sets that request field only when the configured base family is exactly `qwen3.5`. Existing temperature, JSON mode, timeouts, response parsing, other model payloads and external-provider consent/fallback remain unchanged. Custom namespace aliases are outside this exact family match.
Seven new offline cases verify both text/JSON for the requested tag and bare family, and unchanged payloads for qwen2.5, qwen3 and a lookalike family. Four Qwen3.5 cases failed ordinarily before the two-line change; three compatibility cases passed. The full sidecar suite then passed **56/56**, zero failures/errors/skips,8.45seconds,20October13:32UTC. The prior49-case baseline grows by seven; no model calls are part of these tests. Raw RED/GREEN logs and JUnit are retained under `.artifacts/release-20261020/model-compat/`.
Live activation, deployed adapter identity, GPU residency and final-response smoke remain pending behind the release. P5-13's deployed-identity/366-call/independent-human-review/UI requirements remain unmet and its review feature stays disabled. Production readiness remains NO-GO independently of the owner's release authorization.
+2
View File
@@ -646,6 +646,8 @@ def _ollama_generate(prompt: str, *, json_mode: bool, temperature: float, timeou
"stream": False, "stream": False,
"options": {"temperature": temperature}, "options": {"temperature": temperature},
} }
if OLLAMA_MODEL.split(":", 1)[0] == "qwen3.5":
payload["think"] = False
if json_mode: if json_mode:
payload["format"] = "json" payload["format"] = "json"
body = _http_post_json(f"{OLLAMA_BASE_URL}/api/generate", payload, {}, timeout) body = _http_post_json(f"{OLLAMA_BASE_URL}/api/generate", payload, {}, timeout)
+50
View File
@@ -8,6 +8,7 @@ import textwrap
import zipfile import zipfile
from pathlib import Path from pathlib import Path
import pytest
from docx import Document from docx import Document
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from PIL import Image from PIL import Image
@@ -253,6 +254,55 @@ def test_provider_defaults_to_ollama_and_is_unchanged(monkeypatch):
assert captured["body"]["options"]["temperature"] == 0.1 assert captured["body"]["options"]["temperature"] == 0.1
@pytest.mark.parametrize("ollama_model", ["qwen3.5:9b-q4_K_M", "qwen3.5"])
@pytest.mark.parametrize("json_mode", [False, True])
def test_qwen35_requests_nonthinking_output_without_consuming_thinking(monkeypatch, ollama_model, json_mode):
module = load_app_module(monkeypatch, ollama_model=ollama_model)
captured = {}
output = '{"score": 7}' if json_mode else "Rewritten CV text"
def fake_urlopen(req, timeout=None):
captured["body"] = json.loads(req.data.decode("utf-8"))
captured["timeout"] = timeout
captured["url"] = req.full_url
return _FakeResponse({
"response": output if captured["body"].get("think") is False else "",
"thinking": "Private reasoning must never become candidate text.",
})
monkeypatch.setattr(module.urllib_request, "urlopen", fake_urlopen)
result = module._ollama_generate_json("synthetic prompt") if json_mode else module._ollama_generate_text("synthetic prompt")
assert result == ({"score": 7} if json_mode else output)
expected = {
"model": ollama_model,
"prompt": "synthetic prompt",
"stream": False,
"options": {"temperature": 0.1 if json_mode else 0.2},
"think": False,
}
if json_mode:
expected["format"] = "json"
assert captured["body"] == expected
assert captured["url"] == f"{module.OLLAMA_BASE_URL}/api/generate"
assert captured["timeout"] == (120 if json_mode else 180)
@pytest.mark.parametrize("ollama_model", ["qwen2.5:7b", "qwen3:8b", "qwen3.5-plus:9b"])
def test_other_ollama_model_payloads_remain_unchanged(monkeypatch, ollama_model):
module = load_app_module(monkeypatch, ollama_model=ollama_model)
captured = {}
_install_fake_urlopen(monkeypatch, module, {"response": " Candidate text ", "thinking": "Reasoning"}, captured)
assert module._ollama_generate_text("synthetic prompt") == "Candidate text"
assert captured["body"] == {
"model": ollama_model,
"prompt": "synthetic prompt",
"stream": False,
"options": {"temperature": 0.2},
}
def test_local_success_wins_even_when_external_fallback_is_permitted(monkeypatch): def test_local_success_wins_even_when_external_fallback_is_permitted(monkeypatch):
monkeypatch.setenv("AI_PROVIDER", "gemini") monkeypatch.setenv("AI_PROVIDER", "gemini")
monkeypatch.setenv("GEMINI_API_KEY", "test-key") monkeypatch.setenv("GEMINI_API_KEY", "test-key")