Text_Agent/docs/ARCHITECTURE.md
dschlueter 5146b7fa30 feat: Pi Text-Agent — initialer Commit (sauberes Repo)
Vollständiges Multi-Agenten-System für Fact-Checking, Artikelschreiben
und Argumentationsanalyse. Zwei Backends: llama.cpp (★ bevorzugt) und Ollama.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 04:21:48 +02:00

255 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ARCHITECTURE.md — System-Architektur
**Projekt:** Pi Text-Agent
**Stand:** 2026-04-16 (nach P3-Features: Claim-Cache, Testkorpus, Test-Runner)
---
## Überblick
Pipeline aus spezialisierten Agenten für:
1. **Webrecherche** — Perplexity Sonar API
2. **Claim-Extraktion** — Fakten aus Freitext (mit automatischem Chunking für lange Texte)
3. **Fact-Checking** — Verifikation einzelner Claims via Perplexity + Ollama
4. **Artikelschreiben** — Nur aus verifizierten Facts
5. **Argumentationsanalyse** — Fehlschlüsse + Qualitätsbewertung
Jeder Agent: Pi-Extension + CLI-Tool. Persistenz via Job-Speicher (`lib/jobs.ts`).
---
## Datenfluß
```
Freitext / Artikel
┌──────▼──────┐
│ollama-claim-extractor│ Ollama: qwen3.5:27b
│ (Chunking) │ Texte >4000 Zeichen → Chunks
└──────┬──────┘ → claims.json (Job-Cache)
│ ClaimSet (JSON)
┌──────▼──────┐
│verify-article│ Perplexity (parallel, max 5)
│ (Orchestrator)│ → perplexity/<id>.json (Job-Cache)
│ │ Ollama-Batch-Verdict (1 Call)
└──────┬──────┘ → report.json (Job-Cache)
│ VerificationReport (JSON)
┌────────────┼────────────┐
│ │
┌──────▼──────┐ ┌───────▼──────┐
│ writer │ │ logic-editor │
│ (--from-job)│ │ │
└──────┬──────┘ └───────┬──────┘
│ article.md (Job-Cache) │ ArgumentMap
▼ ▼
Fertiggestellter Argumentkarte +
Artikel mit Quellen Fehlschluss-Liste
```
---
## Job-Speicher-Datenfluß
```
verify-article.ts --job-id <slug>
├─ Schritt 1: Claims extrahieren
│ → ~/.pi/agent/jobs/<datum>_<slug>/input.txt
│ → ~/.pi/agent/jobs/<datum>_<slug>/claims.json
│ → meta.json: status="verifying"
├─ Schritt 2: Perplexity (pro Claim)
│ → ~/.pi/agent/jobs/<datum>_<slug>/perplexity/c001.json
│ → ~/.pi/agent/jobs/<datum>_<slug>/perplexity/c002.json ...
├─ Schritt 3: Ollama-Batch-Verdict
│ → ~/.pi/agent/jobs/<datum>_<slug>/report.json
│ → meta.json: status="completed"
writer.ts --from-job <slug>
├─ Liest report.json aus Job
├─ Schreibt article.md in Job
└─ meta.json: steps.write ergänzt
```
Bei Unterbrechung: erneuter Aufruf mit gleichem `--job-id` überspringt vorhandene Schritte.
---
## Komponenten
### Agenten (`agenten/`)
#### `ollama-claim-extractor.ts`
- **Input:** Freitext (beliebige Länge)
- **Output:** `ClaimSet` (Array von `Claim`-Objekten)
- **Modell:** `qwen3.5:27b` (Ollama structured output, `num_ctx=8192`)
- **Chunking:** Texte > 4000 Zeichen → Chunks ≤ 3000 Zeichen (Absatzgrenzen), sequenziell verarbeitet, dann dedupliziert + zusammengeführt
- **Retry:** 3 Versuche mit 15s Pause bei `fetch failed`
- **Flags:** `--only-checkable`, `--max-claims`, `--verbose`, `--json`
Claim-Felder: `claim_id`, `text`, `claim_type`, `checkability`, `needs_citation`, `entities`, `time_scope`, `source_sentence`
Checkability-Werte: `checkable`, `partly_checkable`, `not_checkable`
#### `ollama-verifier.ts`
- **Input:** Claim-Text + optionaler Kontext
- **Output:** `VerificationResult`
- **Pipeline:** `searchPerplexity()` → Ollama-Verdict-Synthesis
- **Flags:** `--mode fast|deep`, `--verbose`, `--json`
Status-Werte: `supported`, `contradicted`, `mixed`, `insufficient_evidence`, `needs_human_review`
#### `verify-article.ts` (Orchestrator)
- **Input:** Artikel-Text
- **Output:** `VerificationReport`
- **Ablauf:**
1. `callOllamaClaimExtract()` — alle Claims (mit Chunking bei langen Texten)
2. Für jeden `checkable` Claim: prüfe globalen Cache (`lib/cache.ts`) → prüfe Job-Cache → `searchPerplexity()` parallel (max. 5)
3. Ergebnisse in globalem Cache + Job-Cache speichern
4. Ein Batch-Ollama-Call für alle Verdicts
- **Claim-Cache-Priorität:** globaler Cache (SHA256, 7 Tage) → Job-Cache → live Perplexity
- **Flags:** `--job-id`, `--mode`, `--max-claims`, `--no-cache`, `--verbose`, `--json`
#### `writer.ts`
- **Input:** `VerificationReport` (stdin oder Job-Speicher)
- **Output:** `ArticleDraft`
- **Regel:** Nur `supported` Claims → Artikel; Rest → `excluded_claims`
- **Routing:** lokal (Standard) oder OpenRouter mit `--cloud`
- **Flags:** `--from-report` (stdin), `--from-job <slug>`, `--style`, `--words`, `--lang`, `--cloud`, `--json`
Stile: `journalistic`, `blog`, `academic`, `editorial`, `explanatory`
#### `logic-editor.ts`
- **Input:** Argumentativer Text
- **Output:** `ArgumentMap`
- **Modell:** `deepseek-r1:32b` (lokal) oder `--cloud` (OpenRouter)
- **Flags:** `--only-fallacies`, `--verbose`, `--json`
12 Fehlschluss-Typen: `ad_hominem`, `straw_man`, `false_dichotomy`, `slippery_slope`, `appeal_to_authority`, `hasty_generalization`, `circular_reasoning`, `red_herring`, `appeal_to_emotion`, `false_cause`, `bandwagon`, `anecdotal`
#### `research-web.ts`
- Standalone (keine relativen Imports), direkt in Pi-Extensions-Root
- Perplexity `sonar` / `sonar-pro`
---
### Shared Libraries (`lib/`)
#### `lib/perplexity.ts`
- `searchPerplexity(query, opts)``PerplexityResult`
- `formatSourcesForPrompt(sources, maxLen)` → String
- Exponentielles Retry (3×), Quell-Deduplizierung, Kostenberechnung
- Kostenmodell: `sonar` $1/Mio Tokens, `sonar-pro` $3/$15 (In/Out)
#### `lib/router.ts`
- `routeModel(task, complexity)``{ provider, model }`
- `callOpenRouter(model, messages, opts)``{ text, promptTokens, completionTokens, latencyMs }`
- `estimateOpenRouterCost(model, in, out)` → USD
- ENV-Overrides: `ROUTER_FORCE_LOCAL=1`, `ROUTER_FORCE_CLOUD=1`
- Standard: alle Tasks → lokal (Ollama) wenn `complexity: "low"` oder kein `OPENROUTER_API_KEY`
#### `lib/logger.ts`
- `createLogger({ verbose?, jobId? })``Logger`
- `Logger.info/warn/error/debug(msg, data?)` — strukturierte Einträge mit ISO-Timestamp
- Schreibt in `~/.pi/agent/logs/<timestamp>[_jobId].log`
- `verbose=true` → alle Einträge auf stderr; `warn`/`error` immer auf stderr
- `nullLogger` — Null-Objekt für Pi-Extension-Kontext
#### `lib/cache.ts`
- SHA256-basierter File-Cache für Perplexity-Ergebnisse
- `getCached<T>(claimText)` — normalisiert Text, liest JSON wenn nicht abgelaufen
- `setCached<T>(claimText, data)` — schreibt `~/.pi/agent/cache/perplexity/<sha256>.json`
- `claimHash(text)` — exportierter SHA256-Helper
- `pruneCache()` — löscht Einträge älter als 7 Tage
- `cacheStats()``{ total, expired, sizeBytes }`
- TTL: 7 Tage (basiert auf Datei-mtime)
- Normalisierung: lowercase + collapse whitespace → konsistentes Hashing auch bei minimalen Unterschieden
#### `lib/jobs.ts`
- `createJob(slug, model)` → jobDir
- `findJobDir(slug)` → neuestes Verzeichnis mit diesem Slug oder null
- `getOrCreateJob(slug, model)``{ jobDir, isNew }`
- `saveJobFile(jobDir, filename, data)` / `loadJobFile<T>(jobDir, filename)` → T | null
- `jobFileExists(jobDir, filename)` → boolean
- `updateJobMeta(jobDir, updates)` — shallow merge in `meta.json`
- `listJobs()``JobMeta[]` (neueste zuerst)
- `formatJobList(jobs)` → Tabelle für CLI
Job-Status: `created`, `extracting`, `verifying`, `writing`, `completed`, `failed`
---
## VRAM-Constraints (RTX 3090, 24 GB)
| Modell | Gewichte | KV@8192 | Gesamt | Passt? |
|--------|----------|---------|--------|--------|
| qwen3.5:27b Q4_K_M | 15.5 GB | 4.2 GB | ~21 GB | ✓ |
| qwen3.5:27b Q4_K_M @ 16384 | 15.5 GB | 8.4 GB | ~25 GB | ✗ OOM |
| deepseek-r1:32b Q4_K_M | 19 GB | 4.2 GB | ~23 GB | ✓ (knapp) |
**Deshalb `num_ctx=8192` hardcoded** in `ollama-claim-extractor.ts` und `ollama-verifier.ts`.
**Deshalb Chunking** statt größerer Kontext: Texte werden in ≤3000-Zeichen-Stücke zerlegt.
Mit `CUDA_VISIBLE_DEVICES=1,2` (beide RTX 3090): 48 GB VRAM → 70B-Modelle möglich.
---
## Ollama-Integrationsdetails
```typescript
// Structured Output — IMMER so, nie format: "json"
await fetch("http://localhost:11434/api/chat", {
method: "POST",
body: JSON.stringify({
model: "qwen3.5:27b",
messages: [...],
format: { type: "object", additionalProperties: false, properties: {...}, required: [...] },
stream: false,
options: { temperature: 0.1, num_ctx: 8192 }
})
});
```
Retry-Pattern in `ollama-claim-extractor.ts` (Vorlage für andere Agenten):
```typescript
for (let attempt = 1; attempt <= 3; attempt++) {
try {
resp = await fetch(...);
break;
} catch (err) {
if (attempt === 3) throw new Error(`fetch failed nach 3 Versuchen: ${err}`);
await new Promise(r => setTimeout(r, 15_000)); // 15s warten
}
}
```
---
## Deployment-Architektur
```
~/Pi_Agent_Projekts/text_agent/ ← git-Repository
├── agenten/ ◄──── Symlinks ────────────────────────────────┐
└── lib/ ◄────── Symlink ─────────────────────────────────┐ │
│ │
~/.pi/agent/extensions/ │ │
├── lib ────────────────────────────────────────────────────┘ │
└── fact-checker/ │
├── package.json │
├── ollama-claim-extractor.ts ──────────────────────────────────┘
├── ollama-verifier.ts ──────────────────────────────────────────┘
├── verify-article.ts ────────────────────────────────────┘
├── logic-editor.ts ──────────────────────────────────────┘
└── writer.ts ────────────────────────────────────────────┘
~/.pi/agent/jobs/ ← Job-Verzeichnisse (Runtime-Daten, nicht im git)
~/.pi/agent/logs/ ← Log-Dateien (Runtime-Daten, nicht im git)
~/.pi/agent/cache/ ← Perplexity-Claim-Cache (Runtime-Daten, nicht im git)
text_agent/tests/
├── corpus/ ← 10 Testfälle (input.txt, expected.json, notes.md)
├── results/ ← Test-Runner-Outputs (nicht im git, .gitignore)
└── run_corpus.sh ← Precision/Recall-Test-Runner
```