76ba500417
CI / docker (push) Successful in 2m4s
OpenAI-compatible AI gateway with Vue admin UI, multi-provider egress, ingress key governance, monitoring, and security controls. Co-authored-by: Cursor <cursoragent@cursor.com>
177 lines
6.2 KiB
Go
177 lines
6.2 KiB
Go
package provider
|
||
|
||
import (
|
||
"strings"
|
||
|
||
"github.com/rose_cat707/luminary/internal/model"
|
||
)
|
||
|
||
// EndpointType 接口类型,对应入口 /v1/* 与上游 path 映射
|
||
type EndpointType string
|
||
|
||
const (
|
||
EndpointListModels EndpointType = "list_models"
|
||
EndpointTextCompletion EndpointType = "text_completion"
|
||
EndpointChatCompletion EndpointType = "chat_completion"
|
||
EndpointResponses EndpointType = "responses"
|
||
EndpointEmbeddings EndpointType = "embeddings"
|
||
EndpointRerank EndpointType = "rerank"
|
||
EndpointSpeech EndpointType = "speech"
|
||
EndpointTranscription EndpointType = "transcription"
|
||
EndpointImageGeneration EndpointType = "image_generation"
|
||
)
|
||
|
||
type EndpointMeta struct {
|
||
Key EndpointType `json:"key"`
|
||
Label string `json:"label"`
|
||
Method string `json:"method"`
|
||
IngressPath string `json:"ingress_path"`
|
||
Categories []model.ProviderCategory `json:"categories"`
|
||
}
|
||
|
||
var AllEndpoints = []EndpointMeta{
|
||
{Key: EndpointListModels, Label: "List Models", Method: "GET", IngressPath: "/v1/models", Categories: []model.ProviderCategory{model.CategoryLLM, model.CategoryEmbedding, model.CategoryRerank}},
|
||
{Key: EndpointTextCompletion, Label: "Text Completion", Method: "POST", IngressPath: "/v1/completions", Categories: []model.ProviderCategory{model.CategoryLLM}},
|
||
{Key: EndpointChatCompletion, Label: "Chat Completion", Method: "POST", IngressPath: "/v1/chat/completions", Categories: []model.ProviderCategory{model.CategoryLLM}},
|
||
{Key: EndpointResponses, Label: "Responses", Method: "POST", IngressPath: "/v1/responses", Categories: []model.ProviderCategory{model.CategoryLLM}},
|
||
{Key: EndpointEmbeddings, Label: "Embeddings", Method: "POST", IngressPath: "/v1/embeddings", Categories: []model.ProviderCategory{model.CategoryEmbedding}},
|
||
{Key: EndpointRerank, Label: "Rerank", Method: "POST", IngressPath: "/v1/rerank", Categories: []model.ProviderCategory{model.CategoryRerank}},
|
||
{Key: EndpointSpeech, Label: "Speech", Method: "POST", IngressPath: "/v1/audio/speech", Categories: []model.ProviderCategory{model.CategorySpeech}},
|
||
{Key: EndpointTranscription, Label: "Transcription", Method: "POST", IngressPath: "/v1/audio/transcriptions", Categories: []model.ProviderCategory{model.CategoryAudio}},
|
||
{Key: EndpointImageGeneration, Label: "Image Generation", Method: "POST", IngressPath: "/v1/images/generations", Categories: []model.ProviderCategory{model.CategoryImage}},
|
||
}
|
||
|
||
// DefaultPaths 各 Provider 类型的默认上游 path(相对 base_url)
|
||
var DefaultPaths = map[model.ProviderType]map[EndpointType]string{
|
||
model.ProviderOpenAI: {
|
||
EndpointListModels: "/models",
|
||
EndpointTextCompletion: "/completions",
|
||
EndpointChatCompletion: "/chat/completions",
|
||
EndpointResponses: "/responses",
|
||
EndpointEmbeddings: "/embeddings",
|
||
EndpointRerank: "/rerank",
|
||
EndpointSpeech: "/audio/speech",
|
||
EndpointTranscription: "/audio/transcriptions",
|
||
EndpointImageGeneration: "/images/generations",
|
||
},
|
||
model.ProviderAnthropic: {
|
||
EndpointListModels: "/models",
|
||
EndpointChatCompletion: "/messages",
|
||
},
|
||
model.ProviderCustom: {
|
||
EndpointListModels: "/models",
|
||
EndpointTextCompletion: "/completions",
|
||
EndpointChatCompletion: "/chat/completions",
|
||
EndpointResponses: "/responses",
|
||
EndpointEmbeddings: "/embeddings",
|
||
EndpointRerank: "/rerank",
|
||
EndpointSpeech: "/audio/speech",
|
||
EndpointTranscription: "/audio/transcriptions",
|
||
EndpointImageGeneration: "/images/generations",
|
||
},
|
||
model.ProviderAzureOpenAI: {
|
||
EndpointListModels: "/models",
|
||
EndpointChatCompletion: "/chat/completions",
|
||
EndpointResponses: "/responses",
|
||
EndpointEmbeddings: "/embeddings",
|
||
EndpointRerank: "/rerank",
|
||
},
|
||
model.ProviderOpenRouter: {
|
||
EndpointListModels: "/models",
|
||
EndpointChatCompletion: "/chat/completions",
|
||
EndpointResponses: "/responses",
|
||
EndpointEmbeddings: "/embeddings",
|
||
EndpointRerank: "/rerank",
|
||
},
|
||
model.ProviderOllama: {
|
||
EndpointListModels: "/models",
|
||
EndpointChatCompletion: "/chat/completions",
|
||
EndpointEmbeddings: "/embeddings",
|
||
EndpointRerank: "/rerank",
|
||
},
|
||
model.ProviderComfyUI: {},
|
||
}
|
||
|
||
func DefaultPath(providerType model.ProviderType, endpoint EndpointType) string {
|
||
if m, ok := DefaultPaths[providerType]; ok {
|
||
if p, ok := m[endpoint]; ok {
|
||
return p
|
||
}
|
||
}
|
||
if p, ok := DefaultPaths[model.ProviderOpenAI][endpoint]; ok {
|
||
return p
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// DefaultEndpointsForCategory 创建出口时默认启用的 endpoint keys
|
||
func DefaultEndpointsForCategory(cat model.ProviderCategory) []string {
|
||
switch cat {
|
||
case model.CategoryEmbedding:
|
||
return []string{string(EndpointListModels), string(EndpointEmbeddings)}
|
||
case model.CategoryRerank:
|
||
return []string{string(EndpointListModels), string(EndpointRerank)}
|
||
case model.CategoryImage:
|
||
return []string{}
|
||
default:
|
||
return []string{string(EndpointListModels), string(EndpointChatCompletion)}
|
||
}
|
||
}
|
||
|
||
func EndpointsForCategory(cat model.ProviderCategory) []EndpointMeta {
|
||
var out []EndpointMeta
|
||
for _, e := range AllEndpoints {
|
||
for _, c := range e.Categories {
|
||
if c == cat {
|
||
out = append(out, e)
|
||
break
|
||
}
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// ResolveURL 解析最终请求 URL:override 可为相对 path 或完整 URL
|
||
func ResolveURL(baseURL, override, defaultPath string) string {
|
||
if override != "" {
|
||
if strings.HasPrefix(override, "http://") || strings.HasPrefix(override, "https://") {
|
||
return override
|
||
}
|
||
return joinURL(baseURL, override)
|
||
}
|
||
return joinURL(baseURL, defaultPath)
|
||
}
|
||
|
||
func joinURL(base, path string) string {
|
||
base = strings.TrimRight(base, "/")
|
||
if path == "" {
|
||
return base
|
||
}
|
||
if !strings.HasPrefix(path, "/") {
|
||
path = "/" + path
|
||
}
|
||
if base == "" {
|
||
return path
|
||
}
|
||
return base + path
|
||
}
|
||
|
||
func CategoryLabel(cat model.ProviderCategory) string {
|
||
switch cat {
|
||
case model.CategoryLLM:
|
||
return "语言模型"
|
||
case model.CategoryEmbedding:
|
||
return "向量嵌入"
|
||
case model.CategoryRerank:
|
||
return "重排序"
|
||
case model.CategorySpeech:
|
||
return "语音合成"
|
||
case model.CategoryImage:
|
||
return "图像生成"
|
||
case model.CategoryAudio:
|
||
return "音频转写"
|
||
default:
|
||
return string(cat)
|
||
}
|
||
}
|