Files
ai_proxy/server/api/v1/app/ai_proxy.go

152 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package app
import (
"net/http"
"git.echol.cn/loser/ai_proxy/server/global"
"git.echol.cn/loser/ai_proxy/server/model/app"
"git.echol.cn/loser/ai_proxy/server/model/app/request"
"git.echol.cn/loser/ai_proxy/server/model/common/response"
"git.echol.cn/loser/ai_proxy/server/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type AiProxyApi struct{}
var aiProxyService = service.ServiceGroupApp.AppServiceGroup.AiProxyService
var aiApiKeyService = service.ServiceGroupApp.AppServiceGroup.AiApiKeyService
// ChatCompletions OpenAI兼容的聊天补全接口
// @Tags AiProxy
// @Summary 聊天补全OpenAI兼容
// @accept application/json
// @Produce application/json
// @Param data body request.ChatCompletionRequest true "聊天请求"
// @Success 200 {object} response.ChatCompletionResponse "聊天响应"
// @Router /v1/chat/completions [post]
func (a *AiProxyApi) ChatCompletions(c *gin.Context) {
var req request.ChatCompletionRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
// 获取 API Key 信息
apiKeyInfo, exists := c.Get("ai_api_key")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "未找到API密钥信息"})
return
}
apiKey := apiKeyInfo.(*app.AiApiKey)
// 验证模型权限
if req.Model != "" && !aiApiKeyService.CheckModelPermission(apiKey, req.Model) {
c.JSON(http.StatusForbidden, gin.H{
"error": gin.H{
"message": "该API密钥无权使用此模型: " + req.Model,
"type": "invalid_request_error",
"code": "model_not_allowed",
},
})
return
}
// 处理流式响应
if req.Stream {
aiProxyService.ProcessChatCompletionStream(c, &req)
return
}
// 处理普通响应
resp, err := aiProxyService.ProcessChatCompletion(c.Request.Context(), &req)
if err != nil {
global.GVA_LOG.Error("处理聊天请求失败!", zap.Error(err))
response.FailWithMessage(err.Error(), c)
return
}
c.JSON(200, resp)
}
// ListModels 获取可用模型列表
// @Tags AiProxy
// @Summary 获取可用模型列表OpenAI兼容
// @accept application/json
// @Produce application/json
// @Success 200 {object} response.ModelListResponse "模型列表"
// @Router /v1/models [get]
func (a *AiProxyApi) ListModels(c *gin.Context) {
// 获取 API Key 信息
apiKeyInfo, exists := c.Get("ai_api_key")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "未找到API密钥信息"})
return
}
apiKey := apiKeyInfo.(*app.AiApiKey)
// 获取可用模型列表
models, err := aiProxyService.GetAvailableModels(apiKey)
if err != nil {
global.GVA_LOG.Error("获取模型列表失败!", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(200, models)
}
// ClaudeMessages Claude API兼容的消息接口
// @Tags AiProxy
// @Summary 消息接口Claude兼容
// @accept application/json
// @Produce application/json
// @Param data body request.ClaudeMessageRequest true "消息请求"
// @Success 200 {object} response.ClaudeMessageResponse "消息响应"
// @Router /v1/messages [post]
func (a *AiProxyApi) ClaudeMessages(c *gin.Context) {
var req request.ClaudeMessageRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
// 获取 API Key 信息
apiKeyInfo, exists := c.Get("ai_api_key")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "未找到API密钥信息"})
return
}
apiKey := apiKeyInfo.(*app.AiApiKey)
// 验证模型权限
if req.Model != "" && !aiApiKeyService.CheckModelPermission(apiKey, req.Model) {
c.JSON(http.StatusForbidden, gin.H{
"error": gin.H{
"message": "该API密钥无权使用此模型: " + req.Model,
"type": "invalid_request_error",
"code": "model_not_allowed",
},
})
return
}
// 处理流式响应
if req.Stream {
aiProxyService.ProcessClaudeMessageStream(c, &req)
return
}
// 处理普通响应
resp, err := aiProxyService.ProcessClaudeMessage(c.Request.Context(), &req)
if err != nil {
global.GVA_LOG.Error("处理Claude消息请求失败!", zap.Error(err))
response.FailWithMessage(err.Error(), c)
return
}
c.JSON(200, resp)
}