loggin added
This commit is contained in:
69
agent/logger.go
Normal file
69
agent/logger.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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] + "..."
|
||||
}
|
||||
|
||||
13
main.go
13
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user