mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
chore: move resolved upstream merge off main
This commit is contained in:
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user