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>
This commit is contained in:
commit
5146b7fa30
62 changed files with 11279 additions and 0 deletions
567
agenten/ollama-logic-editor.ts
Normal file
567
agenten/ollama-logic-editor.ts
Normal file
|
|
@ -0,0 +1,567 @@
|
|||
/**
|
||||
* ollama-logic-editor.ts
|
||||
* Pi-Extension + CLI: Argumentationsanalyse via Ollama (deepseek-r1:32b)
|
||||
*
|
||||
* Analysiert einen Text auf:
|
||||
* - Hauptthese und Unterthesen
|
||||
* - Explizite Prämissen und Belege
|
||||
* - Schlussfolgerungen
|
||||
* - Implizite Annahmen
|
||||
* - Logische Fehlschlüsse (Ad Hominem, Strohmann, etc.)
|
||||
* - Verbesserungsvorschläge
|
||||
*
|
||||
* Routing: deepseek-r1:32b lokal (Standard) oder OpenRouter (--cloud / high complexity)
|
||||
* HINWEIS: analyze_logic_llama (llama-logic-editor.ts) bevorzugen für einheitliches Backend.
|
||||
*
|
||||
* Als Pi-Extension: ~/.pi/agent/extensions/fact-checker/ (via Symlink)
|
||||
* Als CLI:
|
||||
* npx tsx agenten/ollama-logic-editor.ts "Artikeltext..."
|
||||
* npx tsx agenten/ollama-logic-editor.ts --cloud "Kontroverseller Kommentar..."
|
||||
* npx tsx agenten/ollama-logic-editor.ts --json "$(cat kommentar.txt)"
|
||||
*/
|
||||
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { routeModel, callOpenRouter, estimateOpenRouterCost } from "../lib/router.js";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Typen
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type FallacyType =
|
||||
| "ad_hominem" | "straw_man" | "false_dichotomy" | "slippery_slope"
|
||||
| "circular_reasoning" | "appeal_to_authority" | "hasty_generalization"
|
||||
| "false_causation" | "appeal_to_emotion" | "overgeneralization"
|
||||
| "cherry_picking" | "other";
|
||||
|
||||
type Severity = "minor" | "moderate" | "critical";
|
||||
type EvidenceStrength = "strong" | "moderate" | "weak";
|
||||
type OverallQuality = "strong" | "adequate" | "weak" | "flawed";
|
||||
|
||||
type ArgumentMap = {
|
||||
schema_version: "1.0.0";
|
||||
thesis: string;
|
||||
sub_theses: string[];
|
||||
premises: string[];
|
||||
evidence: Array<{ claim: string; supports_thesis: boolean; strength: EvidenceStrength }>;
|
||||
conclusions: string[];
|
||||
implicit_assumptions: string[];
|
||||
fallacies: Array<{
|
||||
type: FallacyType;
|
||||
description: string;
|
||||
location: string;
|
||||
severity: Severity;
|
||||
}>;
|
||||
revision_suggestions: string[];
|
||||
overall_quality: OverallQuality;
|
||||
quality_notes: string;
|
||||
};
|
||||
|
||||
type OllamaResponse = {
|
||||
message?: { content?: string };
|
||||
eval_count?: number;
|
||||
prompt_eval_count?: number;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Konfiguration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const OLLAMA_HOST = process.env.OLLAMA_HOST ?? "http://localhost:11434";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Ollama-Schema für strukturierte Argumentationsanalyse
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const ARGUMENT_MAP_SCHEMA = {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
thesis: { type: "string" },
|
||||
sub_theses: { type: "array", items: { type: "string" } },
|
||||
premises: { type: "array", items: { type: "string" } },
|
||||
evidence: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
claim: { type: "string" },
|
||||
supports_thesis: { type: "boolean" },
|
||||
strength: { type: "string", enum: ["strong", "moderate", "weak"] },
|
||||
},
|
||||
required: ["claim", "supports_thesis", "strength"],
|
||||
},
|
||||
},
|
||||
conclusions: { type: "array", items: { type: "string" } },
|
||||
implicit_assumptions: { type: "array", items: { type: "string" } },
|
||||
fallacies: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
type: {
|
||||
type: "string",
|
||||
enum: [
|
||||
"ad_hominem", "straw_man", "false_dichotomy", "slippery_slope",
|
||||
"circular_reasoning", "appeal_to_authority", "hasty_generalization",
|
||||
"false_causation", "appeal_to_emotion", "overgeneralization",
|
||||
"cherry_picking", "other",
|
||||
],
|
||||
},
|
||||
description: { type: "string" },
|
||||
location: { type: "string" },
|
||||
severity: { type: "string", enum: ["minor", "moderate", "critical"] },
|
||||
},
|
||||
required: ["type", "description", "location", "severity"],
|
||||
},
|
||||
},
|
||||
revision_suggestions: { type: "array", items: { type: "string" } },
|
||||
overall_quality: { type: "string", enum: ["strong", "adequate", "weak", "flawed"] },
|
||||
quality_notes: { type: "string" },
|
||||
},
|
||||
required: [
|
||||
"thesis", "sub_theses", "premises", "evidence", "conclusions",
|
||||
"implicit_assumptions", "fallacies", "revision_suggestions",
|
||||
"overall_quality", "quality_notes",
|
||||
],
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// System-Prompt
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LOGIC_SYSTEM_PROMPT = `Du bist ein Experte für kritisches Denken, Rhetorik und formale Logik.
|
||||
Antworte ausschließlich auf Deutsch.
|
||||
Analysiere den folgenden Text auf seine Argumentationsstruktur.
|
||||
|
||||
Extrahiere:
|
||||
1. thesis: Die zentrale Hauptbehauptung als vollständiger Satz
|
||||
2. sub_theses: Untergeordnete Thesen die die Hauptthese stützen
|
||||
3. premises: Ausdrücklich genannte Voraussetzungen und Grundannahmen
|
||||
4. evidence: Verwendete Belege (Fakten, Statistiken, Zitate, Studien) — beachte ob sie die These wirklich stützen
|
||||
5. conclusions: Explizite Schlussfolgerungen die aus den Prämissen gezogen werden
|
||||
6. implicit_assumptions: Nicht ausgesprochene Annahmen die das Argument voraussetzt
|
||||
|
||||
Fehlschluss-Typen:
|
||||
- ad_hominem: Person statt Argument angegriffen
|
||||
- straw_man: Gegnerposition verzerrt dargestellt
|
||||
- false_dichotomy: Falsche Zweiteilung (nur A oder B, obwohl mehr möglich)
|
||||
- slippery_slope: Kettenreaktion ohne Beleg
|
||||
- circular_reasoning: These wird durch sich selbst begründet
|
||||
- appeal_to_authority: Autorität als einziger Beleg
|
||||
- hasty_generalization: Einzelfall → Allgemeinregel
|
||||
- false_causation: Korrelation als Kausalität dargestellt
|
||||
- appeal_to_emotion: Emotionen statt Argumente
|
||||
- overgeneralization: Zu weit gefasste Verallgemeinerung
|
||||
- cherry_picking: Nur passende Fakten ausgewählt
|
||||
- other: Sonstiger Fehlschluss
|
||||
|
||||
Für jeden Fehlschluss:
|
||||
- type: einer der oben genannten Typen
|
||||
- description: Was genau ist der Fehlschluss? (1-2 Sätze, auf Deutsch)
|
||||
- location: Das WÖRTLICHE ZITAT aus dem Originaltext wo der Fehlschluss vorkommt (max. 120 Zeichen, kein Feldname, kein JSON-Schlüssel)
|
||||
- severity: minor/moderate/critical
|
||||
|
||||
overall_quality:
|
||||
- strong: Kohärentes, gut belegtes Argument mit klarer Struktur
|
||||
- adequate: Akzeptable Argumentation mit kleineren Lücken
|
||||
- weak: Erhebliche Mängel, Lücken überwiegen
|
||||
- flawed: Fundamentale logische Fehler oder schwere Fehlschlüsse
|
||||
|
||||
revision_suggestions: Konkrete, umsetzbare Verbesserungsvorschläge
|
||||
quality_notes: 2-4 Sätze Begründung der Gesamtbewertung
|
||||
|
||||
Antworte NUR mit dem JSON-Objekt. Kein Freitext.`;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Ollama-Analyse
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function analyzeWithOllama(
|
||||
text: string,
|
||||
model: string,
|
||||
signal?: AbortSignal
|
||||
): Promise<{ map: ArgumentMap; tokensIn: number; tokensOut: number; latencyMs: number }> {
|
||||
const t0 = Date.now();
|
||||
|
||||
const body = {
|
||||
model,
|
||||
messages: [
|
||||
{ role: "system", content: LOGIC_SYSTEM_PROMPT },
|
||||
{ role: "user", content: `Analysiere die Argumentationsstruktur:\n\n---\n${text}\n---` },
|
||||
],
|
||||
format: ARGUMENT_MAP_SCHEMA,
|
||||
stream: false,
|
||||
options: { temperature: 0.1, num_ctx: 8192 },
|
||||
};
|
||||
|
||||
const resp = await fetch(`${OLLAMA_HOST}/api/chat`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
signal,
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
const errText = await resp.text().catch(() => "");
|
||||
throw new Error(`Ollama Fehler ${resp.status}: ${errText}`);
|
||||
}
|
||||
|
||||
const data = (await resp.json()) as OllamaResponse;
|
||||
const raw = data.message?.content ?? "";
|
||||
if (!raw.trim()) throw new Error("Leere Ollama-Antwort");
|
||||
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(raw);
|
||||
} catch {
|
||||
throw new Error(`Kein gültiges JSON von Ollama: ${raw.slice(0, 200)}`);
|
||||
}
|
||||
|
||||
const map: ArgumentMap = { schema_version: "1.0.0", ...(parsed as Omit<ArgumentMap, "schema_version">) };
|
||||
|
||||
return { map, tokensIn: data.prompt_eval_count ?? 0, tokensOut: data.eval_count ?? 0, latencyMs: Date.now() - t0 };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OpenRouter-Analyse (Freitext → strukturiertes Parsing)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const OPENROUTER_LOGIC_PROMPT = `${LOGIC_SYSTEM_PROMPT}
|
||||
|
||||
WICHTIG: Antworte mit einem einzigen JSON-Objekt. Kein Markdown-Wrapper, kein Freitext davor oder danach.`;
|
||||
|
||||
async function analyzeWithOpenRouter(
|
||||
text: string,
|
||||
model: string,
|
||||
signal?: AbortSignal
|
||||
): Promise<{ map: ArgumentMap; costUSD: number; latencyMs: number }> {
|
||||
const result = await callOpenRouter(
|
||||
model,
|
||||
[
|
||||
{ role: "system", content: OPENROUTER_LOGIC_PROMPT },
|
||||
{ role: "user", content: `Analysiere die Argumentationsstruktur:\n\n---\n${text}\n---` },
|
||||
],
|
||||
{ temperature: 0.1, maxTokens: 3000, signal }
|
||||
);
|
||||
|
||||
// OpenRouter gibt Freitext zurück — JSON extrahieren
|
||||
const jsonMatch = result.text.match(/\{[\s\S]*\}/);
|
||||
if (!jsonMatch) throw new Error("Kein JSON in OpenRouter-Antwort gefunden");
|
||||
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(jsonMatch[0]);
|
||||
} catch {
|
||||
throw new Error(`Ungültiges JSON von OpenRouter: ${result.text.slice(0, 200)}`);
|
||||
}
|
||||
|
||||
const costUSD = estimateOpenRouterCost(model, result.promptTokens, result.completionTokens);
|
||||
const map: ArgumentMap = { schema_version: "1.0.0", ...(parsed as Omit<ArgumentMap, "schema_version">) };
|
||||
|
||||
return { map, costUSD, latencyMs: result.latencyMs };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hauptfunktion
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type AnalysisResult = {
|
||||
map: ArgumentMap;
|
||||
provider: "ollama" | "openrouter";
|
||||
model: string;
|
||||
costUSD: number;
|
||||
latencyMs: number;
|
||||
};
|
||||
|
||||
export async function analyzeLogic(
|
||||
text: string,
|
||||
options?: {
|
||||
forceCloud?: boolean;
|
||||
model?: string;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
): Promise<AnalysisResult> {
|
||||
const complexity = text.length > 2000 ? "high" : "medium";
|
||||
const decision = routeModel(
|
||||
options?.forceCloud ? "deep_reasoning" : "logic_analysis",
|
||||
complexity
|
||||
);
|
||||
const model = options?.model ?? decision.model;
|
||||
|
||||
if (decision.provider === "openrouter" || options?.forceCloud) {
|
||||
const { map, costUSD, latencyMs } = await analyzeWithOpenRouter(text, model, options?.signal);
|
||||
return { map, provider: "openrouter", model, costUSD, latencyMs };
|
||||
}
|
||||
|
||||
const { map, latencyMs } = await analyzeWithOllama(text, model, options?.signal);
|
||||
return { map, provider: "ollama", model, costUSD: 0, latencyMs };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Formatierung
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const QUALITY_LABEL: Record<OverallQuality, string> = {
|
||||
strong: "STARK",
|
||||
adequate: "AUSREICHEND",
|
||||
weak: "SCHWACH",
|
||||
flawed: "FEHLERHAFT",
|
||||
};
|
||||
|
||||
const QUALITY_ICON: Record<OverallQuality, string> = {
|
||||
strong: "✓",
|
||||
adequate: "~",
|
||||
weak: "⚠",
|
||||
flawed: "✗",
|
||||
};
|
||||
|
||||
const FALLACY_LABEL: Record<FallacyType, string> = {
|
||||
ad_hominem: "Ad Hominem",
|
||||
straw_man: "Strohmann",
|
||||
false_dichotomy: "Falsche Dichotomie",
|
||||
slippery_slope: "Schiefe Ebene",
|
||||
circular_reasoning: "Zirkelschluss",
|
||||
appeal_to_authority: "Autoritätsargument",
|
||||
hasty_generalization: "Vorschnelle Generalisierung",
|
||||
false_causation: "Falsche Kausalität",
|
||||
appeal_to_emotion: "Appell an Emotionen",
|
||||
overgeneralization: "Überverallgemeinerung",
|
||||
cherry_picking: "Rosinenpickerei",
|
||||
other: "Sonstiger Fehlschluss",
|
||||
};
|
||||
|
||||
const SEVERITY_ICON: Record<Severity, string> = {
|
||||
minor: "·",
|
||||
moderate: "⚠",
|
||||
critical: "✗",
|
||||
};
|
||||
|
||||
function formatArgumentMap(result: AnalysisResult): string {
|
||||
const { map } = result;
|
||||
const lines: string[] = [];
|
||||
const q = map.overall_quality;
|
||||
|
||||
lines.push(`## Argumentationsanalyse`);
|
||||
lines.push(`**Gesamtqualität: ${QUALITY_ICON[q]} ${QUALITY_LABEL[q]}**`);
|
||||
lines.push(map.quality_notes);
|
||||
lines.push("");
|
||||
|
||||
lines.push(`**Hauptthese:**`);
|
||||
lines.push(`> ${map.thesis}`);
|
||||
lines.push("");
|
||||
|
||||
if (map.sub_theses.length > 0) {
|
||||
lines.push(`**Unterthesen (${map.sub_theses.length}):**`);
|
||||
map.sub_theses.forEach((t) => lines.push(`- ${t}`));
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
if (map.premises.length > 0) {
|
||||
lines.push(`**Prämissen:**`);
|
||||
map.premises.forEach((p) => lines.push(`- ${p}`));
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
if (map.evidence.length > 0) {
|
||||
lines.push(`**Belege (${map.evidence.length}):**`);
|
||||
map.evidence.forEach((e) => {
|
||||
const icon = e.supports_thesis ? "✓" : "✗";
|
||||
const str = e.strength === "strong" ? "stark" : e.strength === "moderate" ? "mittel" : "schwach";
|
||||
lines.push(`${icon} [${str}] ${e.claim}`);
|
||||
});
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
if (map.conclusions.length > 0) {
|
||||
lines.push(`**Schlussfolgerungen:**`);
|
||||
map.conclusions.forEach((c) => lines.push(`- ${c}`));
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
if (map.implicit_assumptions.length > 0) {
|
||||
lines.push(`**Implizite Annahmen (${map.implicit_assumptions.length}):**`);
|
||||
map.implicit_assumptions.forEach((a) => lines.push(`- _${a}_`));
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
if (map.fallacies.length > 0) {
|
||||
lines.push(`**Fehlschlüsse (${map.fallacies.length}):**`);
|
||||
map.fallacies.forEach((f) => {
|
||||
lines.push(`${SEVERITY_ICON[f.severity]} **${FALLACY_LABEL[f.type]}** (${f.severity})`);
|
||||
lines.push(` ${f.description}`);
|
||||
lines.push(` _"${f.location}"_`);
|
||||
lines.push("");
|
||||
});
|
||||
} else {
|
||||
lines.push(`_Keine Fehlschlüsse erkannt._`);
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
if (map.revision_suggestions.length > 0) {
|
||||
lines.push(`**Verbesserungsvorschläge:**`);
|
||||
map.revision_suggestions.forEach((s, i) => lines.push(`${i + 1}. ${s}`));
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
const latSec = (result.latencyMs / 1000).toFixed(1);
|
||||
const costNote = result.costUSD > 0 ? ` · ~$${result.costUSD.toFixed(4)}` : " · kostenlos (lokal)";
|
||||
lines.push(`_[${result.provider === "ollama" ? "Ollama" : "OpenRouter"}: ${result.model}${costNote} · ${latSec}s]_`);
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pi-Extension
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const PARAMS = Type.Object({
|
||||
text: Type.String({
|
||||
description:
|
||||
"Der zu analysierende Text: Artikel, Blogpost, Kommentar, Essay oder Nachrichtentext. " +
|
||||
"Der Text wird auf logische Struktur, Fehlschlüsse und Argumentationsqualität geprüft.",
|
||||
}),
|
||||
cloud: Type.Optional(
|
||||
Type.Boolean({
|
||||
description:
|
||||
"Wenn true: OpenRouter-Modell für tiefere Analyse verwenden (erfordert OPENROUTER_API_KEY). " +
|
||||
"Standard: lokales Ollama (deepseek-r1:32b).",
|
||||
})
|
||||
),
|
||||
model: Type.Optional(
|
||||
Type.String({
|
||||
description: "Modell-Override. Standard wird vom Router entschieden.",
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export default function logicEditorExtension(pi: ExtensionAPI) {
|
||||
pi.registerTool({
|
||||
name: "analyze_logic",
|
||||
label: "Argumentationsanalyse",
|
||||
description:
|
||||
"Analysiert die logische Struktur eines Texts: Hauptthese, Prämissen, Belege, " +
|
||||
"Schlussfolgerungen, implizite Annahmen und logische Fehlschlüsse. " +
|
||||
"Gibt konkrete Verbesserungsvorschläge und eine Qualitätsbewertung. " +
|
||||
"Standard: lokal via deepseek-r1:32b. Mit cloud=true: OpenRouter-Reasoning-Modell.",
|
||||
promptGuidelines: [
|
||||
"PREFER analyze_logic_llama over analyze_logic — it uses llama.cpp (unified backend).",
|
||||
"Use analyze_logic (this tool) only when the user explicitly requests Ollama or OpenRouter.",
|
||||
"Use analyze_logic when the user wants to check the argumentation quality of an article, comment, or essay.",
|
||||
"Use analyze_logic after verify_article to get both factual AND logical quality assessment.",
|
||||
"Always show the full formatted output including fallacies and revision suggestions.",
|
||||
"If fallacies with severity 'critical' are found, highlight them prominently.",
|
||||
"For politically or scientifically sensitive content, recommend cloud=true for deeper analysis.",
|
||||
"The revision_suggestions are actionable — offer to rewrite specific sections if the user wants.",
|
||||
"Combine with verify_article for a complete quality assessment: facts + logic.",
|
||||
],
|
||||
parameters: PARAMS,
|
||||
async execute(_toolCallId, params, signal) {
|
||||
try {
|
||||
const result = await analyzeLogic(params.text, {
|
||||
forceCloud: params.cloud ?? false,
|
||||
model: params.model,
|
||||
signal,
|
||||
});
|
||||
return {
|
||||
content: [{ type: "text", text: formatArgumentMap(result) }],
|
||||
details: {
|
||||
overallQuality: result.map.overall_quality,
|
||||
fallacyCount: result.map.fallacies.length,
|
||||
criticalFallacies: result.map.fallacies.filter((f) => f.severity === "critical").length,
|
||||
provider: result.provider,
|
||||
model: result.model,
|
||||
costUSD: result.costUSD || null,
|
||||
latencyMs: result.latencyMs,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : "Unbekannter Fehler";
|
||||
return { content: [{ type: "text", text: `Argumentationsanalyse fehlgeschlagen: ${msg}` }] };
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CLI
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function runCli() {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length === 0 || args[0] === "--help") {
|
||||
console.log(`
|
||||
Argumentationsanalyse — Logik, Fehlschlüsse und Verbesserungsvorschläge
|
||||
|
||||
Verwendung:
|
||||
npx tsx agenten/logic-editor.ts [Optionen] "Text..."
|
||||
npx tsx agenten/logic-editor.ts "$(cat artikel.txt)"
|
||||
|
||||
Optionen:
|
||||
--cloud OpenRouter verwenden (stärker, kostenpflichtig)
|
||||
--model <name> Modell-Override
|
||||
--only-fallacies Nur Fehlschlüsse ausgeben (kein vollständiger Bericht)
|
||||
--json Ausgabe als JSON
|
||||
--help Diese Hilfe
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let forceCloud = false;
|
||||
let model: string | undefined;
|
||||
let jsonOutput = false;
|
||||
let onlyFallacies = false;
|
||||
const textParts: string[] = [];
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
if (arg === "--cloud") forceCloud = true;
|
||||
else if (arg === "--model" && args[i + 1]) model = args[++i];
|
||||
else if (arg === "--json") jsonOutput = true;
|
||||
else if (arg === "--only-fallacies") onlyFallacies = true;
|
||||
else if (!arg.startsWith("--")) textParts.push(arg);
|
||||
}
|
||||
|
||||
const text = textParts.join(" ").trim();
|
||||
if (!text) { console.error("Fehler: Kein Text."); process.exit(1); }
|
||||
|
||||
if (!jsonOutput) console.error(`\nAnalyse via ${forceCloud ? "OpenRouter" : "Ollama"}...\n`);
|
||||
|
||||
try {
|
||||
const result = await analyzeLogic(text, { forceCloud, model });
|
||||
|
||||
if (onlyFallacies) {
|
||||
if (jsonOutput) {
|
||||
console.log(JSON.stringify(result.map.fallacies, null, 2));
|
||||
} else {
|
||||
const { map } = result;
|
||||
if (map.fallacies.length === 0) {
|
||||
console.log("Keine Fehlschlüsse erkannt.");
|
||||
} else {
|
||||
console.log(`## Fehlschlüsse (${map.fallacies.length})\n`);
|
||||
map.fallacies.forEach((f) => {
|
||||
const icon = SEVERITY_ICON[f.severity];
|
||||
const label = FALLACY_LABEL[f.type];
|
||||
console.log(`${icon} **${label}** (${f.severity})`);
|
||||
console.log(` ${f.description}`);
|
||||
console.log(` _"${f.location}"_\n`);
|
||||
});
|
||||
const latSec = (result.latencyMs / 1000).toFixed(1);
|
||||
console.log(`_[${result.provider === "ollama" ? "Ollama" : "OpenRouter"}: ${result.model} · ${latSec}s]_`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(jsonOutput ? JSON.stringify(result.map, null, 2) : formatArgumentMap(result));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Fehler:", err instanceof Error ? err.message : err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
if (process.argv[1] === __filename) runCli();
|
||||
Loading…
Add table
Add a link
Reference in a new issue