init code
This commit is contained in:
159
agent/loop.go
Normal file
159
agent/loop.go
Normal file
@@ -0,0 +1,159 @@
|
||||
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] + "..."
|
||||
}
|
||||
Reference in New Issue
Block a user