tools update als fallback zu xml
This commit is contained in:
124
agent/loop.go
124
agent/loop.go
@@ -2,6 +2,7 @@ package agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -100,7 +101,6 @@ func (a *AgentLoop) Run() error {
|
|||||||
func (a *AgentLoop) runTask(task prd.Task) error {
|
func (a *AgentLoop) runTask(task prd.Task) error {
|
||||||
executor := NewToolExecutor(a.workDir)
|
executor := NewToolExecutor(a.workDir)
|
||||||
|
|
||||||
// Frischer Kontext pro Task
|
|
||||||
messages := []openai.ChatCompletionMessageParamUnion{
|
messages := []openai.ChatCompletionMessageParamUnion{
|
||||||
openai.SystemMessage(systemPrompt),
|
openai.SystemMessage(systemPrompt),
|
||||||
openai.UserMessage(fmt.Sprintf(
|
openai.UserMessage(fmt.Sprintf(
|
||||||
@@ -133,7 +133,7 @@ func (a *AgentLoop) runTask(task prd.Task) error {
|
|||||||
openai.ChatCompletionNewParams{
|
openai.ChatCompletionNewParams{
|
||||||
Model: a.model,
|
Model: a.model,
|
||||||
Messages: messages,
|
Messages: messages,
|
||||||
Tools: Tools, // ← Tool Calling
|
Tools: Tools,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
cancel()
|
cancel()
|
||||||
@@ -147,12 +147,19 @@ func (a *AgentLoop) runTask(task prd.Task) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
choice := resp.Choices[0]
|
choice := resp.Choices[0]
|
||||||
|
|
||||||
// Antwort zur History hinzufügen
|
|
||||||
messages = append(messages, choice.Message.ToParam())
|
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
|
// 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)
|
a.log.ChatMessage("assistant", choice.Message.Content)
|
||||||
nudge := "Nutze die bereitgestellten Tools. Rufe task_complete auf wenn du fertig bist."
|
nudge := "Nutze die bereitgestellten Tools. Rufe task_complete auf wenn du fertig bist."
|
||||||
a.log.ChatMessage("user", nudge)
|
a.log.ChatMessage("user", nudge)
|
||||||
@@ -161,9 +168,11 @@ func (a *AgentLoop) runTask(task prd.Task) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tool-Calls ausführen
|
// Tool-Calls ausführen
|
||||||
for _, toolCall := range choice.Message.ToolCalls {
|
for _, toolCall := range toolCalls {
|
||||||
a.log.Info(" 🔧 %s(%s)", toolCall.Function.Name,
|
a.log.Info(" 🔧 %s(%s)",
|
||||||
truncate(toolCall.Function.Arguments, 80))
|
toolCall.Function.Name,
|
||||||
|
truncate(toolCall.Function.Arguments, 80),
|
||||||
|
)
|
||||||
|
|
||||||
result, done := executor.Execute(toolCall)
|
result, done := executor.Execute(toolCall)
|
||||||
a.log.ChatMessage("tool",
|
a.log.ChatMessage("tool",
|
||||||
@@ -173,7 +182,7 @@ func (a *AgentLoop) runTask(task prd.Task) error {
|
|||||||
messages = append(messages, openai.ToolMessage(result, toolCall.ID))
|
messages = append(messages, openai.ToolMessage(result, toolCall.ID))
|
||||||
|
|
||||||
if done {
|
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)
|
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 ─────────────────────────────────────
|
// ─── Hilfsfunktionen ─────────────────────────────────────
|
||||||
|
|
||||||
func truncate(s string, max int) string {
|
func truncate(s string, max int) string {
|
||||||
@@ -225,7 +326,6 @@ func formatResponse(resp *openai.ChatCompletion, elapsed time.Duration) string {
|
|||||||
resp.Usage.TotalTokens,
|
resp.Usage.TotalTokens,
|
||||||
))
|
))
|
||||||
|
|
||||||
// Tool-Calls anzeigen
|
|
||||||
if len(resp.Choices[0].Message.ToolCalls) > 0 {
|
if len(resp.Choices[0].Message.ToolCalls) > 0 {
|
||||||
sb.WriteString(" Tool-Calls :\n")
|
sb.WriteString(" Tool-Calls :\n")
|
||||||
for _, tc := range resp.Choices[0].Message.ToolCalls {
|
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),
|
truncate(tc.Function.Arguments, 100),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else if content := resp.Choices[0].Message.Content; content != "" {
|
||||||
content := resp.Choices[0].Message.Content
|
|
||||||
sb.WriteString(" Content :\n")
|
sb.WriteString(" Content :\n")
|
||||||
for _, line := range strings.Split(content, "\n") {
|
for _, line := range strings.Split(content, "\n") {
|
||||||
sb.WriteString(fmt.Sprintf(" %s\n", line))
|
sb.WriteString(fmt.Sprintf(" %s\n", line))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user