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,
"options": {"temperature": temperature},
}
if OLLAMA_MODEL.split(":", 1)[0] == "qwen3.5":
payload["think"] = False
if json_mode:
payload["format"] = "json"
body = _http_post_json(f"{OLLAMA_BASE_URL}/api/generate", payload, {}, timeout)
+50
View File
@@ -8,6 +8,7 @@ import textwrap
import zipfile
from pathlib import Path
import pytest
from docx import Document
from fastapi.testclient import TestClient
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
@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):
monkeypatch.setenv("AI_PROVIDER", "gemini")
monkeypatch.setenv("GEMINI_API_KEY", "test-key")