feat(deepseek): add support for Responses API and Chat Prefix Completion
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
/gophergate
|
/gophergate
|
||||||
|
/gophergate_*
|
||||||
/llm-proxy
|
/llm-proxy
|
||||||
/llm-proxy-go
|
/llm-proxy-go
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ A unified, high-performance LLM proxy gateway built in Go. It provides OpenAI-co
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Unified API:** OpenAI-compatible `/v1/chat/completions`, `/v1/images/generations`, `/v1/responses`, and `/v1/models` endpoints.
|
- **Unified API:** OpenAI-compatible `/v1/chat/completions`, `/v1/images/generations`, `/v1/responses`, and `/v1/models` endpoints.
|
||||||
- The `/v1/responses` endpoint (OpenAI Responses API) is currently supported for OpenAI models only. Non-OpenAI providers (Gemini, DeepSeek, Moonshot, Grok, Ollama, Xiaomi) return a "not supported" response.
|
- The `/v1/responses` endpoint (OpenAI Responses API) is supported for OpenAI and DeepSeek models. Non-supported providers (Gemini, Moonshot, Grok, Ollama, Xiaomi) return a "not supported" response.
|
||||||
- **Multi-Provider Support:**
|
- **Multi-Provider Support:**
|
||||||
- **OpenAI:** GPT-4o, GPT-4o Mini, GPT-5, GPT-5.4, o1/o3/o4 reasoning models, DALL-E 2/3 image generation.
|
- **OpenAI:** GPT-4o, GPT-4o Mini, GPT-5, GPT-5.4, o1/o3/o4 reasoning models, DALL-E 2/3 image generation.
|
||||||
- **Google Gemini:** Gemini 2.5 Flash/Pro, Gemini 3 Flash/Pro previews, Imagen 3 image generation.
|
- **Google Gemini:** Gemini 2.5 Flash/Pro, Gemini 3 Flash/Pro previews, Imagen 3 image generation.
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ func Init(path string) (*DB, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Connect to SQLite
|
// Connect to SQLite
|
||||||
dsn := fmt.Sprintf("file:%s?_pragma=foreign_keys(1)", path)
|
dsn := fmt.Sprintf("file:%s?_pragma=foreign_keys(1)&_busy_timeout=5000", path)
|
||||||
db, err := sqlx.Connect("sqlite", dsn)
|
db, err := sqlx.Connect("sqlite", dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ type ChatMessage struct {
|
|||||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||||
Name *string `json:"name,omitempty"`
|
Name *string `json:"name,omitempty"`
|
||||||
ToolCallID *string `json:"tool_call_id,omitempty"`
|
ToolCallID *string `json:"tool_call_id,omitempty"`
|
||||||
|
Prefix *bool `json:"prefix,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContentPart struct {
|
type ContentPart struct {
|
||||||
@@ -168,6 +169,7 @@ type UnifiedMessage struct {
|
|||||||
ToolCalls []ToolCall
|
ToolCalls []ToolCall
|
||||||
Name *string
|
Name *string
|
||||||
ToolCallID *string
|
ToolCallID *string
|
||||||
|
Prefix *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type UnifiedContentPart struct {
|
type UnifiedContentPart struct {
|
||||||
|
|||||||
@@ -257,9 +257,70 @@ func (p *DeepSeekProvider) ImageGeneration(ctx context.Context, req *models.Imag
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *DeepSeekProvider) Responses(ctx context.Context, req *models.ResponsesRequest) (*models.ResponsesResponse, error) {
|
func (p *DeepSeekProvider) Responses(ctx context.Context, req *models.ResponsesRequest) (*models.ResponsesResponse, error) {
|
||||||
return nil, fmt.Errorf("responses API not supported by deepseek")
|
stream := req.Stream != nil && *req.Stream
|
||||||
|
body := BuildOpenAIResponsesBody(req, stream)
|
||||||
|
|
||||||
|
resp, err := p.client.R().
|
||||||
|
SetContext(ctx).
|
||||||
|
SetHeader("Authorization", "Bearer "+p.apiKey).
|
||||||
|
SetBody(body).
|
||||||
|
Post(fmt.Sprintf("%s/responses", p.config.BaseURL))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("responses request failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !resp.IsSuccess() {
|
||||||
|
msg := resp.String()
|
||||||
|
if msg == "" && resp.RawBody() != nil {
|
||||||
|
if bodyBytes, err := io.ReadAll(resp.RawBody()); err == nil {
|
||||||
|
msg = string(bodyBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("DeepSeek Responses API error (%d): %s", resp.StatusCode(), msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
var respJSON map[string]interface{}
|
||||||
|
if err := json.Unmarshal(resp.Body(), &respJSON); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse responses response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseOpenAIResponsesResponse(respJSON, req.Model)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DeepSeekProvider) ResponsesStream(ctx context.Context, req *models.ResponsesRequest) (<-chan *models.ResponsesStreamChunk, error) {
|
func (p *DeepSeekProvider) ResponsesStream(ctx context.Context, req *models.ResponsesRequest) (<-chan *models.ResponsesStreamChunk, error) {
|
||||||
return nil, fmt.Errorf("responses API not supported by deepseek")
|
body := BuildOpenAIResponsesBody(req, true)
|
||||||
|
|
||||||
|
resp, err := p.client.R().
|
||||||
|
SetContext(ctx).
|
||||||
|
SetHeader("Authorization", "Bearer "+p.apiKey).
|
||||||
|
SetBody(body).
|
||||||
|
SetDoNotParseResponse(true).
|
||||||
|
Post(fmt.Sprintf("%s/responses", p.config.BaseURL))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("responses stream request failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !resp.IsSuccess() {
|
||||||
|
msg := resp.String()
|
||||||
|
if msg == "" && resp.RawBody() != nil {
|
||||||
|
if bodyBytes, err := io.ReadAll(resp.RawBody()); err == nil {
|
||||||
|
msg = string(bodyBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("DeepSeek Responses API error (%d): %s", resp.StatusCode(), msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan *models.ResponsesStreamChunk)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
err := StreamOpenAIResponses(resp.RawBody(), ch)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("DeepSeek Responses stream error: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return ch, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,6 +158,9 @@ func MessagesToOpenAIJSON(messages []models.UnifiedMessage) ([]interface{}, erro
|
|||||||
if m.Name != nil {
|
if m.Name != nil {
|
||||||
msg["name"] = *m.Name
|
msg["name"] = *m.Name
|
||||||
}
|
}
|
||||||
|
if m.Prefix != nil {
|
||||||
|
msg["prefix"] = *m.Prefix
|
||||||
|
}
|
||||||
result = append(result, msg)
|
result = append(result, msg)
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|||||||
@@ -700,6 +700,7 @@ func (s *Server) handleChatCompletions(c *gin.Context) {
|
|||||||
ToolCalls: msg.ToolCalls,
|
ToolCalls: msg.ToolCalls,
|
||||||
Name: msg.Name,
|
Name: msg.Name,
|
||||||
ToolCallID: msg.ToolCallID,
|
ToolCallID: msg.ToolCallID,
|
||||||
|
Prefix: msg.Prefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle multimodal content
|
// Handle multimodal content
|
||||||
|
|||||||
Reference in New Issue
Block a user