diff --git a/agent/loop.go b/agent/loop.go index b96a655..a546022 100644 --- a/agent/loop.go +++ b/agent/loop.go @@ -2,6 +2,7 @@ package agent import ( "context" + "encoding/json" "fmt" "strings" "time" @@ -100,7 +101,6 @@ func (a *AgentLoop) Run() error { func (a *AgentLoop) runTask(task prd.Task) error { executor := NewToolExecutor(a.workDir) - // Frischer Kontext pro Task messages := []openai.ChatCompletionMessageParamUnion{ openai.SystemMessage(systemPrompt), openai.UserMessage(fmt.Sprintf( @@ -133,7 +133,7 @@ func (a *AgentLoop) runTask(task prd.Task) error { openai.ChatCompletionNewParams{ Model: a.model, Messages: messages, - Tools: Tools, // ← Tool Calling + Tools: Tools, }, ) cancel() @@ -147,12 +147,19 @@ func (a *AgentLoop) runTask(task prd.Task) error { } choice := resp.Choices[0] - - // Antwort zur History hinzufΓΌgen messages = append(messages, choice.Message.ToParam()) + // Echte Tool-Calls vom SDK + toolCalls := choice.Message.ToolCalls + + // Fallback: XML-Format parsen wenn Modell kein natives Tool Calling nutzt + if len(toolCalls) == 0 && strings.Contains(choice.Message.Content, " +// hello.go +// package main... +// +func parseXMLToolCalls(content string) []openai.ChatCompletionMessageToolCall { + var calls []openai.ChatCompletionMessageToolCall + remaining := content + callID := 0 + + for { + // Funktionsname extrahieren + start := strings.Index(remaining, "") + if nameEnd == -1 { + break + } + funcName := strings.TrimSpace(remaining[nameStart : nameStart+nameEnd]) + + // Block bis extrahieren + blockEnd := strings.Index(remaining, "") + if blockEnd == -1 { + break + } + block := remaining[start : blockEnd+len("")] + + // Parameter extrahieren und als JSON serialisieren + params := extractXMLParams(block) + argsJSON, err := json.Marshal(params) + if err != nil { + remaining = remaining[blockEnd+len(""):] + continue + } + + callID++ + calls = append(calls, openai.ChatCompletionMessageToolCall{ + ID: fmt.Sprintf("xml-call-%d", callID), + Type: "function", + Function: openai.ChatCompletionMessageToolCallFunction{ + Name: funcName, + Arguments: string(argsJSON), + }, + }) + + remaining = remaining[blockEnd+len(""):] + } + + return calls +} + +// extractXMLParams extrahiert alle value aus einem Block +func extractXMLParams(block string) map[string]string { + params := make(map[string]string) + remaining := block + + for { + start := strings.Index(remaining, "") + if keyEnd == -1 { + break + } + key := strings.TrimSpace(remaining[keyStart : keyStart+keyEnd]) + + // Value extrahieren + valueStart := keyStart + keyEnd + 1 + closeTag := "" + valueEnd := strings.Index(remaining[valueStart:], closeTag) + if valueEnd == -1 { + break + } + value := strings.TrimSpace(remaining[valueStart : valueStart+valueEnd]) + + params[key] = value + remaining = remaining[valueStart+valueEnd+len(closeTag):] + } + + return params +} + // ─── Hilfsfunktionen ───────────────────────────────────── func truncate(s string, max int) string { @@ -225,7 +326,6 @@ func formatResponse(resp *openai.ChatCompletion, elapsed time.Duration) string { resp.Usage.TotalTokens, )) - // Tool-Calls anzeigen if len(resp.Choices[0].Message.ToolCalls) > 0 { sb.WriteString(" Tool-Calls :\n") for _, tc := range resp.Choices[0].Message.ToolCalls { @@ -234,12 +334,12 @@ func formatResponse(resp *openai.ChatCompletion, elapsed time.Duration) string { truncate(tc.Function.Arguments, 100), )) } - } else { - content := resp.Choices[0].Message.Content + } else if content := resp.Choices[0].Message.Content; content != "" { sb.WriteString(" Content :\n") for _, line := range strings.Split(content, "\n") { sb.WriteString(fmt.Sprintf(" %s\n", line)) } } + return sb.String() }