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>
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user