From f26005fda6d05b884d1a72b4dc01492affeacf46 Mon Sep 17 00:00:00 2001 From: "Christoph K." Date: Wed, 25 Feb 2026 12:19:14 +0100 Subject: [PATCH] loggin added --- agent/logger.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ agent/loop.go | 63 +++++++++++++++++++++----------------------- main.go | 13 +++++++++- 3 files changed, 110 insertions(+), 35 deletions(-) create mode 100644 agent/logger.go diff --git a/agent/logger.go b/agent/logger.go new file mode 100644 index 0000000..66142ef --- /dev/null +++ b/agent/logger.go @@ -0,0 +1,69 @@ +package agent + +import ( + "fmt" + "strings" +) + +type Logger struct { + verbose bool +} + +func NewLogger(verbose bool) *Logger { + return &Logger{verbose: verbose} +} + +func (l *Logger) Info(format string, args ...any) { + fmt.Printf(format+"\n", args...) +} + +func (l *Logger) ChatMessage(role string, content string) { + if !l.verbose { + return + } + + width := 60 + var icon, border string + + switch role { + case "system": + icon = "⚙️ SYSTEM" + border = strings.Repeat("═", width) + case "user": + icon = "👤 USER" + border = strings.Repeat("─", width) + case "assistant": + icon = "🤖 ASSISTANT" + border = strings.Repeat("─", width) + case "tool": + icon = "🔧 TOOL RESULT" + border = strings.Repeat("·", width) + } + + fmt.Printf("\n%s\n", border) + fmt.Printf(" %s\n", icon) + fmt.Printf("%s\n", border) + fmt.Println(content) + fmt.Printf("%s\n", border) +} + +func (l *Logger) TaskStart(title string) { + fmt.Printf("\n🔄 Starte Task: %s\n", title) + fmt.Println(strings.Repeat("─", 60)) +} + +func (l *Logger) TaskDone(title string) { + fmt.Printf("✅ Task abgeschlossen: %s\n", title) +} + +func (l *Logger) TaskFailed(title string, retries int) { + fmt.Printf("❌ Task fehlgeschlagen nach %d Versuchen: %s\n", retries, title) +} + +func (l *Logger) Turn(n int) { + if l.verbose { + fmt.Printf("\n┌─ Turn %d %s\n", n, strings.Repeat("─", 50)) + } else { + fmt.Printf(" 💭 Turn %d...\n", n) + } +} diff --git a/agent/loop.go b/agent/loop.go index 4d0d155..57e50fe 100644 --- a/agent/loop.go +++ b/agent/loop.go @@ -15,7 +15,7 @@ import ( const ( baseURL = "http://127.0.0.1:12434/v1" maxRetries = 3 - maxTurns = 10 // Sicherheitslimit pro Task + maxTurns = 10 ) var systemPrompt = `Du bist ein autonomer Coding-Agent. Du bekommst einen Task und erledigst ihn vollständig. @@ -36,9 +36,10 @@ type AgentLoop struct { model string workDir string prdFile string + log *Logger } -func NewAgentLoop(model, workDir, prdFile string) *AgentLoop { +func NewAgentLoop(model, workDir, prdFile string, verbose bool) *AgentLoop { client := openai.NewClient( oaioption.WithBaseURL(baseURL), oaioption.WithAPIKey("ollama"), @@ -48,6 +49,7 @@ func NewAgentLoop(model, workDir, prdFile string) *AgentLoop { model: model, workDir: workDir, prdFile: prdFile, + log: NewLogger(verbose), } } @@ -63,53 +65,56 @@ func (a *AgentLoop) Run() error { pending++ } } - fmt.Printf("📋 %d Tasks gefunden, %d offen\n\n", len(tasks), pending) + a.log.Info("📋 %d Tasks gefunden, %d offen", len(tasks), pending) for _, task := range tasks { if task.Completed { - fmt.Printf("✅ Überspringe (bereits erledigt): %s\n", task.Title) + a.log.Info("✅ Überspringe (bereits erledigt): %s", task.Title) continue } - fmt.Printf("\n🔄 Starte Task: %s\n", task.Title) - fmt.Println(strings.Repeat("─", 50)) + a.log.TaskStart(task.Title) 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 + a.log.Info("🔁 Retry %d/%d...", attempt, maxRetries) + time.Sleep(time.Duration(attempt) * 2 * time.Second) } - err := a.runTask(task) - if err == nil { + if err := a.runTask(task); err == nil { success = true break + } else { + a.log.Info("⚠️ Fehler: %v", err) } - fmt.Printf("⚠️ Fehler: %v\n", err) } if success { prd.MarkTaskComplete(a.prdFile, task.Title) - fmt.Printf("✅ Task abgeschlossen: %s\n", task.Title) + a.log.TaskDone(task.Title) } else { - fmt.Printf("❌ Task fehlgeschlagen nach %d Versuchen: %s\n", maxRetries, task.Title) + a.log.TaskFailed(task.Title, maxRetries) } } - fmt.Println("\n🎉 Alle Tasks abgearbeitet!") + a.log.Info("\n🎉 Alle Tasks abgearbeitet!") return nil } func (a *AgentLoop) runTask(task prd.Task) error { - // FRISCHER Kontext für jeden Task + // Frischer Kontext pro Task messages := []openai.ChatCompletionMessageParamUnion{ openai.SystemMessage(systemPrompt), openai.UserMessage(fmt.Sprintf("Task: %s\nArbeitsverzeichnis: %s", task.Title, a.workDir)), } + // System-Prompt und initialen User-Message loggen + a.log.ChatMessage("system", systemPrompt) + a.log.ChatMessage("user", fmt.Sprintf("Task: %s\nArbeitsverzeichnis: %s", task.Title, a.workDir)) + for turn := 0; turn < maxTurns; turn++ { - fmt.Printf(" 💭 Turn %d...\n", turn+1) + a.log.Turn(turn + 1) resp, err := a.client.Chat.Completions.New( context.Background(), @@ -123,37 +128,27 @@ func (a *AgentLoop) runTask(task prd.Task) error { } response := resp.Choices[0].Message.Content - fmt.Printf(" 🤖 %s\n", truncate(response, 200)) - - // Antwort zur History hinzufügen + a.log.ChatMessage("assistant", response) messages = append(messages, openai.AssistantMessage(response)) - // Completion Detection: Layer 1 - Signal Token + // Completion Detection if strings.Contains(response, "TASK_COMPLETE") { - return nil // Erfolg! + return nil } // 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 + a.log.ChatMessage("tool", toolOutput) 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.", - )) + // Kein Tool, kein TASK_COMPLETE → anstupsen + nudge := "Bitte fahre fort. Wenn der Task erledigt ist, schreibe TASK_COMPLETE." + a.log.ChatMessage("user", nudge) + messages = append(messages, openai.UserMessage(nudge)) } 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] + "..." -} diff --git a/main.go b/main.go index d79449d..3686a73 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "bufio" "context" + "flag" "fmt" "log" "os" @@ -52,15 +53,25 @@ func selectModel(client *openai.Client) string { } func main() { + // Flags definieren + verbose := flag.Bool("verbose", false, "Zeigt alle Chat-Nachrichten vollständig an") + prdFile := flag.String("prd", "PRD.md", "Pfad zur PRD-Datei") + workDir := flag.String("dir", ".", "Arbeitsverzeichnis") + flag.Parse() + client := openai.NewClient( oaioption.WithBaseURL(baseURL), oaioption.WithAPIKey("ollama"), ) fmt.Println("🤖 LLM Agent") + if *verbose { + fmt.Println("🔍 Verbose-Modus aktiv") + } + model := selectModel(&client) - loop := agent.NewAgentLoop(model, ".", "PRD.md") + loop := agent.NewAgentLoop(model, *workDir, *prdFile, *verbose) if err := loop.Run(); err != nil { log.Fatalf("Agent fehlgeschlagen: %v", err) }