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>
11 KiB
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:
- Webrecherche — Perplexity Sonar API
- Claim-Extraktion — Fakten aus Freitext (mit automatischem Chunking für lange Texte)
- Fact-Checking — Verifikation einzelner Claims via Perplexity + Ollama
- Artikelschreiben — Nur aus verifizierten Facts
- 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 vonClaim-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:
callOllamaClaimExtract()— alle Claims (mit Chunking bei langen Texten)- Für jeden
checkableClaim: prüfe globalen Cache (lib/cache.ts) → prüfe Job-Cache →searchPerplexity()parallel (max. 5) - Ergebnisse in globalem Cache + Job-Cache speichern
- 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
supportedClaims → 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)→PerplexityResultformatSourcesForPrompt(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 keinOPENROUTER_API_KEY
lib/logger.ts
createLogger({ verbose?, jobId? })→LoggerLogger.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/errorimmer auf stderrnullLogger— 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 abgelaufensetCached<T>(claimText, data)— schreibt~/.pi/agent/cache/perplexity/<sha256>.jsonclaimHash(text)— exportierter SHA256-HelperpruneCache()— löscht Einträge älter als 7 TagecacheStats()—{ 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)→ jobDirfindJobDir(slug)→ neuestes Verzeichnis mit diesem Slug oder nullgetOrCreateJob(slug, model)→{ jobDir, isNew }saveJobFile(jobDir, filename, data)/loadJobFile<T>(jobDir, filename)→ T | nulljobFileExists(jobDir, filename)→ booleanupdateJobMeta(jobDir, updates)— shallow merge inmeta.jsonlistJobs()→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
// 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):
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