updated DS compat
This commit is contained in:
@@ -835,7 +835,13 @@ type streamDelta struct {
|
|||||||
func cleanMessagesForLLM(msgs []Message) []Message {
|
func cleanMessagesForLLM(msgs []Message) []Message {
|
||||||
out := make([]Message, len(msgs))
|
out := make([]Message, len(msgs))
|
||||||
for i, m := range msgs {
|
for i, m := range msgs {
|
||||||
out[i] = Message{Role: m.Role, Content: m.Content, ToolCalls: m.ToolCalls, ToolCallID: m.ToolCallID}
|
out[i] = Message{
|
||||||
|
Role: m.Role,
|
||||||
|
Content: m.Content,
|
||||||
|
ReasoningContent: m.ReasoningContent,
|
||||||
|
ToolCalls: m.ToolCalls,
|
||||||
|
ToolCallID: m.ToolCallID,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
@@ -855,7 +861,8 @@ func filterText(s string) string {
|
|||||||
|
|
||||||
func sanitizeMessages(msgs []Message) {
|
func sanitizeMessages(msgs []Message) {
|
||||||
for i := range msgs {
|
for i := range msgs {
|
||||||
if msgs[i].Role == "assistant" && len(msgs[i].ToolCalls) > 0 {
|
if msgs[i].Role == "assistant" {
|
||||||
|
if len(msgs[i].ToolCalls) > 0 {
|
||||||
for j := range msgs[i].ToolCalls {
|
for j := range msgs[i].ToolCalls {
|
||||||
tc := &msgs[i].ToolCalls[j]
|
tc := &msgs[i].ToolCalls[j]
|
||||||
tc.Function.Arguments = filterText(tc.Function.Arguments)
|
tc.Function.Arguments = filterText(tc.Function.Arguments)
|
||||||
@@ -866,6 +873,10 @@ func sanitizeMessages(msgs []Message) {
|
|||||||
tc.Function.Arguments = string(fixed)
|
tc.Function.Arguments = string(fixed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if msgs[i].ReasoningContent != "" {
|
||||||
|
msgs[i].ReasoningContent = filterText(msgs[i].ReasoningContent)
|
||||||
|
}
|
||||||
} else if msgs[i].Role == "tool" && msgs[i].Content != nil {
|
} else if msgs[i].Role == "tool" && msgs[i].Content != nil {
|
||||||
msgs[i].Content = strp(filterText(*msgs[i].Content))
|
msgs[i].Content = strp(filterText(*msgs[i].Content))
|
||||||
}
|
}
|
||||||
|
|||||||
+107
@@ -7,6 +7,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -2282,3 +2283,109 @@ func TestSOCKS5ProxySupport(t *testing.T) {
|
|||||||
t.Fatalf("timed out waiting for SOCKS5 handshake through proxy")
|
t.Fatalf("timed out waiting for SOCKS5 handshake through proxy")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCleanMessagesForLLMPreservesReasoningContent(t *testing.T) {
|
||||||
|
msgs := []Message{
|
||||||
|
{Role: "user", Content: strp("hello")},
|
||||||
|
{Role: "assistant", Content: strp("done"), ReasoningContent: "thinking steps"},
|
||||||
|
{Role: "tool", ToolCallID: "tc1", Content: strp("result")},
|
||||||
|
}
|
||||||
|
cleaned := cleanMessagesForLLM(msgs)
|
||||||
|
if len(cleaned) != 3 {
|
||||||
|
t.Fatalf("expected 3 messages, got %d", len(cleaned))
|
||||||
|
}
|
||||||
|
if cleaned[1].ReasoningContent != "thinking steps" {
|
||||||
|
t.Errorf("expected ReasoningContent %q, got %q", "thinking steps", cleaned[1].ReasoningContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify JSON serialization includes reasoning_content for assistant
|
||||||
|
b, err := json.Marshal(cleaned[1])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal error: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(b), `"reasoning_content":"thinking steps"`) {
|
||||||
|
t.Errorf("expected JSON to contain reasoning_content, got %s", string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify JSON serialization omits reasoning_content when empty
|
||||||
|
bUser, err := json.Marshal(cleaned[0])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal error: %v", err)
|
||||||
|
}
|
||||||
|
if strings.Contains(string(bUser), "reasoning_content") {
|
||||||
|
t.Errorf("expected JSON to omit reasoning_content for empty, got %s", string(bUser))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLLMPreservesReasoningContentInRequestBody(t *testing.T) {
|
||||||
|
var receivedBody []byte
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
receivedBody, err = io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("read body err: %v", err)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"choices":[{"message":{"role":"assistant","content":"reply"}}]}`))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
cfg := defCfg
|
||||||
|
cfg.Endpoint = srv.URL
|
||||||
|
cfg.Stream = false
|
||||||
|
cfg.APIKey = "-"
|
||||||
|
|
||||||
|
msgs := []Message{
|
||||||
|
{Role: "user", Content: strp("call tool")},
|
||||||
|
{
|
||||||
|
Role: "assistant",
|
||||||
|
Content: strp(""),
|
||||||
|
ReasoningContent: "deep thoughts about tool",
|
||||||
|
ToolCalls: []ToolCall{
|
||||||
|
{
|
||||||
|
ID: "call_1",
|
||||||
|
Type: "function",
|
||||||
|
Function: struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Arguments string `json:"arguments"`
|
||||||
|
}{Name: "shell_exec", Arguments: `{"command":"ls"}`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{Role: "tool", ToolCallID: "call_1", Content: strp("file.txt\n\nexit: 0")},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := llm(context.Background(), &cfg, msgs, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected llm error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var reqPayload struct {
|
||||||
|
Messages []map[string]any `json:"messages"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(receivedBody, &reqPayload); err != nil {
|
||||||
|
t.Fatalf("unmarshal request payload: %v", err)
|
||||||
|
}
|
||||||
|
if len(reqPayload.Messages) != 3 {
|
||||||
|
t.Fatalf("expected 3 messages in request, got %d", len(reqPayload.Messages))
|
||||||
|
}
|
||||||
|
asstMsg := reqPayload.Messages[1]
|
||||||
|
rc, ok := asstMsg["reasoning_content"].(string)
|
||||||
|
if !ok || rc != "deep thoughts about tool" {
|
||||||
|
t.Errorf("expected assistant message reasoning_content %q, got %v", "deep thoughts about tool", asstMsg["reasoning_content"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSanitizeMessagesCleansReasoningContent(t *testing.T) {
|
||||||
|
msgs := []Message{
|
||||||
|
{Role: "assistant", ReasoningContent: "clean\x00\u200B\u00A0reasoning"},
|
||||||
|
}
|
||||||
|
sanitizeMessages(msgs)
|
||||||
|
if strings.ContainsAny(msgs[0].ReasoningContent, "\x00") || strings.Contains(msgs[0].ReasoningContent, "\u200B") {
|
||||||
|
t.Errorf("sanitizeMessages did not strip control/invisible characters from ReasoningContent: %q", msgs[0].ReasoningContent)
|
||||||
|
}
|
||||||
|
if msgs[0].ReasoningContent != "cleanreasoning" {
|
||||||
|
t.Errorf("expected 'cleanreasoning', got %q", msgs[0].ReasoningContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user