mirror of
https://github.com/coaidev/coai.git
synced 2025-05-20 05:20:15 +09:00
feat: support deepseek thinking
This commit is contained in:
parent
403c2b898b
commit
fc81fbfc95
@ -5,8 +5,9 @@ import (
|
|||||||
"chat/adapter/baichuan"
|
"chat/adapter/baichuan"
|
||||||
"chat/adapter/bing"
|
"chat/adapter/bing"
|
||||||
"chat/adapter/claude"
|
"chat/adapter/claude"
|
||||||
"chat/adapter/common"
|
adaptercommon "chat/adapter/common"
|
||||||
"chat/adapter/dashscope"
|
"chat/adapter/dashscope"
|
||||||
|
"chat/adapter/deepseek"
|
||||||
"chat/adapter/hunyuan"
|
"chat/adapter/hunyuan"
|
||||||
"chat/adapter/midjourney"
|
"chat/adapter/midjourney"
|
||||||
"chat/adapter/openai"
|
"chat/adapter/openai"
|
||||||
@ -35,6 +36,7 @@ var channelFactories = map[string]adaptercommon.FactoryCreator{
|
|||||||
globals.SkylarkChannelType: skylark.NewChatInstanceFromConfig,
|
globals.SkylarkChannelType: skylark.NewChatInstanceFromConfig,
|
||||||
globals.ZhinaoChannelType: zhinao.NewChatInstanceFromConfig,
|
globals.ZhinaoChannelType: zhinao.NewChatInstanceFromConfig,
|
||||||
globals.MidjourneyChannelType: midjourney.NewChatInstanceFromConfig,
|
globals.MidjourneyChannelType: midjourney.NewChatInstanceFromConfig,
|
||||||
|
globals.DeepseekChannelType: deepseek.NewChatInstanceFromConfig,
|
||||||
|
|
||||||
globals.MoonshotChannelType: openai.NewChatInstanceFromConfig, // openai format
|
globals.MoonshotChannelType: openai.NewChatInstanceFromConfig, // openai format
|
||||||
globals.GroqChannelType: openai.NewChatInstanceFromConfig, // openai format
|
globals.GroqChannelType: openai.NewChatInstanceFromConfig, // openai format
|
||||||
|
174
adapter/deepseek/chat.go
Normal file
174
adapter/deepseek/chat.go
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package deepseek
|
||||||
|
|
||||||
|
import (
|
||||||
|
adaptercommon "chat/adapter/common"
|
||||||
|
"chat/globals"
|
||||||
|
"chat/utils"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChatInstance struct {
|
||||||
|
Endpoint string
|
||||||
|
ApiKey string
|
||||||
|
isFirstReasoning bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChatInstance) GetEndpoint() string {
|
||||||
|
return c.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChatInstance) GetApiKey() string {
|
||||||
|
return c.ApiKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChatInstance) GetHeader() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": fmt.Sprintf("Bearer %s", c.GetApiKey()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChatInstance(endpoint, apiKey string) *ChatInstance {
|
||||||
|
return &ChatInstance{
|
||||||
|
Endpoint: endpoint,
|
||||||
|
ApiKey: apiKey,
|
||||||
|
isFirstReasoning: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChatInstanceFromConfig(conf globals.ChannelConfig) adaptercommon.Factory {
|
||||||
|
return NewChatInstance(
|
||||||
|
conf.GetEndpoint(),
|
||||||
|
conf.GetRandomSecret(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChatInstance) GetChatEndpoint() string {
|
||||||
|
return fmt.Sprintf("%s/chat/completions", c.GetEndpoint())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChatInstance) GetChatBody(props *adaptercommon.ChatProps, stream bool) interface{} {
|
||||||
|
return ChatRequest{
|
||||||
|
Model: props.Model,
|
||||||
|
Messages: props.Message,
|
||||||
|
MaxTokens: props.MaxTokens,
|
||||||
|
Stream: stream,
|
||||||
|
Temperature: props.Temperature,
|
||||||
|
TopP: props.TopP,
|
||||||
|
PresencePenalty: props.PresencePenalty,
|
||||||
|
FrequencyPenalty: props.FrequencyPenalty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func processChatResponse(data string) *ChatResponse {
|
||||||
|
if form := utils.UnmarshalForm[ChatResponse](data); form != nil {
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func processChatStreamResponse(data string) *ChatStreamResponse {
|
||||||
|
if form := utils.UnmarshalForm[ChatStreamResponse](data); form != nil {
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func processChatErrorResponse(data string) *ChatStreamErrorResponse {
|
||||||
|
if form := utils.UnmarshalForm[ChatStreamErrorResponse](data); form != nil {
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChatInstance) ProcessLine(data string) (string, error) {
|
||||||
|
if form := processChatStreamResponse(data); form != nil {
|
||||||
|
if len(form.Choices) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
delta := form.Choices[0].Delta
|
||||||
|
if delta.ReasoningContent != nil {
|
||||||
|
content := *delta.ReasoningContent
|
||||||
|
// replace double newlines with single newlines for markdown
|
||||||
|
if strings.Contains(content, "\n\n") {
|
||||||
|
content = strings.ReplaceAll(content, "\n\n", "\n")
|
||||||
|
}
|
||||||
|
if c.isFirstReasoning {
|
||||||
|
c.isFirstReasoning = false
|
||||||
|
return fmt.Sprintf(">%s", content), nil
|
||||||
|
}
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
return delta.Content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if form := processChatErrorResponse(data); form != nil {
|
||||||
|
if form.Error.Message != "" {
|
||||||
|
return "", errors.New(fmt.Sprintf("deepseek error: %s", form.Error.Message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChatInstance) CreateChatRequest(props *adaptercommon.ChatProps) (string, error) {
|
||||||
|
res, err := utils.Post(
|
||||||
|
c.GetChatEndpoint(),
|
||||||
|
c.GetHeader(),
|
||||||
|
c.GetChatBody(props, false),
|
||||||
|
props.Proxy,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil || res == nil {
|
||||||
|
return "", fmt.Errorf("deepseek error: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
data := utils.MapToStruct[ChatResponse](res)
|
||||||
|
if data == nil {
|
||||||
|
return "", fmt.Errorf("deepseek error: cannot parse response")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data.Choices) == 0 {
|
||||||
|
return "", fmt.Errorf("deepseek error: no choices")
|
||||||
|
}
|
||||||
|
|
||||||
|
message := data.Choices[0].Message
|
||||||
|
content := message.Content
|
||||||
|
if message.ReasoningContent != nil {
|
||||||
|
content = fmt.Sprintf(">%s\n\n%s", *message.ReasoningContent, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChatInstance) CreateStreamChatRequest(props *adaptercommon.ChatProps, callback globals.Hook) error {
|
||||||
|
c.isFirstReasoning = true
|
||||||
|
err := utils.EventScanner(&utils.EventScannerProps{
|
||||||
|
Method: "POST",
|
||||||
|
Uri: c.GetChatEndpoint(),
|
||||||
|
Headers: c.GetHeader(),
|
||||||
|
Body: c.GetChatBody(props, true),
|
||||||
|
Callback: func(data string) error {
|
||||||
|
partial, err := c.ProcessLine(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return callback(&globals.Chunk{Content: partial})
|
||||||
|
},
|
||||||
|
}, props.Proxy)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if form := processChatErrorResponse(err.Body); form != nil {
|
||||||
|
if form.Error.Type == "" && form.Error.Message == "" {
|
||||||
|
return errors.New(utils.ToMarkdownCode("json", err.Body))
|
||||||
|
}
|
||||||
|
return errors.New(fmt.Sprintf("deepseek error: %s (type: %s)", form.Error.Message, form.Error.Type))
|
||||||
|
}
|
||||||
|
return err.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
9
adapter/deepseek/reflect.go
Normal file
9
adapter/deepseek/reflect.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package deepseek
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
var _ = reflect.TypeOf(ChatInstance{})
|
||||||
|
var _ = reflect.TypeOf(ChatRequest{})
|
||||||
|
var _ = reflect.TypeOf(ChatResponse{})
|
||||||
|
var _ = reflect.TypeOf(ChatStreamResponse{})
|
||||||
|
var _ = reflect.TypeOf(ChatStreamErrorResponse{})
|
56
adapter/deepseek/struct.go
Normal file
56
adapter/deepseek/struct.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package deepseek
|
||||||
|
|
||||||
|
import (
|
||||||
|
"chat/globals"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeepSeek API is similar to OpenAI API with additional reasoning content
|
||||||
|
|
||||||
|
type ChatRequest struct {
|
||||||
|
Model string `json:"model"`
|
||||||
|
Messages []globals.Message `json:"messages"`
|
||||||
|
MaxTokens *int `json:"max_tokens,omitempty"`
|
||||||
|
Stream bool `json:"stream"`
|
||||||
|
Temperature *float32 `json:"temperature,omitempty"`
|
||||||
|
TopP *float32 `json:"top_p,omitempty"`
|
||||||
|
PresencePenalty *float32 `json:"presence_penalty,omitempty"`
|
||||||
|
FrequencyPenalty *float32 `json:"frequency_penalty,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChatResponse is the native http request body for deepseek
|
||||||
|
type ChatResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Object string `json:"object"`
|
||||||
|
Created int64 `json:"created"`
|
||||||
|
Model string `json:"model"`
|
||||||
|
Choices []struct {
|
||||||
|
Index int `json:"index"`
|
||||||
|
Message globals.Message `json:"message"`
|
||||||
|
FinishReason string `json:"finish_reason"`
|
||||||
|
} `json:"choices"`
|
||||||
|
Usage struct {
|
||||||
|
PromptTokens int `json:"prompt_tokens"`
|
||||||
|
CompletionTokens int `json:"completion_tokens"`
|
||||||
|
TotalTokens int `json:"total_tokens"`
|
||||||
|
} `json:"usage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChatStreamResponse is the stream response body for deepseek
|
||||||
|
type ChatStreamResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Object string `json:"object"`
|
||||||
|
Created int64 `json:"created"`
|
||||||
|
Model string `json:"model"`
|
||||||
|
Choices []struct {
|
||||||
|
Delta globals.Message `json:"delta"`
|
||||||
|
Index int `json:"index"`
|
||||||
|
FinishReason string `json:"finish_reason"`
|
||||||
|
} `json:"choices"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatStreamErrorResponse struct {
|
||||||
|
Error struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
@ -66,6 +66,7 @@ export const ChannelTypes: Record<string, string> = {
|
|||||||
groq: "Groq Cloud",
|
groq: "Groq Cloud",
|
||||||
bing: "New Bing",
|
bing: "New Bing",
|
||||||
slack: "Slack Claude",
|
slack: "Slack Claude",
|
||||||
|
deepseek: "深度求索 DeepSeek",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ShortChannelTypes: Record<string, string> = {
|
export const ShortChannelTypes: Record<string, string> = {
|
||||||
@ -85,6 +86,7 @@ export const ShortChannelTypes: Record<string, string> = {
|
|||||||
groq: "Groq",
|
groq: "Groq",
|
||||||
bing: "Bing",
|
bing: "Bing",
|
||||||
slack: "Slack",
|
slack: "Slack",
|
||||||
|
deepseek: "深度求索",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChannelInfos: Record<string, ChannelInfo> = {
|
export const ChannelInfos: Record<string, ChannelInfo> = {
|
||||||
|
@ -25,6 +25,7 @@ const (
|
|||||||
MidjourneyChannelType = "midjourney"
|
MidjourneyChannelType = "midjourney"
|
||||||
MoonshotChannelType = "moonshot"
|
MoonshotChannelType = "moonshot"
|
||||||
GroqChannelType = "groq"
|
GroqChannelType = "groq"
|
||||||
|
DeepseekChannelType = "deepseek"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -3,12 +3,13 @@ package globals
|
|||||||
type Hook func(data *Chunk) error
|
type Hook func(data *Chunk) error
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Name *string `json:"name,omitempty"`
|
Name *string `json:"name,omitempty"`
|
||||||
FunctionCall *FunctionCall `json:"function_call,omitempty"` // only `function` role
|
FunctionCall *FunctionCall `json:"function_call,omitempty"` // only `function` role
|
||||||
ToolCallId *string `json:"tool_call_id,omitempty"` // only `tool` role
|
ToolCallId *string `json:"tool_call_id,omitempty"` // only `tool` role
|
||||||
ToolCalls *ToolCalls `json:"tool_calls,omitempty"` // only `assistant` role
|
ToolCalls *ToolCalls `json:"tool_calls,omitempty"` // only `assistant` role
|
||||||
|
ReasoningContent *string `json:"reasoning_content,omitempty"` // only for deepseek reasoner models
|
||||||
}
|
}
|
||||||
|
|
||||||
type Chunk struct {
|
type Chunk struct {
|
||||||
|
@ -133,6 +133,8 @@ const (
|
|||||||
SkylarkPlus = "skylark-plus-public"
|
SkylarkPlus = "skylark-plus-public"
|
||||||
SkylarkPro = "skylark-pro-public"
|
SkylarkPro = "skylark-pro-public"
|
||||||
SkylarkChat = "skylark-chat"
|
SkylarkChat = "skylark-chat"
|
||||||
|
DeepseekV3 = "deepseek-chat"
|
||||||
|
DeepseekR1 = "deepseek-reasoner"
|
||||||
)
|
)
|
||||||
|
|
||||||
var OpenAIDalleModels = []string{
|
var OpenAIDalleModels = []string{
|
||||||
|
Loading…
Reference in New Issue
Block a user