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:
parent
19b6bbf5a2
commit
7bc5e93613
3 changed files with 48 additions and 3 deletions
|
|
@ -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 |
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue