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 (
|
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
13
main.go
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user