LM-Studio-Matrix mit gemma-4-e4b: zwoelf Laeufe und vier Befunde
Skill 12.0.0 bis 12.2.0 und Adapter 2.0.0 bis 2.2.0. Werkzeugfreigabe: Die Shell-Rechte des OpenCode-Adapters sind jetzt eine Denylist wie beim Claude-Adapter statt einer Allowlist. Ausloeser war der erste Qwen-Lauf, dessen einzige beide Werkzeugaufrufe an einer Pipeline scheiterten. Kontrolltest belegt beide Richtungen: Get-ChildItem | Format-Table laeuft durch, rm wird verweigert, die Datei bleibt bestehen. Lokaler Betrieb: Der Preflight entlaedt alle Modelle vor jedem Lauf und laedt den Kontext auf das Modellmaximum. Gemessen waren zuvor beide Modelle gleichzeitig geladen - 168 MiB frei von 16,3 GB. Qwen 27B passt auf dieser Karte nicht und wurde durch qwen3.5-9b ersetzt, in Q4_K_M wie Gemma. Messinstrument: analyse-anforderungen.py erkennt Feldnamen in Markdown- Fettschrift und Modulpraefixe in IDs. Der erste lokale Lauf mit Artefakten wurde sonst mit null Anforderungen gezaehlt statt mit neun. Regressionsprobe an vier Claude-Laeufen unveraendert. Befunde: Der Standard-Ausgabeblock, mit dem Claude sieben Artefakte erzeugt, liefert bei gemma-4-e4b null von sechs Laeufen ein Ergebnis - das Modell loest den Pfad relativ zum Arbeitsverzeichnis auf. Ein Lauf startete alle sieben vorgesehenen Rollen und lieferte neun formkonforme Anforderungen. Ein anderer erzeugte sieben richtig benannte Dateien ohne eine einzige formkonforme Anforderung. Und eine Shell-Umleitung schrieb an der Denylist vorbei in den eingefrorenen Snapshot - gefunden vom Vorher/Nachher-Vergleich, nicht von der Regel. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
654339464e
commit
28e927013b
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Erzeugt das Messprotokoll-Geruest eines Laufs aus seinen Rohdaten.
|
||||
|
||||
Fuellt ausschliesslich Felder, die aus `RawResult.json`, `_meta` und dem
|
||||
Dateibestand **belegbar** sind. Alles Uebrige bleibt als Platzhalter stehen und
|
||||
ist von Hand zu ergaenzen - insbesondere der Abschnitt *Anmerkungen*, der die
|
||||
Deutung enthaelt und nicht generierbar ist.
|
||||
|
||||
Nicht ermittelbare Groessen werden als `nicht erfasst` ausgewiesen, niemals
|
||||
geschaetzt (Randbedingung des Skills).
|
||||
|
||||
python protokoll-geruest.py "<Laufverzeichnis>" [--ueberschreiben]
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
EBENEN_DATEIEN = ("StRS.md", "SyRS.md", "SwRS.md")
|
||||
|
||||
|
||||
def sha256(pfad: Path) -> str:
|
||||
return hashlib.sha256(pfad.read_bytes()).hexdigest().upper()
|
||||
|
||||
|
||||
def lies(pfad: Path, standard: str = "nicht erfasst") -> str:
|
||||
try:
|
||||
return pfad.read_text(encoding="utf-8").strip() or standard
|
||||
except OSError:
|
||||
return standard
|
||||
|
||||
|
||||
def zahl(n) -> str:
|
||||
"""Tausenderpunkte nach deutscher Schreibweise."""
|
||||
try:
|
||||
return f"{int(n):,}".replace(",", ".")
|
||||
except (TypeError, ValueError):
|
||||
return "nicht erfasst"
|
||||
|
||||
|
||||
def dauer(ms: int) -> str:
|
||||
s = ms // 1000
|
||||
return f"{s // 3600:02d}:{(s % 3600) // 60:02d}:{s % 60:02d}"
|
||||
|
||||
|
||||
def git_kopf(repo: Path) -> str:
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["git", "-C", str(repo), "rev-parse", "HEAD"],
|
||||
capture_output=True, text=True, check=False,
|
||||
)
|
||||
return r.stdout.strip() or "nicht erfasst"
|
||||
except OSError:
|
||||
return "nicht erfasst"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Protokollgeruest erzeugen")
|
||||
parser.add_argument("lauf")
|
||||
parser.add_argument("--repo", default="C:/DEV/MasterArbeit")
|
||||
parser.add_argument("--ueberschreiben", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
lauf = Path(args.lauf).resolve()
|
||||
ziel = lauf / "Protokoll.md"
|
||||
if ziel.exists() and not args.ueberschreiben:
|
||||
print(f"{ziel} existiert bereits - mit --ueberschreiben erzwingen.")
|
||||
return 1
|
||||
|
||||
roh = json.loads((lauf / "RawResult.json").read_text(encoding="utf-8"))
|
||||
meta = lauf / "_meta"
|
||||
u = roh.get("usage", {})
|
||||
lr = roh.get("local_runtime") or {}
|
||||
erfasst = roh.get("usage_captured", True)
|
||||
|
||||
def tok(feld: str) -> str:
|
||||
return zahl(u.get(feld, 0)) if erfasst else "nicht erfasst"
|
||||
|
||||
# Ebene und Iteration aus der Ablagestruktur ableiten
|
||||
teile = lauf.parts
|
||||
iteration = next((p for p in teile if p.startswith("Iteration")), "?")
|
||||
zelle = "/".join(teile[teile.index(iteration):-1]) if iteration in teile else "?"
|
||||
|
||||
# Die Prompt-Datei liegt im Versuchsordner, also oberhalb der
|
||||
# Bedingungsebenen; deren Anzahl schwankt mit der Modell-ID (ein- oder
|
||||
# zweiteilig). Deshalb aufwaerts suchen statt eine feste Tiefe annehmen.
|
||||
prompt_datei = None
|
||||
for eltern in lauf.parents[:8]:
|
||||
treffer = sorted(eltern.glob("*_Prompt.md"))
|
||||
if treffer:
|
||||
prompt_datei = treffer[-1]
|
||||
break
|
||||
anforderungen = lies(meta / "anforderungen.md", "*(nicht ausgewertet)*")
|
||||
dateien = sorted(p.name for p in (lauf / "Ergebnisse").glob("*") if p.is_file())
|
||||
vorher, nachher = lies(meta / "before.txt", ""), lies(meta / "after.txt", "")
|
||||
|
||||
text = f"""# Messprotokoll – {zelle}
|
||||
|
||||
> **GERUEST** – maschinell aus den Rohdaten erzeugt. Die Abschnitte *Anmerkungen*
|
||||
> und *Gueltigkeit* sind von Hand zu pruefen und zu ergaenzen.
|
||||
|
||||
## Lauf
|
||||
- **Prompt-Datei:** `{prompt_datei.name if prompt_datei else 'nicht erfasst'}`
|
||||
- **SHA-256 (Prompt):** `{sha256(prompt_datei) if prompt_datei else 'nicht erfasst'}`
|
||||
- **Startzeit:** {lies(meta / 'startzeit.txt')}
|
||||
- **Endzeit:** {lies(meta / 'endzeit.txt')}
|
||||
- **Dauer gesamt:** {dauer(roh.get('duration_ms', 0))} (API: nicht erfasst – OpenCode liefert keine separate API-Zeit)
|
||||
- **Root-Verzeichnis:** `{args.repo}/QuellCode/CentronERP`
|
||||
- **Codebasis-Commit:** `{git_kopf(Path(args.repo))}` (vor dem Lauf dirty: {'nein' if not vorher else 'ja – ' + vorher.replace(chr(10), '; ')})
|
||||
- **Snapshot-Zustand:** bereinigt von KI-Konfigurationen: ja; Remote entkoppelt: ja
|
||||
|
||||
## Werkzeugkonfiguration
|
||||
- **Skill-Version:** {lauf.name.split('_v')[-1].split('-')[0] if '_v' in lauf.name else 'nicht erfasst'}
|
||||
- **Werkzeugadapter:** OpenCode, `opencode-adapter.py --provider {roh.get('provider')}` (Adapter-Version {roh.get('adapter_version')})
|
||||
- **CLI-Version:** OpenCode {roh.get('opencode_version', 'nicht erfasst')}
|
||||
- **Modell (angefordert):** `{roh.get('model_requested')}`
|
||||
- **Modell (tatsaechlich):** `{roh.get('model')}`
|
||||
- **Kontrolle Modell:** {'bestanden' if roh.get('model') == roh.get('model_requested') else '**verletzt**'}
|
||||
- **Effort:** `{roh.get('effort')}` angefordert, **{'wirksam' if roh.get('effort_applied') else 'nicht wirksam'}** (`effort_applied: {str(roh.get('effort_applied')).lower()}`)
|
||||
- **Ablage:** `{zelle}/`
|
||||
- **Agentenmodus:** `{roh.get('mode')}`
|
||||
- **Kontextfenster:** {zahl(roh.get('context_window', 0))} Tokens geladen (Modellmaximum {zahl(lr.get('max_context_length', 0))})
|
||||
- **Sampling-Parameter:** nicht steuerbar
|
||||
- **Lokaler Modellbetrieb:** Runtime `{lr.get('compatibility_type', '?')}`, `lms` {lr.get('lms_version', '?')}, Architektur `{lr.get('arch', '?')}`, **Quantisierung `{lr.get('quantization', '?')}`**, {lr.get('parallel_slots', '?')} Slots, GPU-Offload `{lr.get('gpu_offload', '?')}`, alleiniges Modell: {str(lr.get('alleiniges_modell', '?')).lower()}
|
||||
- **Abbruchsicherungen:** `--stall-timeout 0`, `--max-runtime` siehe Matrixskript
|
||||
- **Subagenten:** `spawned` = {roh.get('subagent_stats', {}).get('spawned', 0)}, `completed` = {roh.get('subagent_stats', {}).get('completed', 0)}, `failed` = {roh.get('subagent_stats', {}).get('failed', 0)}
|
||||
- **Rollen:** {json.dumps(roh.get('subagent_stats', {}).get('by_type', {}), ensure_ascii=False)}
|
||||
|
||||
## Validierungsstichprobe
|
||||
- **Stand:** entfaellt
|
||||
|
||||
## Verbrauch
|
||||
|
||||
| Messgroesse | Wert |
|
||||
|---|---|
|
||||
| Input-Tokens | {tok('prompt_tokens')} |
|
||||
| Output-Tokens | {tok('completion_tokens')} |
|
||||
| Reasoning-Tokens | {tok('reasoning_tokens')} |
|
||||
| Cache-Write-/Cache-Read-Tokens | nicht erfasst – der lokale Server liefert keine |
|
||||
| Agent-Turns | {roh.get('num_turns', 0)} |
|
||||
|
||||
**Tokens gesamt: {tok('total_tokens')}.** Kosten `0` – lokaler Betrieb ({roh.get('cost_source', '')}).
|
||||
{'' if erfasst else chr(10) + '**Achtung:** ' + roh.get('usage_note', '') + chr(10)}
|
||||
## Gefundene Anforderungen
|
||||
|
||||
{anforderungen}
|
||||
|
||||
## Ergebnis
|
||||
- **Status:** `is_error: {str(roh.get('is_error')).lower()}`, `subtype: {roh.get('subtype')}`, `exit_code: {roh.get('exit_code')}`, `timed_out: {str(roh.get('timed_out')).lower()}`
|
||||
- **Session-ID:** `{roh.get('session_id')}`
|
||||
- **Werkzeugaufrufe:** {roh.get('tool_call_count', 0)} – {json.dumps(roh.get('tool_call_types', {}), ensure_ascii=False)}
|
||||
- **Kontrolle Agentenmodus:** `subagent_stats.spawned` = {roh.get('subagent_stats', {}).get('spawned', 0)}{' – korrekt fuer solo' if roh.get('mode') == 'solo' else ''}
|
||||
- **Gueltigkeit:** *(pruefen)* {'**Fehlmessung** – Ergebnisverzeichnis leer.' if not dateien else 'Ergebnisdateien vorhanden.'}
|
||||
- **Erzeugte Dateien:** {', '.join(dateien) if dateien else 'keine'}
|
||||
- **Root unveraendert:** {'ja' if vorher == nachher else '**nein – pruefen**'}
|
||||
- **Fehlermeldungen:** {json.dumps(roh.get('errors', []), ensure_ascii=False)}
|
||||
|
||||
## Anmerkungen/Auffaelligkeiten
|
||||
|
||||
*(von Hand zu ergaenzen)*
|
||||
"""
|
||||
ziel.write_text(text, encoding="utf-8")
|
||||
print(f"{ziel}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user