loggin added

This commit is contained in:
Christoph K.
2026-02-25 12:19:14 +01:00
parent ba045478b3
commit f26005fda6
3 changed files with 110 additions and 35 deletions

69
agent/logger.go Normal file
View File

@@ -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)
}
}

View File

@@ -15,7 +15,7 @@ import (
const ( const (
baseURL = "http://127.0.0.1:12434/v1" baseURL = "http://127.0.0.1:12434/v1"
maxRetries = 3 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. 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 model string
workDir string workDir string
prdFile string prdFile string
log *Logger
} }
func NewAgentLoop(model, workDir, prdFile string) *AgentLoop { func NewAgentLoop(model, workDir, prdFile string, verbose bool) *AgentLoop {
client := openai.NewClient( client := openai.NewClient(
oaioption.WithBaseURL(baseURL), oaioption.WithBaseURL(baseURL),
oaioption.WithAPIKey("ollama"), oaioption.WithAPIKey("ollama"),
@@ -48,6 +49,7 @@ func NewAgentLoop(model, workDir, prdFile string) *AgentLoop {
model: model, model: model,
workDir: workDir, workDir: workDir,
prdFile: prdFile, prdFile: prdFile,
log: NewLogger(verbose),
} }
} }
@@ -63,53 +65,56 @@ func (a *AgentLoop) Run() error {
pending++ 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 { for _, task := range tasks {
if task.Completed { if task.Completed {
fmt.Printf("✅ Überspringe (bereits erledigt): %s\n", task.Title) a.log.Info("✅ Überspringe (bereits erledigt): %s", task.Title)
continue continue
} }
fmt.Printf("\n🔄 Starte Task: %s\n", task.Title) a.log.TaskStart(task.Title)
fmt.Println(strings.Repeat("─", 50))
success := false success := false
for attempt := 1; attempt <= maxRetries; attempt++ { for attempt := 1; attempt <= maxRetries; attempt++ {
if attempt > 1 { if attempt > 1 {
fmt.Printf("🔁 Retry %d/%d...\n", attempt, maxRetries) a.log.Info("🔁 Retry %d/%d...", attempt, maxRetries)
time.Sleep(time.Duration(attempt) * 2 * time.Second) // Backoff time.Sleep(time.Duration(attempt) * 2 * time.Second)
} }
err := a.runTask(task) if err := a.runTask(task); err == nil {
if err == nil {
success = true success = true
break break
} else {
a.log.Info("⚠️ Fehler: %v", err)
} }
fmt.Printf("⚠️ Fehler: %v\n", err)
} }
if success { if success {
prd.MarkTaskComplete(a.prdFile, task.Title) prd.MarkTaskComplete(a.prdFile, task.Title)
fmt.Printf("✅ Task abgeschlossen: %s\n", task.Title) a.log.TaskDone(task.Title)
} else { } 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 return nil
} }
func (a *AgentLoop) runTask(task prd.Task) error { func (a *AgentLoop) runTask(task prd.Task) error {
// FRISCHER Kontext für jeden Task // Frischer Kontext pro Task
messages := []openai.ChatCompletionMessageParamUnion{ messages := []openai.ChatCompletionMessageParamUnion{
openai.SystemMessage(systemPrompt), openai.SystemMessage(systemPrompt),
openai.UserMessage(fmt.Sprintf("Task: %s\nArbeitsverzeichnis: %s", task.Title, a.workDir)), 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++ { 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( resp, err := a.client.Chat.Completions.New(
context.Background(), context.Background(),
@@ -123,37 +128,27 @@ func (a *AgentLoop) runTask(task prd.Task) error {
} }
response := resp.Choices[0].Message.Content response := resp.Choices[0].Message.Content
fmt.Printf(" 🤖 %s\n", truncate(response, 200)) a.log.ChatMessage("assistant", response)
// Antwort zur History hinzufügen
messages = append(messages, openai.AssistantMessage(response)) messages = append(messages, openai.AssistantMessage(response))
// Completion Detection: Layer 1 - Signal Token // Completion Detection
if strings.Contains(response, "TASK_COMPLETE") { if strings.Contains(response, "TASK_COMPLETE") {
return nil // Erfolg! return nil
} }
// Tool Execution // Tool Execution
toolOutput, hadTools := ExecuteTools(response, a.workDir) toolOutput, hadTools := ExecuteTools(response, a.workDir)
if hadTools { if hadTools {
fmt.Printf(" 🔧 Tool-Output: %s\n", truncate(toolOutput, 150)) a.log.ChatMessage("tool", toolOutput)
// Tool-Ergebnis zurück ans LLM
messages = append(messages, openai.UserMessage(toolOutput)) messages = append(messages, openai.UserMessage(toolOutput))
continue continue
} }
// Kein Tool, kein TASK_COMPLETE → LLM anstupsen // Kein Tool, kein TASK_COMPLETE → anstupsen
messages = append(messages, openai.UserMessage( nudge := "Bitte fahre fort. Wenn der Task erledigt ist, schreibe TASK_COMPLETE."
"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) 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] + "..."
}

13
main.go
View File

@@ -3,6 +3,7 @@ package main
import ( import (
"bufio" "bufio"
"context" "context"
"flag"
"fmt" "fmt"
"log" "log"
"os" "os"
@@ -52,15 +53,25 @@ func selectModel(client *openai.Client) string {
} }
func main() { 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( client := openai.NewClient(
oaioption.WithBaseURL(baseURL), oaioption.WithBaseURL(baseURL),
oaioption.WithAPIKey("ollama"), oaioption.WithAPIKey("ollama"),
) )
fmt.Println("🤖 LLM Agent") fmt.Println("🤖 LLM Agent")
if *verbose {
fmt.Println("🔍 Verbose-Modus aktiv")
}
model := selectModel(&client) model := selectModel(&client)
loop := agent.NewAgentLoop(model, ".", "PRD.md") loop := agent.NewAgentLoop(model, *workDir, *prdFile, *verbose)
if err := loop.Run(); err != nil { if err := loop.Run(); err != nil {
log.Fatalf("Agent fehlgeschlagen: %v", err) log.Fatalf("Agent fehlgeschlagen: %v", err)
} }