Measure package and prompt redundancy in retained trials
This commit is contained in:
1 parent
fa1a0ab86b
commit
500ae27beb
3 files changed
+7279
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,160 @@
|
||||
# Redundanzbilanz vom 16.10.2026
|
||||
|
||||
## Umfang und Methode
|
||||
|
||||
Vier erhaltene, durch PackageReader validierte Case-Pakete untersucht: Bug 20 g108
|
||||
(alter investigation_v1-Stand) und Bug 17 g124 (v9), g110 (v10), g148 (v11).
|
||||
Keines ist ein abgeschlossenes OUTPUT_READY-Paket. Der neueste Stand dient als
|
||||
Hauptbeispiel; ältere Versionen sind keine gleichwertigen Qualitätsbenchmarks.
|
||||
Keine LLM-Aufrufe, kein Umbau oder Löschen von Paketinhalt.
|
||||
|
||||
Reproduzierbares Werkzeug: `scripts/redundancy_audit.py`, Aufruf mit einem oder
|
||||
mehreren Paketpfaden und `--output <bericht.json>`.
|
||||
Aggregierte Messdaten ohne Quelltext-/Promptinhalte:
|
||||
`measurements/redundancy-audit-20261016.json`.
|
||||
Messstand: Code nach fa1a0ab, historische Pakete und Prompts unverändert.
|
||||
Alle MB/KB/GB dezimal, alle Textmessungen UTF-8-Bytes, keine Tokenzählung.
|
||||
|
||||
## 1. Paketgröße und fehlende Kompression
|
||||
|
||||
| Paket | Dateien | ZIP auf Platte | DEFLATE-6-Payload, simuliert |
|
||||
|---|---:|---:|---:|
|
||||
| Bug 20 g108 | 517 | 63,22 MB | 20,17 MB |
|
||||
| Bug 17 g124 | 576 | 34,61 MB | 9,97 MB |
|
||||
| Bug 17 g110 | 547 | 33,61 MB | 9,79 MB |
|
||||
| Bug 17 g148 | 658 | 36,56 MB | 10,32 MB |
|
||||
|
||||
**Alle ZIP-Einträge verwenden ZIP_STORED, also keine ZIP-Kompression.** Die
|
||||
Simulation komprimiert jeden Eintrag separat mit raw DEFLATE, Stufe 6, und prüft
|
||||
für jeden Eintrag die byteidentische Dekompression. ZIP-Header/Verzeichnis kommen
|
||||
zur simulierten Payload hinzu. Beim jüngsten Paket ergeben sich damit ungefähr
|
||||
10,53 MB statt 36,56 MB, rund 71 % weniger Speicher. Dies ist keine Änderung am
|
||||
Format oder am Inhalt und reduziert keine LLM-Tokens. Laufzeitkosten und die
|
||||
Writer-/Sicherheitsprüfungen wären bei einer Implementierung zu testen.
|
||||
|
||||
## 2. Zusammensetzung des neuesten Pakets
|
||||
|
||||
| Inhalt Bug 17 g148 | Bytes in MB | Anteil an 36,34 MB Dateiinhalten |
|
||||
|---|---:|---:|
|
||||
| Entpackte Originaldateien | 18,576 | 51,1 % |
|
||||
| Ursprüngliche TAR.GZ-Archive | 5,224 | 14,4 % |
|
||||
| Investigation-Kontexte | 7,686 | 21,1 % |
|
||||
| Weitere Analyseartefakte | 2,104 | 5,8 % |
|
||||
| Gespeicherte LLM-Requests | 1,896 | 5,2 % |
|
||||
| Aufbereiteter Case | 0,406 | 1,1 % |
|
||||
| Sonstiges: Historie, Forschung, LLM-Ergebnisse, Manifest | 0,450 | 1,2 % |
|
||||
|
||||
Die ZIP-Verzeichnis-/Headerdaten ergänzen weitere 0,214 MB.
|
||||
Die größte einzelne Datei ist das originale wowsrc `source/hits.json` mit
|
||||
5,015 MB. Weitere große Originaldateien sind Code-/SQL-Referenzsammlungen.
|
||||
Ihre fachliche Relevanz wurde hier nicht beurteilt.
|
||||
|
||||
### Exakte Duplikate versus doppelte Repräsentationen
|
||||
|
||||
- Byteidentische vollständige Dateien: nur **0,185 MB** zusätzliche Kopien im
|
||||
neuesten Paket. Reines Deduplizieren ganzer Dateien löst das Hauptproblem nicht.
|
||||
- **225 entpackte Originaldateien mit 18,576 MB** sind zusätzlich byteidentisch
|
||||
innerhalb der beiden archivierten TAR.GZ-Dateien vorhanden. Das ist eine
|
||||
doppelte Repräsentation derselben Originaldaten. Die TAR.GZ-Dateien selbst
|
||||
belegen 5,224 MB; diese Werte dürfen nicht als zwei unabhängige Einsparungen
|
||||
addiert werden. Eine neue Referenz-/Lesestrategie wäre nötig, um eine Kopie
|
||||
wegzulassen. Bestehende Originaldateien dürfen gemäß Architektur nicht gelöscht
|
||||
werden. Bei neuen Paketen wäre eine bewusste Import-/Formatentscheidung nötig.
|
||||
- **5,097 MB kanonisch identische Feldwerte** werden allein durch wiederholte
|
||||
Speicherung von `case`, `case_sources`, `tool_schema` und `interpretation` in
|
||||
den 17 Investigation-Kontexten zusätzlich abgelegt. Das ist eine Untergrenze
|
||||
für Kontextredundanz: wachsende Evidenzlisten enthalten weitere wiederholte
|
||||
Unterobjekte. Neu entworfene Kontexte könnten auf unveränderliche Artefakte
|
||||
innerhalb desselben Pakets verweisen, statt diese jedes Mal einzubetten.
|
||||
- Kontext-Snapshot und daraus erzeugter Request speichern teilweise dieselben
|
||||
Inhalte in verschiedenen Darstellungen. Diese Überlappung ist nachweisbar,
|
||||
aber hier nicht als zusätzliche unabhängige Einsparsumme quantifiziert.
|
||||
|
||||
## 3. Aktuelle Investigation-Prompts
|
||||
|
||||
17 gespeicherte v11-Requests ergeben **1,401 MB Nachrichteninhalt** (System und
|
||||
User, ohne äußere JSON-Transporthülle). Die einzelnen Aufrufe wachsen von
|
||||
54,1 KB auf 105,0 KB. Bei der bestehenden Cache-Partition wächst die separat
|
||||
gelieferte Live-Darstellung von 6,2 KB auf 63,2 KB; diese Transportmessung enthält
|
||||
eine andere JSON-Hülle und ist deshalb kein direkter Summand der 1,401 MB.
|
||||
|
||||
### Wiederholung zwischen Aufrufen
|
||||
|
||||
| Komponente | Insgesamt | Unterschiedliche vollständige Werte | Wiederholt |
|
||||
|---|---:|---:|---:|
|
||||
| Aufbereitete Quellen `sources` | 338,2 KB | 19,9 KB | 318,3 KB |
|
||||
| Output-Schema | 150,0 KB | 8,8 KB | 141,2 KB |
|
||||
| Interpretation | 63,8 KB | 3,8 KB | 60,0 KB |
|
||||
| Evidenzobjekte, einzeln verglichen | 218,8 KB | 29,5 KB | 189,3 KB |
|
||||
| Quellcodezeilen in `shared_sources` | 92,7 KB | 8,0 KB | 84,7 KB |
|
||||
|
||||
Evidenzobjekte werden einzeln nach kanonischem JSON verglichen. Quellcodezeilen
|
||||
werden nach Quellmetadaten, Zeilennummer und Inhalt verglichen, nicht allein nach
|
||||
Zeilentext. 3.352 Zeilenvorkommen entsprechen 280 unterschiedlichen belegten
|
||||
Zeilen. Alle Werte enthalten weiterhin die notwendigen unterschiedlichen Daten;
|
||||
ein Übertragungsprotokoll benötigt zusätzlich IDs/Referenzen und Cache-Verwaltung.
|
||||
|
||||
**Die statischen Wiederholungen sind zum großen Teil bereits durch den stabilen
|
||||
Fallcache abgedeckt.** Im zugehörigen vorangegangenen Live-Test meldeten alle
|
||||
17 Investigation-Antworten 19.987 gecachte Eingabetokens. Die gespeicherten
|
||||
Requests enthalten trotzdem die vollständige kanonische Anfrage für die
|
||||
Nachvollziehbarkeit. Ihre Größe darf nicht mit erneut ungecacht übertragenen
|
||||
oder voll berechneten Tokens gleichgesetzt werden.
|
||||
|
||||
Die Evidenz und Codezeilen liegen dagegen in der veränderlichen Live-Darstellung.
|
||||
Ein weiterer Ansatz ist ein versionierter Recherche-Cache beziehungsweise eine
|
||||
exakt rekonstruierbare Aufteilung in bereits bekannte und neu hinzugekommene
|
||||
Belege. Nicht einfach Belege weglassen: Der frühere Versuch mit selektiver
|
||||
Historie führte zu Nachladeproblemen und wurde ausdrücklich zurückgenommen.
|
||||
|
||||
### Wiederholung innerhalb eines Aufrufs und JSON-Struktur
|
||||
|
||||
Exakt identische längere Stringwerte (ab 80 UTF-8-Bytes) wiederholen sich in den
|
||||
17 v11-User-Dokumenten zusammen um **10,5 KB**, entsprechend **0,75 %** des
|
||||
gesamten System-/User-Nachrichteninhalts. Der Wert ist kein vollständiges Maß
|
||||
aller Redundanz: kurze Codezeilen, ähnliche Texte und überlappende Strukturen
|
||||
werden nicht als identische lange Strings erkannt. Eine Ersetzung bräuchte
|
||||
Referenzen und würde weniger als diese Bruttosumme sparen.
|
||||
|
||||
Zum Vergleich: Die alte Bug-20-Investigation v1 enthält 1,310 MB solcher
|
||||
wiederholter Strings bei 4,123 MB Nachrichteninhalt, rund 31,8 %. Dieser konkrete
|
||||
Mechanismus wurde in der aktuellen Darstellung also bereits stark reduziert.
|
||||
Die Fälle/Anzahl der Schritte sind verschieden; daraus folgt kein kontrollierter
|
||||
Qualitäts- oder Kostenvergleich.
|
||||
|
||||
Allein die JSON-Schlüsselnamen samt Anführungszeichen und Doppelpunkt belegen
|
||||
bei v11 insgesamt **415,4 KB**, rund 29,7 % des Nachrichteninhalts. Das ist kein
|
||||
versprochenes Einsparpotenzial von 29,7 %: Schlüssel sind für Bedeutung und
|
||||
Schema nötig; Kürzungen benötigen ein verständliches gemeinsames Wörterbuch.
|
||||
Der Wert zeigt aber, dass noch erhebliche Strukturkosten existieren. Beispiele
|
||||
sind wiederkehrende Fakt-/Quellprovenienz und Evidenz-Records. Innerhalb aller
|
||||
übermittelten Evidenzobjekte belegen `record` 72,9 KB und `record_ref` 13,7 KB,
|
||||
während `content` 127,4 KB belegt (zzgl. Schlüssel/Struktur).
|
||||
|
||||
## 4. Konsequenzen und Reihenfolge
|
||||
|
||||
1. **ZIP-Kompression und Aufbewahrung vollständiger Generationen:** großer,
|
||||
direkt messbarer Speichergewinn ohne Informationsverlust. Die 20-GB-Regel
|
||||
gilt unabhängig davon weiter. Keine Auswirkung auf API-Tokenkosten.
|
||||
2. **Gespeicherte Kontexte referenzieren unveränderliche Paketartefakte:**
|
||||
Fallbasis/Schemas einmal, je Schritt nur Änderungen und Referenzen. Eine
|
||||
Rekonstruktion muss exakt sein und auch nach Transport/Neustart funktionieren.
|
||||
Keine Abhängigkeit von einem externen Dienstgedächtnis.
|
||||
3. **Live-Recherchekontext effizienter übermitteln:** die 189,3 KB wiederholten
|
||||
Evidenzobjekte und 84,7 KB wiederholten Quellzeilen sind konkrete Ansatzpunkte.
|
||||
Diese Zahlen und die Strukturkosten überlappen teilweise; nicht addieren.
|
||||
Zuerst verlustfreie Struktur-/Cache-Ansätze prüfen, erst danach inhaltliche
|
||||
Auswahl mit nachgewiesener Qualität. Nicht den gesamten bisherigen Befund
|
||||
erneut durch eine zusätzliche LLM zusammenfassen lassen.
|
||||
4. **Provenienz für das Modell reduzieren:** doppelt mitgesendete interne
|
||||
Speicherpfade, ausführliche Records und technische Verwaltungsfelder anhand
|
||||
ihrer tatsächlich nötigen Funktion prüfen. Die vollständige Provenienz bleibt
|
||||
in der Akte; Beleg-ID, Quelle, Abfrageumfang und Versionsbezug müssen für die
|
||||
LLM verständlich bleiben. Das ist eine Designentscheidung, keine bereits
|
||||
validierte verlustfreie Ersetzung.
|
||||
|
||||
Es gibt aktuell keine belastbare Grundlage, eine Einsparung in Euro für diese
|
||||
Schritte zu versprechen. Die vier vorhandenen Pakete erklären die Speicher- und
|
||||
Textredundanz; sie zeigen nicht, welche fachlichen Informationen gefahrlos
|
||||
entfallen dürfen. Ein nachfolgender Qualitätstest muss diese Frage getrennt
|
||||
prüfen. An produktivem Verhalten wurde für diese Bilanz nichts geändert.
|
||||
@@ -0,0 +1,271 @@
|
||||
"""Read-only offline audit; reports aggregate sizes/hashes, never source contents.
|
||||
|
||||
All byte counts use UTF-8 or ZIP directory sizes. Potential savings overlap and
|
||||
must not be added together. This is a measurement, not a package migration.
|
||||
"""
|
||||
import argparse
|
||||
from collections import Counter, defaultdict
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tarfile
|
||||
import zipfile
|
||||
import zlib
|
||||
|
||||
from viano_bug_analysis.package import PackageReader
|
||||
from viano_bug_analysis.llm.gemini import GeminiProvider
|
||||
|
||||
|
||||
def encoded(value):
|
||||
return json.dumps(value, ensure_ascii=False, sort_keys=True,
|
||||
separators=(',', ':')).encode('utf-8')
|
||||
|
||||
|
||||
def digest(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def category(name):
|
||||
if name.startswith('original/'):
|
||||
return 'original_archives' if name.endswith('/archive.tar.gz') else 'original_members'
|
||||
if name.startswith('llm/'):
|
||||
return 'llm_requests' if name.endswith('/request.json') else 'llm_other'
|
||||
if '/contexts/' in name:
|
||||
return 'analysis_contexts'
|
||||
return name.split('/')[0]
|
||||
|
||||
|
||||
def repeated(groups):
|
||||
result = []
|
||||
for entries in groups.values():
|
||||
if len(entries) > 1:
|
||||
result.append({'copies': len(entries),
|
||||
'raw_duplicate_bytes': sum(e[1] for e in entries[1:]),
|
||||
'compressed_duplicate_bytes': sum(e[2] for e in entries[1:]),
|
||||
'paths': [e[0] for e in entries]})
|
||||
return sorted(result, key=lambda v: v['raw_duplicate_bytes'], reverse=True)
|
||||
|
||||
|
||||
def audit(path):
|
||||
# Validate through the normal bounded reader before inspecting ZIP metadata.
|
||||
with PackageReader(path) as reader:
|
||||
manifest = reader.manifest
|
||||
files = set(reader.files)
|
||||
groups = defaultdict(list)
|
||||
canonical = defaultdict(list)
|
||||
categories = defaultdict(Counter)
|
||||
requests = []
|
||||
archive_members = Counter()
|
||||
metadata_fields = Counter()
|
||||
deflate_estimate = Counter()
|
||||
compression_methods = Counter()
|
||||
evidence_items = {}
|
||||
source_lines = {}
|
||||
evidence_sizes = Counter()
|
||||
context_components = defaultdict(lambda: [0, 0, 0])
|
||||
context_values = set()
|
||||
with zipfile.ZipFile(path) as z:
|
||||
infos = {v.filename: v for v in z.infolist() if not v.is_dir()}
|
||||
for name, info in infos.items():
|
||||
data = z.read(name)
|
||||
compressor = zlib.compressobj(level=6, wbits=-15)
|
||||
compressed = compressor.compress(data) + compressor.flush()
|
||||
assert zlib.decompress(compressed, wbits=-15) == data
|
||||
deflate_size = len(compressed)
|
||||
deflate_estimate[category(name)] += deflate_size
|
||||
compression_methods[str(info.compress_type)] += 1
|
||||
cat = categories[category(name)]
|
||||
cat.update(files=1, raw_bytes=len(data), compressed_bytes=info.compress_size)
|
||||
groups[digest(data)].append((name, len(data), info.compress_size))
|
||||
if name.endswith('/archive.tar.gz'):
|
||||
prefix = name.removesuffix('archive.tar.gz') + 'members/'
|
||||
with tarfile.open(fileobj=io.BytesIO(data), mode='r|gz') as tar:
|
||||
for member in tar:
|
||||
if not member.isfile():
|
||||
continue
|
||||
target = prefix + member.name
|
||||
if target in files and member.size == infos[target].file_size:
|
||||
with tar.extractfile(member) as stream:
|
||||
same = digest(stream.read(member.size + 1)) == digest(z.read(target))
|
||||
if same:
|
||||
archive_members.update(files=1, raw_bytes=member.size,
|
||||
outer_zip_member_compressed_bytes=infos[target].compress_size)
|
||||
if not name.endswith('.json'):
|
||||
continue
|
||||
try:
|
||||
doc = json.loads(data)
|
||||
except (ValueError, UnicodeError):
|
||||
continue
|
||||
canonical[digest(encoded(doc))].append((name, len(data), info.compress_size))
|
||||
if category(name) == 'analysis_contexts' and isinstance(doc, dict):
|
||||
for field, value in doc.items():
|
||||
b = encoded(value)
|
||||
key = (field, digest(b))
|
||||
context_components[field][0] += len(b)
|
||||
context_components[field][2] += 1
|
||||
if key not in context_values:
|
||||
context_components[field][1] += len(b)
|
||||
context_values.add(key)
|
||||
if name.startswith('llm/operations/') and name.endswith('/request.json'):
|
||||
requests.append((name, doc))
|
||||
by_prompt = defaultdict(list)
|
||||
system_groups = Counter()
|
||||
block_groups = defaultdict(lambda: [0, 0, 0])
|
||||
per_call = []
|
||||
def leaves(value, at='$'):
|
||||
if isinstance(value, dict):
|
||||
for k, v in value.items():
|
||||
if isinstance(v, (dict, list)):
|
||||
yield from leaves(v, at + '.' + k)
|
||||
elif isinstance(v, str) and len(v.encode('utf-8')) >= 80:
|
||||
yield at + '.' + k, v
|
||||
elif isinstance(value, list):
|
||||
for v in value:
|
||||
yield from leaves(v, at + '[]')
|
||||
for name, req in requests:
|
||||
messages = req.get('messages', [])
|
||||
msg_bytes = sum(len(m['content'].encode('utf-8')) for m in messages)
|
||||
systems = [m['content'] for m in messages if m['role'] == 'system']
|
||||
for value in systems:
|
||||
system_groups[(digest(value.encode()), len(value.encode()))] += 1
|
||||
user_docs = []
|
||||
strings = defaultdict(list)
|
||||
keys = Counter()
|
||||
def count_keys(value):
|
||||
if isinstance(value, dict):
|
||||
for k, v in value.items():
|
||||
keys[k] += len(encoded(k)) + 1
|
||||
count_keys(v)
|
||||
elif isinstance(value, list):
|
||||
for v in value:
|
||||
count_keys(v)
|
||||
for m in messages:
|
||||
if m['role'] != 'user':
|
||||
continue
|
||||
try:
|
||||
doc = json.loads(m['content'])
|
||||
except ValueError:
|
||||
continue
|
||||
user_docs.append(doc)
|
||||
view = doc.get('input', {}) if isinstance(doc, dict) else {}
|
||||
if isinstance(view, dict):
|
||||
for item in view.get('evidence', {}).values() if isinstance(view.get('evidence'), dict) else []:
|
||||
evidence_items[digest(encoded(item))] = (evidence_items.get(digest(encoded(item)), (0, len(encoded(item))))[0] + 1, len(encoded(item)))
|
||||
if isinstance(item, dict):
|
||||
for field, value in item.items():
|
||||
evidence_sizes[field] += len(encoded(value))
|
||||
shared = view.get('shared_sources', {})
|
||||
for source in shared.values() if isinstance(shared, dict) else []:
|
||||
if not isinstance(source, dict):
|
||||
continue
|
||||
meta = source.get('metadata', {})
|
||||
identity = digest(encoded(meta))
|
||||
for block in source.get('blocks', []):
|
||||
for offset, line in enumerate(block.get('lines', [])):
|
||||
key = (identity, block['start_line'] + offset, digest(encoded(line)))
|
||||
count, size = source_lines.get(key, (0, len(encoded(line))))
|
||||
source_lines[key] = (count + 1, size)
|
||||
count_keys(doc)
|
||||
for at, value in leaves(doc):
|
||||
strings[digest(value.encode())].append((at, len(encoded(value))))
|
||||
if isinstance(doc, dict):
|
||||
for k, v in doc.items():
|
||||
items = v.items() if k == 'input' and isinstance(v, dict) else [(k, v)]
|
||||
for field, item in items:
|
||||
label = 'input.' + field if k == 'input' else field
|
||||
b = encoded(item)
|
||||
key = (req.get('prompt_version'), label, digest(b))
|
||||
block_groups[key][0] += 1
|
||||
block_groups[key][1] += len(b)
|
||||
block_groups[key][2] = len(b)
|
||||
dup_strings = sum(sum(n for _, n in entries[1:])
|
||||
for entries in strings.values())
|
||||
metadata_fields.update(keys)
|
||||
partition = None
|
||||
if req.get('prompt_version', '').startswith('investigation_project_'):
|
||||
system, prefix, live = GeminiProvider._messages(req, stable_case_context=True)
|
||||
partition = {'prefix_bytes': len(encoded([system, prefix])),
|
||||
'live_bytes': len(encoded(live)),
|
||||
'prefix_hash': digest(encoded([system, prefix]))}
|
||||
row = {'ref': name, 'prompt': req.get('prompt_version'),
|
||||
'message_utf8_bytes': msg_bytes,
|
||||
'user_repeated_strings_ge80_bytes': dup_strings,
|
||||
'json_key_bytes': sum(keys.values()),
|
||||
'top_json_keys_by_bytes': keys.most_common(8),
|
||||
'stable_partition': partition}
|
||||
per_call.append(row)
|
||||
by_prompt[req.get('prompt_version')].append(row)
|
||||
components = defaultdict(lambda: [0, 0, 0])
|
||||
for (prompt, label, _), (count, total, size) in block_groups.items():
|
||||
val = components[(prompt, label)]
|
||||
val[0] += total
|
||||
val[1] += size
|
||||
val[2] += count
|
||||
exact = repeated(groups)
|
||||
canon = repeated(canonical)
|
||||
summary = {
|
||||
'path': path.as_posix(),
|
||||
'identity': {k: manifest[k] for k in ('case_id', 'package_id', 'package_generation', 'state')},
|
||||
'zip_bytes': path.stat().st_size,
|
||||
'raw_member_bytes': sum(v.file_size for v in infos.values()),
|
||||
'compressed_member_bytes': sum(v.compress_size for v in infos.values()),
|
||||
'members': len(infos),
|
||||
'zip_compression_methods': dict(compression_methods),
|
||||
'deflate6_estimated_payload_bytes': sum(deflate_estimate.values()),
|
||||
'deflate6_by_category': dict(deflate_estimate),
|
||||
'categories': dict(categories),
|
||||
'stored_context_components': {k:{'total_bytes':v[0], 'unique_value_bytes':v[1],
|
||||
'repeated_value_bytes':v[0]-v[1], 'occurrences':v[2]} for k,v in sorted(context_components.items(), key=lambda kv:kv[1][0], reverse=True)},
|
||||
'largest_members': [{'path': v.filename, 'raw_bytes': v.file_size,
|
||||
'compressed_bytes': v.compress_size} for v in sorted(infos.values(), key=lambda v:v.compress_size, reverse=True)[:12]],
|
||||
'exact_duplicate_groups': len(exact),
|
||||
'exact_duplicate_raw_bytes': sum(v['raw_duplicate_bytes'] for v in exact),
|
||||
'exact_duplicate_compressed_bytes': sum(v['compressed_duplicate_bytes'] for v in exact),
|
||||
'largest_exact_duplicate_groups': exact[:8],
|
||||
'canonical_json_duplicate_raw_bytes': sum(v['raw_duplicate_bytes'] for v in canon),
|
||||
'original_tar_members_also_stored_separately': dict(archive_members),
|
||||
'request_count': len(requests),
|
||||
'research_repetition': {
|
||||
'evidence_item_total_bytes': sum(n*s for n,s in evidence_items.values()),
|
||||
'evidence_item_unique_bytes': sum(s for n,s in evidence_items.values()),
|
||||
'evidence_item_fields_bytes':dict(evidence_sizes),
|
||||
'source_line_total_bytes':sum(n*s for n,s in source_lines.values()),
|
||||
'source_line_unique_bytes':sum(s for n,s in source_lines.values()),
|
||||
'source_line_occurrences':sum(n for n,s in source_lines.values()),
|
||||
'source_line_unique_count':len(source_lines)},
|
||||
'prompts': {p: {'calls':len(rows), 'message_utf8_bytes':sum(r['message_utf8_bytes'] for r in rows),
|
||||
'within_call_repeated_strings_ge80_bytes':sum(r['user_repeated_strings_ge80_bytes'] for r in rows)} for p, rows in by_prompt.items()},
|
||||
'system_message_total_bytes':sum(size*count for (_,size),count in system_groups.items()),
|
||||
'system_message_repeated_bytes':sum(size*(count-1) for (_,size),count in system_groups.items()),
|
||||
'top_level_components': [{'prompt':p,'field':label,'total_canonical_bytes':v[0],
|
||||
'unique_value_bytes':v[1], 'repeated_value_bytes':v[0]-v[1], 'occurrences':v[2]}
|
||||
for (p,label),v in sorted(components.items(), key=lambda kv:kv[1][0], reverse=True)],
|
||||
'json_keys_by_bytes':metadata_fields.most_common(15),
|
||||
'calls': per_call,
|
||||
}
|
||||
assert sum(v['raw_bytes'] for v in categories.values()) == summary['raw_member_bytes']
|
||||
assert sum(v['compressed_bytes'] for v in categories.values()) == summary['compressed_member_bytes']
|
||||
return summary
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('packages', nargs='+', type=Path)
|
||||
parser.add_argument('--output', required=True, type=Path)
|
||||
args = parser.parse_args()
|
||||
report = {'measurement':'redundancy_audit_v1', 'units':'bytes, UTF-8, decimal MB/GB',
|
||||
'limitations': ['No paid API calls; no quality or token savings inferred.',
|
||||
'Categories are disjoint; duplicate, archive-copy and repeated-string measures overlap.',
|
||||
'Compressed duplicate sizes are an upper bound, not a compatible deletion proposal.',
|
||||
'Repeated strings are counted within user messages, minimum 80 UTF-8 bytes; references and JSON framing not deducted.',
|
||||
'Component repetition compares whole canonical top-level values within each prompt version, ignoring order of calls.',
|
||||
'Saved requests are canonical gateway input, not literal cached HTTP payload.'],
|
||||
'packages':[audit(p) for p in args.packages]}
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(json.dumps(report, ensure_ascii=False, indent=2)+'\n', encoding='utf-8')
|
||||
print(json.dumps({'output':str(args.output),'packages':len(report['packages'])}))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user