catch schema-incomplete ollama responses instead of crashing the batch

This commit is contained in:
Andrew Pimenov 2026-08-16 20:23:19 +03:00
parent 12145cc82a
commit 93127e37d8
2 changed files with 21 additions and 4 deletions

View file

@ -252,16 +252,19 @@ def analyze_tracks(
}
try:
data = call(settings, prompt)
entry = {**data, **stamp, "key_line_verified": verify_key_line(data["key_line"], text)}
# Широкий except намеренно: трёхчасовой прогон не должен падать целиком из-за
# одного зависшего/битого ответа локальной модели. KeyboardInterrupt и SystemExit
# наследуются от BaseException и сюда не попадают - Ctrl+C прерывает прогон.
# одного зависшего/битого ответа локальной модели - в том числе синтаксически
# валидного JSON без обязательного поля (KeyError здесь же, не только в call()).
# KeyboardInterrupt и SystemExit наследуются от BaseException и сюда не попадают -
# Ctrl+C прерывает прогон.
except Exception as e:
cache[tid] = {**stamp, "error": f"{type(e).__name__}: {e}"}
failed += 1
print(f" [{i}/{len(pending)}] {info['title']} - ошибка: {type(e).__name__}")
else:
cache[tid] = {**data, **stamp, "key_line_verified": verify_key_line(data["key_line"], text)}
print(f" [{i}/{len(pending)}] {info['title']} - {', '.join(data['mood'])}")
cache[tid] = entry
print(f" [{i}/{len(pending)}] {info['title']} - {', '.join(entry.get('mood', []))}")
_atomic_write_json(cache_file, cache)
if failed:

View file

@ -246,6 +246,20 @@ def test_analyze_records_error_marker_and_continues(tmp_path: Path):
assert result["200"]["summary"] == "Герой бродит по городу.", "таймаут одного трека не роняет батч"
def test_analyze_records_error_marker_for_schema_incomplete_response(tmp_path: Path):
# Ollama иногда возвращает синтаксически валидный JSON без обязательного поля -
# это не исключение внутри call(), падает на доступе к data[...] после успешного возврата.
def call(settings, prompt):
if "Тоска" in prompt:
return {k: v for k, v in GOOD_RESPONSE.items() if k != "key_line"}
return dict(GOOD_RESPONSE)
result = analyze.analyze_tracks(TRACKS, LYRICS, tmp_path, _settings(), call=call)
assert "KeyError" in result["100"]["error"]
assert result["200"]["summary"] == "Герой бродит по городу.", "битый ответ одного трека не роняет батч"
def test_analyze_retries_error_entries_on_next_run(tmp_path: Path):
def failing(settings, prompt):
raise TimeoutError("модель зависла")