Merge 3a8e1f6952 into eb0653074b
This commit is contained in:
commit
459b6499a0
2 changed files with 64 additions and 5 deletions
|
|
@ -419,6 +419,7 @@ func parseStreamResponse(
|
||||||
onChunk func(accumulated string),
|
onChunk func(accumulated string),
|
||||||
) (*LLMResponse, error) {
|
) (*LLMResponse, error) {
|
||||||
var textContent strings.Builder
|
var textContent strings.Builder
|
||||||
|
var reasoningContent strings.Builder
|
||||||
var finishReason string
|
var finishReason string
|
||||||
var usage *UsageInfo
|
var usage *UsageInfo
|
||||||
|
|
||||||
|
|
@ -451,7 +452,8 @@ func parseStreamResponse(
|
||||||
var chunk struct {
|
var chunk struct {
|
||||||
Choices []struct {
|
Choices []struct {
|
||||||
Delta struct {
|
Delta struct {
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
ReasoningContent string `json:"reasoning_content"`
|
||||||
ToolCalls []struct {
|
ToolCalls []struct {
|
||||||
Index int `json:"index"`
|
Index int `json:"index"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
|
@ -488,6 +490,11 @@ func parseStreamResponse(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accumulate reasoning content (e.g. DeepSeek thinking-mode tokens)
|
||||||
|
if choice.Delta.ReasoningContent != "" {
|
||||||
|
reasoningContent.WriteString(choice.Delta.ReasoningContent)
|
||||||
|
}
|
||||||
|
|
||||||
// Accumulate tool call deltas
|
// Accumulate tool call deltas
|
||||||
for _, tc := range choice.Delta.ToolCalls {
|
for _, tc := range choice.Delta.ToolCalls {
|
||||||
acc, ok := activeTools[tc.Index]
|
acc, ok := activeTools[tc.Index]
|
||||||
|
|
@ -544,10 +551,11 @@ func parseStreamResponse(
|
||||||
}
|
}
|
||||||
|
|
||||||
return &LLMResponse{
|
return &LLMResponse{
|
||||||
Content: textContent.String(),
|
Content: textContent.String(),
|
||||||
ToolCalls: toolCalls,
|
ReasoningContent: reasoningContent.String(),
|
||||||
FinishReason: finishReason,
|
ToolCalls: toolCalls,
|
||||||
Usage: usage,
|
FinishReason: finishReason,
|
||||||
|
Usage: usage,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1195,6 +1195,57 @@ func TestProviderChatStream_CustomHeadersInjected(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProviderChatStream_ParsesReasoningContent(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/event-stream")
|
||||||
|
_, _ = w.Write([]byte(
|
||||||
|
"data: {\"choices\":[{\"delta\":{\"reasoning_content\":\"Let me \",\"content\":\"Checking \",\"tool_calls\":[{\"index\":0,\"id\":\"call_1\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"{\\\"city\\\":\"}}]}}]}\n\n",
|
||||||
|
))
|
||||||
|
_, _ = w.Write([]byte(
|
||||||
|
"data: {\"choices\":[{\"delta\":{\"reasoning_content\":\"think step by step.\",\"content\":\"the weather\",\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"\\\"Hangzhou\\\"}\"}}]},\"finish_reason\":\"tool_calls\"}],\"usage\":{\"prompt_tokens\":10,\"completion_tokens\":6,\"total_tokens\":16}}\n\n",
|
||||||
|
))
|
||||||
|
_, _ = w.Write([]byte("data: [DONE]\n\n"))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
p := NewProvider("key", server.URL, "")
|
||||||
|
out, err := p.ChatStream(
|
||||||
|
t.Context(),
|
||||||
|
[]Message{{Role: "user", Content: "weather?"}},
|
||||||
|
nil,
|
||||||
|
"deepseek-v4-flash",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ChatStream() error = %v", err)
|
||||||
|
}
|
||||||
|
if out.Content != "Checking the weather" {
|
||||||
|
t.Fatalf("Content = %q, want %q", out.Content, "Checking the weather")
|
||||||
|
}
|
||||||
|
if out.ReasoningContent != "Let me think step by step." {
|
||||||
|
t.Fatalf("ReasoningContent = %q, want %q", out.ReasoningContent, "Let me think step by step.")
|
||||||
|
}
|
||||||
|
if len(out.ToolCalls) != 1 {
|
||||||
|
t.Fatalf("len(ToolCalls) = %d, want 1", len(out.ToolCalls))
|
||||||
|
}
|
||||||
|
if out.ToolCalls[0].ID != "call_1" {
|
||||||
|
t.Fatalf("ToolCalls[0].ID = %q, want %q", out.ToolCalls[0].ID, "call_1")
|
||||||
|
}
|
||||||
|
if out.ToolCalls[0].Name != "get_weather" {
|
||||||
|
t.Fatalf("ToolCalls[0].Name = %q, want %q", out.ToolCalls[0].Name, "get_weather")
|
||||||
|
}
|
||||||
|
if out.ToolCalls[0].Arguments["city"] != "Hangzhou" {
|
||||||
|
t.Fatalf("ToolCalls[0].Arguments[city] = %v, want %q", out.ToolCalls[0].Arguments["city"], "Hangzhou")
|
||||||
|
}
|
||||||
|
if out.FinishReason != "tool_calls" {
|
||||||
|
t.Fatalf("FinishReason = %q, want %q", out.FinishReason, "tool_calls")
|
||||||
|
}
|
||||||
|
if out.Usage == nil || out.Usage.TotalTokens != 16 {
|
||||||
|
t.Fatalf("Usage = %#v, want total tokens 16", out.Usage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type roundTripperFunc func(*http.Request) (*http.Response, error)
|
type roundTripperFunc func(*http.Request) (*http.Response, error)
|
||||||
|
|
||||||
func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue