chore: move resolved upstream merge off main

This commit is contained in:
afjcjsbx
2026-05-23 17:15:00 +02:00
parent 848bf77381
commit fbea699936
6 changed files with 255 additions and 2 deletions
+37
View File
@@ -605,6 +605,43 @@ func TestProcessMessage_PassesExplicitThinkingOffToProviderWithoutThinkingCapabi
}
}
func TestProcessMessage_PassesDeepSeekThinkingLevelToThinkingCapableProvider(t *testing.T) {
cfg := &config.Config{
Agents: config.AgentsConfig{
Defaults: config.AgentDefaults{
Workspace: t.TempDir(),
ModelName: "deepseek-v4-flash",
MaxTokens: 4096,
MaxToolIterations: 10,
},
},
ModelList: []*config.ModelConfig{{
ModelName: "deepseek-v4-flash",
Provider: "deepseek",
Model: "deepseek-v4-flash",
ThinkingLevel: "xhigh",
}},
}
provider := &thinkingRecordingProvider{}
al := NewAgentLoop(cfg, bus.NewMessageBus(), provider)
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
Channel: "pico",
ChatID: "chat-1",
Content: "hello",
}))
if err != nil {
t.Fatalf("processMessage() error = %v", err)
}
if response != "Mock response" {
t.Fatalf("processMessage() response = %q, want %q", response, "Mock response")
}
if got := provider.lastOptions["thinking_level"]; got != "xhigh" {
t.Fatalf("thinking_level option = %#v, want %q", got, "xhigh")
}
}
func TestProcessMessage_SuppressesReasoningWhenThinkingOff(t *testing.T) {
cfg := &config.Config{
Agents: config.AgentsConfig{
+8 -1
View File
@@ -71,7 +71,14 @@ func (p *Pipeline) SetupTurn(ctx context.Context, ts *turnState) (*turnExecution
history, messages, fit = trimHistoryToFitContextWindow(
history,
func(trimmedHistory []providers.Message) []providers.Message {
rebuildPromptReq := promptBuildRequestForTurn(ts, trimmedHistory, summary, ts.userMessage, ts.media, cfg)
rebuildPromptReq := promptBuildRequestForTurn(
ts,
trimmedHistory,
summary,
ts.userMessage,
ts.media,
cfg,
)
rebuildPromptReq.ActiveSkills = append([]string(nil), contextualSkills...)
rebuilt := ts.agent.ContextBuilder.BuildMessagesFromPrompt(rebuildPromptReq)
return resolveMediaRefs(rebuilt, p.MediaStore, maxMediaSize)
+24
View File
@@ -171,6 +171,30 @@ func TestCreateProviderFromConfig_UsesExplicitProvider(t *testing.T) {
}
}
func TestCreateProviderFromConfig_DeepSeekSupportsThinking(t *testing.T) {
cfg := &config.ModelConfig{
ModelName: "deepseek-v4-flash",
Provider: "deepseek",
Model: "deepseek-v4-flash",
}
cfg.SetAPIKey("test-key")
provider, modelID, err := CreateProviderFromConfig(cfg)
if err != nil {
t.Fatalf("CreateProviderFromConfig() error = %v", err)
}
if modelID != "deepseek-v4-flash" {
t.Fatalf("modelID = %q, want %q", modelID, "deepseek-v4-flash")
}
tc, ok := provider.(ThinkingCapable)
if !ok {
t.Fatalf("provider %T should implement ThinkingCapable for DeepSeek", provider)
}
if !tc.SupportsThinking() {
t.Fatalf("DeepSeek provider SupportsThinking() = false, want true")
}
}
func TestCreateProviderFromConfig_PreservesExplicitProviderPrefixedModel(t *testing.T) {
cfg := &config.ModelConfig{
ModelName: "test-openai",
+7
View File
@@ -89,6 +89,13 @@ func (p *HTTPProvider) SupportsNativeSearch() bool {
return p.delegate.SupportsNativeSearch()
}
func (p *HTTPProvider) SupportsThinking() bool {
if p == nil || p.delegate == nil {
return false
}
return p.delegate.SupportsThinking()
}
func (p *HTTPProvider) SetProviderName(providerName string) {
if p == nil || p.delegate == nil {
return
+37 -1
View File
@@ -14,6 +14,7 @@ import (
"strings"
"time"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/providers/common"
"github.com/sipeed/picoclaw/pkg/providers/messageutil"
"github.com/sipeed/picoclaw/pkg/providers/protocoltypes"
@@ -204,7 +205,16 @@ func (p *Provider) buildRequestBody(
func (p *Provider) applyThinkingControl(requestBody map[string]any, model string, options map[string]any) {
level, ok := normalizedThinkingLevel(options)
if !ok || level != "off" {
if !ok {
return
}
if p.SupportsThinking() {
p.applyDeepSeekThinkingControl(requestBody, level)
return
}
if level != "off" {
return
}
@@ -216,6 +226,28 @@ func (p *Provider) applyThinkingControl(requestBody map[string]any, model string
}
}
func (p *Provider) applyDeepSeekThinkingControl(requestBody map[string]any, level string) {
switch level {
case "off":
requestBody["thinking"] = map[string]any{"type": "disabled"}
case "low", "medium", "high":
requestBody["thinking"] = map[string]any{"type": "enabled"}
requestBody["reasoning_effort"] = "high"
case "xhigh":
requestBody["thinking"] = map[string]any{"type": "enabled"}
requestBody["reasoning_effort"] = "max"
case "adaptive":
logger.WarnCF("provider.openai_compat",
`DeepSeek does not support thinking_level="adaptive"; using provider default thinking behavior`,
map[string]any{
"provider": p.providerName,
"api_base": p.apiBase,
"thinking_level": level,
},
)
}
}
func normalizedThinkingLevel(options map[string]any) (string, bool) {
raw, ok := options["thinking_level"].(string)
if !ok {
@@ -290,6 +322,10 @@ func (p *Provider) SetProviderName(providerName string) {
p.providerName = strings.ToLower(strings.TrimSpace(providerName))
}
func (p *Provider) SupportsThinking() bool {
return strings.EqualFold(strings.TrimSpace(p.providerName), "deepseek") || isDeepSeekHost(p.apiBase)
}
func (p *Provider) prepareMessagesForRequest(messages []Message) []Message {
if len(messages) == 0 {
return nil
@@ -8,11 +8,13 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"sync"
"testing"
"time"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/providers/common"
"github.com/sipeed/picoclaw/pkg/providers/protocoltypes"
)
@@ -125,6 +127,146 @@ func TestBuildRequestBody_PreservesDoubaoRequestWhenThinkingLevelIsNotOff(t *tes
}
}
func TestBuildRequestBody_MapsDeepSeekThinkingLevels(t *testing.T) {
p := NewProvider("key", "https://api.deepseek.com/v1", "")
p.SetProviderName("deepseek")
tests := []struct {
name string
level string
wantThinkingType string
wantEffort any
}{
{name: "off", level: "off", wantThinkingType: "disabled"},
{name: "low", level: "low", wantThinkingType: "enabled", wantEffort: "high"},
{name: "medium", level: "medium", wantThinkingType: "enabled", wantEffort: "high"},
{name: "high", level: "high", wantThinkingType: "enabled", wantEffort: "high"},
{name: "xhigh", level: "xhigh", wantThinkingType: "enabled", wantEffort: "max"},
{name: "adaptive", level: "adaptive"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
body := p.buildRequestBody(
[]Message{{Role: "user", Content: "hi"}},
nil,
"deepseek-v4-pro",
map[string]any{"thinking_level": tt.level},
)
if tt.wantThinkingType == "" {
if _, ok := body["thinking"]; ok {
t.Fatalf("thinking should be omitted for %q, got %#v", tt.level, body["thinking"])
}
} else {
thinking, ok := body["thinking"].(map[string]any)
if !ok {
t.Fatalf("thinking = %#v, want map", body["thinking"])
}
if got := thinking["type"]; got != tt.wantThinkingType {
t.Fatalf("thinking.type = %#v, want %q", got, tt.wantThinkingType)
}
}
if tt.wantEffort == nil {
if _, ok := body["reasoning_effort"]; ok {
t.Fatalf("reasoning_effort should be omitted for %q, got %#v", tt.level, body["reasoning_effort"])
}
} else if got := body["reasoning_effort"]; got != tt.wantEffort {
t.Fatalf("reasoning_effort = %#v, want %#v", got, tt.wantEffort)
}
})
}
}
func TestBuildRequestBody_MapsDeepSeekThinkingLevelsByHost(t *testing.T) {
p := NewProvider("key", "https://api.deepseek.com/v1", "")
body := p.buildRequestBody(
[]Message{{Role: "user", Content: "hi"}},
nil,
"deepseek-v4-flash",
map[string]any{"thinking_level": "xhigh"},
)
thinking, ok := body["thinking"].(map[string]any)
if !ok {
t.Fatalf("thinking = %#v, want map", body["thinking"])
}
if got := thinking["type"]; got != "enabled" {
t.Fatalf("thinking.type = %#v, want enabled", got)
}
if got := body["reasoning_effort"]; got != "max" {
t.Fatalf("reasoning_effort = %#v, want max", got)
}
}
func TestBuildRequestBody_DeepSeekExtraBodyStillOverridesThinkingFields(t *testing.T) {
extraBody := map[string]any{
"thinking": map[string]any{"type": "disabled"},
"reasoning_effort": "max",
}
p := NewProvider("key", "https://api.deepseek.com/v1", "", WithExtraBody(extraBody))
p.SetProviderName("deepseek")
body := p.buildRequestBody(
[]Message{{Role: "user", Content: "hi"}},
nil,
"deepseek-v4-pro",
map[string]any{"thinking_level": "high"},
)
thinking, ok := body["thinking"].(map[string]any)
if !ok {
t.Fatalf("thinking = %#v, want map", body["thinking"])
}
if got := thinking["type"]; got != "disabled" {
t.Fatalf("thinking.type = %#v, want disabled from extra_body override", got)
}
if got := body["reasoning_effort"]; got != "max" {
t.Fatalf("reasoning_effort = %#v, want max from extra_body override", got)
}
}
func TestBuildRequestBody_WarnsForUnsupportedDeepSeekAdaptiveThinkingLevel(t *testing.T) {
logFile := t.TempDir() + "/deepseek-adaptive-warning.log"
prevLevel := logger.GetLevel()
logger.SetLevel(logger.WARN)
if err := logger.EnableFileLogging(logFile); err != nil {
t.Fatalf("EnableFileLogging() error = %v", err)
}
defer func() {
logger.DisableFileLogging()
logger.SetLevel(prevLevel)
}()
p := NewProvider("key", "https://api.deepseek.com/v1", "")
p.SetProviderName("deepseek")
body := p.buildRequestBody(
[]Message{{Role: "user", Content: "hi"}},
nil,
"deepseek-v4-pro",
map[string]any{"thinking_level": "adaptive"},
)
if _, ok := body["thinking"]; ok {
t.Fatalf("thinking should be omitted for adaptive, got %#v", body["thinking"])
}
if _, ok := body["reasoning_effort"]; ok {
t.Fatalf("reasoning_effort should be omitted for adaptive, got %#v", body["reasoning_effort"])
}
data, err := os.ReadFile(logFile)
if err != nil {
t.Fatalf("ReadFile(%q) error = %v", logFile, err)
}
logs := string(data)
if !strings.Contains(logs, `thinking_level=\"adaptive\"`) {
t.Fatalf("warning log = %q, want adaptive warning message", logs)
}
}
func TestProviderChat_ParsesToolCalls(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := map[string]any{