perf: add HTTP connection pooling across providers and disable proxy buffering on SSE streams
This commit is contained in:
@@ -22,7 +22,7 @@ type DeepSeekProvider struct {
|
|||||||
|
|
||||||
func NewDeepSeekProvider(cfg config.DeepSeekConfig, apiKey string) *DeepSeekProvider {
|
func NewDeepSeekProvider(cfg config.DeepSeekConfig, apiKey string) *DeepSeekProvider {
|
||||||
return &DeepSeekProvider{
|
return &DeepSeekProvider{
|
||||||
client: resty.New().SetTimeout(10 * time.Minute),
|
client: NewOptimizedRestyClient(10 * time.Minute),
|
||||||
config: cfg,
|
config: cfg,
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type GeminiProvider struct {
|
|||||||
|
|
||||||
func NewGeminiProvider(cfg config.GeminiConfig, apiKey string) *GeminiProvider {
|
func NewGeminiProvider(cfg config.GeminiConfig, apiKey string) *GeminiProvider {
|
||||||
return &GeminiProvider{
|
return &GeminiProvider{
|
||||||
client: resty.New().SetTimeout(10 * time.Minute),
|
client: NewOptimizedRestyClient(10 * time.Minute),
|
||||||
config: cfg,
|
config: cfg,
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type GrokProvider struct {
|
|||||||
|
|
||||||
func NewGrokProvider(cfg config.GrokConfig, apiKey string) *GrokProvider {
|
func NewGrokProvider(cfg config.GrokConfig, apiKey string) *GrokProvider {
|
||||||
return &GrokProvider{
|
return &GrokProvider{
|
||||||
client: resty.New().SetTimeout(10 * time.Minute),
|
client: NewOptimizedRestyClient(10 * time.Minute),
|
||||||
config: cfg,
|
config: cfg,
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,15 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gophergate/internal/models"
|
"gophergate/internal/models"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var keySanitizeRegex = regexp.MustCompile(`(?i)(key|api_key|secret)=[^&]+`)
|
var keySanitizeRegex = regexp.MustCompile(`(?i)(key|api_key|secret)=[^&]+`)
|
||||||
@@ -18,6 +23,34 @@ func SanitizeURL(rawURL string) string {
|
|||||||
return keySanitizeRegex.ReplaceAllString(rawURL, "$1=REDACTED")
|
return keySanitizeRegex.ReplaceAllString(rawURL, "$1=REDACTED")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared HTTP transport configured with high connection pooling, TCP keep-alive,
|
||||||
|
// and HTTP/2 multiplexing to minimize latency when connecting to upstream LLM providers.
|
||||||
|
var sharedHTTPTransport = &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).DialContext,
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
MaxIdleConns: 200,
|
||||||
|
MaxIdleConnsPerHost: 50,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOptimizedRestyClient creates a resty client equipped with HTTP connection pooling.
|
||||||
|
func NewOptimizedRestyClient(timeout time.Duration) *resty.Client {
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: sharedHTTPTransport,
|
||||||
|
}
|
||||||
|
client := resty.NewWithClient(httpClient)
|
||||||
|
if timeout > 0 {
|
||||||
|
client.SetTimeout(timeout)
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func sanitizeFunctionName(name string) string {
|
func sanitizeFunctionName(name string) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type MoonshotProvider struct {
|
|||||||
|
|
||||||
func NewMoonshotProvider(cfg config.MoonshotConfig, apiKey string) *MoonshotProvider {
|
func NewMoonshotProvider(cfg config.MoonshotConfig, apiKey string) *MoonshotProvider {
|
||||||
return &MoonshotProvider{
|
return &MoonshotProvider{
|
||||||
client: resty.New().SetTimeout(10 * time.Minute),
|
client: NewOptimizedRestyClient(10 * time.Minute),
|
||||||
config: cfg,
|
config: cfg,
|
||||||
apiKey: strings.TrimSpace(apiKey),
|
apiKey: strings.TrimSpace(apiKey),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,9 @@ type OllamaProvider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewOllamaProvider(cfg config.OllamaConfig) *OllamaProvider {
|
func NewOllamaProvider(cfg config.OllamaConfig) *OllamaProvider {
|
||||||
client := resty.New()
|
client := NewOptimizedRestyClient(15 * time.Minute)
|
||||||
// Set reasonable timeouts for local Ollama server (longer for larger models)
|
// Set reasonable timeouts for local Ollama server (longer for larger models)
|
||||||
// For streaming, we want a very long timeout or none at all to handle generation time
|
// For streaming, we want a very long timeout or none at all to handle generation time
|
||||||
client.SetTimeout(15 * time.Minute)
|
|
||||||
client.SetRetryCount(2)
|
client.SetRetryCount(2)
|
||||||
client.SetRetryWaitTime(1 * time.Second)
|
client.SetRetryWaitTime(1 * time.Second)
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type OpenAIProvider struct {
|
|||||||
|
|
||||||
func NewOpenAIProvider(cfg config.OpenAIConfig, apiKey string) *OpenAIProvider {
|
func NewOpenAIProvider(cfg config.OpenAIConfig, apiKey string) *OpenAIProvider {
|
||||||
return &OpenAIProvider{
|
return &OpenAIProvider{
|
||||||
client: resty.New().SetTimeout(10 * time.Minute),
|
client: NewOptimizedRestyClient(10 * time.Minute),
|
||||||
config: cfg,
|
config: cfg,
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type XiaomiProvider struct {
|
|||||||
|
|
||||||
func NewXiaomiProvider(cfg config.XiaomiConfig, apiKey string) *XiaomiProvider {
|
func NewXiaomiProvider(cfg config.XiaomiConfig, apiKey string) *XiaomiProvider {
|
||||||
return &XiaomiProvider{
|
return &XiaomiProvider{
|
||||||
client: resty.New().SetTimeout(10 * time.Minute),
|
client: NewOptimizedRestyClient(10 * time.Minute),
|
||||||
config: cfg,
|
config: cfg,
|
||||||
apiKey: strings.TrimSpace(apiKey),
|
apiKey: strings.TrimSpace(apiKey),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -403,6 +403,7 @@ func (s *Server) handleResponses(c *gin.Context) {
|
|||||||
c.Header("Content-Type", "text/event-stream")
|
c.Header("Content-Type", "text/event-stream")
|
||||||
c.Header("Cache-Control", "no-cache")
|
c.Header("Cache-Control", "no-cache")
|
||||||
c.Header("Connection", "keep-alive")
|
c.Header("Connection", "keep-alive")
|
||||||
|
c.Header("X-Accel-Buffering", "no")
|
||||||
|
|
||||||
var lastUsage *models.ResponsesUsage
|
var lastUsage *models.ResponsesUsage
|
||||||
c.Stream(func(w io.Writer) bool {
|
c.Stream(func(w io.Writer) bool {
|
||||||
@@ -765,6 +766,7 @@ func (s *Server) handleChatCompletions(c *gin.Context) {
|
|||||||
c.Header("Content-Type", "text/event-stream")
|
c.Header("Content-Type", "text/event-stream")
|
||||||
c.Header("Cache-Control", "no-cache")
|
c.Header("Cache-Control", "no-cache")
|
||||||
c.Header("Connection", "keep-alive")
|
c.Header("Connection", "keep-alive")
|
||||||
|
c.Header("X-Accel-Buffering", "no")
|
||||||
|
|
||||||
var lastUsage *models.Usage
|
var lastUsage *models.Usage
|
||||||
c.Stream(func(w io.Writer) bool {
|
c.Stream(func(w io.Writer) bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user