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

11 KiB
Raw Permalink Blame History

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

// 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