Merge pull request #2657 from lc6464/fix-deepseek-v4-thinking-history

fix(reasoning): persist canonical history for DeepSeek and web chat
This commit is contained in:
美電球
2026-04-25 15:08:48 +08:00
committed by GitHub
17 changed files with 1172 additions and 73 deletions
+39 -12
View File
@@ -16,6 +16,7 @@ import (
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/memory"
"github.com/sipeed/picoclaw/pkg/providers"
"github.com/sipeed/picoclaw/pkg/providers/messageutil"
"github.com/sipeed/picoclaw/pkg/session"
"github.com/sipeed/picoclaw/pkg/utils"
)
@@ -49,6 +50,7 @@ type sessionListItem struct {
type sessionChatMessage struct {
Role string `json:"role"`
Content string `json:"content"`
Kind string `json:"kind,omitempty"`
Media []string `json:"media,omitempty"`
Attachments []sessionChatAttachment `json:"attachments,omitempty"`
}
@@ -154,6 +156,9 @@ func (h *Handler) readSessionMessages(path string, skip int) ([]providers.Messag
if err := json.Unmarshal(line, &msg); err != nil {
continue
}
if messageutil.IsTransientAssistantThoughtMessage(msg) {
continue
}
msgs = append(msgs, msg)
}
if err := scanner.Err(); err != nil {
@@ -474,6 +479,18 @@ func sessionChatMessagePreview(msg sessionChatMessage) string {
}
func visibleSessionMessages(messages []providers.Message, toolFeedbackMaxArgsLength int) []sessionChatMessage {
return sessionTranscriptMessages(messages, toolFeedbackMaxArgsLength, false)
}
func detailSessionMessages(messages []providers.Message, toolFeedbackMaxArgsLength int) []sessionChatMessage {
return sessionTranscriptMessages(messages, toolFeedbackMaxArgsLength, true)
}
func sessionTranscriptMessages(
messages []providers.Message,
toolFeedbackMaxArgsLength int,
includeThoughts bool,
) []sessionChatMessage {
transcript := make([]sessionChatMessage, 0, len(messages))
for _, msg := range messages {
@@ -495,11 +512,14 @@ func visibleSessionMessages(messages []providers.Message, toolFeedbackMaxArgsLen
}
case "assistant":
// Reasoning-only assistant messages are transient display artifacts and
// should not be restored from session history.
if assistantMessageTransientThought(msg) {
if messageutil.IsTransientAssistantThoughtMessage(msg) {
continue
}
if includeThoughts {
if thoughtMsg, ok := assistantThoughtMessage(msg); ok {
transcript = append(transcript, thoughtMsg)
}
}
toolSummaryMessages := visibleAssistantToolSummaryMessages(msg.ToolCalls, toolFeedbackMaxArgsLength)
if len(toolSummaryMessages) > 0 {
@@ -681,18 +701,25 @@ func sessionAttachmentType(attachment providers.Attachment) string {
}
}
func assistantMessageTransientThought(msg providers.Message) bool {
return strings.TrimSpace(msg.Content) == "" &&
strings.TrimSpace(msg.ReasoningContent) != "" &&
len(msg.ToolCalls) == 0 &&
len(msg.Media) == 0 &&
len(msg.Attachments) == 0
}
func assistantMessageInternalOnly(msg providers.Message) bool {
return strings.TrimSpace(msg.Content) == handledToolResponseSummaryText
}
func assistantThoughtMessage(msg providers.Message) (sessionChatMessage, bool) {
reasoning := strings.TrimSpace(msg.ReasoningContent)
if reasoning == "" {
return sessionChatMessage{}, false
}
if reasoning == strings.TrimSpace(msg.Content) {
return sessionChatMessage{}, false
}
return sessionChatMessage{
Role: "assistant",
Content: reasoning,
Kind: "thought",
}, true
}
func visibleAssistantToolSummaryMessages(
toolCalls []providers.ToolCall,
toolFeedbackMaxArgsLength int,
@@ -986,7 +1013,7 @@ func (h *Handler) handleGetSession(w http.ResponseWriter, r *http.Request) {
}
}
messages := visibleSessionMessages(sess.Messages, toolFeedbackMaxArgsLength)
messages := detailSessionMessages(sess.Messages, toolFeedbackMaxArgsLength)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{