From c43257b7be10c41e8b934a902d9e1b54a15a707a Mon Sep 17 00:00:00 2001 From: "Christoph K." Date: Tue, 3 Mar 2026 22:24:17 +0100 Subject: [PATCH] loop verbessert --- agent/loop.go | 94 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 11 deletions(-) diff --git a/agent/loop.go b/agent/loop.go index d3dc08e..be4f9e9 100644 --- a/agent/loop.go +++ b/agent/loop.go @@ -18,20 +18,16 @@ const ( maxTurns = 10 ) -var systemPrompt = `Du bist ein Coding-Agent. Erledige den gegebenen Task. - -TOOLS: +var systemPrompt = `Du bist ein Coding-Agent und programmierst Go. +Erledige deine Aufgabe mit folgenden Tools: TOOL:READ_FILE:pfad -TOOL:WRITE_FILE:pfad -<<< -inhalt ->>> +TOOL:WRITE_FILE:pfad:<<>> TOOL:LIST_FILES:pfad REGELN: - Nutze relative Pfade - Kein Markdown in Dateiinhalten -- Task erledigt: schreibe TASK_COMPLETE` +- Wenn Task erledigt: schreibe nur TASK_COMPLETE` type AgentLoop struct { client *openai.Client @@ -131,8 +127,7 @@ func (a *AgentLoop) runTask(task prd.Task) error { totalChars += len(fmt.Sprintf("%v", m)) } start := time.Now() - a.log.Debug("MODEL REQUEST: model=%s totalChars=%d messages=%#v", a.model, totalChars, messages) - + a.log.Debug("MODEL REQUEST: model=%s ~%d Zeichen\n%s", a.model, totalChars, formatMessages(messages)) resp, err := a.client.Chat.Completions.New( context.Background(), openai.ChatCompletionNewParams{ @@ -141,7 +136,9 @@ func (a *AgentLoop) runTask(task prd.Task) error { }, ) elapsed := time.Since(start) - a.log.Debug("MODEL RESPONSE (elapsed=%s): %#v", elapsed, resp) + if resp != nil && len(resp.Choices) > 0 { + a.log.Debug("MODEL RESPONSE\n%s", formatResponse(resp, elapsed)) + } if err != nil { return fmt.Errorf("API-Fehler (~%d Zeichen im Kontext): %w", totalChars, err) } @@ -152,6 +149,13 @@ func (a *AgentLoop) runTask(task prd.Task) error { // Completion Detection if isTaskComplete(response) { + if turn == 0 { + // LLM hat sofort TASK_COMPLETE ohne Tool-Call → nichts wurde getan + nudge := "Du hast die Datei noch nicht erstellt! Nutze zuerst WRITE_FILE, dann schreibe TASK_COMPLETE." + a.log.ChatMessage("user", nudge) + messages = append(messages, openai.UserMessage(nudge)) + continue // nächster Turn + } return nil } @@ -192,3 +196,71 @@ func isTaskComplete(response string) bool { } return false } + +// formatMessages gibt die Chat-History lesbar aus +func formatMessages(messages []openai.ChatCompletionMessageParamUnion) string { + var sb strings.Builder + for i, m := range messages { + var role, content string + + switch { + case m.OfSystem != nil: + role = "system" + if len(m.OfSystem.Content.OfString.Value) > 0 { + content = m.OfSystem.Content.OfString.Value + } + case m.OfUser != nil: + role = "user" + if len(m.OfUser.Content.OfString.Value) > 0 { + content = m.OfUser.Content.OfString.Value + } + case m.OfAssistant != nil: + role = "assistant" + if len(m.OfAssistant.Content.OfString.Value) > 0 { + content = m.OfAssistant.Content.OfString.Value + } + default: + role = "unknown" + content = fmt.Sprintf("%+v", m) + } + + // Inhalt auf 120 Zeichen kürzen für Übersicht + preview := content + if len(preview) > 120 { + preview = preview[:120] + "..." + } + // Zeilenumbrüche für einzeilige Darstellung ersetzen + preview = strings.ReplaceAll(preview, "\n", "↵") + + sb.WriteString(fmt.Sprintf(" [%d] %-10s : %s\n", i, role, preview)) + } + return sb.String() +} + +func formatResponse(resp *openai.ChatCompletion, elapsed time.Duration) string { + var sb strings.Builder + + sb.WriteString(fmt.Sprintf(" ID : %s\n", resp.ID)) + sb.WriteString(fmt.Sprintf(" Modell : %s\n", resp.Model)) + sb.WriteString(fmt.Sprintf(" Elapsed : %s\n", elapsed.Round(time.Millisecond))) + sb.WriteString(fmt.Sprintf(" Finish-Reason : %s\n", resp.Choices[0].FinishReason)) + sb.WriteString(fmt.Sprintf(" Tokens : prompt=%d completion=%d total=%d\n", + resp.Usage.PromptTokens, + resp.Usage.CompletionTokens, + resp.Usage.TotalTokens, + )) + + // Tokens/Sekunde aus den Timing-Daten (Ollama-spezifisch) + if timings, ok := resp.JSON.ExtraFields["timings"]; ok { + sb.WriteString(fmt.Sprintf(" Timings : %s\n", timings.Raw())) + } + + sb.WriteString(fmt.Sprintf(" Content :\n")) + // Inhalt eingerückt und vollständig ausgeben + content := resp.Choices[0].Message.Content + for _, line := range strings.Split(content, "\n") { + sb.WriteString(fmt.Sprintf(" %s\n", line)) + } + + return sb.String() +}