mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 20:14:50 +00:00
fix(providers): preserve streamed content whitespace (#1324)
Co-authored-by: GoClaw Operator <operator@goclaw>
This commit is contained in:
1 parent
e529fb5f80
commit
7e3cdc8371
4 files changed
+62
-5
No files matched your search
@@ -112,10 +112,8 @@ func (a *OpenAIAdapter) FromStreamChunk(data []byte) (*StreamChunk, error) {
|
||||
|
||||
// Text content
|
||||
if delta.Content != "" {
|
||||
normalized := normalizeControlOutput(delta.Content, "", nil)
|
||||
sc.Content = normalized.Content
|
||||
sc.Thinking = strings.TrimSpace(strings.Join([]string{sc.Thinking, normalized.Thinking}, "\n"))
|
||||
hasContent = sc.Content != "" || sc.Thinking != ""
|
||||
sc.Content = delta.Content
|
||||
hasContent = true
|
||||
}
|
||||
|
||||
if !hasContent {
|
||||
|
||||
@@ -185,6 +185,20 @@ func TestOpenAIAdapter_FromStreamChunk(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("content delta preserves leading whitespace", func(t *testing.T) {
|
||||
raw := []byte(`{"choices":[{"delta":{"content":" xin lỗi"}}]}`)
|
||||
sc, err := a.FromStreamChunk(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if sc == nil {
|
||||
t.Fatal("want stream chunk, got nil")
|
||||
}
|
||||
if sc.Content != " xin lỗi" {
|
||||
t.Errorf("want leading whitespace preserved, got %q", sc.Content)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("reasoning_content delta", func(t *testing.T) {
|
||||
raw := []byte(`{"choices":[{"delta":{"reasoning_content":"thinking..."}}]}`)
|
||||
sc, err := a.FromStreamChunk(raw)
|
||||
|
||||
@@ -70,7 +70,6 @@ func normalizeControlOutput(content, thinking string, tools []ToolDefinition) co
|
||||
out.Content, extracted = extractThinkingTags(out.Content)
|
||||
out.Thinking = joinThinking(out.Thinking, extracted)
|
||||
|
||||
out.Content = strings.TrimSpace(out.Content)
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@@ -286,6 +286,52 @@ func TestOpenAIProviderChatStream_NormalizesSplitKimiTextToolCallBeforeEmittingC
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenAIProviderChatStream_PreservesWhitespaceAcrossContentDeltas(t *testing.T) {
|
||||
events := []string{
|
||||
`data: {"choices":[{"delta":{"content":"Dạ"}}]}` + "\n\n",
|
||||
`data: {"choices":[{"delta":{"content":" em"}}]}` + "\n\n",
|
||||
`data: {"choices":[{"delta":{"content":" xin"}}]}` + "\n\n",
|
||||
`data: {"choices":[{"delta":{"content":" lỗi"}}]}` + "\n\n",
|
||||
`data: {"choices":[{"delta":{"content":" anh"}}]}` + "\n\n",
|
||||
`data: {"choices":[{"finish_reason":"stop","delta":{}}]}` + "\n\n",
|
||||
}
|
||||
srv := newOpenAISSEServer(t, events)
|
||||
defer srv.Close()
|
||||
|
||||
p := NewOpenAIProvider("openai-compatible", "sk", srv.URL, "model")
|
||||
var streamed strings.Builder
|
||||
resp, err := p.ChatStream(context.Background(), ChatRequest{
|
||||
Messages: []Message{{Role: "user", Content: "say it"}},
|
||||
}, func(chunk StreamChunk) {
|
||||
streamed.WriteString(chunk.Content)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ChatStream: %v", err)
|
||||
}
|
||||
|
||||
const want = "Dạ em xin lỗi anh"
|
||||
if resp.Content != want {
|
||||
t.Fatalf("Content = %q, want %q", resp.Content, want)
|
||||
}
|
||||
if got := streamed.String(); got != want {
|
||||
t.Fatalf("streamed content = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestControlOutputNormalizer_PreservesWhitespaceAroundNormalText(t *testing.T) {
|
||||
n := newControlOutputNormalizer(nil)
|
||||
var got strings.Builder
|
||||
got.WriteString(n.Append("Dạ").Content)
|
||||
got.WriteString(n.Append(" em").Content)
|
||||
got.WriteString(n.Append(" xin").Content)
|
||||
got.WriteString(n.Append(" lỗi").Content)
|
||||
got.WriteString(n.Finish().Content)
|
||||
|
||||
if got.String() != "Dạ em xin lỗi" {
|
||||
t.Fatalf("normalized stream content = %q, want %q", got.String(), "Dạ em xin lỗi")
|
||||
}
|
||||
}
|
||||
|
||||
func newOpenAIJSONServer(t *testing.T, body string) *httptest.Server {
|
||||
t.Helper()
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in new issue
Block a user