fix(llm): pusta odpowiedz modelu to blad, nie pusta strona
Objaw zgloszony przez uzytkownika: w Kalendarzu horoskop wyswietla sie
poprawnie, w Interpretacjach zapytanie wychodzi, wraca — i NIC sie nie
pokazuje. Bez zadnego komunikatu.
Przyczyna: model potrafi oddac pusta tresc (finish_reason=length,
completion_tokens=0), a generate() zwracalo wtedy pusty tekst BEZ bledu.
Widok sprawdza {% if prompt_result.horoscope %} -> falsz -> nie renderuje nic,
a llm_error nie jest ustawiony -> zero wyjasnienia. Cicha awaria.
Asymetria miedzy ekranami wynika z rozmiaru promptu: natalny (13 obiektow x
fasety x opisy z bazy) wypelnia okno kontekstu modelu lokalnego i na odpowiedz
nie zostaje miejsca; okresowy jest mniejszy i sie miesci.
- wspolny straznik _require_text() dla obu dostawcow: pusta lub bialoznakowa
odpowiedz podnosi LLMError,
- komunikat PROWADZI DO PRZYCZYNY: podaje finish_reason i zuzycie tokenow oraz
radzi zmniejszyc budzet promptu / zwiekszyc num_ctx / LLM_MAX_TOKENS,
- Anthropic sprowadzony do wspolnego ksztaltu diagnostyki (input/output_tokens).
Dzieki temu uzytkownik widzi powod ORAZ gotowy prompt do recznego uzycia.
Zweryfikowane na zywym stosie z atrapa modelu oddajaca pusta tresc: zamiast
pustej strony pojawia sie pelny komunikat z diagnostyka. Testy: 148 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -210,3 +210,58 @@ def test_cloud_without_key_is_rejected_clearly(monkeypatch):
|
||||
def test_local_needs_no_key(monkeypatch):
|
||||
_clear(monkeypatch)
|
||||
assert factory.build_provider("local").api_key == ""
|
||||
|
||||
|
||||
# ------------------------------------------- pusta odpowiedz modelu (cicha awaria)
|
||||
# Regresja: model potrafi oddac pusta tresc (prompt zjadl caly kontekst ->
|
||||
# finish_reason=length, completion_tokens=0). Wczesniej generate() zwracalo pusty
|
||||
# tekst BEZ bledu, widok nic nie renderowal i uzytkownik dostawal pusta strone
|
||||
# bez zadnego wyjasnienia. Pusta odpowiedz MUSI byc bledem.
|
||||
|
||||
def test_empty_completion_raises_instead_of_silent_blank(monkeypatch):
|
||||
monkeypatch.setattr(httpx, "Client", _mock_client(lambda r: httpx.Response(200, json={
|
||||
"model": "llama3.1:8b",
|
||||
"choices": [{"message": {"content": ""}, "finish_reason": "length"}],
|
||||
"usage": {"prompt_tokens": 8000, "completion_tokens": 0},
|
||||
})))
|
||||
with pytest.raises(LLMError, match="pustą odpowiedź"):
|
||||
ChatCompletionsProvider("local", "http://x/v1", "m").generate("p", 2000)
|
||||
|
||||
|
||||
def test_empty_completion_explains_context_window(monkeypatch):
|
||||
"""Komunikat ma prowadzic do przyczyny, a nie tylko stwierdzac fakt."""
|
||||
monkeypatch.setattr(httpx, "Client", _mock_client(lambda r: httpx.Response(200, json={
|
||||
"choices": [{"message": {"content": " "}, "finish_reason": "length"}],
|
||||
"usage": {"prompt_tokens": 8000, "completion_tokens": 0},
|
||||
})))
|
||||
with pytest.raises(LLMError) as ei:
|
||||
ChatCompletionsProvider("local", "http://x/v1", "m").generate("p", 2000)
|
||||
msg = str(ei.value)
|
||||
assert "kontekstu" in msg and "budżet" in msg
|
||||
assert "tokeny promptu=8000" in msg # diagnostyka w tresci bledu
|
||||
|
||||
|
||||
def test_whitespace_only_is_treated_as_empty(monkeypatch):
|
||||
monkeypatch.setattr(httpx, "Client", _mock_client(lambda r: httpx.Response(200, json={
|
||||
"choices": [{"message": {"content": "\n\n \t "}}],
|
||||
})))
|
||||
with pytest.raises(LLMError, match="pustą odpowiedź"):
|
||||
ChatCompletionsProvider("local", "http://x/v1", "m").generate("p", 100)
|
||||
|
||||
|
||||
def test_anthropic_empty_completion_raises(monkeypatch):
|
||||
monkeypatch.setattr(httpx, "Client", _mock_client(lambda r: httpx.Response(200, json={
|
||||
"model": "claude-x", "content": [], "stop_reason": "max_tokens",
|
||||
"usage": {"input_tokens": 9000, "output_tokens": 0},
|
||||
})))
|
||||
with pytest.raises(LLMError, match="pustą odpowiedź"):
|
||||
AnthropicProvider("https://api.anthropic.com", "claude-x", "klucz").generate("p", 100)
|
||||
|
||||
|
||||
def test_normal_response_still_passes(monkeypatch):
|
||||
"""Straznik nie moze psuc poprawnej odpowiedzi."""
|
||||
monkeypatch.setattr(httpx, "Client", _mock_client(lambda r: httpx.Response(200, json={
|
||||
"choices": [{"message": {"content": "Horoskop."}, "finish_reason": "stop"}],
|
||||
"usage": {"prompt_tokens": 100, "completion_tokens": 20},
|
||||
})))
|
||||
assert ChatCompletionsProvider("local", "http://x/v1", "m").generate("p", 100).text == "Horoskop."
|
||||
|
||||
Reference in New Issue
Block a user