feat: rede_korrigieren.py -m/--model zur Modellwahl

Das Modell steckt in den Cache-Schlüsseln; ein anderes Modell rechnet neu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-10-10 19:54:32 +02:00
commit 7bc5e93613
3 changed files with 48 additions and 3 deletions

View file

@ -130,6 +130,7 @@ werden trotzdem umgewandelt; der Exit-Code ist dann 1.
| `-i`, `--input` | Eingabedatei: Rohabschrift (`.txt`) oder Untertitel (`.vtt`, wird wie mit `vtt2txt.py` umgewandelt), Pflicht | | `-i`, `--input` | Eingabedatei: Rohabschrift (`.txt`) oder Untertitel (`.vtt`, wird wie mit `vtt2txt.py` umgewandelt), Pflicht |
| `-o`, `--output` | Ausgabedatei; Standard: `<Eingabe ohne Endung>.korrigiert.txt` neben der Eingabe | | `-o`, `--output` | Ausgabedatei; Standard: `<Eingabe ohne Endung>.korrigiert.txt` neben der Eingabe |
| `-g`, `--glossar` | Glossar mit festen Ersetzungen und Schreibweisen | | `-g`, `--glossar` | Glossar mit festen Ersetzungen und Schreibweisen |
| `-m`, `--model` | Ollama-Modell; Standard: `qwen3:32b` (Konstante `MODEL`) |
## Ablauf ## Ablauf
@ -205,7 +206,7 @@ dazu Muster, Ausgabeverzeichnis und Fehlerfälle.
Jeder fertige Abschnitt und jede Absatzgliederung wird in `<Ausgabe>.parts/` Jeder fertige Abschnitt und jede Absatzgliederung wird in `<Ausgabe>.parts/`
gespeichert. Ein erneuter Lauf überspringt alles, was schon berechnet ist. gespeichert. Ein erneuter Lauf überspringt alles, was schon berechnet ist.
Ändern sich Modell, Prompt, Glossar oder Text eines Abschnitts, wird genau dieser Ändern sich Modell (auch per `-m`), Prompt, Glossar oder Text eines Abschnitts, wird genau dieser
Teil neu berechnet. Den Ordner kann man jederzeit löschen. Teil neu berechnet. Den Ordner kann man jederzeit löschen.
## Einstellungen ## Einstellungen
@ -214,7 +215,7 @@ Oben in `rede_korrigieren.py`:
| Konstante | Standard | Bedeutung | | Konstante | Standard | Bedeutung |
|---|---|---| |---|---|---|
| `MODEL` | `qwen3:32b` | Ollama-Modell | | `MODEL` | `qwen3:32b` | Ollama-Modell (Standard für `-m`) |
| `CHUNK_SIZE` | 1200 | Wörter pro Korrekturabschnitt | | `CHUNK_SIZE` | 1200 | Wörter pro Korrekturabschnitt |
| `MAX_WORD_DEVIATION` | 0.05 | Warnschwelle Wortzahl | | `MAX_WORD_DEVIATION` | 0.05 | Warnschwelle Wortzahl |
| `MAX_PARAGRAPH_SENTENCES` | 10 | längster erlaubter Absatz | | `MAX_PARAGRAPH_SENTENCES` | 10 | längster erlaubter Absatz |

View file

@ -355,7 +355,7 @@ def build_paragraphs(text, parts_dir):
def parse_args(): def parse_args():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Korrigiert eine automatische Abschrift einer deutschen Rede " description="Korrigiert eine automatische Abschrift einer deutschen Rede "
f"mit {MODEL} über Ollama." "mit einem lokalen Sprachmodell über Ollama."
) )
parser.add_argument( parser.add_argument(
"-i", "--input", required=True, type=Path, "-i", "--input", required=True, type=Path,
@ -369,6 +369,9 @@ def parse_args():
"-g", "--glossar", type=Path, "-g", "--glossar", type=Path,
help="Glossar: ein Begriff pro Zeile oder feste Ersetzung „falsch => richtig“", help="Glossar: ein Begriff pro Zeile oder feste Ersetzung „falsch => richtig“",
) )
parser.add_argument(
"-m", "--model", default=MODEL, help=f"Ollama-Modell (Standard: {MODEL})"
)
args = parser.parse_args() args = parser.parse_args()
if not args.input.is_file(): if not args.input.is_file():
@ -383,7 +386,12 @@ def parse_args():
def main(): def main():
global MODEL
args = parse_args() args = parse_args()
# All model calls and cache keys read MODEL, so a different model never
# reuses cached parts of another
MODEL = args.model
print(f"Modell: {MODEL}")
source = args.input source = args.input
target = args.output target = args.output
parts_dir = target.with_suffix(".parts") parts_dir = target.with_suffix(".parts")

View file

@ -46,6 +46,8 @@ def no_model_calls(*args, **kwargs):
def run_main(monkeypatch, *args): def run_main(monkeypatch, *args):
monkeypatch.setattr(sys, "argv", ["rede_korrigieren.py", *map(str, args)]) monkeypatch.setattr(sys, "argv", ["rede_korrigieren.py", *map(str, args)])
# main() sets the module-wide model; restore it after the test
monkeypatch.setattr(rk, "MODEL", rk.MODEL)
rk.main() rk.main()
@ -301,6 +303,40 @@ def test_main_vtt_input_matches_txt_input(sample_copy, monkeypatch):
) == expected.read_text(encoding="utf-8") ) == expected.read_text(encoding="utf-8")
def test_main_model_option(sample_copy, monkeypatch):
default = rk.MODEL
used = []
def correct(chunk, index, system):
used.append(rk.MODEL)
return chunk.strip()
monkeypatch.setattr(rk, "correct_chunk", correct)
monkeypatch.setattr(rk, "find_paragraph_starts", every_nth_start(5))
parts = sample_copy.parent / "ramstein.de.korrigiert.parts"
run_main(monkeypatch, "-i", sample_copy)
default_parts = {p.name for p in parts.glob("part*")}
run_main(monkeypatch, "-i", sample_copy, "-m", "anderes:7b")
assert set(used) == {default, "anderes:7b"}
# Another model must not reuse the cached parts of the default model
assert {p.name for p in parts.glob("part*")} - default_parts
def test_correct_chunk_sends_model(monkeypatch):
sent = {}
def urlopen(request, timeout=None):
sent.update(json.loads(request.data))
return FakeResponse(stream_body(["Text."]))
monkeypatch.setattr(rk, "MODEL", "anderes:7b")
monkeypatch.setattr(rk.urllib.request, "urlopen", urlopen)
rk.correct_chunk("Text.", 1, "system")
assert sent["model"] == "anderes:7b"
def test_main_warns_on_word_count_deviation(sample_copy, monkeypatch, capsys): def test_main_warns_on_word_count_deviation(sample_copy, monkeypatch, capsys):
def shortening(chunk, index, system): def shortening(chunk, index, system):
words = chunk.split() words = chunk.split()