Analysedatum: 2025-10-29 Analysierte Komponenten:
src/llm/agent.ts- Hauptagent-Klassesrc/llm/sub-agent.ts- Sub-Agent Orchestratorsrc/llm/ollama-client.ts- Ollama API Clientsrc/llm/tool-format-parser.ts- Tool Format Handlingsrc/llm/callback-loop.ts- Callback Loop Systemsrc/llm/model-manager.ts- Modell-Verwaltungsrc/tools/tool-manager.ts- Tool Execution
Verantwortung: Hauptausführungs-Engine für LLM-basierte Agenten
Struktur:
- OllamaClient: Kommunikation mit Ollama API
- ModelManager: Modellauswahl und Verwaltung
- ToolManager: Tool Registration und Execution
- conversationHistory: Message-History als ArrayIterationszyklus:
- API-Call mit retry-Logik (bis 3 Versuche)
- Response-Verarbeitung (Thinking-Extraktion, Tool-Normalisierung)
- Tool-Call-Ausführung (parallel)
- Tool-Results zur History hinzufügen
- Neue Iteration bis zum finalen Response
Konfiguration:
maxIterations: Standard 50 (erhöht von 10)maxRetries: Standard 3 für API-Callsverbose: Debug-Output
Verantwortung: Delegation von Aufgaben an spezialisierte Agenten
Features:
- Parallel-Execution von Tasks
- Sequential-Execution für abhängige Tasks
- Smart-Execution (Prioritätsgruppen-basiert)
- Task-States: pending, in_progress, completed, failed
Sub-Agent-Typen:
- CodeReviewer (qwen3-coder:30b)
- FastExecutor (granite4:micro)
- Reasoner (gpt-oss:20b)
- FileExpert (granite4:micro)
Verantwortung: Claude ↔ Ollama Handoff-System zur Timeout-Vermeidung
Features:
- Task-Queuing und Priorisierung
- Persistierung von Task-States (JSON)
- Claude-Review-Prompts generieren
- Retry-Mechanismus bei Fehlern
Iteration-Limit: 50 (maxIterations)
Struktur (agent.ts lines 48, 66-71):
private conversationHistory: Message[] = [];
// Message-Typen:
- system: System-Prompt
- user: User-Eingabe
- assistant: LLM-Response (mit thinking und tool_calls)
- tool: Tool-Execution-ResultOperationen:
setSystemPrompt(): Ersetzt existierende system-MessageaddUserMessage(): Append-OnlygetHistory(): Rückgabe der kompletten HistoryclearHistory(): Entfernt alle außer system-Message
Probleme:
- Speicherlecks: History wächst unbegrenzt mit jedem Iteration
- Keine Größen-Limits: Große Projekte können Context-Fenster überlaufen
- Keine History-Kompression: Alte Iterationen nicht zusammengefasst
- Denk-Prozess nicht gespeichert:
thinkingwird in Message gespeichert, aber nicht persistent
Dateien: src/llm/agent.ts (lines 48, 184, 249)
Severität: KRITISCH
Beschreibung:
// Jede Iteration fügt hinzu:
// 1. Assistant-Message (vollständiger Response)
// 2. Tool-Call-Results (eine Message pro Tool)
// Mit maxIterations=50 und 5+ Tools pro Iteration:
// potentiell 50 * 6 = 300+ MessagesImpact:
- Context-Fenster wird schnell gefüllt
- API-Latenz steigt exponentiell
- Kosten für längere Prompts
- Kann Token-Limits überschreiten (max_tokens Config wird nicht beachtet)
Reproduktion:
- maxIterations=50 mit jedem Tool-Call
- Nach ~20 Iterationen erreicht Context-Grenze
- Quality degradiert dramatisch
Dateien: src/llm/tool-format-parser.ts (lines 112-158)
Severität: KRITISCH
Beschreibung:
Tool-Call-Extraktion hat mehrere Fehler:
- Python-Pattern zu simpel:
// Zeile 138:
const pythonPattern = /\b(\w+)\s*\(\s*[^)]*\s*\)/g;
// Problem: Matched ALLE Funktionsaufrufe, auch nicht-Tools
// Z.B. "let x = parseInt(5)" würde matched- XML-Format-Anfälligkeit:
// Zeile 118-119:
const xmlPattern = /<function[=\s]+[^>]+>[\s\S]*?<\/function>/g;
// Problem: Non-greedy .* kann bei mehreren Funktionen brechen
// Zudem unterschiedliche Klammer-Stile nicht gehändelt- Keine Validierung gegen knownTools:
// Zeile 173-175:
const validCalls = extracted.filter(call =>
knownTools.includes(call.function.name)
);
// Problem: Falls Filter alles removes, wird trotzdem mit leerem Array fortgefahrenImpact:
- Tool-Calls können silent dropped werden
- Agent müssen "halluzinierte" Tools aufrufen
- Error-Handling schlägt fehl
Dateien: src/llm/agent.ts (lines 142-162)
Severität: HOCH
Beschreibung:
await new Promise(resolve => setTimeout(resolve, 1000 * retryCount));
// Mit 3 Retries: 1s, 2s, 3s = 6s Wartzeit
// Aber bei maxRetries=5: könnte 1+2+3+4+5=15s sein
// Keine Jitter, keine Exponential Backoff mit BasisProblem: Deterministische Wartezeiten können zu Thundering Herd führen
Dateien: src/llm/agent.ts (lines 164-168)
Severität: HOCH
Beschreibung:
if (!response) {
throw new Error('Failed to get response after retries');
}
let assistantMessage = response.choices[0].message;
// Problem: Keine Checks für:
// - response.choices existiert?
// - response.choices[0] existiert?
// - message.content kann undefined seinDateien: src/llm/tool-format-parser.ts (lines 30-42)
Severität: HOCH
Beschreibung:
// Regex hat mehrere Gruppen, aber keine Fehlerbehandlung
// Z.B. bei malformed XML:
// "<parameter=file_path>test.txt<parameter>" (fehlendes /)
// würde vollständig gemisstFallbeispiel:
Input: <function=read_file><parameter=path>/home/test</parameter><parameter=encoding>utf-8</parameter>
Output: Korrekt geparst
Input: <function=read_file><parameter=path>/home/test</parameter><param=encoding>utf-8</parameter>
Output: Encoding wird gemisst (typo in tag name)
Dateien: src/llm/agent.ts (lines 19-41)
Severität: MITTEL
Beschreibung:
// Multiple Patterns aber:
// 1. Erstes Match wird genommen
// 2. Keine Verschachtelung möglich
// 3. Multiline-Handling könnte fehlschlagen bei CDATA
const thinkingPatterns = [
/<thinking>([\s\S]*?)<\/thinking>/i,
// ... weitere Patterns
];
// Problem:
// - Z.B. "<thinking>foo <!-- comment --> bar</thinking>"
// - würde nicht sauber geparstDateien: src/llm/sub-agent.ts (lines 104-114)
Severität: MITTEL
Beschreibung:
async executeSequential(tasks: SubAgentTask[], verbose: boolean = false): Promise<SubAgentResult[]> {
for (const task of tasks) {
const result = await this.executeTask(task, verbose);
if (!result.success) {
break; // Stoppt sofort, aber:
// - Keine Notification an Parent
// - Keine Kontextspeicherung für Retry
}
}
}Dateien: src/llm/callback-loop.ts (lines 105-114, 283-326)
Severität: MITTEL
Beschreibung:
// Scenario: Parallel Task-Ausführung
// 1. Task 1 startet, updatedAt = T1
// 2. Task 2 startet, updatedAt = T2
// 3. Task 1 updated save(), save() schreibt T1-State
// 4. Task 2 updated save(), überschreibt mit T2-State
// 5. Task 1 hatte Updates, aber Task 2 überschreibt sie
// Problem: Kein Lock-Mechanismus, nur async/awaitDateien: src/llm/sub-agent.ts (lines 170-187)
Severität: MITTEL
Beschreibung:
export const SubAgentTypes = {
CodeReviewer: {
model: 'qwen3-coder:30b', // Was wenn Modell nicht installiert?
systemPrompt: '...'
},
// ...
};
// Keine Fallback-Logik wenn Modell nicht existiert
// Sollte ModelManager.selectModelForTask() verwendenDateien: src/llm/callback-loop.ts (lines 166-236)
Severität: MITTEL
Beschreibung:
task.status = 'in_progress';
task.updatedAt = Date.now();
await this.save(); // Zwischen setzen und save() können andere Threads Task ändern
try {
// Ausführung dauert lange
const result = await agent.run(...);
} catch (error) {
// Wenn save() fehlschlägt, Status ist inkonsistent
task.error = error.message;
}Dateien: src/llm/agent.ts (lines 212-250)
Severität: MITTEL
Beschreibung:
// agent.ts line 213
const results = await this.toolManager.executeTools(assistantMessage.tool_calls);
// Problem: Wenn Validierung in tool-manager fehlschlägt:
// - Error-String wird zurückgegeben
// - Agent sieht nur Error-Message, nicht die Arguments
// - Schwer zu debuggenBeispiel:
Tool-Call: { name: 'read_file', arguments: '{"path": 123}' }
Expected: { path: string }
Error: "Zod validation failed"
// Agent weiß nicht, dass der Typ falsch war
Dateien: src/llm/agent.ts (lines 160)
Severität: MITTEL
Beschreibung:
// Mit maxRetries=3: 1s + 2s + 3s = 6s
// Mit maxRetries=5: 1s + 2s + 3s + 4s + 5s = 15s
// Keine Obergrenze wie MAX_BACKOFF_MSDateien: src/llm/agent.ts (lines 271-282)
Severität: MITTEL
Beschreibung:
async *runStream(userMessage: string, agentConfig: AgentConfig = {}): AsyncGenerator<string, void, unknown> {
// ... setup ...
const response = await this.run(userMessage, agentConfig); // Nutzt NON-STREAMING run()!
yield response;
}
// Problem:
// - Streaming wird nicht genutzt
// - Gibt nur Final-Response
// - Tools werden NICHT streamed
// - "userMessage" wird zweimal verwendet (auch in run())Dateien: src/llm/agent.ts (lines 212-213)
Severität: MITTEL
Beschreibung:
const results = await this.toolManager.executeTools(assistantMessage.tool_calls);
// Kein Timeout! Wenn ein Tool hängt:
// - Gesamter Agent blockiert
// - Kein maxDuration-Parameter
// - Parent-Process kann nicht abbrechenDateien: src/llm/agent.ts (lines 189-240)
Severität: MITTEL
Beschreibung:
if (verbose) {
console.log(`[Tool Results:] ${result.result}`);
}
// Problem:
// - Keine Sanitization
// - Secrets in File-Paths/Inhalten sichtbar
// - Logs könnten in CI/CD landen
// - Z.B. API-Keys in ArgumentsDateien: src/llm/agent.ts (lines 171-177)
Severität: NIEDRIG
Beschreibung:
if (assistantMessage.content) {
const { thinking, cleanContent } = extractThinking(assistantMessage.content);
if (thinking) {
assistantMessage.thinking = thinking; // Speichert als separate Property
assistantMessage.content = cleanContent;
}
}
// Nachbemerkung: Gutes Design, aber:
// - thinking wird NICHT über Netzwerk serialisiert
// - Wird nicht in Tool-Results persistiertDateien: src/llm/callback-loop.ts (lines 187, 207)
Severität: NIEDRIG
Beschreibung:
task.result = result; // Vollständiger String, potenziell MB groß
// Für große Projekte sind Task-Resultate später schwer zu handhaben
// Sollte nur Summary/Pointer speichernDateien: src/llm/model-manager.ts (lines 78-81)
Severität: NIEDRIG
Beschreibung:
async initialize(): Promise<void> {
const response = await this.client.listModels(); // Network-Call
this.availableModels = response.models;
}
// Problem: Wird oft aufgerufen, aber Modelle ändern sich selten
// Sollte mit TTL gecacht werden (z.B. 5 Minuten)Dateien: src/llm/agent.ts (lines 184, 249)
Metrik:
Szenario: 50 Iterationen, 3 Tools pro Iteration
- Iteration 1: +2 Messages (assistant + 3 tool_results = 1 + 3)
- Iteration 2: +2 Messages
- ...
- Iteration 50: +2 Messages
Total: ~100 Messages * ~200 Tokens durchschnitt = 20.000 Tokens
Initial Context: 4.000 Tokens
Final: 24.000 Tokens
Mit 32K Context-Fenster: 75% gefüllt nach 50 Iterationen
Lösung: History-Compression nach N Iterationen
Dateien: src/llm/agent.ts (lines 142-162)
Analyse:
Worst-Case: 3 Retries * 6 Sekunden Backoff = 18 Sekunden Wartezeit
- Für jeden fehlgeschlagenen API-Call
Mit 50 Iterations und Retry-Rate von 10%:
- 5 API-Calls würden fehlschlagen
- 5 * 18 Sekunden = 90 Sekunden nur Wartezeit!
Problem: Exponentieller Backoff ohne Jitter erzeugt Thundering Herd
Dateien: src/llm/tool-format-parser.ts (lines 18-57)
Analyse:
// Zwei konkurrierende Regex-Gruppen:
const paramRegex = /<parameter[=\s]+([^>]+)>([^<]*)<\/parameter>/g;
const altParamRegex = /<parameter=([^>]+)>([^<]*)<\/parameter>/g;
// Problem: Beide werden ausgeführt, selbst wenn erste matches
// Bei 100 Parametern: ~200 Regex-Matches
// Für jeden Tool-Call in jedem IterationDateien: src/llm/agent.ts (lines 212-213), src/tools/tool-manager.ts (lines 149-164)
Analyse:
Positiv: executeTools() ist parallel (Promise.all)
Aber Problem in agent.ts:
- Wartet auf ALL results: await this.toolManager.executeTools(...)
- Nur DANN liest Errors
- Liest Results sequenziell in console.log (lines 227-240)
- Schreibt Messages sequenziell (lines 243-250)
Impact: 1ms * 100 Messages = 100ms overhead pro Iteration
Mit 50 Iterationen = 5 Sekunden nur I/O
Dateien: src/llm/agent.ts (lines 19-41)
Analyse:
for (const pattern of thinkingPatterns) {
const match = content.match(pattern);
if (match) {
// ... extract ...
break;
}
}
// 5 Pattern-Tests gegen potenziell 10KB Response-Text
// Worst-Case: 5 * 10KB Regex-Matching
// Für jeden ResponseDateien: src/llm/sub-agent.ts (lines 46)
const subAgent = new Agent(this.config, this.toolManager, this.modelManager);
// Bei 50 parallel Tasks: 50 neue Agent-Instanzen
// Jede mit eigener conversationHistory
Memory Impact:
- Agent-Overhead: ~2KB je Instanz
- conversationHistory: 10KB+ je Instanz
- 50 * 12KB = 600KB nur für Sub-AgentsDateien: src/llm/callback-loop.ts (lines 187)
Ein Result kann sein:
- 50KB für Dateiinhalt-Analyse
- 100+ Tools-Outputs
- Bei 100 Tasks: 5MB+ in Memory
Wenn viele parallel Tasks laufen: Speicher-Druck
Problem: Unbegrenztes Wachstum der Conversation History
Lösung:
// In agent.ts nach jeder Iteration:
const MAX_HISTORY_SIZE = 40000; // tokens
const COMPRESS_THRESHOLD = 35000;
private compressHistory(): void {
const historySize = this.estimateTokens(this.conversationHistory);
if (historySize > COMPRESS_THRESHOLD) {
// Behalte system + letzten 5 Iterationen
const systemMsg = this.conversationHistory[0];
const recentMsgs = this.conversationHistory.slice(-15); // ~5 Iterationen
// Erstelle Zusammenfassung der alten Iterationen
const summary = `[Previously executed: ${this.iteration - 5} iterations successfully, maintaining context for recent work]`;
this.conversationHistory = [
systemMsg,
{ role: 'user', content: summary },
...recentMsgs
];
}
}
private estimateTokens(messages: Message[]): number {
return messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0) / 4, 0);
}Impact: Reduziert History-Overhead von 20K tokens auf ~8K
Problem: Keine Checks für Response-Struktur vor Zugriff
Lösung:
// In agent.ts, neue Methode:
private validateAndExtractMessage(response: ChatCompletionResponse): Message {
if (!response?.choices?.[0]?.message) {
throw new Error(
'Invalid API response structure: missing choices[0].message'
);
}
const message = response.choices[0].message;
if (!message.content && !message.tool_calls) {
throw new Error(
'Invalid message: neither content nor tool_calls present'
);
}
return message;
}
// Dann in run():
let assistantMessage = this.validateAndExtractMessage(response);Impact: Frühe Fehler-Erkennung, verhindert undefined-access
Problem: Zu viele False-Positives und Edge Cases
Lösung:
// Neue Datei: src/llm/tool-parser-v2.ts
class ToolCallParser {
private knownTools: Set<string>;
constructor(knownToolNames: string[]) {
this.knownTools = new Set(knownToolNames);
}
// Single regex mit klarem Format
private readonly XML_PATTERN =
/<function=([a-zA-Z_]\w*)\s*>([\s\S]*?)<\/function>/gi;
parseXML(text: string): ToolCall[] {
const calls: ToolCall[] = [];
let match;
while ((match = this.XML_PATTERN.exec(text)) !== null) {
const funcName = match[1];
// Validiere Tool-Name zuerst
if (!this.knownTools.has(funcName)) continue;
try {
const params = this.parseParameters(match[2]);
calls.push({
id: `call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
type: 'function',
function: {
name: funcName,
arguments: JSON.stringify(params)
}
});
} catch (e) {
console.warn(`Failed to parse tool call: ${funcName}`);
}
}
return calls;
}
private parseParameters(paramBlock: string): Record<string, string> {
const params: Record<string, string> = {};
const paramPattern = /<parameter=([a-zA-Z_]\w*)>\s*([^<]*?)\s*<\/parameter>/gi;
let match;
while ((match = paramPattern.exec(paramBlock)) !== null) {
params[match[1]] = match[2].trim();
}
return params;
}
}Impact: Verhindert False-Positives, bessere Error-Messages
Problem: Keine Limits für Tool-Ausführungszeit
Lösung:
// In tool-manager.ts:
async executeTool(
toolCall: ToolCall,
timeoutMs: number = 30000 // 30 Sekunden Default
): Promise<any> {
const tool = this.tools.get(toolCall.function.name);
if (!tool) {
throw new Error(`Tool not found: ${toolCall.function.name}`);
}
// Parse und Validierung
let args: any;
try {
args = JSON.parse(toolCall.function.arguments);
} catch (error) {
throw new Error(`Invalid tool arguments JSON: ${error}`);
}
const validatedArgs = tool.schema.parse(args);
// Execute mit Timeout
return Promise.race([
tool.executor(validatedArgs),
new Promise((_, reject) =>
setTimeout(
() => reject(new Error(`Tool execution timeout: ${toolCall.function.name} exceeded ${timeoutMs}ms`)),
timeoutMs
)
)
]);
}
// In agent.ts, run() Methode:
const results = await this.toolManager.executeTools(
assistantMessage.tool_calls,
this.config.toolTimeoutMs ?? 30000 // Konfigurierbar
);Impact: Verhindert Agent-Blockieren bei langsamen Tools
Problem: Deterministische Wartezeiten verursachen Thundering Herd
Lösung:
// In agent.ts, neue Methode:
private calculateBackoffMs(retryCount: number): number {
const baseMs = 1000;
const maxMs = 30000; // 30 Sekunden Maximum
// Exponential: 1s, 2s, 4s, 8s, 16s
const exponential = Math.min(baseMs * Math.pow(2, retryCount - 1), maxMs);
// Jitter: +/- 20%
const jitter = exponential * (0.8 + Math.random() * 0.4);
return Math.floor(jitter);
}
// Dann in Retry-Loop:
catch (error) {
retryCount++;
if (retryCount >= maxRetries) {
throw error;
}
const backoffMs = this.calculateBackoffMs(retryCount);
if (verbose) {
console.log(`[Retry ${retryCount}/${maxRetries}] Waiting ${backoffMs}ms...`);
}
await new Promise(resolve => setTimeout(resolve, backoffMs));
}Impact: Reduziert Peak-Load bei API-Fehlern
Problem: Schwierig zu debuggen, welche Tool-Calls hängten
Lösung:
// Neue Konfiguration in agent.ts:
interface AgentConfig {
// ... existing ...
toolCallLogging?: boolean;
toolCallLogDir?: string;
}
// In run() Methode:
if (agentConfig.toolCallLogging) {
const logDir = agentConfig.toolCallLogDir || './tool-call-logs';
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const logPath = `${logDir}/iteration-${this.iteration}-${timestamp}.json`;
await fs.writeFile(logPath, JSON.stringify({
iteration: this.iteration,
toolCalls: assistantMessage.tool_calls,
executionResults: results,
timestamp: Date.now()
}, null, 2));
}Impact: Besseres Debugging für Tool-Call-Fehler
Problem: Sequential execution stoppt bei erstem Fehler ohne Kontext
Lösung:
// In sub-agent.ts:
async executeSequential(
tasks: SubAgentTask[],
verbose: boolean = false,
stopOnError: boolean = true // Konfigurierbar
): Promise<SubAgentResult[]> {
const results: SubAgentResult[] = [];
let failedCount = 0;
for (const task of tasks) {
const result = await this.executeTask(task, verbose);
results.push(result);
if (!result.success) {
failedCount++;
if (verbose) {
console.log(`[Sub-Agent] Task ${task.id} failed: ${result.error}`);
}
if (stopOnError) {
// Report die bisherigen Ergebnisse
console.log(`[Sub-Agent] Stopping after ${failedCount} failures`);
break;
}
}
}
return results;
}Impact: Besseres Error-Handling und Reporting
Problem: Streaming-Funktion nutzt Non-Streaming run()
Lösung:
async *runStream(
userMessage: string,
agentConfig: AgentConfig = {}
): AsyncGenerator<string, void, unknown> {
const maxIterations = agentConfig.maxIterations || 50;
const model = agentConfig.model || this.modelManager.selectModelForTask('code');
if (agentConfig.systemPrompt) {
this.setSystemPrompt(agentConfig.systemPrompt);
}
this.addUserMessage(userMessage);
let iterations = 0;
while (iterations < maxIterations) {
iterations++;
// Streaming API Call
let fullResponse = '';
for await (const chunk of this.client.chatCompletionStream({
model,
messages: this.conversationHistory,
tools: this.toolManager.getToolsForOllama(),
temperature: this.config.temperature,
max_tokens: this.config.maxTokens,
})) {
if (chunk.choices?.[0]?.delta?.content) {
fullResponse += chunk.choices[0].delta.content;
yield chunk.choices[0].delta.content;
}
}
// Rest der Logik wie in run()
// ... tool_call handling, etc ...
}
}Impact: True Streaming für besseres UX
Problem: Keine Locks bei concurrent Task-Updates
Lösung:
// In callback-loop.ts:
private updateLock: Promise<void> = Promise.resolve();
private async executeTask(task: CallbackTask): Promise<void> {
// Ensure sequential updates
await this.updateLock;
this.updateLock = (async () => {
task.status = 'in_progress';
task.updatedAt = Date.now();
await this.save();
try {
// Execute task...
} finally {
task.updatedAt = Date.now();
await this.save();
}
})();
await this.updateLock;
}Impact: Verhindert State-Corruption bei parallelen Updates
Problem: Hardcoded Modelnamen, keine Fallback-Logik
Lösung:
// In sub-agent.ts:
async executeTask(task: SubAgentTask, verbose: boolean = false): Promise<SubAgentResult> {
const startTime = Date.now();
try {
const subAgent = new Agent(this.config, this.toolManager, this.modelManager);
// Use configured model oder fallback zu best available
let model = task.model;
if (!model || !this.modelManager.isModelAvailable(model)) {
model = this.modelManager.selectModelForTask('code');
if (verbose) {
console.log(`[Sub-Agent] Model ${task.model} not available, using ${model}`);
}
}
const agentConfig: AgentConfig = {
model,
systemPrompt: task.systemPrompt,
verbose,
maxIterations: 5,
};
const result = await subAgent.run(task.description, agentConfig);
return {
id: task.id,
success: true,
result,
duration: Date.now() - startTime
};
} catch (error) {
// ... error handling
}
}Impact: Robuster gegen Model-Ausfälle
Problem: Tool-Fehler sind schwer zu debuggen
Lösung:
// In tool-manager.ts:
async executeTool(toolCall: ToolCall, timeoutMs: number = 30000): Promise<any> {
const tool = this.tools.get(toolCall.function.name);
if (!tool) {
throw new Error(`Tool not found: ${toolCall.function.name}`);
}
let args: any;
try {
args = JSON.parse(toolCall.function.arguments);
} catch (error) {
const err = new Error(
`Invalid JSON in tool arguments: ${error instanceof Error ? error.message : String(error)}\n` +
`Tool: ${toolCall.function.name}\n` +
`Raw arguments: ${toolCall.function.arguments}`
);
throw err;
}
try {
const validatedArgs = tool.schema.parse(args);
return await Promise.race([
tool.executor(validatedArgs),
new Promise((_, reject) =>
setTimeout(
() => reject(new Error(`Tool timeout: ${toolCall.function.name} (${timeoutMs}ms)`)),
timeoutMs
)
)
]);
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
err.message = `Tool execution failed: ${toolCall.function.name}\n${err.message}`;
throw err;
}
}Impact: Bessere Error-Messages für Debugging
Problem: listModels() wird oft aufgerufen ohne Caching
Lösung:
// In model-manager.ts:
export class ModelManager {
private client: OllamaClient;
private config: Config;
private availableModels: OllamaModel[] = [];
private lastModelsUpdate: number = 0;
private modelsCacheTTL: number = 300000; // 5 Minuten
constructor(config: Config, cacheTTLMs: number = 300000) {
this.client = new OllamaClient(config.ollamaUrl);
this.config = config;
this.modelsCacheTTL = cacheTTLMs;
}
async initialize(): Promise<void> {
await this.refreshModelsIfNeeded();
}
private async refreshModelsIfNeeded(): Promise<void> {
const now = Date.now();
if (now - this.lastModelsUpdate > this.modelsCacheTTL) {
const response = await this.client.listModels();
this.availableModels = response.models;
this.lastModelsUpdate = now;
}
}
getAvailableModels(): OllamaModel[] {
return this.availableModels; // Cache wird verwendet
}
}Impact: Reduziert Network-Calls für Model-Info
// Neue Klasse: src/llm/agent-metrics.ts
export interface IterationMetrics {
iterationNum: number;
historySize: number;
toolCallCount: number;
executionTime: number;
apiCallDuration: number;
toolExecutionDuration: number;
}
// In agent.ts:
private metrics: IterationMetrics[] = [];
// Collect metrics jede Iteration// In agent.ts:
private sanitizeForLogging(content: string): string {
// Remove API-Keys, secrets, etc
return content
.replace(/api[_-]?key[:\s]*['"]*([^'"\s]+)/gi, 'API_KEY_REDACTED')
.replace(/token[:\s]*['"]*([^'"\s]+)/gi, 'TOKEN_REDACTED')
.replace(/password[:\s]*['"]*([^'"\s]+)/gi, 'PASSWORD_REDACTED');
}// In agent.ts:
private thinkingHistory: Array<{ iteration: number; thinking: string }> = [];
// Store thinking from each iteration
if (assistantMessage.thinking) {
this.thinkingHistory.push({
iteration: this.iteration,
thinking: assistantMessage.thinking
});
}- Response Structure Validation (2-3 Stunden)
- Message History Compression (4-5 Stunden)
- Tool Timeout Implementation (2 Stunden)
Geschätzter Aufwand: 8-10 Stunden Impact: Blockiert Crashes und Hangs verhindern
- Robust Tool Format Parser (4-5 Stunden)
- Exponential Backoff mit Jitter (2 Stunden)
- Tool Error Context Improvement (3 Stunden)
Geschätzter Aufwand: 9-10 Stunden Impact: Robustheit gegen Edge Cases
- Fix runStream() für Tools (4-5 Stunden)
- Sub-Agent Model Fallback (3 Stunden)
- CallbackLoop Race Condition (3 Stunden)
Geschätzter Aufwand: 10-11 Stunden Impact: Feature-Vollständigkeit und Race-Conditions
- ModelManager Caching
- Metrics/Tracing
- Performance Profiling
- Tool-Parser mit Edge Cases
- Message History Compression
- Backoff Calculation
- Model Fallback Logic
- Agent mit Tools über komplette Loop
- Sub-Agent Parallel/Sequential Execution
- CallbackLoop Task Processing
- 50 Iterationen mit 5+ Tools
- Sub-Agent Parallel mit 20+ Tasks
- History Compression Trigger
- Memory Usage Monitoring
- Memory Leak durch unbegrenzte Message History
- Keine Response-Struktur Validierung
- Fragiles Tool-Call Parsing
- Fehlende Timeouts für Tool-Execution
- History-Wachstum (O(n) pro Iteration)
- Sub-Agent Memory-Overhead
- Thinking-Extraction mit mehreren Regex-Tests
- Sequenzielles Message-Schreiben
- Robuste Error-Recovery
- Bessere Logging/Debugging
- Caching für Model-Info
- True Streaming für runStream()
Gesamte Löschungszeit (Phase 1+2): ~19-20 Stunden Kritikalitätsfaktor: HOCH - Diese Probleme blockieren Produktion bei längeren Runs