package agent import ( "context" "fmt" "strings" "time" "github.com/openai/openai-go" oaioption "github.com/openai/openai-go/option" "llm-agent/prd" ) const ( baseURL = "http://127.0.0.1:12434/v1" maxRetries = 3 maxTurns = 10 // Sicherheitslimit pro Task ) var systemPrompt = `Du bist ein autonomer Coding-Agent. Du bekommst einen Task und erledigst ihn vollständig. Du hast folgende Tools zur Verfügung: - TOOL:READ_FILE:pfad → Datei lesen - TOOL:WRITE_FILE:pfad:inhalt → Datei schreiben - TOOL:LIST_FILES:pfad → Verzeichnis auflisten Regeln: 1. Analysiere den Task zuerst 2. Nutze die Tools um Dateien zu lesen/schreiben 3. Wenn der Task vollständig erledigt ist, schreibe am Ende: TASK_COMPLETE 4. Bei Fehlern beschreibe das Problem klar` type AgentLoop struct { client *openai.Client model string workDir string prdFile string } func NewAgentLoop(model, workDir, prdFile string) *AgentLoop { client := openai.NewClient( oaioption.WithBaseURL(baseURL), oaioption.WithAPIKey("ollama"), ) return &AgentLoop{ client: &client, model: model, workDir: workDir, prdFile: prdFile, } } func (a *AgentLoop) Run() error { tasks, err := prd.ParseTasks(a.prdFile) if err != nil { return fmt.Errorf("PRD lesen fehlgeschlagen: %w", err) } pending := 0 for _, t := range tasks { if !t.Completed { pending++ } } fmt.Printf("📋 %d Tasks gefunden, %d offen\n\n", len(tasks), pending) for _, task := range tasks { if task.Completed { fmt.Printf("✅ Überspringe (bereits erledigt): %s\n", task.Title) continue } fmt.Printf("\n🔄 Starte Task: %s\n", task.Title) fmt.Println(strings.Repeat("─", 50)) success := false for attempt := 1; attempt <= maxRetries; attempt++ { if attempt > 1 { fmt.Printf("🔁 Retry %d/%d...\n", attempt, maxRetries) time.Sleep(time.Duration(attempt) * 2 * time.Second) // Backoff } err := a.runTask(task) if err == nil { success = true break } fmt.Printf("⚠️ Fehler: %v\n", err) } if success { prd.MarkTaskComplete(a.prdFile, task.Title) fmt.Printf("✅ Task abgeschlossen: %s\n", task.Title) } else { fmt.Printf("❌ Task fehlgeschlagen nach %d Versuchen: %s\n", maxRetries, task.Title) } } fmt.Println("\n🎉 Alle Tasks abgearbeitet!") return nil } func (a *AgentLoop) runTask(task prd.Task) error { // FRISCHER Kontext für jeden Task messages := []openai.ChatCompletionMessageParamUnion{ openai.SystemMessage(systemPrompt), openai.UserMessage(fmt.Sprintf("Task: %s\nArbeitsverzeichnis: %s", task.Title, a.workDir)), } for turn := 0; turn < maxTurns; turn++ { fmt.Printf(" 💭 Turn %d...\n", turn+1) resp, err := a.client.Chat.Completions.New( context.Background(), openai.ChatCompletionNewParams{ Model: a.model, Messages: messages, }, ) if err != nil { return fmt.Errorf("API-Fehler: %w", err) } response := resp.Choices[0].Message.Content fmt.Printf(" 🤖 %s\n", truncate(response, 200)) // Antwort zur History hinzufügen messages = append(messages, openai.AssistantMessage(response)) // Completion Detection: Layer 1 - Signal Token if strings.Contains(response, "TASK_COMPLETE") { return nil // Erfolg! } // Tool Execution toolOutput, hadTools := ExecuteTools(response, a.workDir) if hadTools { fmt.Printf(" 🔧 Tool-Output: %s\n", truncate(toolOutput, 150)) // Tool-Ergebnis zurück ans LLM messages = append(messages, openai.UserMessage(toolOutput)) continue } // Kein Tool, kein TASK_COMPLETE → LLM anstupsen messages = append(messages, openai.UserMessage( "Bitte fahre fort. Wenn der Task erledigt ist, schreibe TASK_COMPLETE.", )) } return fmt.Errorf("maximale Turns (%d) erreicht ohne TASK_COMPLETE", maxTurns) } func truncate(s string, max int) string { if len(s) <= max { return s } return s[:max] + "..." }