返回 DeepSeek-Reasonix
reasoning_replay.go
根目录 / internal / provider / anthropic / reasoning_replay.go
1 package anthropic
2
3 import (
4 "encoding/json"
5
6 "reasonix/internal/provider"
7 )
8
9 func (c *client) replayMessages(messages []provider.Message) []provider.Message {
10 messages, _ = provider.ProjectReplaySafeMessages(c, messages)
11 return messages
12 }
13
14 func (c *client) replayReasoningBlock(m provider.Message) (contentBlock, bool) {
15 // DeepSeek's thinking mode requires every historical assistant turn's
16 // thinking block to be passed back whenever the request declares tools —
17 // including plain question-answer turns with no tool activity.
18 if c.deepseek && m.ReasoningContent != "" {
19 return contentBlock{Type: "thinking", Thinking: m.ReasoningContent}, true
20 }
21 if !c.deepseek && c.replaysReceivedThinking() && m.ReasoningSignature != "" {
22 return contentBlock{Type: "thinking", Thinking: m.ReasoningContent, Signature: m.ReasoningSignature}, true
23 }
24 if !c.nativeAnthropic && !c.deepseek && c.replaysReceivedThinking() && m.ReasoningContent != "" {
25 return contentBlock{Type: "thinking", Thinking: m.ReasoningContent}, true
26 }
27 return contentBlock{}, false
28 }
29
30 // mergeThinkingFirst merges appended blocks into an existing same-role
31 // message's content. A thinking block must lead its message, so a leading
32 // thinking run in the appended blocks moves to the front: after projection
33 // degrades a reasoning-less turn to plain text, a following healthy assistant
34 // turn merging into it must stay [thinking, text, ...], never [text, ...].
35 func mergeThinkingFirst(existing, blocks []contentBlock) []contentBlock {
36 head := leadingThinkingBlocks(blocks)
37 if head == 0 || len(existing) == 0 {
38 return append(existing, blocks...)
39 }
40 merged := make([]contentBlock, 0, len(existing)+len(blocks))
41 merged = append(merged, blocks[:head]...)
42 merged = append(merged, existing...)
43 merged = append(merged, blocks[head:]...)
44 return merged
45 }
46
47 // leadingThinkingBlocks counts the signed or redacted blocks at the head.
48 func leadingThinkingBlocks(blocks []contentBlock) int {
49 head := 0
50 for head < len(blocks) && (blocks[head].Type == "thinking" || blocks[head].Type == "redacted_thinking") {
51 head++
52 }
53 return head
54 }
55
56 func (c *client) applyDeepSeekThinking(r *anthRequest, req provider.Request) {
57 r.Temperature = req.Temperature
58 t := c.thinking
59 if t != "disabled" {
60 t = "enabled"
61 }
62 if c.effort == "disabled" {
63 t = "disabled"
64 }
65 effort := c.effort
66 if req.EffortOverride != "" {
67 effort = req.EffortOverride
68 if effort == "disabled" {
69 t = "disabled"
70 } else if c.thinking != "disabled" {
71 t = "enabled"
72 }
73 }
74
75 r.Thinking = &thinkingConfig{Type: t}
76 if t == "disabled" {
77 return
78 }
79 if effort != "" {
80 r.OutputConfig = &outputConfig{Effort: effort}
81 }
82 }
83
84 func (c *client) ReasoningReplayCapabilities() provider.ReasoningReplayCapabilities {
85 if c.deepseek {
86 return provider.ReasoningReplayCapabilities{Format: "anthropic-thinking"}
87 }
88 return provider.ReasoningReplayCapabilities{Format: "anthropic-thinking", RequireSignature: c.nativeAnthropic}
89 }
90
91 func (c *client) replayReasoningBlocks(m provider.Message) []contentBlock {
92 if !c.deepseek && c.replaysReceivedThinking() && len(m.ThinkingBlocks) > 0 {
93 blocks := make([]contentBlock, 0, len(m.ThinkingBlocks))
94 for _, b := range m.ThinkingBlocks {
95 blocks = append(blocks, contentBlock{Type: b.Type, Thinking: b.Thinking, Signature: b.Signature, Data: b.Data})
96 }
97 return blocks
98 }
99 if block, ok := c.replayReasoningBlock(m); ok {
100 return []contentBlock{block}
101 }
102 return nil
103 }
104
105 func (b contentBlock) MarshalJSON() ([]byte, error) {
106 type plain contentBlock
107 if b.Type == "thinking" && b.Thinking == "" {
108 return json.Marshal(struct {
109 plain
110 Thinking string `json:"thinking"`
111 }{plain(b), b.Thinking})
112 }
113 return json.Marshal(plain(b))
114 }
115
116 func thinkingSignature(b *provider.ThinkingBlock, delta string) string {
117 if b != nil {
118 return b.Signature
119 }
120 return delta
121 }
122
123 func (c *client) replaysSignedThinking() bool {
124 return c.thinking == "adaptive" || (c.nativeAnthropic && c.thinking == "enabled")
125 }
126
127 func (c *client) replaysReceivedThinking() bool {
128 return c.replaysSignedThinking() || (!c.nativeAnthropic && c.thinking == "enabled" && c.effort != "disabled")
129 }
130
130 lines GO