tools update als fallback zu xml

This commit is contained in:
Christoph K.
2026-03-04 07:45:12 +01:00
parent c26ccce817
commit 076ab0d0c0

View File

@@ -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, "<function=") {
a.log.Debug("XML-Fallback: Parse Tool-Calls aus Content")
toolCalls = parseXMLToolCalls(choice.Message.Content)
}
// Kein Tool-Call → LLM hat nur Text geantwortet
if len(choice.Message.ToolCalls) == 0 {
if len(toolCalls) == 0 {
a.log.ChatMessage("assistant", choice.Message.Content)
nudge := "Nutze die bereitgestellten Tools. Rufe task_complete auf wenn du fertig bist."
a.log.ChatMessage("user", nudge)
@@ -161,9 +168,11 @@ func (a *AgentLoop) runTask(task prd.Task) error {
}
// Tool-Calls ausführen
for _, toolCall := range choice.Message.ToolCalls {
a.log.Info(" 🔧 %s(%s)", toolCall.Function.Name,
truncate(toolCall.Function.Arguments, 80))
for _, toolCall := range toolCalls {
a.log.Info(" 🔧 %s(%s)",
toolCall.Function.Name,
truncate(toolCall.Function.Arguments, 80),
)
result, done := executor.Execute(toolCall)
a.log.ChatMessage("tool",
@@ -173,7 +182,7 @@ func (a *AgentLoop) runTask(task prd.Task) error {
messages = append(messages, openai.ToolMessage(result, toolCall.ID))
if done {
return nil // task_complete aufgerufen → Erfolg
return nil // task_complete → Erfolg
}
}
}
@@ -181,6 +190,98 @@ func (a *AgentLoop) runTask(task prd.Task) error {
return fmt.Errorf("maximale Turns (%d) erreicht", maxTurns)
}
// ─── XML Fallback Parser ──────────────────────────────────
// parseXMLToolCalls parst Tool-Calls im XML-Format das manche Modelle nutzen:
//
// <function=write_file>
// <parameter=path>hello.go</parameter>
// <parameter=content>package main...</parameter>
// </function>
func parseXMLToolCalls(content string) []openai.ChatCompletionMessageToolCall {
var calls []openai.ChatCompletionMessageToolCall
remaining := content
callID := 0
for {
// Funktionsname extrahieren
start := strings.Index(remaining, "<function=")
if start == -1 {
break
}
nameStart := start + len("<function=")
nameEnd := strings.Index(remaining[nameStart:], ">")
if nameEnd == -1 {
break
}
funcName := strings.TrimSpace(remaining[nameStart : nameStart+nameEnd])
// Block bis </function> extrahieren
blockEnd := strings.Index(remaining, "</function>")
if blockEnd == -1 {
break
}
block := remaining[start : blockEnd+len("</function>")]
// Parameter extrahieren und als JSON serialisieren
params := extractXMLParams(block)
argsJSON, err := json.Marshal(params)
if err != nil {
remaining = remaining[blockEnd+len("</function>"):]
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("</function>"):]
}
return calls
}
// extractXMLParams extrahiert alle <parameter=key>value</parameter> aus einem Block
func extractXMLParams(block string) map[string]string {
params := make(map[string]string)
remaining := block
for {
start := strings.Index(remaining, "<parameter=")
if start == -1 {
break
}
// Key extrahieren
keyStart := start + len("<parameter=")
keyEnd := strings.Index(remaining[keyStart:], ">")
if keyEnd == -1 {
break
}
key := strings.TrimSpace(remaining[keyStart : keyStart+keyEnd])
// Value extrahieren
valueStart := keyStart + keyEnd + 1
closeTag := "</parameter>"
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()
}