Compare commits
16 Commits
05d957324a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdee77f459 | ||
| 8a6d9d3324 | |||
|
|
dc9f52fb7c | ||
|
|
b17458d153 | ||
|
|
b14ca1d243 | ||
|
|
4ee1cc4984 | ||
|
|
bbe1b12251 | ||
|
|
d569b74a20 | ||
|
|
a2d3d3170a | ||
|
|
3823407d49 | ||
|
|
496f2a5b43 | ||
|
|
20a4c71d02 | ||
|
|
dad5e5ea0c | ||
|
|
b689f24502 | ||
|
|
630fc1643e | ||
|
|
1d750307c4 |
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"_variables": {
|
"_variables": {
|
||||||
"lastUpdateCheck": 1754571688630
|
"lastUpdateCheck": 1755901660216
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
13
.env.example
13
.env.example
@@ -59,8 +59,7 @@ FORENSIC_AUDIT_RETENTION_HOURS=24
|
|||||||
FORENSIC_AUDIT_MAX_ENTRIES=50
|
FORENSIC_AUDIT_MAX_ENTRIES=50
|
||||||
|
|
||||||
# === AI SEMANTIC SEARCH ===
|
# === AI SEMANTIC SEARCH ===
|
||||||
# Enable semantic search (highly recommended for better results)
|
# semantic search
|
||||||
REMOVE_AI_EMBEDDINGS_ENABLED=true
|
|
||||||
AI_EMBEDDINGS_ENDPOINT=https://api.mistral.ai/v1/embeddings
|
AI_EMBEDDINGS_ENDPOINT=https://api.mistral.ai/v1/embeddings
|
||||||
AI_EMBEDDINGS_API_KEY=your-embeddings-api-key-here
|
AI_EMBEDDINGS_API_KEY=your-embeddings-api-key-here
|
||||||
AI_EMBEDDINGS_MODEL=mistral-embed
|
AI_EMBEDDINGS_MODEL=mistral-embed
|
||||||
@@ -101,17 +100,11 @@ AI_SOFTWARE_SELECTION_RATIO=0.5 # 50% software tools (increase for more tool re
|
|||||||
|
|
||||||
# AI selection limits
|
# AI selection limits
|
||||||
AI_MAX_SELECTED_ITEMS=25
|
AI_MAX_SELECTED_ITEMS=25
|
||||||
AI_MAX_TOOLS_TO_ANALYZE=20
|
|
||||||
AI_MAX_CONCEPTS_TO_ANALYZE=10
|
|
||||||
|
|
||||||
# Efficiency thresholds
|
# Efficiency thresholds
|
||||||
AI_EMBEDDINGS_MIN_TOOLS=8
|
AI_EMBEDDINGS_MIN_TOOLS=8
|
||||||
AI_EMBEDDINGS_MAX_REDUCTION_RATIO=0.75
|
AI_EMBEDDINGS_MAX_REDUCTION_RATIO=0.75
|
||||||
|
|
||||||
# Fallback limits when embeddings are disabled
|
|
||||||
AI_NO_EMBEDDINGS_TOOL_LIMIT=25
|
|
||||||
AI_NO_EMBEDDINGS_CONCEPT_LIMIT=10
|
|
||||||
|
|
||||||
# === Rate Limiting & Timing ===
|
# === Rate Limiting & Timing ===
|
||||||
AI_MICRO_TASK_TOTAL_LIMIT=30
|
AI_MICRO_TASK_TOTAL_LIMIT=30
|
||||||
AI_MICRO_TASK_DELAY_MS=500
|
AI_MICRO_TASK_DELAY_MS=500
|
||||||
@@ -121,10 +114,6 @@ AI_RATE_LIMIT_DELAY_MS=2000
|
|||||||
AI_EMBEDDINGS_BATCH_SIZE=10
|
AI_EMBEDDINGS_BATCH_SIZE=10
|
||||||
AI_EMBEDDINGS_BATCH_DELAY_MS=1000
|
AI_EMBEDDINGS_BATCH_DELAY_MS=1000
|
||||||
|
|
||||||
# === Context Management ===
|
|
||||||
REMOVE_AI_MAX_CONTEXT_TOKENS=4000
|
|
||||||
REMOVE_AI_MAX_PROMPT_TOKENS=2500
|
|
||||||
|
|
||||||
# === Confidence Scoring ===
|
# === Confidence Scoring ===
|
||||||
CONFIDENCE_SEMANTIC_WEIGHT=0.5
|
CONFIDENCE_SEMANTIC_WEIGHT=0.5
|
||||||
CONFIDENCE_SUITABILITY_WEIGHT=0.5
|
CONFIDENCE_SUITABILITY_WEIGHT=0.5
|
||||||
|
|||||||
374467
data/embeddings.json
374467
data/embeddings.json
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -10,15 +10,14 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/node": "^9.3.0",
|
"@astrojs/node": "^9.4.3",
|
||||||
"@aws-sdk/client-s3": "^3.864.0",
|
"astro": "^5.13.7",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.864.0",
|
|
||||||
"astro": "^5.12.3",
|
|
||||||
"cookie": "^1.0.2",
|
"cookie": "^1.0.2",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.6.1",
|
||||||
"jose": "^5.2.0",
|
"jose": "^5.10.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"simple-boost": "^2.0.2",
|
||||||
"zod": "^3.25.76"
|
"zod": "^3.25.76"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1130,16 +1130,12 @@ class AIQueryInterface {
|
|||||||
const lowConfidenceSteps = auditTrail.filter(entry => (entry.confidence || 0) < 60).length;
|
const lowConfidenceSteps = auditTrail.filter(entry => (entry.confidence || 0) < 60).length;
|
||||||
const mediumConfidenceSteps = auditTrail.length - highConfidenceSteps - lowConfidenceSteps;
|
const mediumConfidenceSteps = auditTrail.length - highConfidenceSteps - lowConfidenceSteps;
|
||||||
|
|
||||||
// FIX 1: Count actual AI decision actions only
|
|
||||||
const aiDecisionCount = auditTrail.filter(entry => entry.action === 'ai-decision').length;
|
const aiDecisionCount = auditTrail.filter(entry => entry.action === 'ai-decision').length;
|
||||||
|
|
||||||
// FIX 2: Count actual similarity search actions, not metadata flags
|
|
||||||
const embeddingsUsageCount = auditTrail.filter(entry => entry.action === 'similarity-search').length;
|
const embeddingsUsageCount = auditTrail.filter(entry => entry.action === 'similarity-search').length;
|
||||||
|
|
||||||
// FIX 3: Maintain tool selection count (this was correct)
|
|
||||||
const toolSelectionCount = auditTrail.filter(entry => entry.action === 'selection-decision').length;
|
const toolSelectionCount = auditTrail.filter(entry => entry.action === 'selection-decision').length;
|
||||||
|
|
||||||
// Additional diagnostic counts for debugging
|
|
||||||
const microTaskCount = auditTrail.filter(entry =>
|
const microTaskCount = auditTrail.filter(entry =>
|
||||||
entry.action === 'ai-decision' && entry.metadata?.microTaskType
|
entry.action === 'ai-decision' && entry.metadata?.microTaskType
|
||||||
).length;
|
).length;
|
||||||
@@ -1152,7 +1148,6 @@ class AIQueryInterface {
|
|||||||
entry.action === 'phase-enhancement'
|
entry.action === 'phase-enhancement'
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
// Enhanced insights with diagnostic information
|
|
||||||
const keyInsights = [];
|
const keyInsights = [];
|
||||||
const potentialIssues = [];
|
const potentialIssues = [];
|
||||||
|
|
||||||
@@ -1172,7 +1167,6 @@ class AIQueryInterface {
|
|||||||
keyInsights.push(`${microTaskCount} spezialisierte Micro-Task-Analysen durchgeführt`);
|
keyInsights.push(`${microTaskCount} spezialisierte Micro-Task-Analysen durchgeführt`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect mode-specific patterns for validation
|
|
||||||
if (phaseToolSelectionCount > 0 || phaseEnhancementCount > 0) {
|
if (phaseToolSelectionCount > 0 || phaseEnhancementCount > 0) {
|
||||||
keyInsights.push('Workflow-Modus: Phasenspezifische Analyse durchgeführt');
|
keyInsights.push('Workflow-Modus: Phasenspezifische Analyse durchgeführt');
|
||||||
} else if (microTaskCount >= 3) {
|
} else if (microTaskCount >= 3) {
|
||||||
@@ -1216,10 +1210,9 @@ class AIQueryInterface {
|
|||||||
keyInsights.push('Mehrheit der Analyseschritte mit hoher Sicherheit');
|
keyInsights.push('Mehrheit der Analyseschritte mit hoher Sicherheit');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate expected counts based on mode detection
|
|
||||||
const isWorkflowMode = phaseToolSelectionCount > 0 || phaseEnhancementCount > 0;
|
const isWorkflowMode = phaseToolSelectionCount > 0 || phaseEnhancementCount > 0;
|
||||||
const expectedMinAI = isWorkflowMode ? 11 : 8; // Workflow: 5 common + 6 phase selections, Tool: 5 common + 3 evaluations
|
const expectedMinAI = isWorkflowMode ? 11 : 8;
|
||||||
const expectedMinEmbeddings = 1; // Both modes should have initial search
|
const expectedMinEmbeddings = 1;
|
||||||
|
|
||||||
if (aiDecisionCount < expectedMinAI) {
|
if (aiDecisionCount < expectedMinAI) {
|
||||||
potentialIssues.push(`${expectedMinAI - aiDecisionCount} fehlende KI-Entscheidungen für ${isWorkflowMode ? 'Workflow' : 'Tool'}-Modus`);
|
potentialIssues.push(`${expectedMinAI - aiDecisionCount} fehlende KI-Entscheidungen für ${isWorkflowMode ? 'Workflow' : 'Tool'}-Modus`);
|
||||||
@@ -1250,7 +1243,6 @@ class AIQueryInterface {
|
|||||||
analysisQuality,
|
analysisQuality,
|
||||||
keyInsights,
|
keyInsights,
|
||||||
potentialIssues,
|
potentialIssues,
|
||||||
// Debug information
|
|
||||||
debugCounts: {
|
debugCounts: {
|
||||||
microTaskCount,
|
microTaskCount,
|
||||||
phaseToolSelectionCount,
|
phaseToolSelectionCount,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
|
|
||||||
<!-- Semantic Search Toggle - Inline -->
|
<!-- Semantic Search Toggle - Inline -->
|
||||||
<div id="semantic-search-container" class="semantic-search-inline hidden">
|
<div id="semantic-search-container" class="semantic-search-inline hidden">
|
||||||
<label class="semantic-toggle-wrapper" title="Semantische Suche verwendet Embeddings. Dadurch kann mit natürlicher Sprache/Begriffen gesucht werden, die Ergebnisse richten sich nach der euklidischen Distanz.">
|
<label class="semantic-toggle-wrapper" title="Semantische Suche verwendet Embeddings. Dadurch kann mit natürlicher Sprache/Begriffen gesucht werden, die Ergebnisse richten sich nach der cosinus-Distanz.">
|
||||||
<input type="checkbox" id="semantic-search-enabled" disabled/>
|
<input type="checkbox" id="semantic-search-enabled" disabled/>
|
||||||
<div class="semantic-checkbox-custom"></div>
|
<div class="semantic-checkbox-custom"></div>
|
||||||
<span class="semantic-toggle-label">
|
<span class="semantic-toggle-label">
|
||||||
@@ -358,7 +358,7 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
try {
|
try {
|
||||||
const res = await fetch('/api/ai/embeddings-status');
|
const res = await fetch('/api/ai/embeddings-status');
|
||||||
const { embeddings } = await res.json();
|
const { embeddings } = await res.json();
|
||||||
semanticSearchAvailable = embeddings?.enabled && embeddings?.initialized;
|
semanticSearchAvailable = embeddings?.initialized;
|
||||||
|
|
||||||
if (semanticSearchAvailable) {
|
if (semanticSearchAvailable) {
|
||||||
elements.semanticContainer.classList.remove('hidden');
|
elements.semanticContainer.classList.remove('hidden');
|
||||||
|
|||||||
@@ -1,218 +1,263 @@
|
|||||||
// src/config/prompts.ts
|
// src/config/prompts.ts
|
||||||
|
|
||||||
|
const RELEVANCE_RUBRIC = `
|
||||||
|
TASK RELEVANCE (INTEGER 0–100, NO %):
|
||||||
|
- 55–65 = Basis/ok
|
||||||
|
- 66–75 = Gut geeignet
|
||||||
|
- 76–85 = Sehr gut geeignet
|
||||||
|
- >85 = Nur bei nahezu perfekter Übereinstimmung
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
const STRICTNESS = `
|
||||||
|
STRICTNESS:
|
||||||
|
- Output MUST be pure JSON (no prose, no code fences, no trailing commas).
|
||||||
|
- Use EXACT item names as provided (casing/spelling must match).
|
||||||
|
- Do NOT invent items or fields. If unsure, select fewer.
|
||||||
|
`.trim();
|
||||||
|
|
||||||
export const AI_PROMPTS = {
|
export const AI_PROMPTS = {
|
||||||
|
enhancementQuestions: (input: string) => {
|
||||||
|
return `Sie sind DFIR-Experte. Ein Nutzer beschreibt unten ein Szenario/Problem.
|
||||||
|
|
||||||
|
ZIEL:
|
||||||
|
- Stellen Sie NUR dann 1–3 präzise Rückfragen, wenn entscheidende forensische Lücken die weitere Analyse/Toolauswahl PHASENREIHENFOLGE oder EVIDENCE-STRATEGIE wesentlich beeinflussen würden.
|
||||||
|
- Wenn ausreichend abgedeckt: Geben Sie eine leere Liste [] zurück.
|
||||||
|
|
||||||
|
PRIORITÄT DER THEMEN (in dieser Reihenfolge prüfen):
|
||||||
|
1) Available Evidence & Artefakte (z.B. RAM-Dump, Disk-Image, Logs, PCAP, Registry, Cloud/Audit-Logs)
|
||||||
|
2) Scope/Systems (konkrete Plattformen/Assets/Identitäten/Netzsegmente)
|
||||||
|
3) Investigation Objectives (Ziele: IOC-Extraktion, Timeline, Impact, Attribution)
|
||||||
|
4) Timeline/Timeframe (kritische Zeitfenster, Erhalt flüchtiger Daten)
|
||||||
|
5) Legal & Compliance (Chain of Custody, Aufbewahrung, DSGVO/Branchenvorgaben)
|
||||||
|
6) Technical Constraints (Ressourcen, Zugriffsrechte, Tooling/EDR)
|
||||||
|
|
||||||
|
FRAGEN-QUALITÄT:
|
||||||
|
- Forensisch spezifisch und entscheidungsrelevant (keine Allgemeinplätze).
|
||||||
|
- Eine Frage pro Thema, keine Dopplungen.
|
||||||
|
- Antwortbar vom Nutzer (keine Spekulation, keine “Beweise senden”-Aufforderungen).
|
||||||
|
- Maximal 18 Wörter, endet mit "?".
|
||||||
|
|
||||||
|
VALIDIERUNG:
|
||||||
|
- Stellen Sie NUR Fragen zu Themen, die im Nutzertext NICHT hinreichend konkret beantwortet sind (keine Wiederholung bereits gegebener Details).
|
||||||
|
- Wenn alle priorisierten Themen ausreichend sind → [].
|
||||||
|
|
||||||
|
ANTWORTFORMAT (NUR JSON, KEIN ZUSÄTZLICHER TEXT):
|
||||||
|
[
|
||||||
|
"präzise Frage 1?",
|
||||||
|
"präzise Frage 2?",
|
||||||
|
"präzise Frage 3?"
|
||||||
|
]
|
||||||
|
|
||||||
|
NUTZER-EINGABE:
|
||||||
|
${input}`.trim();
|
||||||
|
},
|
||||||
toolSelection: (mode: string, userQuery: string, maxSelectedItems: number) => {
|
toolSelection: (mode: string, userQuery: string, maxSelectedItems: number) => {
|
||||||
const modeInstruction = mode === 'workflow'
|
const modeInstruction =
|
||||||
? 'Workflow mit 15-25 Items über alle Phasen. PFLICHT: Mindestens 40% Methoden, Rest Tools/Konzepte.'
|
mode === 'workflow'
|
||||||
: 'Spezifische Lösung mit 4-10 Items. PFLICHT: Mindestens 30% Methoden wenn verfügbar.';
|
? 'Workflow mit 15–25 Items über alle Phasen. Pflicht: ~40% Methoden, Rest Software/Konzepte (falls verfügbar).'
|
||||||
|
: 'Spezifische Lösung mit 4–10 Items. Pflicht: ≥30% Methoden (falls verfügbar).';
|
||||||
|
|
||||||
return `Du bist ein DFIR-Experte. Wähle die BESTEN Items aus dem vorgefilterten Set.
|
return `Du bist DFIR-Experte. Wähle die BESTEN Items aus dem bereits semantisch vorgefilterten Set für die konkrete Aufgabe.
|
||||||
|
|
||||||
AUSWAHLMETHODE:
|
|
||||||
'✓ Semantisch relevante Items bereits vorgefiltert\n✓ Wähle die BESTEN für die konkrete Aufgabe'}
|
|
||||||
|
|
||||||
${modeInstruction}
|
${modeInstruction}
|
||||||
|
|
||||||
ANFRAGE: "${userQuery}"
|
ANFRAGE: "${userQuery}"
|
||||||
|
|
||||||
VERFÜGBARE ITEM-TYPEN:
|
ITEM-TYPEN:
|
||||||
- TOOLS (type: "software"/"method") → praktische Anwendungen und Vorgehensweisen
|
- TOOLS (type: "software" | "method")
|
||||||
- KONZEPTE (type: "concept") → theoretisches Wissen und Methodiken
|
- KONZEPTE (type: "concept")
|
||||||
|
|
||||||
AUSWAHLSTRATEGIE:
|
AUSWAHLPRINZIPIEN:
|
||||||
1. **ERSTE PRIORITÄT: Relevanz zur Anfrage**
|
1) Relevanz zur Anfrage (direkt anwendbar, adressiert Kernproblem)
|
||||||
- Direkt anwendbar auf das Problem
|
2) Ausgewogene Mischung (Praxis: selectedTools; Methodik: selectedConcepts)
|
||||||
- Löst die Kernherausforderung
|
3) Qualität > Quantität (lieber weniger, dafür passgenau)
|
||||||
|
4) Keine Erfindungen. Wenn etwas nicht passt, wähle weniger.
|
||||||
2. **ZWEITE PRIORITÄT: Ausgewogene Mischung**
|
|
||||||
- Tools/Methoden für praktische Umsetzung → selectedTools
|
|
||||||
- Konzepte für methodisches Verständnis → selectedConcepts
|
|
||||||
- WICHTIG: Auch Konzepte auswählen, nicht nur Tools!
|
|
||||||
|
|
||||||
3. **QUALITÄT > QUANTITÄT**
|
|
||||||
- Lieber weniger perfekte Items als viele mittelmäßige
|
|
||||||
- Jedes Item muss begründbar sein
|
|
||||||
|
|
||||||
4. **TASK RELEVANCE REALISM**
|
|
||||||
- Gib realistische Bewertungen (50-85% typisch)
|
|
||||||
- Vermeide übertriebene 90-100% Scores
|
|
||||||
- Nur bei perfekter Übereinstimmung >85%
|
|
||||||
|
|
||||||
AUSWAHLREGELN:
|
AUSWAHLREGELN:
|
||||||
- Wähle ${mode === 'workflow' ? '15-25' : '4-10'} Items total, max ${maxSelectedItems}
|
- Wähle ${mode === 'workflow' ? '15–25' : '4–10'} Items total (max ${maxSelectedItems})
|
||||||
- BEIDE Arrays füllen: selectedTools UND selectedConcepts
|
- Fülle BEIDE Arrays: selectedTools UND selectedConcepts
|
||||||
- Mindestens 1-2 Konzepte auswählen für methodische Fundierung
|
- Mindestens 1–2 Konzepte (falls verfügbar)
|
||||||
- Tools: 40% Methoden (type="method"), Rest Software (type="software")
|
- Bevorzugt ~40% Methoden (Workflow) bzw. ≥30% Methoden (Tool-Modus), sofern vorhanden
|
||||||
|
- Sortiere selectedTools grob nach Eignung (bestes zuerst)
|
||||||
|
|
||||||
ANTWORT AUSSCHLIESSLICH IM JSON-FORMAT:
|
Skalenhinweis (für spätere Schritte – einheitlich):
|
||||||
|
${RELEVANCE_RUBRIC}
|
||||||
|
|
||||||
|
${STRICTNESS}
|
||||||
|
|
||||||
|
ANTWORT (NUR JSON):
|
||||||
{
|
{
|
||||||
"selectedTools": ["ToolName1", "MethodName1", ...],
|
"selectedTools": ["ToolName1", "MethodName1", "..."],
|
||||||
"selectedConcepts": ["ConceptName1", "ConceptName2", ...],
|
"selectedConcepts": ["ConceptName1", "ConceptName2", "..."],
|
||||||
"reasoning": "Kurze Begründung mit Erwähnung der Tool/Konzept-Balance"
|
"reasoning": "Sehr kurz: Balance/Abdeckung begründen"
|
||||||
}`;
|
}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
toolSelectionWithData: (basePrompt: string, toolsToSend: any[], conceptsToSend: any[]) => {
|
toolSelectionWithData: (basePrompt: string, toolsToSend: any[], conceptsToSend: any[]) => {
|
||||||
return `${basePrompt}
|
return `${basePrompt}
|
||||||
|
|
||||||
VERFÜGBARE TOOLS (${toolsToSend.length} Items - Methoden und Software):
|
VERFÜGBARE TOOLS (${toolsToSend.length}):
|
||||||
${JSON.stringify(toolsToSend, null, 2)}
|
${JSON.stringify(toolsToSend, null, 2)}
|
||||||
|
|
||||||
VERFÜGBARE KONZEPTE (${conceptsToSend.length} Items - theoretisches Wissen):
|
VERFÜGBARE KONZEPTE (${conceptsToSend.length}):
|
||||||
${JSON.stringify(conceptsToSend, null, 2)}
|
${JSON.stringify(conceptsToSend, null, 2)}
|
||||||
|
|
||||||
WICHTIGER HINWEIS: Wähle sowohl aus TOOLS als auch aus KONZEPTEN aus! Konzepte sind essentiell für methodische Fundierung.
|
WICHTIG:
|
||||||
|
- Wähle nur aus obigen Listen. Keine neuen Namen.
|
||||||
|
- Nutze exakte Namen. Keine Synonyme/Varianten.
|
||||||
|
|
||||||
TASK RELEVANCE GUIDELINES:
|
Hinweis zur einheitlichen Relevanz-Skala:
|
||||||
- 50-65%: Grundlegend relevant, aber nicht optimal
|
${RELEVANCE_RUBRIC}
|
||||||
- 66-75%: Gut geeignet für die Aufgabe
|
|
||||||
- 76-85%: Sehr gut geeignet, klare Vorteile
|
${STRICTNESS}`;
|
||||||
- 86-100%: NUR für perfekte Übereinstimmung verwenden`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
scenarioAnalysis: (isWorkflow: boolean, userQuery: string) => {
|
scenarioAnalysis: (isWorkflow: boolean, userQuery: string) => {
|
||||||
const analysisType = isWorkflow ? 'Szenario' : 'Problem';
|
const analysisType = isWorkflow ? 'Szenario' : 'Problem';
|
||||||
const focus = isWorkflow ?
|
const focus = isWorkflow
|
||||||
'Angriffsvektoren, betroffene Systeme, Zeitkritikalität' :
|
? 'Angriffsvektoren, betroffene Systeme, Zeitkritikalität'
|
||||||
'Kernherausforderung, verfügbare Daten, methodische Anforderungen';
|
: 'Kernherausforderung, verfügbare Daten, methodische Anforderungen';
|
||||||
|
|
||||||
return `DFIR-Experte: Analysiere das ${analysisType}.
|
return `DFIR-Experte: Analysiere das ${analysisType}.
|
||||||
|
|
||||||
${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}"
|
${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}"
|
||||||
|
|
||||||
Fokus: ${focus}
|
Fokus: ${focus}
|
||||||
|
|
||||||
Antwort: Fließtext ohne Listen, max 100 Wörter.`;
|
Antwort: Fließtext, max 100 Wörter. Keine Liste, keine Einleitung.`;
|
||||||
},
|
},
|
||||||
|
|
||||||
investigationApproach: (isWorkflow: boolean, userQuery: string) => {
|
investigationApproach: (isWorkflow: boolean, userQuery: string) => {
|
||||||
const approachType = isWorkflow ? 'Untersuchungsansatz' : 'Lösungsansatz';
|
const approachType = isWorkflow ? 'Untersuchungsansatz' : 'Lösungsansatz';
|
||||||
const focus = isWorkflow ?
|
const focus = isWorkflow
|
||||||
'Triage-Prioritäten, Phasenabfolge, Kontaminationsvermeidung' :
|
? 'Triage-Prioritäten, Phasenabfolge, Kontaminationsvermeidung'
|
||||||
'Methodenauswahl, Validierung, Integration';
|
: 'Methodenauswahl, Validierung, Integration';
|
||||||
|
|
||||||
return `Entwickle einen ${approachType}.
|
return `Entwickle einen ${approachType}.
|
||||||
|
|
||||||
${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}"
|
${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}"
|
||||||
|
|
||||||
Fokus: ${focus}
|
Fokus: ${focus}
|
||||||
|
|
||||||
Antwort: Fließtext ohne Listen, max 100 Wörter.`;
|
Antwort: Fließtext, max 100 Wörter.`;
|
||||||
},
|
},
|
||||||
|
|
||||||
criticalConsiderations: (isWorkflow: boolean, userQuery: string) => {
|
criticalConsiderations: (isWorkflow: boolean, userQuery: string) => {
|
||||||
const focus = isWorkflow ?
|
const focus = isWorkflow
|
||||||
'Beweissicherung vs. Gründlichkeit, Chain of Custody' :
|
? 'Beweissicherung vs. Gründlichkeit, Chain of Custody'
|
||||||
'Tool-Validierung, False Positives/Negatives, Qualifikationen';
|
: 'Tool-Validierung, False Positives/Negatives, Qualifikationen';
|
||||||
|
|
||||||
return `Identifiziere kritische Überlegungen.
|
return `Identifiziere kritische Überlegungen.
|
||||||
|
|
||||||
${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}"
|
${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}"
|
||||||
|
|
||||||
Fokus: ${focus}
|
Fokus: ${focus}
|
||||||
|
|
||||||
Antwort: Fließtext ohne Listen, max 100 Wörter.`;
|
Antwort: Fließtext, max 100 Wörter.`;
|
||||||
},
|
},
|
||||||
|
|
||||||
phaseToolSelection: (userQuery: string, phase: any, phaseTools: any[]) => {
|
phaseToolSelection: (userQuery: string, phase: any, phaseTools: any[]) => {
|
||||||
const methods = phaseTools.filter(t => t.type === 'method');
|
const methods = phaseTools.filter(t => t.type === 'method');
|
||||||
const tools = phaseTools.filter(t => t.type === 'software');
|
const tools = phaseTools.filter(t => t.type === 'software');
|
||||||
|
|
||||||
if (phaseTools.length === 0) {
|
if (phaseTools.length === 0) {
|
||||||
return `Keine Methoden/Tools für Phase "${phase.name}" verfügbar. Antworte mit leerem Array: []`;
|
return `Keine Methoden/Tools für Phase "${phase.name}" verfügbar. Antworte mit leerem Array: []`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `Du bist ein DFIR-Experte. Wähle die 2-3 BESTEN Items für Phase "${phase.name}".
|
return `Wähle die 2–3 BESTEN Items für Phase "${phase.name}".
|
||||||
|
|
||||||
SZENARIO: "${userQuery}"
|
SZENARIO: "${userQuery}"
|
||||||
PHASE: ${phase.name} - ${phase.description || ''}
|
PHASE: ${phase.name} — ${phase.description || ''}
|
||||||
|
|
||||||
VERFÜGBARE ITEMS (bereits von KI vorausgewählt):
|
VERFÜGBARE ITEMS:
|
||||||
${methods.length > 0 ? `
|
${methods.length > 0 ? `
|
||||||
METHODEN (${methods.length}):
|
METHODEN (${methods.length}):
|
||||||
${methods.map((method: any) =>
|
${methods.map((m: any) =>
|
||||||
`- ${method.name}
|
`- ${m.name}
|
||||||
Typ: ${method.type}
|
Typ: ${m.type}
|
||||||
Beschreibung: ${method.description}
|
Beschreibung: ${m.description}
|
||||||
Domains: ${method.domains?.join(', ') || 'N/A'}
|
Domains: ${m.domains?.join(', ') || 'N/A'}
|
||||||
Skill Level: ${method.skillLevel}`
|
Skill Level: ${m.skillLevel}`
|
||||||
).join('\n\n')}
|
).join('\n\n')}
|
||||||
` : 'Keine Methoden verfügbar'}
|
` : 'Keine Methoden verfügbar'}
|
||||||
|
|
||||||
${tools.length > 0 ? `
|
${tools.length > 0 ? `
|
||||||
SOFTWARE TOOLS (${tools.length}):
|
SOFTWARE (${tools.length}):
|
||||||
${tools.map((tool: any) =>
|
${tools.map((t: any) =>
|
||||||
`- ${tool.name}
|
`- ${t.name}
|
||||||
Typ: ${tool.type}
|
Typ: ${t.type}
|
||||||
Beschreibung: ${tool.description}
|
Beschreibung: ${t.description}
|
||||||
Plattformen: ${tool.platforms?.join(', ') || 'N/A'}
|
Plattformen: ${t.platforms?.join(', ') || 'N/A'}
|
||||||
Skill Level: ${tool.skillLevel}`
|
Skill Level: ${t.skillLevel}`
|
||||||
).join('\n\n')}
|
).join('\n\n')}
|
||||||
` : 'Keine Software-Tools verfügbar'}
|
` : 'Keine Software-Tools verfügbar'}
|
||||||
|
|
||||||
AUSWAHLREGELN FÜR PHASE "${phase.name}":
|
REGELN:
|
||||||
1. Wähle die 2-3 BESTEN Items für diese spezifische Phase
|
1) 2–3 Items, direkt phasenrelevant; mind. 1 Methode, falls verfügbar
|
||||||
2. Priorisiere Items, die DIREKT für "${phase.name}" relevant sind
|
2) Begründung pro Item (präzise, anwendungsbezogen)
|
||||||
3. Mindestens 1 Methode wenn verfügbar, Rest Software-Tools
|
3) Verwende EXAKTE Namen aus den Listen. Keine Erfindungen.
|
||||||
4. Begründe WARUM jedes Item für diese Phase optimal ist
|
|
||||||
|
|
||||||
TASK RELEVANCE GUIDELINES:
|
${RELEVANCE_RUBRIC}
|
||||||
- 60-70%: Grundlegend für diese Phase geeignet
|
|
||||||
- 71-80%: Gut geeignet, klare Phasenrelevanz
|
|
||||||
- 81-90%: Sehr gut geeignet, optimal für Phase
|
|
||||||
- 91-100%: NUR für perfekte Phasenübereinstimmung
|
|
||||||
|
|
||||||
WICHTIG: Verwende EXAKT die Namen wie oben aufgelistet (ohne Präfixe wie M1./T2.)!
|
${STRICTNESS}
|
||||||
|
|
||||||
ANTWORT AUSSCHLIESSLICH IM JSON-FORMAT OHNE JEGLICHEN TEXT AUSSERHALB:
|
ANTWORT (NUR JSON):
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"toolName": "Exakter Name aus der Liste oben",
|
"toolName": "Exakter Name",
|
||||||
"taskRelevance": 75,
|
"taskRelevance": 0,
|
||||||
"justification": "Detaillierte Begründung (60-80 Wörter) warum optimal für ${phase.name} - erkläre Anwendung, Vorteile und spezifische Relevanz",
|
"justification": "60–80 Wörter zur phasenspezifischen Eignung",
|
||||||
"limitations": ["Mögliche Einschränkung für diese Phase"]
|
"limitations": ["Optionale spezifische Einschränkung"]
|
||||||
}
|
}
|
||||||
]`;
|
]`;
|
||||||
},
|
},
|
||||||
|
|
||||||
toolEvaluation: (userQuery: string, tool: any, rank: number, taskRelevance: number) => {
|
toolEvaluation: (userQuery: string, tool: any, rank: number) => {
|
||||||
const itemType = tool.type === 'method' ? 'Methode' : 'Tool';
|
const itemType = tool.type === 'method' ? 'Methode' : 'Tool';
|
||||||
|
|
||||||
return `Erkläre die Anwendung dieser/dieses ${itemType}.
|
return `Bewerte diese/diesen ${itemType} ausschließlich bzgl. des PROBLEMS.
|
||||||
|
|
||||||
PROBLEM: "${userQuery}"
|
PROBLEM: "${userQuery}"
|
||||||
${itemType.toUpperCase()}: ${tool.name} (${taskRelevance}% Eignung)
|
${itemType.toUpperCase()}: ${tool.name}
|
||||||
TYP: ${tool.type}
|
TYP: ${tool.type}
|
||||||
|
|
||||||
Bereits als Rang ${rank} bewertet.
|
ANWEISUNGEN:
|
||||||
|
- Nur vorhandene Metadaten nutzen (keine Annahmen, keine Websuche).
|
||||||
|
- "taskRelevance" als GANZZAHL 0–100 nach einheitlicher Skala vergeben.
|
||||||
|
- Realistische Scores i.d.R. 60–80, >85 nur bei nahezu perfektem Fit.
|
||||||
|
- Keine Texte außerhalb des JSON.
|
||||||
|
|
||||||
ANTWORT AUSSCHLIESSLICH IM JSON-FORMAT OHNE JEGLICHEN TEXT AUSSERHALB DER JSON-STRUKTUR:
|
${RELEVANCE_RUBRIC}
|
||||||
|
${STRICTNESS}
|
||||||
|
|
||||||
|
ANTWORT (NUR JSON):
|
||||||
{
|
{
|
||||||
"detailed_explanation": "Warum und wie einsetzen",
|
"detailed_explanation": "Warum und wie einsetzen",
|
||||||
"implementation_approach": "Konkrete Schritte",
|
"implementation_approach": "Konkrete Schritte",
|
||||||
"pros": ["Vorteil 1", "Vorteil 2"],
|
"pros": ["Vorteil 1", "Vorteil 2"],
|
||||||
"limitations": ["Einschränkung 1"],
|
"limitations": ["Einschränkung 1"],
|
||||||
"alternatives": "Alternative Ansätze"
|
"alternatives": "Kurz zu sinnvollen Alternativen",
|
||||||
|
"taskRelevance": 0
|
||||||
}`;
|
}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
backgroundKnowledgeSelection: (userQuery: string, mode: string, selectedToolNames: string[], availableConcepts: any[]) => {
|
backgroundKnowledgeSelection: (userQuery: string, mode: string, selectedToolNames: string[], availableConcepts: any[]) => {
|
||||||
return `Wähle 2-4 relevante Konzepte.
|
return `Wähle 2–4 Konzepte, die das Verständnis/den Einsatz der ausgewählten Tools verbessern.
|
||||||
|
|
||||||
${mode === 'workflow' ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}"
|
${mode === 'workflow' ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}"
|
||||||
AUSGEWÄHLTE TOOLS: ${selectedToolNames.join(', ')}
|
AUSGEWÄHLTE TOOLS: ${selectedToolNames.join(', ')}
|
||||||
|
|
||||||
VERFÜGBARE KONZEPTE (${availableConcepts.length} KI-kuratiert):
|
VERFÜGBARE KONZEPTE (${availableConcepts.length}):
|
||||||
${availableConcepts.map((c: any) =>
|
${availableConcepts.map((c: any) => `- ${c.name}: ${c.description}...`).join('\n')}
|
||||||
`- ${c.name}: ${c.description}...`
|
|
||||||
).join('\n')}
|
|
||||||
|
|
||||||
ANTWORT AUSSCHLIESSLICH IM JSON-FORMAT OHNE JEGLICHEN TEXT AUSSERHALB DER JSON-STRUKTUR:
|
REGELN:
|
||||||
|
- Nur Konzepte aus obiger Liste wählen.
|
||||||
|
- Relevanz kurz und konkret begründen.
|
||||||
|
|
||||||
|
${STRICTNESS}
|
||||||
|
|
||||||
|
ANTWORT (NUR JSON):
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"conceptName": "Name",
|
"conceptName": "Exakter Name",
|
||||||
"relevance": "Warum kritisch für Methodik"
|
"relevance": "Warum dieses Konzept hier methodisch wichtig ist"
|
||||||
}
|
}
|
||||||
]`;
|
]`;
|
||||||
},
|
},
|
||||||
@@ -224,27 +269,14 @@ ANTWORT AUSSCHLIESSLICH IM JSON-FORMAT OHNE JEGLICHEN TEXT AUSSERHALB DER JSON-S
|
|||||||
tool: any,
|
tool: any,
|
||||||
completionContext: string
|
completionContext: string
|
||||||
) => {
|
) => {
|
||||||
return `Du bist ein DFIR-Experte. Erkläre warum dieses Tool nachträglich zur Vervollständigung hinzugefügt wurde.
|
return `Begründe knapp die Nachergänzung für Phase "${phase.name}".
|
||||||
|
|
||||||
KONTEXT DER NACHTRÄGLICHEN ERGÄNZUNG:
|
|
||||||
- Ursprüngliche KI-Auswahl war zu spezifisch/eng gefasst
|
|
||||||
- Phase "${phase.name}" war unterrepräsentiert in der initialen Auswahl
|
|
||||||
- Semantische Suche fand zusätzlich relevante Tools für diese Phase
|
|
||||||
- Tool wird nachträglich hinzugefügt um Vollständigkeit zu gewährleisten
|
|
||||||
|
|
||||||
URSPRÜNGLICHE ANFRAGE: "${originalQuery}"
|
URSPRÜNGLICHE ANFRAGE: "${originalQuery}"
|
||||||
PHASE ZU VERVOLLSTÄNDIGEN: ${phase.name} - ${phase.description || ''}
|
PHASE: ${phase.name} — ${phase.description || ''}
|
||||||
HINZUGEFÜGTES TOOL: ${selectedToolName} (${tool.type})
|
HINZUGEFÜGTES TOOL: ${selectedToolName} (${tool.type})
|
||||||
TOOL-BESCHREIBUNG: ${tool.description}
|
KONTEXT: ${completionContext}
|
||||||
|
|
||||||
BEGRÜNDUNGSKONTEXT: ${completionContext}
|
Antwort: Prägnanter Fließtext, max 40 Wörter, keine Einleitung, keine Liste.`;
|
||||||
|
|
||||||
Erstelle eine präzise Begründung (max. 40 Wörter), die erklärt:
|
|
||||||
1. WARUM dieses Tool nachträglich hinzugefügt wurde
|
|
||||||
2. WIE es die ${phase.name}-Phase ergänzt
|
|
||||||
3. DASS es die ursprünglich zu spezifische Auswahl erweitert
|
|
||||||
|
|
||||||
Antwort: Prägnanter Fließtext, knappe Begründung für Nachergänzung. Vermeide Begriffe wie "Das Tool" und gib keinen einleitenden Text wie "Begründung (40 Wörter):" an.`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
generatePhaseCompletionPrompt(
|
generatePhaseCompletionPrompt(
|
||||||
@@ -253,48 +285,48 @@ Antwort: Prägnanter Fließtext, knappe Begründung für Nachergänzung. Vermeid
|
|||||||
candidateTools: any[],
|
candidateTools: any[],
|
||||||
candidateConcepts: any[]
|
candidateConcepts: any[]
|
||||||
): string {
|
): string {
|
||||||
return `Du bist ein DFIR-Experte. Die initiale KI-Auswahl war zu spezifisch - die Phase "${phase.name}" ist unterrepräsentiert.
|
return `Unterrepräsentierte Phase: "${phase.name}". Ergänze 1–2 passende Items aus der semantischen Nachsuche.
|
||||||
|
|
||||||
KONTEXT: Die Hauptauswahl hat zu wenige Tools für "${phase.name}" identifiziert. Wähle jetzt ergänzende Tools aus semantischer Nachsuche.
|
ORIGINALANFRAGE: "${originalQuery}"
|
||||||
|
PHASE: ${phase.name} — ${phase.description || ''}
|
||||||
|
|
||||||
ORIGINAL ANFRAGE: "${originalQuery}"
|
KANDIDATEN — TOOLS (${candidateTools.length}):
|
||||||
UNTERREPRÄSENTIERTE PHASE: ${phase.name} - ${phase.description || ''}
|
${candidateTools.map((t: any) => `
|
||||||
|
- ${t.name} (${t.type})
|
||||||
SEMANTISCH GEFUNDENE KANDIDATEN für Nachergänzung:
|
Beschreibung: ${t.description}
|
||||||
|
Skill Level: ${t.skillLevel}
|
||||||
VERFÜGBARE TOOLS (${candidateTools.length}):
|
|
||||||
${candidateTools.map((tool: any) => `
|
|
||||||
- ${tool.name} (${tool.type})
|
|
||||||
Beschreibung: ${tool.description}
|
|
||||||
Skill Level: ${tool.skillLevel}
|
|
||||||
`).join('')}
|
`).join('')}
|
||||||
|
|
||||||
${candidateConcepts.length > 0 ? `
|
${candidateConcepts.length > 0 ? `
|
||||||
VERFÜGBARE KONZEPTE (${candidateConcepts.length}):
|
KANDIDATEN — KONZEPTE (${candidateConcepts.length}):
|
||||||
${candidateConcepts.map((concept: any) => `
|
${candidateConcepts.map((c: any) => `
|
||||||
- ${concept.name}
|
- ${c.name}
|
||||||
Beschreibung: ${concept.description}
|
Beschreibung: ${c.description}
|
||||||
`).join('')}
|
`).join('')}
|
||||||
` : ''}
|
` : ''}
|
||||||
|
|
||||||
AUSWAHLREGELN FÜR NACHERGÄNZUNG:
|
REGELN:
|
||||||
1. Wähle 1-2 BESTE Methoden/Tools die die ${phase.name}-Phase optimal ergänzen
|
- Wähle 1–2 Tools/Methoden, die ${phase.name} sinnvoll ergänzen (keine Ersetzung).
|
||||||
2. Methoden/Tools müssen für die ursprüngliche Anfrage relevant sein
|
- Nur aus obigen Kandidaten wählen; exakte Namen verwenden.
|
||||||
3. Ergänzen, nicht ersetzen - erweitere die zu spezifische Erstauswahl
|
- Kurze Begründung, warum diese Ergänzung nötig ist.
|
||||||
4. Realistische Task Relevance (70-85% typisch für Nachergänzungen)
|
|
||||||
|
|
||||||
ANTWORT AUSSCHLIESSLICH IM JSON-FORMAT:
|
Skalenhinweis (einheitlich):
|
||||||
|
${RELEVANCE_RUBRIC}
|
||||||
|
|
||||||
|
${STRICTNESS}
|
||||||
|
|
||||||
|
ANTWORT (NUR JSON):
|
||||||
{
|
{
|
||||||
"selectedTools": ["ToolName1", "ToolName2"],
|
"selectedTools": ["ToolName1", "ToolName2"],
|
||||||
"selectedConcepts": ["ConceptName1"],
|
"selectedConcepts": ["ConceptName1"],
|
||||||
"completionReasoning": "Kurze Erklärung warum diese Nachergänzung für ${phase.name} notwendig war"
|
"completionReasoning": "Kurze Erklärung zur Ergänzung der ${phase.name}-Phase"
|
||||||
}`;
|
}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
finalRecommendations: (isWorkflow: boolean, userQuery: string, selectedToolNames: string[]) => {
|
finalRecommendations: (isWorkflow: boolean, userQuery: string, selectedToolNames: string[]) => {
|
||||||
const focus = isWorkflow ?
|
const focus = isWorkflow
|
||||||
'Workflow-Schritte, Best Practices, Objektivität' :
|
? 'Knappe Workflow-Schritte & Best Practices; neutral formulieren'
|
||||||
'Methodische Überlegungen, Validierung, Qualitätssicherung';
|
: 'Methodische Überlegungen, Validierung, Qualitätssicherung';
|
||||||
|
|
||||||
return `Erstelle ${isWorkflow ? 'Workflow-Empfehlung' : 'methodische Überlegungen'}.
|
return `Erstelle ${isWorkflow ? 'Workflow-Empfehlung' : 'methodische Überlegungen'}.
|
||||||
|
|
||||||
@@ -302,33 +334,31 @@ ${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}"
|
|||||||
AUSGEWÄHLT: ${selectedToolNames.join(', ')}${selectedToolNames.length > 5 ? '...' : ''}
|
AUSGEWÄHLT: ${selectedToolNames.join(', ')}${selectedToolNames.length > 5 ? '...' : ''}
|
||||||
|
|
||||||
Fokus: ${focus}
|
Fokus: ${focus}
|
||||||
|
Antwort: Fließtext, max ${isWorkflow ? '100' : '80'} Wörter. Keine Liste.`;
|
||||||
Antwort: Fließtext ohne Listen, max ${isWorkflow ? '100' : '80'} Wörter.`;
|
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export function getPrompt(key: 'enhancementQuestions', input: string): string;
|
||||||
export function getPrompt(key: 'toolSelection', mode: string, userQuery: string, maxSelectedItems: number): string;
|
export function getPrompt(key: 'toolSelection', mode: string, userQuery: string, maxSelectedItems: number): string;
|
||||||
export function getPrompt(key: 'toolSelectionWithData', basePrompt: string, toolsToSend: any[], conceptsToSend: any[]): string;
|
export function getPrompt(key: 'toolSelectionWithData', basePrompt: string, toolsToSend: any[], conceptsToSend: any[]): string;
|
||||||
export function getPrompt(key: 'scenarioAnalysis', isWorkflow: boolean, userQuery: string): string;
|
export function getPrompt(key: 'scenarioAnalysis', isWorkflow: boolean, userQuery: string): string;
|
||||||
export function getPrompt(key: 'investigationApproach', isWorkflow: boolean, userQuery: string): string;
|
export function getPrompt(key: 'investigationApproach', isWorkflow: boolean, userQuery: string): string;
|
||||||
export function getPrompt(key: 'criticalConsiderations', isWorkflow: boolean, userQuery: string): string;
|
export function getPrompt(key: 'criticalConsiderations', isWorkflow: boolean, userQuery: string): string;
|
||||||
export function getPrompt(key: 'phaseToolSelection', userQuery: string, phase: any, phaseTools: any[]): string;
|
export function getPrompt(key: 'phaseToolSelection', userQuery: string, phase: any, phaseTools: any[]): string;
|
||||||
export function getPrompt(key: 'toolEvaluation', userQuery: string, tool: any, rank: number, taskRelevance: number): string;
|
export function getPrompt(key: 'toolEvaluation', userQuery: string, tool: any, rank: number): string;
|
||||||
export function getPrompt(key: 'backgroundKnowledgeSelection', userQuery: string, mode: string, selectedToolNames: string[], availableConcepts: any[]): string;
|
export function getPrompt(key: 'backgroundKnowledgeSelection', userQuery: string, mode: string, selectedToolNames: string[], availableConcepts: any[]): string;
|
||||||
export function getPrompt(key: 'phaseCompletionReasoning', originalQuery: string, phase: any, selectedToolName: string, tool: any, completionContext: string): string;
|
export function getPrompt(key: 'phaseCompletionReasoning', originalQuery: string, phase: any, selectedToolName: string, tool: any, completionContext: string): string;
|
||||||
export function getPrompt(key: 'finalRecommendations', isWorkflow: boolean, userQuery: string, selectedToolNames: string[]): string;
|
export function getPrompt(key: 'finalRecommendations', isWorkflow: boolean, userQuery: string, selectedToolNames: string[]): string;
|
||||||
export function getPrompt(key: 'generatePhaseCompletionPrompt', originalQuery: string, phase: any, candidateTools: any[], candidateConcepts: any[]): string;
|
export function getPrompt(key: 'generatePhaseCompletionPrompt', originalQuery: string, phase: any, candidateTools: any[], candidateConcepts: any[]): string;
|
||||||
|
|
||||||
export function getPrompt(promptKey: keyof typeof AI_PROMPTS, ...args: any[]): string {
|
export function getPrompt(promptKey: keyof typeof AI_PROMPTS, ...args: any[]): string {
|
||||||
try {
|
try {
|
||||||
const promptFunction = AI_PROMPTS[promptKey];
|
const f = AI_PROMPTS[promptKey];
|
||||||
if (typeof promptFunction === 'function') {
|
if (typeof f === 'function') return (f as (...a: any[]) => string)(...args);
|
||||||
return (promptFunction as (...args: any[]) => string)(...args);
|
console.error(`[PROMPTS] Invalid prompt key: ${promptKey}`);
|
||||||
} else {
|
return 'Error: Invalid prompt configuration';
|
||||||
console.error(`[PROMPTS] Invalid prompt key: ${promptKey}`);
|
} catch (err) {
|
||||||
return 'Error: Invalid prompt configuration';
|
console.error(`[PROMPTS] Error generating prompt ${promptKey}:`, err);
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`[PROMPTS] Error generating prompt ${promptKey}:`, error);
|
|
||||||
return 'Error: Failed to generate prompt';
|
return 'Error: Failed to generate prompt';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,44 @@ tools:
|
|||||||
accessType: download
|
accessType: download
|
||||||
license: Apache-2.0
|
license: Apache-2.0
|
||||||
knowledgebase: false
|
knowledgebase: false
|
||||||
|
- name: Thorium
|
||||||
|
icon: ⚛️
|
||||||
|
type: software
|
||||||
|
description: >-
|
||||||
|
CISAs portable Hybrid-Analyse-Tool für die schnelle Untersuchung von Windows-
|
||||||
|
Systemen auf bösartige Aktivitäten. Scannt mit kuratierten YARA- und
|
||||||
|
Sigma-Regeln Arbeitsspeicher, Prozesse, Dateisystem, Netzwerkverbindungen und
|
||||||
|
Systemprotokolle. Ideal für schnelle Triage im Incident Response, sowohl live als auch
|
||||||
|
auf gemounteten Images. Die Ausgabe erfolgt in strukturierten JSON-Reports.
|
||||||
|
domains:
|
||||||
|
- incident-response
|
||||||
|
- malware-analysis
|
||||||
|
phases:
|
||||||
|
- examination
|
||||||
|
- analysis
|
||||||
|
platforms:
|
||||||
|
- Linux
|
||||||
|
related_software:
|
||||||
|
- Loki
|
||||||
|
- YARA
|
||||||
|
- Velociraptor
|
||||||
|
skillLevel: intermediate
|
||||||
|
accessType: download
|
||||||
|
url: https://github.com/cisagov/thorium
|
||||||
|
license: MIT
|
||||||
|
knowledgebase: false
|
||||||
|
tags:
|
||||||
|
- cli
|
||||||
|
- triage
|
||||||
|
- fast-scan
|
||||||
|
- ioc-matching
|
||||||
|
- yara-scan
|
||||||
|
- sigma-rules
|
||||||
|
- memory-analysis
|
||||||
|
- process-analysis
|
||||||
|
- filesystem-scanning
|
||||||
|
- log-analysis
|
||||||
|
- portable
|
||||||
- name: Volatility 3
|
- name: Volatility 3
|
||||||
type: software
|
type: software
|
||||||
description: >-
|
description: >-
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ import BaseLayout from '../layouts/BaseLayout.astro';
|
|||||||
|
|
||||||
<div style="display: grid; gap: 1.25rem;">
|
<div style="display: grid; gap: 1.25rem;">
|
||||||
<div style="background-color: var(--color-bg-secondary); padding: 1.25rem; border-radius: 0.5rem;">
|
<div style="background-color: var(--color-bg-secondary); padding: 1.25rem; border-radius: 0.5rem;">
|
||||||
<h4 style="margin: 0 0 0.5rem 0; color: var(--color-accent);">🔍 Vorschläge</h4>
|
<h4 style="margin: 0 0 0.5rem 0; color: var(--color-accent);">📝 Vorschläge</h4>
|
||||||
<p style="margin: 0;">
|
<p style="margin: 0;">
|
||||||
Du hast eine Idee, wie wir den Hub erweitern können? Reiche deinen Vorschlag unkompliziert
|
Du hast eine Idee, wie wir den Hub erweitern können? Reiche deinen Vorschlag unkompliziert
|
||||||
über unsere <a href="/contribute#vorschlaege">/contribute</a>-Seite ein.
|
über unsere <a href="/contribute#vorschlaege">/contribute</a>-Seite ein.
|
||||||
@@ -210,15 +210,54 @@ import BaseLayout from '../layouts/BaseLayout.astro';
|
|||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
||||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||||
</svg>
|
</svg>
|
||||||
Git‑Repository besuchen
|
Git-Repository besuchen
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Lightning Support Section with simple-boost integration -->
|
||||||
<div style="background-color: var(--color-bg-secondary); padding: 1.25rem; border-radius: 0.5rem;">
|
<div style="background-color: var(--color-bg-secondary); padding: 1.25rem; border-radius: 0.5rem;">
|
||||||
<h4 style="margin: 0 0 0.5rem 0; color: var(--color-accent);">⚡ Unterstützung</h4>
|
<h4 style="margin: 0 0 0.75rem 0; color: var(--color-accent); display: flex; align-items: center; gap: 0.5rem;">
|
||||||
<p style="margin: 0;">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
Kleine Spenden zur Infrastruktur-Finanzierung nehme ich auch gerne an, wenn es sein muss.
|
<polygon points="13,2 3,14 12,14 11,22 21,10 12,10 13,2"/>
|
||||||
Fragt einfach nach der Lightning-Adresse oder BTC-Adresse!
|
</svg>
|
||||||
|
⚡ Unterstützung
|
||||||
|
</h4>
|
||||||
|
<p style="margin: 0 0 1rem 0; font-size: 0.875rem; line-height: 1.5;">
|
||||||
|
Kleine Spenden zur Server-Finanzierung sind willkommen.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 1rem;">
|
||||||
|
<!-- Simple EUR Payment -->
|
||||||
|
<div style="display: flex; gap: 0.75rem; align-items: center; justify-content: center; max-width: 300px; margin: 0 auto;">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="eur-amount"
|
||||||
|
min="0.01"
|
||||||
|
step="0.01"
|
||||||
|
placeholder="0,50"
|
||||||
|
value="0.5"
|
||||||
|
style="width: 80px; padding: 0.5rem; border: 1px solid var(--color-border); border-radius: 0.375rem; font-size: 0.875rem; text-align: center;">
|
||||||
|
<span style="font-size: 0.875rem; color: var(--color-text-secondary);">€</span>
|
||||||
|
<simple-boost
|
||||||
|
id="eur-boost"
|
||||||
|
class="bootstrap"
|
||||||
|
nwc="nostr+walletconnect://4fe05896e1faf09d1902ea24ef589f65a9606d1710420a9574ce331e3c7f486b?relay=wss://nostr.mikoshi.de&secret=bdfc861fe71e8d9e375b7a2484052e92def7caf4b317d8f6537b784d3cd6eb3b"
|
||||||
|
amount="0.5"
|
||||||
|
currency="eur"
|
||||||
|
memo="ForensicPathways Unterstützung - Vielen Dank!"
|
||||||
|
style="background-color: var(--color-accent); color: white; border: none; border-radius: 0.375rem; padding: 0.5rem 1rem; font-size: 0.875rem; cursor: pointer;">
|
||||||
|
⚡ Senden
|
||||||
|
</simple-boost>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 1rem; padding: 0.75rem; background-color: var(--color-bg); border-radius: 0.375rem; border-left: 3px solid var(--color-accent);">
|
||||||
|
<p style="margin: 0; font-size: 0.75rem; color: var(--color-text-secondary); line-height: 1.4; text-align: center;">
|
||||||
|
<strong>⚡ Lightning-Unterstützung:</strong> Betrag eingeben und senden.
|
||||||
|
Benötigt eine Lightning-Wallet wie <a href="https://getalby.com" target="_blank" rel="noopener" style="color: var(--color-accent);">Alby</a> oder
|
||||||
|
<a href="https://phoenix.acinq.co" target="_blank" rel="noopener" style="color: var(--color-accent);">Phoenix</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -231,4 +270,70 @@ import BaseLayout from '../layouts/BaseLayout.astro';
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// TODO: cleanup
|
||||||
|
import('simple-boost').then(() => {
|
||||||
|
console.log('Simple-boost loaded successfully from local dependencies');
|
||||||
|
|
||||||
|
setupDynamicAmounts();
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Failed to load simple-boost:', error);
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.type = 'module';
|
||||||
|
script.src = '/node_modules/simple-boost/dist/simple-boost.js';
|
||||||
|
script.onload = () => {
|
||||||
|
console.log('Simple-boost fallback loaded');
|
||||||
|
setupDynamicAmounts();
|
||||||
|
};
|
||||||
|
script.onerror = () => console.error('Simple-boost fallback failed');
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupDynamicAmounts() {
|
||||||
|
const eurBoost = document.getElementById('eur-boost');
|
||||||
|
const eurInput = document.getElementById('eur-amount') as HTMLInputElement;
|
||||||
|
|
||||||
|
if (eurBoost && eurInput) {
|
||||||
|
eurBoost.addEventListener('click', (e) => {
|
||||||
|
const amount = parseFloat(eurInput.value) || 0.5;
|
||||||
|
eurBoost.setAttribute('amount', amount.toString());
|
||||||
|
console.log('EUR amount set to:', amount);
|
||||||
|
});
|
||||||
|
|
||||||
|
eurInput.addEventListener('input', () => {
|
||||||
|
const amount = parseFloat(eurInput.value) || 0.5;
|
||||||
|
eurBoost.setAttribute('amount', amount.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
simple-boost {
|
||||||
|
--simple-boost-primary: var(--color-warning);
|
||||||
|
--simple-boost-primary-hover: var(--color-accent);
|
||||||
|
--simple-boost-text: white;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
simple-boost:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.15) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
simple-boost .simple-boost-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading state styling */
|
||||||
|
simple-boost[loading] {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -9,8 +9,8 @@ export const GET: APIRoute = async () => {
|
|||||||
await embeddingsService.waitForInitialization();
|
await embeddingsService.waitForInitialization();
|
||||||
|
|
||||||
const stats = embeddingsService.getStats();
|
const stats = embeddingsService.getStats();
|
||||||
const status = stats.enabled && stats.initialized ? 'ready' :
|
const status = stats.initialized ? 'ready' :
|
||||||
stats.enabled && !stats.initialized ? 'initializing' : 'disabled';
|
!stats.initialized ? 'initializing' : 'disabled';
|
||||||
|
|
||||||
console.log(`[EMBEDDINGS-STATUS-API] Service status: ${status}, stats:`, stats);
|
console.log(`[EMBEDDINGS-STATUS-API] Service status: ${status}, stats:`, stats);
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,53 @@ import { apiError, apiServerError, createAuthErrorResponse } from '../../../util
|
|||||||
import { enqueueApiCall } from '../../../utils/rateLimitedQueue.js';
|
import { enqueueApiCall } from '../../../utils/rateLimitedQueue.js';
|
||||||
import { aiService } from '../../../utils/aiService.js';
|
import { aiService } from '../../../utils/aiService.js';
|
||||||
import { JSONParser } from '../../../utils/jsonUtils.js';
|
import { JSONParser } from '../../../utils/jsonUtils.js';
|
||||||
|
import { getPrompt } from '../../../config/prompts.js';
|
||||||
|
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
|
|
||||||
const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
|
const RATE_LIMIT_WINDOW_MS =
|
||||||
const RATE_LIMIT_WINDOW = 60 * 1000;
|
Number.isFinite(parseInt(process.env.RATE_LIMIT_WINDOW_MS ?? '', 10))
|
||||||
const RATE_LIMIT_MAX = 5;
|
? parseInt(process.env.RATE_LIMIT_WINDOW_MS!, 10)
|
||||||
|
: 60_000;
|
||||||
|
|
||||||
|
const RATE_LIMIT_MAX =
|
||||||
|
Number.isFinite(parseInt(process.env.AI_RATE_LIMIT_MAX_REQUESTS ?? '', 10))
|
||||||
|
? parseInt(process.env.AI_RATE_LIMIT_MAX_REQUESTS!, 10)
|
||||||
|
: 5;
|
||||||
|
|
||||||
|
const INPUT_MIN_CHARS = 40;
|
||||||
|
const INPUT_MAX_CHARS = 1000;
|
||||||
|
const Q_MIN_LEN = 15;
|
||||||
|
const Q_MAX_LEN = 160;
|
||||||
|
const Q_MAX_COUNT = 3;
|
||||||
|
const AI_TEMPERATURE = 0.3;
|
||||||
|
const CLEANER_TEMPERATURE = 0.0;
|
||||||
|
|
||||||
|
const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
|
||||||
|
|
||||||
|
function checkRateLimit(userId: string): boolean {
|
||||||
|
const now = Date.now();
|
||||||
|
const entry = rateLimitStore.get(userId);
|
||||||
|
if (!entry || now > entry.resetTime) {
|
||||||
|
rateLimitStore.set(userId, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (entry.count >= RATE_LIMIT_MAX) return false;
|
||||||
|
entry.count++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupExpiredRateLimits(): void {
|
||||||
|
const now = Date.now();
|
||||||
|
for (const [userId, entry] of rateLimitStore.entries()) {
|
||||||
|
if (now > entry.resetTime) rateLimitStore.delete(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setInterval(cleanupExpiredRateLimits, 5 * 60 * 1000);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helpers
|
||||||
|
*/
|
||||||
function sanitizeInput(input: string): string {
|
function sanitizeInput(input: string): string {
|
||||||
return input
|
return input
|
||||||
.replace(/```[\s\S]*?```/g, '[CODE_BLOCK_REMOVED]')
|
.replace(/```[\s\S]*?```/g, '[CODE_BLOCK_REMOVED]')
|
||||||
@@ -19,79 +59,24 @@ function sanitizeInput(input: string): string {
|
|||||||
.replace(/\b(system|assistant|user)\s*[:]/gi, '[ROLE_REMOVED]')
|
.replace(/\b(system|assistant|user)\s*[:]/gi, '[ROLE_REMOVED]')
|
||||||
.replace(/\b(ignore|forget|disregard)\s+(previous|all|your)\s+(instructions?|context|rules?)/gi, '[INSTRUCTION_REMOVED]')
|
.replace(/\b(ignore|forget|disregard)\s+(previous|all|your)\s+(instructions?|context|rules?)/gi, '[INSTRUCTION_REMOVED]')
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 1000);
|
.slice(0, INPUT_MAX_CHARS);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkRateLimit(userId: string): boolean {
|
function stripJsonFences(s: string): string {
|
||||||
const now = Date.now();
|
return s.replace(/^```json\s*/i, '')
|
||||||
const userLimit = rateLimitStore.get(userId);
|
.replace(/^```\s*/i, '')
|
||||||
|
.replace(/\s*```\s*$/, '')
|
||||||
if (!userLimit || now > userLimit.resetTime) {
|
.trim();
|
||||||
rateLimitStore.set(userId, { count: 1, resetTime: now + RATE_LIMIT_WINDOW });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userLimit.count >= RATE_LIMIT_MAX) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
userLimit.count++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanupExpiredRateLimits(): void {
|
|
||||||
const now = Date.now();
|
|
||||||
for (const [userId, limit] of rateLimitStore.entries()) {
|
|
||||||
if (now > limit.resetTime) {
|
|
||||||
rateLimitStore.delete(userId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setInterval(cleanupExpiredRateLimits, 5 * 60 * 1000);
|
|
||||||
|
|
||||||
function createEnhancementPrompt(input: string): string {
|
|
||||||
return `Sie sind ein DFIR-Experte mit Spezialisierung auf forensische Methodik. Ein Nutzer beschreibt ein Szenario oder Problem. Analysieren Sie die Eingabe auf Vollständigkeit für eine wissenschaftlich fundierte Untersuchung.
|
|
||||||
|
|
||||||
ANALYSIEREN SIE DIESE FORENSISCHEN KATEGORIEN:
|
|
||||||
1. **Incident Context**: Was ist passiert? Welche Angriffsvektoren oder technischen Probleme liegen vor?
|
|
||||||
2. **Affected Systems**: Welche spezifischen Technologien/Plattformen sind betroffen? (Windows/Linux/ICS/SCADA/Mobile/Cloud/Network Infrastructure)
|
|
||||||
3. **Available Evidence**: Welche forensischen Datenquellen stehen zur Verfügung? (RAM-Dumps, Disk-Images, Log-Files, Network-Captures, Registry-Hives)
|
|
||||||
4. **Investigation Objectives**: Was soll erreicht werden? (IOC-Extraktion, Timeline-Rekonstruktion, Attribution, Impact-Assessment)
|
|
||||||
5. **Timeline Constraints**: Wie zeitkritisch ist die Untersuchung?
|
|
||||||
6. **Legal & Compliance**: Rechtliche Anforderungen, Chain of Custody, Compliance-Rahmen (DSGVO, sector-specific regulations)
|
|
||||||
7. **Technical Constraints**: Verfügbare Ressourcen, Skills, Infrastrukturbeschränkungen
|
|
||||||
|
|
||||||
WENN die Beschreibung alle kritischen forensischen Aspekte abdeckt: Geben Sie eine leere Liste [] zurück.
|
|
||||||
|
|
||||||
WENN wichtige Details fehlen: Formulieren Sie 2-3 präzise Fragen, die die kritischsten Lücken für eine wissenschaftlich fundierte Analyse schließen.
|
|
||||||
|
|
||||||
QUALITÄTSKRITERIEN FÜR FRAGEN:
|
|
||||||
- Forensisch spezifisch, nicht allgemein (NICHT: "Mehr Details?")
|
|
||||||
- Methodisch relevant (NICHT: "Wann passierte das?")
|
|
||||||
- Priorisiert nach Auswirkung auf die Untersuchungsqualität
|
|
||||||
- Die Frage soll maximal 20 Wörter umfassen
|
|
||||||
|
|
||||||
ANTWORTFORMAT (NUR JSON, KEIN ZUSÄTZLICHER TEXT):
|
|
||||||
[
|
|
||||||
"spezifische Frage 1?",
|
|
||||||
"spezifische Frage 2?",
|
|
||||||
"spezifische Frage 3?"
|
|
||||||
]
|
|
||||||
|
|
||||||
NUTZER-EINGABE:
|
|
||||||
${input}
|
|
||||||
`.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler
|
||||||
|
*/
|
||||||
export const POST: APIRoute = async ({ request }) => {
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
try {
|
try {
|
||||||
const authResult = await withAPIAuth(request, 'ai');
|
const auth = await withAPIAuth(request, 'ai');
|
||||||
if (!authResult.authenticated) {
|
if (!auth.authenticated) return createAuthErrorResponse();
|
||||||
return createAuthErrorResponse();
|
const userId = auth.userId;
|
||||||
}
|
|
||||||
|
|
||||||
const userId = authResult.userId;
|
|
||||||
|
|
||||||
if (!checkRateLimit(userId)) {
|
if (!checkRateLimit(userId)) {
|
||||||
return apiError.rateLimit('Enhancement rate limit exceeded');
|
return apiError.rateLimit('Enhancement rate limit exceeded');
|
||||||
@@ -100,61 +85,38 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { input } = body;
|
const { input } = body;
|
||||||
|
|
||||||
if (!input || typeof input !== 'string' || input.length < 40) {
|
if (!input || typeof input !== 'string' || input.length < INPUT_MIN_CHARS) {
|
||||||
return apiError.badRequest('Input too short for enhancement (minimum 40 characters)');
|
return apiError.badRequest(`Input too short for enhancement (minimum ${INPUT_MIN_CHARS} characters)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sanitizedInput = sanitizeInput(input);
|
const sanitizedInput = sanitizeInput(input);
|
||||||
if (sanitizedInput.length < 40) {
|
if (sanitizedInput.length < INPUT_MIN_CHARS) {
|
||||||
return apiError.badRequest('Input too short after sanitization');
|
return apiError.badRequest('Input too short after sanitization');
|
||||||
}
|
}
|
||||||
|
|
||||||
const systemPrompt = createEnhancementPrompt(sanitizedInput);
|
const taskId = `enhance_${userId}_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
||||||
const taskId = `enhance_${userId}_${Date.now()}_${Math.random().toString(36).substr(2, 4)}`;
|
const questionsPrompt = getPrompt('enhancementQuestions', sanitizedInput);
|
||||||
|
|
||||||
console.log(`[ENHANCE-API] Processing enhancement request for user: ${userId}`);
|
|
||||||
|
|
||||||
const aiResponse = await enqueueApiCall(() =>
|
|
||||||
aiService.callAI(systemPrompt, {
|
|
||||||
temperature: 0.7
|
|
||||||
}), taskId);
|
|
||||||
|
|
||||||
if (!aiResponse.content) {
|
console.log(`[ENHANCE-API] Processing enhancement request for user: ${userId}`);
|
||||||
|
|
||||||
|
const aiResponse = await enqueueApiCall(
|
||||||
|
() => aiService.callAI(questionsPrompt, { temperature: AI_TEMPERATURE }),
|
||||||
|
taskId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!aiResponse?.content) {
|
||||||
return apiServerError.unavailable('No enhancement response');
|
return apiServerError.unavailable('No enhancement response');
|
||||||
}
|
}
|
||||||
|
|
||||||
let questions;
|
let parsed: unknown = JSONParser.safeParseJSON(stripJsonFences(aiResponse.content), null);
|
||||||
try {
|
|
||||||
const cleanedContent = aiResponse.content
|
|
||||||
.replace(/^```json\s*/i, '')
|
|
||||||
.replace(/\s*```\s*$/, '')
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
questions = JSONParser.safeParseJSON(cleanedContent, []);
|
|
||||||
|
|
||||||
if (!Array.isArray(questions)) {
|
|
||||||
throw new Error('Response is not an array');
|
|
||||||
}
|
|
||||||
|
|
||||||
questions = questions
|
|
||||||
.filter(q => typeof q === 'string' && q.length > 20 && q.length < 200)
|
|
||||||
.filter(q => q.includes('?'))
|
|
||||||
.filter(q => {
|
|
||||||
const forensicsTerms = ['forensisch', 'log', 'dump', 'image', 'artefakt', 'evidence', 'incident', 'system', 'netzwerk', 'zeitraum', 'verfügbar'];
|
|
||||||
const lowerQ = q.toLowerCase();
|
|
||||||
return forensicsTerms.some(term => lowerQ.includes(term));
|
|
||||||
})
|
|
||||||
.map(q => q.trim())
|
|
||||||
.slice(0, 3);
|
|
||||||
|
|
||||||
if (questions.length === 0) {
|
|
||||||
questions = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
let questions: string[] = Array.isArray(parsed) ? parsed : [];
|
||||||
console.error('[ENHANCE-API] Failed to parse enhancement response:', aiResponse.content);
|
questions = questions
|
||||||
questions = [];
|
.filter(q => typeof q === 'string')
|
||||||
}
|
.map(q => q.trim())
|
||||||
|
.filter(q => q.endsWith('?'))
|
||||||
|
.filter(q => q.length >= Q_MIN_LEN && q.length <= Q_MAX_LEN)
|
||||||
|
.slice(0, Q_MAX_COUNT);
|
||||||
|
|
||||||
console.log(`[ENHANCE-API] User: ${userId}, Questions generated: ${questions.length}, Input length: ${sanitizedInput.length}`);
|
console.log(`[ENHANCE-API] User: ${userId}, Questions generated: ${questions.length}, Input length: ${sanitizedInput.length}`);
|
||||||
|
|
||||||
@@ -162,14 +124,14 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
success: true,
|
success: true,
|
||||||
questions,
|
questions,
|
||||||
taskId,
|
taskId,
|
||||||
inputComplete: questions.length === 0
|
inputComplete: questions.length === 0
|
||||||
}), {
|
}), {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error('[ENHANCE-API] Enhancement error:', error);
|
console.error('[ENHANCE-API] Enhancement error:', err);
|
||||||
return apiServerError.internal('Enhancement processing failed');
|
return apiServerError.internal('Enhancement processing failed');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,13 +37,6 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
|
|
||||||
const { embeddingsService } = await import('../../../utils/embeddings.js');
|
const { embeddingsService } = await import('../../../utils/embeddings.js');
|
||||||
|
|
||||||
if (!embeddingsService.isEnabled()) {
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ success: false, error: 'Semantic search not available' }),
|
|
||||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await embeddingsService.waitForInitialization();
|
await embeddingsService.waitForInitialization();
|
||||||
|
|
||||||
const similarItems = await embeddingsService.findSimilar(
|
const similarItems = await embeddingsService.findSimilar(
|
||||||
|
|||||||
@@ -1,61 +1,72 @@
|
|||||||
|
/* PALETTE OPTION 1: BLUEPRINT & AMBER */
|
||||||
:root {
|
:root {
|
||||||
/* Light Theme Colors */
|
/* Light Theme */
|
||||||
--color-bg: #fff;
|
--color-bg: #ffffff;
|
||||||
--color-bg-secondary: #f8fafc;
|
--color-bg-secondary: #f1f5f9; /* Slate 100 */
|
||||||
--color-bg-tertiary: #e2e8f0;
|
--color-bg-tertiary: #e2e8f0; /* Slate 200 */
|
||||||
--color-text: #1e293b;
|
--color-text: #0f172a; /* Slate 900 */
|
||||||
--color-text-secondary: #64748b;
|
--color-text-secondary: #475569; /* Slate 600 */
|
||||||
--color-border: #cbd5e1;
|
--color-border: #cbd5e1; /* Slate 300 */
|
||||||
--color-primary: #2563eb;
|
|
||||||
--color-primary-hover: #1d4ed8;
|
--color-primary: #334155; /* Slate 700 - A strong, serious primary */
|
||||||
--color-accent: #059669;
|
--color-primary-hover: #1e293b; /* Slate 800 */
|
||||||
--color-accent-hover: #047857;
|
|
||||||
|
--color-accent: #b45309; /* A sharp, focused amber for highlights */
|
||||||
|
--color-accent-hover: #92400e;
|
||||||
|
|
||||||
--color-warning: #d97706;
|
--color-warning: #d97706;
|
||||||
--color-error: #dc2626;
|
--color-error: #be123c; /* A deeper, more serious red */
|
||||||
|
|
||||||
--color-hosted: #7c3aed;
|
/* Card/Tag Category Colors */
|
||||||
--color-hosted-bg: #f3f0ff;
|
--color-hosted: #4f46e5; /* Indigo */
|
||||||
--color-oss: #059669;
|
--color-hosted-bg: #eef2ff;
|
||||||
--color-oss-bg: #ecfdf5;
|
--color-oss: #0d9488; /* Teal */
|
||||||
--color-method: #0891b2;
|
--color-oss-bg: #f0fdfa;
|
||||||
--color-method-bg: #f0f9ff;
|
--color-method: #0891b2; /* Cyan */
|
||||||
--color-concept: #ea580c;
|
--color-method-bg: #ecfeff;
|
||||||
|
--color-concept: #c2410c; /* Orange */
|
||||||
--color-concept-bg: #fff7ed;
|
--color-concept-bg: #fff7ed;
|
||||||
|
|
||||||
/* Shadows */
|
/* Shadows (Crisper) */
|
||||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 5%);
|
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 6%);
|
||||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 10%);
|
--shadow-md: 0 3px 5px -1px rgb(0 0 0 / 8%);
|
||||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 10%);
|
--shadow-lg: 0 8px 12px -3px rgb(0 0 0 / 10%);
|
||||||
|
|
||||||
/* Transitions */
|
/* Transitions */
|
||||||
--transition-fast: all 0.2s ease;
|
--transition-fast: all 0.2s ease;
|
||||||
--transition-medium: all 0.3s ease;
|
--transition-medium: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
--color-bg: #0f172a;
|
/* Dark Theme */
|
||||||
--color-bg-secondary: #1e293b;
|
--color-bg: #0f172a; /* Slate 900 */
|
||||||
--color-bg-tertiary: #334155;
|
--color-bg-secondary: #1e293b; /* Slate 800 */
|
||||||
--color-text: #f1f5f9;
|
--color-bg-tertiary: #334155; /* Slate 700 */
|
||||||
--color-text-secondary: #94a3b8;
|
--color-text: #f1f5f9; /* Slate 100 */
|
||||||
--color-border: #475569;
|
--color-text-secondary: #94a3b8; /* Slate 400 */
|
||||||
--color-primary: #3b82f6;
|
--color-border: #475569; /* Slate 600 */
|
||||||
--color-primary-hover: #60a5fa;
|
|
||||||
--color-accent: #10b981;
|
--color-primary: #64748b; /* Slate 500 */
|
||||||
--color-accent-hover: #34d399;
|
--color-primary-hover: #94a3b8; /* Slate 400 */
|
||||||
|
|
||||||
|
--color-accent: #f59e0b; /* A brighter amber for dark mode contrast */
|
||||||
|
--color-accent-hover: #fbbf24;
|
||||||
|
|
||||||
--color-warning: #f59e0b;
|
--color-warning: #f59e0b;
|
||||||
--color-error: #f87171;
|
--color-error: #f43f5e;
|
||||||
|
|
||||||
--color-hosted: #a855f7;
|
/* Card/Tag Category Colors */
|
||||||
--color-hosted-bg: #2e1065;
|
--color-hosted: #818cf8; /* Indigo */
|
||||||
--color-oss: #10b981;
|
--color-hosted-bg: #3730a3;
|
||||||
--color-oss-bg: #064e3b;
|
--color-oss: #2dd4bf; /* Teal */
|
||||||
--color-method: #0891b2;
|
--color-oss-bg: #115e59;
|
||||||
|
--color-method: #22d3ee; /* Cyan */
|
||||||
--color-method-bg: #164e63;
|
--color-method-bg: #164e63;
|
||||||
--color-concept: #f97316;
|
--color-concept: #fb923c; /* Orange */
|
||||||
--color-concept-bg: #7c2d12;
|
--color-concept-bg: #7c2d12;
|
||||||
|
|
||||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 30%);
|
/* Shadows (Subtler for dark mode) */
|
||||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 40%);
|
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 20%);
|
||||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 50%);
|
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 30%);
|
||||||
|
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 40%);
|
||||||
}
|
}
|
||||||
@@ -470,15 +470,36 @@ class AIPipeline {
|
|||||||
pipelineStart: number,
|
pipelineStart: number,
|
||||||
toolsDataHash: string
|
toolsDataHash: string
|
||||||
): Promise<{ completed: number; failed: number }> {
|
): Promise<{ completed: number; failed: number }> {
|
||||||
const topTools = context.filteredData.tools.slice(0, 3);
|
const candidates = context.filteredData.tools || [];
|
||||||
|
if (!Array.isArray(candidates) || candidates.length === 0) {
|
||||||
for (let i = 0; i < topTools.length; i++) {
|
return { completed: completedTasks, failed: failedTasks };
|
||||||
const evaluationResult = await this.evaluateSpecificTool(context, topTools[i], i + 1, pipelineStart, toolsDataHash);
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < candidates.length; i++) {
|
||||||
|
const evaluationResult = await this.evaluateSpecificTool(context, candidates[i], i + 1, pipelineStart, toolsDataHash);
|
||||||
if (evaluationResult.success) completedTasks++; else failedTasks++;
|
if (evaluationResult.success) completedTasks++; else failedTasks++;
|
||||||
this.trackTokenUsage(evaluationResult.aiUsage);
|
this.trackTokenUsage(evaluationResult.aiUsage);
|
||||||
await this.delay(this.config.microTaskDelay);
|
await this.delay(this.config.microTaskDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(context.selectedTools) && context.selectedTools.length > 0) {
|
||||||
|
context.selectedTools.sort((a: any, b: any) => {
|
||||||
|
const ar = typeof a.taskRelevance === 'number' ? a.taskRelevance : -1;
|
||||||
|
const br = typeof b.taskRelevance === 'number' ? b.taskRelevance : -1;
|
||||||
|
if (br !== ar) return br - ar;
|
||||||
|
|
||||||
|
const aLen = (a.justification || '').length;
|
||||||
|
const bLen = (b.justification || '').length;
|
||||||
|
if (bLen !== aLen) return bLen - aLen;
|
||||||
|
|
||||||
|
const aRank = a.tool?.evaluation?.rank ?? Number.MAX_SAFE_INTEGER;
|
||||||
|
const bRank = b.tool?.evaluation?.rank ?? Number.MAX_SAFE_INTEGER;
|
||||||
|
return aRank - bRank;
|
||||||
|
});
|
||||||
|
|
||||||
|
context.selectedTools = context.selectedTools.slice(0, 3);
|
||||||
|
}
|
||||||
|
|
||||||
return { completed: completedTasks, failed: failedTasks };
|
return { completed: completedTasks, failed: failedTasks };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -849,68 +870,108 @@ class AIPipeline {
|
|||||||
toolsDataHash: string
|
toolsDataHash: string
|
||||||
): Promise<MicroTaskResult> {
|
): Promise<MicroTaskResult> {
|
||||||
const taskStart = Date.now();
|
const taskStart = Date.now();
|
||||||
const existingSelection = context.selectedTools?.find((st: any) => st.tool && st.tool.name === tool.name);
|
|
||||||
const originalTaskRelevance = existingSelection?.taskRelevance || 70;
|
const prompt = getPrompt('toolEvaluation', context.userQuery, tool, rank);
|
||||||
const moderatedTaskRelevance = this.moderateTaskRelevance(originalTaskRelevance);
|
|
||||||
const priority = this.derivePriorityFromScore(moderatedTaskRelevance);
|
|
||||||
|
|
||||||
const prompt = getPrompt('toolEvaluation', context.userQuery, tool, rank, moderatedTaskRelevance);
|
|
||||||
const result = await this.callMicroTaskAI(prompt, context, 'tool-evaluation');
|
const result = await this.callMicroTaskAI(prompt, context, 'tool-evaluation');
|
||||||
|
|
||||||
if (result.success) {
|
if (!result.success) {
|
||||||
const evaluation = JSONParser.safeParseJSON(result.content, {
|
return result;
|
||||||
detailed_explanation: 'Evaluation failed',
|
}
|
||||||
implementation_approach: '',
|
|
||||||
pros: [],
|
const evaluation = JSONParser.safeParseJSON(result.content, null);
|
||||||
limitations: [],
|
|
||||||
alternatives: ''
|
const aiProvided = evaluation && typeof evaluation.taskRelevance === 'number' && Number.isFinite(evaluation.taskRelevance)
|
||||||
});
|
? Math.round(evaluation.taskRelevance)
|
||||||
|
: null;
|
||||||
this.addToolToSelection(context, {
|
|
||||||
...tool,
|
if (aiProvided === null) {
|
||||||
evaluation: {
|
|
||||||
...evaluation,
|
|
||||||
rank,
|
|
||||||
task_relevance: moderatedTaskRelevance
|
|
||||||
}
|
|
||||||
}, 'evaluation', priority, evaluation.detailed_explanation, moderatedTaskRelevance, evaluation.limitations);
|
|
||||||
|
|
||||||
const responseConfidence = auditService.calculateAIResponseConfidence(
|
|
||||||
result.content,
|
|
||||||
{ min: 200, max: 800 },
|
|
||||||
'tool-evaluation'
|
|
||||||
);
|
|
||||||
|
|
||||||
const finalConfidence = Math.max(responseConfidence, moderatedTaskRelevance);
|
|
||||||
|
|
||||||
auditService.addAIDecision(
|
auditService.addAIDecision(
|
||||||
'tool-evaluation',
|
'tool-evaluation',
|
||||||
prompt,
|
prompt,
|
||||||
result.content,
|
result.content,
|
||||||
finalConfidence,
|
0,
|
||||||
`Bewertete Tool "${tool.name}" (Rang ${rank}) - Analysierte Eignung für spezifische Aufgabenstellung mit Fokus auf praktische Anwendbarkeit und methodische Integration`,
|
`Bewertung für "${tool.name}" ignoriert: fehlender/ungültiger taskRelevance`,
|
||||||
taskStart,
|
taskStart,
|
||||||
{
|
{
|
||||||
toolsDataHash: toolsDataHash,
|
toolsDataHash,
|
||||||
microTaskType: 'tool-evaluation',
|
microTaskType: 'tool-evaluation',
|
||||||
toolName: tool.name,
|
toolName: tool.name,
|
||||||
toolType: tool.type,
|
toolType: tool.type,
|
||||||
rank,
|
rank,
|
||||||
originalTaskRelevance,
|
evaluationParsed: false,
|
||||||
moderatedTaskRelevance,
|
|
||||||
responseConfidence,
|
|
||||||
finalConfidence,
|
|
||||||
moderationApplied: originalTaskRelevance !== moderatedTaskRelevance,
|
|
||||||
evaluationParsed: !!evaluation.detailed_explanation,
|
|
||||||
prosCount: evaluation.pros?.length || 0,
|
|
||||||
limitationsCount: evaluation.limitations?.length || 0,
|
|
||||||
decisionBasis: 'ai-analysis',
|
decisionBasis: 'ai-analysis',
|
||||||
aiModel: aiService.getConfig().model,
|
aiModel: aiService.getConfig().model,
|
||||||
...result.aiUsage
|
...(result.aiUsage || {})
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const moderatedTaskRelevance = this.moderateTaskRelevance(aiProvided);
|
||||||
|
const priority = this.derivePriorityFromScore(moderatedTaskRelevance);
|
||||||
|
|
||||||
|
const detailed_explanation = String(evaluation?.detailed_explanation || '').trim();
|
||||||
|
const implementation_approach = String(evaluation?.implementation_approach || '').trim();
|
||||||
|
const pros = Array.isArray(evaluation?.pros) ? evaluation.pros : [];
|
||||||
|
const limitations = Array.isArray(evaluation?.limitations) ? evaluation.limitations : [];
|
||||||
|
const alternatives = String(evaluation?.alternatives || '').trim();
|
||||||
|
|
||||||
|
this.addToolToSelection(
|
||||||
|
context,
|
||||||
|
{
|
||||||
|
...tool,
|
||||||
|
evaluation: {
|
||||||
|
detailed_explanation,
|
||||||
|
implementation_approach,
|
||||||
|
pros,
|
||||||
|
limitations,
|
||||||
|
alternatives,
|
||||||
|
rank,
|
||||||
|
task_relevance: moderatedTaskRelevance
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'evaluation',
|
||||||
|
priority,
|
||||||
|
detailed_explanation,
|
||||||
|
moderatedTaskRelevance,
|
||||||
|
limitations
|
||||||
|
);
|
||||||
|
|
||||||
|
const responseConfidence = auditService.calculateAIResponseConfidence(
|
||||||
|
result.content,
|
||||||
|
{ min: 200, max: 800 },
|
||||||
|
'tool-evaluation'
|
||||||
|
);
|
||||||
|
|
||||||
|
const finalConfidence = Math.max(responseConfidence, moderatedTaskRelevance);
|
||||||
|
|
||||||
|
auditService.addAIDecision(
|
||||||
|
'tool-evaluation',
|
||||||
|
prompt,
|
||||||
|
result.content,
|
||||||
|
finalConfidence,
|
||||||
|
`Bewertete Tool "${tool.name}" (Rang ${rank}) – AI-Score ${aiProvided}, moderiert ${moderatedTaskRelevance}`,
|
||||||
|
taskStart,
|
||||||
|
{
|
||||||
|
toolsDataHash,
|
||||||
|
microTaskType: 'tool-evaluation',
|
||||||
|
toolName: tool.name,
|
||||||
|
toolType: tool.type,
|
||||||
|
rank,
|
||||||
|
aiProvidedTaskRelevance: aiProvided,
|
||||||
|
moderatedTaskRelevance,
|
||||||
|
responseConfidence,
|
||||||
|
finalConfidence,
|
||||||
|
moderationApplied: aiProvided !== moderatedTaskRelevance,
|
||||||
|
evaluationParsed: true,
|
||||||
|
prosCount: pros.length,
|
||||||
|
limitationsCount: limitations.length,
|
||||||
|
decisionBasis: 'ai-analysis',
|
||||||
|
aiModel: aiService.getConfig().model,
|
||||||
|
...(result.aiUsage || {})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -263,10 +263,6 @@ class EmbeddingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async waitForInitialization(): Promise<void> {
|
async waitForInitialization(): Promise<void> {
|
||||||
/*if (!this.config.enabled) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if (this.isInitialized) {
|
if (this.isInitialized) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,14 +12,13 @@ export interface ToolSelectionConfig {
|
|||||||
similarityThreshold: number;
|
similarityThreshold: number;
|
||||||
embeddingSelectionLimit: number;
|
embeddingSelectionLimit: number;
|
||||||
embeddingConceptsLimit: number;
|
embeddingConceptsLimit: number;
|
||||||
noEmbeddingsToolLimit: number;
|
|
||||||
noEmbeddingsConceptLimit: number;
|
|
||||||
embeddingsMinTools: number;
|
embeddingsMinTools: number;
|
||||||
embeddingsMaxReductionRatio: number;
|
embeddingsMaxReductionRatio: number;
|
||||||
methodSelectionRatio: number;
|
methodSelectionRatio: number;
|
||||||
softwareSelectionRatio: number;
|
softwareSelectionRatio: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface SelectionContext {
|
export interface SelectionContext {
|
||||||
userQuery: string;
|
userQuery: string;
|
||||||
mode: string;
|
mode: string;
|
||||||
@@ -51,14 +50,11 @@ class ToolSelector {
|
|||||||
similarityThreshold: this.getEnvFloat('AI_SIMILARITY_THRESHOLD', 0.3),
|
similarityThreshold: this.getEnvFloat('AI_SIMILARITY_THRESHOLD', 0.3),
|
||||||
embeddingSelectionLimit: this.getEnvInt('AI_EMBEDDING_SELECTION_LIMIT', 30),
|
embeddingSelectionLimit: this.getEnvInt('AI_EMBEDDING_SELECTION_LIMIT', 30),
|
||||||
embeddingConceptsLimit: this.getEnvInt('AI_EMBEDDING_CONCEPTS_LIMIT', 15),
|
embeddingConceptsLimit: this.getEnvInt('AI_EMBEDDING_CONCEPTS_LIMIT', 15),
|
||||||
noEmbeddingsToolLimit: this.getEnvInt('AI_NO_EMBEDDINGS_TOOL_LIMIT', 25),
|
|
||||||
noEmbeddingsConceptLimit: this.getEnvInt('AI_NO_EMBEDDINGS_CONCEPT_LIMIT', 10),
|
|
||||||
embeddingsMinTools: this.getEnvInt('AI_EMBEDDINGS_MIN_TOOLS', 8),
|
embeddingsMinTools: this.getEnvInt('AI_EMBEDDINGS_MIN_TOOLS', 8),
|
||||||
embeddingsMaxReductionRatio: this.getEnvFloat('AI_EMBEDDINGS_MAX_REDUCTION_RATIO', 0.75),
|
embeddingsMaxReductionRatio: this.getEnvFloat('AI_EMBEDDINGS_MAX_REDUCTION_RATIO', 0.75),
|
||||||
methodSelectionRatio: this.getEnvFloat('AI_METHOD_SELECTION_RATIO', 0.4),
|
methodSelectionRatio: this.getEnvFloat('AI_METHOD_SELECTION_RATIO', 0.4),
|
||||||
softwareSelectionRatio: this.getEnvFloat('AI_SOFTWARE_SELECTION_RATIO', 0.5)
|
softwareSelectionRatio: this.getEnvFloat('AI_SOFTWARE_SELECTION_RATIO', 0.5),
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[TOOL-SELECTOR] Initialized with config:', this.config);
|
console.log('[TOOL-SELECTOR] Initialized with config:', this.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,43 +181,69 @@ class ToolSelector {
|
|||||||
): Promise<ToolSelectionResult> {
|
): Promise<ToolSelectionResult> {
|
||||||
console.log('[TOOL-SELECTOR] Performing AI selection');
|
console.log('[TOOL-SELECTOR] Performing AI selection');
|
||||||
|
|
||||||
const candidateMethods = candidateTools.filter((tool: any) => tool && tool.type === 'method');
|
const candidateMethods = candidateTools.filter((t: any) => t && t.type === 'method');
|
||||||
const candidateSoftware = candidateTools.filter((tool: any) => tool && tool.type === 'software');
|
const candidateSoftware = candidateTools.filter((t: any) => t && t.type === 'software');
|
||||||
|
|
||||||
console.log('[TOOL-SELECTOR] Candidates:', candidateMethods.length, 'methods,', candidateSoftware.length, 'software,', candidateConcepts.length, 'concepts');
|
console.log('[TOOL-SELECTOR] Candidates:',
|
||||||
|
candidateMethods.length, 'methods,',
|
||||||
|
candidateSoftware.length, 'software,',
|
||||||
|
candidateConcepts.length, 'concepts'
|
||||||
|
);
|
||||||
|
|
||||||
const methodsWithFullData = candidateMethods.map(this.createToolData);
|
const methodsWithFullData = candidateMethods.map(this.createToolData);
|
||||||
const softwareWithFullData = candidateSoftware.map(this.createToolData);
|
const softwareWithFullData = candidateSoftware.map(this.createToolData);
|
||||||
const conceptsWithFullData = candidateConcepts.map(this.createConceptData);
|
const conceptsWithFullData = candidateConcepts.map(this.createConceptData);
|
||||||
|
|
||||||
const maxTools = Math.min(this.config.embeddingSelectionLimit, this.config.noEmbeddingsToolLimit);
|
const maxTools = Math.min(this.config.embeddingSelectionLimit, candidateTools.length);
|
||||||
const maxConcepts = Math.min(this.config.embeddingConceptsLimit, this.config.noEmbeddingsConceptLimit);
|
const maxConcepts = Math.min(this.config.embeddingConceptsLimit, candidateConcepts.length);
|
||||||
const methodLimit = Math.ceil(maxTools * this.config.methodSelectionRatio);
|
|
||||||
const softwareLimit = Math.floor(maxTools * this.config.softwareSelectionRatio);
|
|
||||||
|
|
||||||
const toolsToSend: any[] = [
|
const methodRatio = Math.max(0, Math.min(1, this.config.methodSelectionRatio));
|
||||||
...methodsWithFullData.slice(0, methodLimit),
|
const softwareRatio = Math.max(0, Math.min(1, this.config.softwareSelectionRatio));
|
||||||
...softwareWithFullData.slice(0, softwareLimit),
|
|
||||||
];
|
|
||||||
|
|
||||||
const remainingCapacity = maxTools - toolsToSend.length;
|
let methodLimit = Math.round(maxTools * methodRatio);
|
||||||
if (remainingCapacity > 0) {
|
let softwareLimit = Math.round(maxTools * softwareRatio);
|
||||||
const extraMethods = methodsWithFullData.slice(methodLimit, methodLimit + remainingCapacity);
|
|
||||||
const extraSoftware = softwareWithFullData.slice(softwareLimit, softwareLimit + (remainingCapacity - extraMethods.length));
|
if (methodLimit + softwareLimit > maxTools) {
|
||||||
toolsToSend.push(...extraMethods, ...extraSoftware);
|
const scale = maxTools / (methodLimit + softwareLimit);
|
||||||
|
methodLimit = Math.floor(methodLimit * scale);
|
||||||
|
softwareLimit = Math.floor(softwareLimit * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
const methodsPrimary = methodsWithFullData.slice(0, methodLimit);
|
||||||
|
const softwarePrimary = softwareWithFullData.slice(0, softwareLimit);
|
||||||
|
|
||||||
|
const toolsToSend: any[] = [...methodsPrimary, ...softwarePrimary];
|
||||||
|
|
||||||
|
let mIdx = methodsPrimary.length;
|
||||||
|
let sIdx = softwarePrimary.length;
|
||||||
|
|
||||||
|
while (toolsToSend.length < maxTools && (mIdx < methodsWithFullData.length || sIdx < softwareWithFullData.length)) {
|
||||||
|
const remM = methodsWithFullData.length - mIdx;
|
||||||
|
const remS = softwareWithFullData.length - sIdx;
|
||||||
|
|
||||||
|
if (remS >= remM && sIdx < softwareWithFullData.length) {
|
||||||
|
toolsToSend.push(softwareWithFullData[sIdx++]);
|
||||||
|
} else if (mIdx < methodsWithFullData.length) {
|
||||||
|
toolsToSend.push(methodsWithFullData[mIdx++]);
|
||||||
|
} else if (sIdx < softwareWithFullData.length) {
|
||||||
|
toolsToSend.push(softwareWithFullData[sIdx++]);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const conceptsToSend = conceptsWithFullData.slice(0, maxConcepts);
|
const conceptsToSend = conceptsWithFullData.slice(0, maxConcepts);
|
||||||
|
|
||||||
const basePrompt = getPrompt('toolSelection', mode, userQuery, this.config.maxSelectedItems);
|
console.log('[TOOL-SELECTOR-DEBUG] maxTools:', maxTools, 'maxConcepts:', maxConcepts);
|
||||||
const prompt = getPrompt('toolSelectionWithData', basePrompt, toolsToSend, conceptsToSend);
|
|
||||||
|
|
||||||
console.log('[TOOL-SELECTOR] Sending to AI:',
|
console.log('[TOOL-SELECTOR] Sending to AI:',
|
||||||
toolsToSend.filter((t: any) => t.type === 'method').length, 'methods,',
|
toolsToSend.filter((t: any) => t.type === 'method').length, 'methods,',
|
||||||
toolsToSend.filter((t: any) => t.type === 'software').length, 'software,',
|
toolsToSend.filter((t: any) => t.type === 'software').length, 'software,',
|
||||||
conceptsToSend.length, 'concepts'
|
conceptsToSend.length, 'concepts'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const basePrompt = getPrompt('toolSelection', mode, userQuery, this.config.maxSelectedItems);
|
||||||
|
const prompt = getPrompt('toolSelectionWithData', basePrompt, toolsToSend, conceptsToSend);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await aiService.callAI(prompt);
|
const response = await aiService.callAI(prompt);
|
||||||
const result = JSONParser.safeParseJSON(response.content, null);
|
const result = JSONParser.safeParseJSON(response.content, null);
|
||||||
@@ -250,7 +272,11 @@ class ToolSelector {
|
|||||||
const selectedMethods = selectedTools.filter((t: any) => t && t.type === 'method');
|
const selectedMethods = selectedTools.filter((t: any) => t && t.type === 'method');
|
||||||
const selectedSoftware = selectedTools.filter((t: any) => t && t.type === 'software');
|
const selectedSoftware = selectedTools.filter((t: any) => t && t.type === 'software');
|
||||||
|
|
||||||
console.log('[TOOL-SELECTOR] AI selected:', selectedMethods.length, 'methods,', selectedSoftware.length, 'software,', selectedConcepts.length, 'concepts');
|
console.log('[TOOL-SELECTOR] AI selected:',
|
||||||
|
selectedMethods.length, 'methods,',
|
||||||
|
selectedSoftware.length, 'software,',
|
||||||
|
selectedConcepts.length, 'concepts'
|
||||||
|
);
|
||||||
|
|
||||||
const confidence = confidenceScoring.calculateSelectionConfidence(
|
const confidence = confidenceScoring.calculateSelectionConfidence(
|
||||||
result,
|
result,
|
||||||
|
|||||||
Reference in New Issue
Block a user