WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
package chat
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/models/provider"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
// ExtraConfigThinkingControl is the model parameters.extra_config key for
|
||||
// selecting how ChatOptions.Thinking is translated to provider HTTP fields.
|
||||
// The accepted values mirror the strings the frontend writes (see
|
||||
// ModelEditorDialog.vue): "none", "enable_thinking", "thinking_type",
|
||||
// "chat_template_kwargs".
|
||||
const ExtraConfigThinkingControl = "thinking_control"
|
||||
|
||||
// Wire-format request bodies used by providers that express extended-thinking
|
||||
// through a non-standard top-level field. They embed the standard OpenAI
|
||||
// request so all other fields are marshalled unchanged.
|
||||
|
||||
// QwenChatCompletionRequest adds Aliyun Qwen's `enable_thinking` boolean.
|
||||
type QwenChatCompletionRequest struct {
|
||||
openai.ChatCompletionRequest
|
||||
EnableThinking *bool `json:"enable_thinking,omitempty"`
|
||||
}
|
||||
|
||||
// ThinkingConfig is the `{ "type": "enabled"|"disabled" }` block used by
|
||||
// LKEAP / Volcengine style providers.
|
||||
type ThinkingConfig struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// ThinkingChatCompletionRequest adds the `thinking` object for providers that
|
||||
// use the `{ "thinking": { "type": ... } }` wire format.
|
||||
type ThinkingChatCompletionRequest struct {
|
||||
openai.ChatCompletionRequest
|
||||
Thinking *ThinkingConfig `json:"thinking,omitempty"`
|
||||
}
|
||||
|
||||
// ThinkingStrategy encodes how ChatOptions.Thinking is mapped onto a provider's
|
||||
// HTTP request. Apply returns (customBody, useRawHTTP):
|
||||
// - (nil, false) means "send the standard OpenAI request unchanged" (the
|
||||
// caller keeps using the SDK path).
|
||||
// - a non-nil customBody must be sent verbatim over raw HTTP because it
|
||||
// carries fields the OpenAI SDK would strip.
|
||||
//
|
||||
// When opts.Thinking is nil most strategies emit nothing, deferring to the
|
||||
// model's own default; the exception is enableThinking{alwaysSend: true}
|
||||
// (Aliyun Qwen), which must always pin the field.
|
||||
type ThinkingStrategy interface {
|
||||
Apply(req *openai.ChatCompletionRequest, opts *ChatOptions, isStream bool) (customBody any, useRawHTTP bool)
|
||||
}
|
||||
|
||||
// noThinking sends no thinking-related fields at all.
|
||||
type noThinking struct{}
|
||||
|
||||
func (noThinking) Apply(*openai.ChatCompletionRequest, *ChatOptions, bool) (any, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// enableThinking encodes thinking via Qwen's `enable_thinking` boolean.
|
||||
//
|
||||
// - alwaysSend: pin the field even when opts.Thinking is nil (Aliyun Qwen
|
||||
// thinking models require it on every request; default value is false).
|
||||
// - disableOnNonStream: force enable_thinking=false for non-stream requests
|
||||
// (Qwen3 rejects thinking in non-stream mode).
|
||||
type enableThinking struct {
|
||||
alwaysSend bool
|
||||
disableOnNonStream bool
|
||||
}
|
||||
|
||||
func (s enableThinking) Apply(req *openai.ChatCompletionRequest, opts *ChatOptions, isStream bool) (any, bool) {
|
||||
thinking := false
|
||||
switch {
|
||||
case opts != nil && opts.Thinking != nil:
|
||||
thinking = *opts.Thinking
|
||||
case !s.alwaysSend:
|
||||
return nil, false
|
||||
}
|
||||
if s.disableOnNonStream && !isStream {
|
||||
thinking = false
|
||||
}
|
||||
qwenReq := QwenChatCompletionRequest{ChatCompletionRequest: *req}
|
||||
qwenReq.EnableThinking = &thinking
|
||||
return qwenReq, true
|
||||
}
|
||||
|
||||
// thinkingTypeField encodes thinking via the `{ "thinking": { "type": ... } }`
|
||||
// object (LKEAP / Volcengine). Emits nothing when opts.Thinking is unset.
|
||||
type thinkingTypeField struct{}
|
||||
|
||||
func (thinkingTypeField) Apply(req *openai.ChatCompletionRequest, opts *ChatOptions, _ bool) (any, bool) {
|
||||
if opts == nil || opts.Thinking == nil {
|
||||
return nil, false
|
||||
}
|
||||
r := ThinkingChatCompletionRequest{ChatCompletionRequest: *req}
|
||||
thinkingType := "disabled"
|
||||
if *opts.Thinking {
|
||||
thinkingType = "enabled"
|
||||
}
|
||||
r.Thinking = &ThinkingConfig{Type: thinkingType}
|
||||
return r, true
|
||||
}
|
||||
|
||||
// chatTemplateKwargs encodes thinking via the standard request's
|
||||
// `chat_template_kwargs.enable_thinking` (vLLM / NVIDIA / generic local
|
||||
// deployments). Emits nothing when opts.Thinking is unset.
|
||||
type chatTemplateKwargs struct{}
|
||||
|
||||
func (chatTemplateKwargs) Apply(req *openai.ChatCompletionRequest, opts *ChatOptions, _ bool) (any, bool) {
|
||||
if opts == nil || opts.Thinking == nil {
|
||||
return nil, false
|
||||
}
|
||||
req.ChatTemplateKwargs = map[string]interface{}{
|
||||
"enable_thinking": *opts.Thinking,
|
||||
}
|
||||
return req, true
|
||||
}
|
||||
|
||||
// parseThinkingOverride reads extra_config.thinking_control and returns the
|
||||
// strategy it selects, or nil when unset (the provider adapter's default
|
||||
// strategy then applies). An unrecognized non-empty value falls back to
|
||||
// chat_template_kwargs, preserving the legacy default-mode behavior.
|
||||
func parseThinkingOverride(extraConfig map[string]string) ThinkingStrategy {
|
||||
if extraConfig == nil {
|
||||
return nil
|
||||
}
|
||||
switch strings.ToLower(strings.TrimSpace(extraConfig[ExtraConfigThinkingControl])) {
|
||||
case "":
|
||||
return nil
|
||||
case "none":
|
||||
return noThinking{}
|
||||
case "enable_thinking":
|
||||
return enableThinking{}
|
||||
case "thinking_type":
|
||||
return thinkingTypeField{}
|
||||
default:
|
||||
// "chat_template_kwargs" and any unknown non-empty value.
|
||||
return chatTemplateKwargs{}
|
||||
}
|
||||
}
|
||||
|
||||
// EffectiveThinkingControl reports the provider field that will carry
|
||||
// ChatOptions.Thinking. It intentionally shares the same adapter/override
|
||||
// resolution as the real request path so diagnostics do not guess from the
|
||||
// frontend selection.
|
||||
func EffectiveThinkingControl(config *ChatConfig) string {
|
||||
if config == nil {
|
||||
return "none"
|
||||
}
|
||||
if override := parseThinkingOverride(config.ExtraConfig); override != nil {
|
||||
return thinkingStrategyName(override)
|
||||
}
|
||||
providerName := provider.ProviderName(config.Provider)
|
||||
if providerName == "" {
|
||||
providerName = provider.DetectProvider(config.BaseURL)
|
||||
}
|
||||
return thinkingStrategyName(resolveProvider(providerName, config.ModelName).Thinking())
|
||||
}
|
||||
|
||||
func thinkingStrategyName(strategy ThinkingStrategy) string {
|
||||
switch strategy.(type) {
|
||||
case enableThinking:
|
||||
return "enable_thinking"
|
||||
case thinkingTypeField:
|
||||
return "thinking_type"
|
||||
case chatTemplateKwargs:
|
||||
return "chat_template_kwargs"
|
||||
default:
|
||||
return "none"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user