Skip to content

Commit 8ee3dd7

Browse files
authored
Merge pull request #1121 from semioz/deepseek
Add ReasoningContent field for deepseek-reasoner model
2 parents 9173fef + bbacf8c commit 8ee3dd7

File tree

5 files changed

+111
-0
lines changed

5 files changed

+111
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
8+
"github.com/tmc/langchaingo/llms"
9+
"github.com/tmc/langchaingo/llms/openai"
10+
)
11+
12+
func main() {
13+
// Initialize the OpenAI client with Deepseek model
14+
llm, err := openai.New(
15+
openai.WithModel("deepseek-reasoner"),
16+
)
17+
if err != nil {
18+
log.Fatal(err)
19+
}
20+
21+
ctx := context.Background()
22+
23+
// Create messages for the chat
24+
content := []llms.MessageContent{
25+
llms.TextParts(llms.ChatMessageTypeSystem, "You are a helpful assistant that explains complex topics step by step"),
26+
llms.TextParts(llms.ChatMessageTypeHuman, "Explain how quantum entanglement works and why it's important for quantum computing"),
27+
}
28+
29+
// Generate content with streaming to see both reasoning and final answer in real-time
30+
completion, err := llm.GenerateContent(
31+
ctx,
32+
content,
33+
llms.WithMaxTokens(2000),
34+
llms.WithTemperature(0.7),
35+
llms.WithStreamingFunc(func(ctx context.Context, chunk []byte) error {
36+
fmt.Print(string(chunk))
37+
return nil
38+
}),
39+
)
40+
if err != nil {
41+
log.Fatal(err)
42+
}
43+
44+
// Access the reasoning content and final answer separately
45+
if len(completion.Choices) > 0 {
46+
choice := completion.Choices[0]
47+
fmt.Printf("\n\nReasoning Process:\n%s\n", choice.ReasoningContent)
48+
fmt.Printf("\nFinal Answer:\n%s\n", choice.Content)
49+
}
50+
}

llms/chat_messages.go

+3
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ type AIChatMessage struct {
6363

6464
// ToolCalls represents the model choosing to call tools.
6565
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
66+
67+
// This field is only used with the deepseek-reasoner model and represents the reasoning contents of the assistant message before the final answer.
68+
ReasoningContent string `json:"reasoning_content,omitempty"`
6669
}
6770

6871
func (m AIChatMessage) GetType() ChatMessageType { return ChatMessageTypeAI }

llms/generatecontent.go

+3
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ type ContentChoice struct {
143143

144144
// ToolCalls is a list of tool calls the model asks to invoke.
145145
ToolCalls []ToolCall
146+
147+
// This field is only used with the deepseek-reasoner model and represents the reasoning contents of the assistant message before the final answer.
148+
ReasoningContent string
146149
}
147150

148151
// TextParts is a helper function to create a MessageContent with a role and a

llms/openai/internal/openaiclient/chat.go

+15
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ type ChatMessage struct { //nolint:musttag
157157
// ToolCallID is the ID of the tool call this message is for.
158158
// Only present in tool messages.
159159
ToolCallID string `json:"tool_call_id,omitempty"`
160+
161+
// This field is only used with the deepseek-reasoner model and represents the reasoning contents of the assistant message before the final answer.
162+
ReasoningContent string `json:"reasoning_content,omitempty"`
160163
}
161164

162165
func (m ChatMessage) MarshalJSON() ([]byte, error) {
@@ -181,6 +184,9 @@ func (m ChatMessage) MarshalJSON() ([]byte, error) {
181184
// ToolCallID is the ID of the tool call this message is for.
182185
// Only present in tool messages.
183186
ToolCallID string `json:"tool_call_id,omitempty"`
187+
188+
// This field is only used with the deepseek-reasoner model and represents the reasoning contents of the assistant message before the final answer.
189+
ReasoningContent string `json:"reasoning_content,omitempty"`
184190
}(m)
185191
return json.Marshal(msg)
186192
}
@@ -196,6 +202,9 @@ func (m ChatMessage) MarshalJSON() ([]byte, error) {
196202
// ToolCallID is the ID of the tool call this message is for.
197203
// Only present in tool messages.
198204
ToolCallID string `json:"tool_call_id,omitempty"`
205+
206+
// This field is only used with the deepseek-reasoner model and represents the reasoning contents of the assistant message before the final answer.
207+
ReasoningContent string `json:"reasoning_content,omitempty"`
199208
}(m)
200209
return json.Marshal(msg)
201210
}
@@ -221,6 +230,9 @@ func (m *ChatMessage) UnmarshalJSON(data []byte) error {
221230
// ToolCallID is the ID of the tool call this message is for.
222231
// Only present in tool messages.
223232
ToolCallID string `json:"tool_call_id,omitempty"`
233+
234+
// This field is only used with the deepseek-reasoner model and represents the reasoning contents of the assistant message before the final answer.
235+
ReasoningContent string `json:"reasoning_content,omitempty"`
224236
}{}
225237
err := json.Unmarshal(data, &msg)
226238
if err != nil {
@@ -322,6 +334,8 @@ type StreamedChatResponsePayload struct {
322334
FunctionCall *FunctionCall `json:"function_call,omitempty"`
323335
// ToolCalls is a list of tools that were called in the message.
324336
ToolCalls []*ToolCall `json:"tool_calls,omitempty"`
337+
// This field is only used with the deepseek-reasoner model and represents the reasoning contents of the assistant message before the final answer.
338+
ReasoningContent string `json:"reasoning_content,omitempty"`
325339
} `json:"delta,omitempty"`
326340
FinishReason FinishReason `json:"finish_reason,omitempty"`
327341
} `json:"choices,omitempty"`
@@ -481,6 +495,7 @@ func combineStreamingChatResponse(
481495
chunk := []byte(choice.Delta.Content)
482496
response.Choices[0].Message.Content += choice.Delta.Content
483497
response.Choices[0].FinishReason = choice.FinishReason
498+
response.Choices[0].Message.ReasoningContent = choice.Delta.ReasoningContent
484499

485500
if choice.Delta.FunctionCall != nil {
486501
chunk = updateFunctionCall(response.Choices[0].Message, choice.Delta.FunctionCall)

llms/openai/internal/openaiclient/chat_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,29 @@ func TestParseStreamingChatResponse_FinishReason(t *testing.T) {
3333
assert.Equal(t, FinishReason("stop"), resp.Choices[0].FinishReason)
3434
}
3535

36+
func TestParseStreamingChatResponse_ReasoningContent(t *testing.T) {
37+
t.Parallel()
38+
mockBody := `data: {"choices":[{"index":0,"delta":{"role":"assistant","content":"final answer","reasoning_content":"step-by-step reasoning"},"finish_reason":"stop"}]}`
39+
r := &http.Response{
40+
StatusCode: http.StatusOK,
41+
Body: io.NopCloser(bytes.NewBufferString(mockBody)),
42+
}
43+
44+
req := &ChatRequest{
45+
StreamingFunc: func(_ context.Context, _ []byte) error {
46+
return nil
47+
},
48+
}
49+
50+
resp, err := parseStreamingChatResponse(context.Background(), r, req)
51+
52+
require.NoError(t, err)
53+
assert.NotNil(t, resp)
54+
assert.Equal(t, "final answer", resp.Choices[0].Message.Content)
55+
assert.Equal(t, "step-by-step reasoning", resp.Choices[0].Message.ReasoningContent)
56+
assert.Equal(t, FinishReason("stop"), resp.Choices[0].FinishReason)
57+
}
58+
3659
func TestChatMessage_MarshalUnmarshal(t *testing.T) {
3760
t.Parallel()
3861
msg := ChatMessage{
@@ -52,3 +75,20 @@ func TestChatMessage_MarshalUnmarshal(t *testing.T) {
5275
require.NoError(t, err)
5376
require.Equal(t, msg, msg2)
5477
}
78+
79+
func TestChatMessage_MarshalUnmarshal_WithReasoning(t *testing.T) {
80+
t.Parallel()
81+
msg := ChatMessage{
82+
Role: "assistant",
83+
Content: "final answer",
84+
ReasoningContent: "step-by-step reasoning",
85+
}
86+
text, err := json.Marshal(msg)
87+
require.NoError(t, err)
88+
require.Equal(t, `{"role":"assistant","content":"final answer","reasoning_content":"step-by-step reasoning"}`, string(text))
89+
90+
var msg2 ChatMessage
91+
err = json.Unmarshal(text, &msg2)
92+
require.NoError(t, err)
93+
require.Equal(t, msg, msg2)
94+
}

0 commit comments

Comments
 (0)