Files
team-api/relay/handler/gemini_handler.go
T
fa053529af 图片请求统一异步处理 (#11)
* fix:未导入unicode包

* feat:图片异步转换

* feat:添加R2支持

* feat:新增文件管理

* fix:修复错误信息双写导致响应异常

* chore:渠道列表中添加快捷启用停止按钮

* fix:修复模型异常终端导致报错的问题

* improve:优化无可用渠道的日志打印,减少日志污染

* fix:在线体验出错缺少提示信息

* fix:修复日志库问题,只有请求日志保存到独立的库中

* feat:新增阿里图片模型说明文档

* fix:阿里千问图片生成

* fix:修复图片异步控制无效问题

* improve:优化文件展示,支持缩略图查看

* feat:实时监控中添加图片异步转换池状态信息

* improve:优化在线体验页面布局

* fix:修复异步任务状态显示异常问题

* feat:图片异步任务停止时等待任务完成后再退出程序

* chore:调整存储路径

---------

Co-authored-by: qian <wuchengqian5@163.com>
Co-authored-by: chengqian <wuchengqian@kkidc.com>
2026-07-18 00:26:13 +08:00

123 lines
4.0 KiB
Go

package handler
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/gogf/gf/v2/frame/g"
"github.com/qianfree/team-api/relay/common"
"github.com/qianfree/team-api/relay/constant"
"github.com/qianfree/team-api/relay/dto"
)
// HandleGeminiGenerateContent 处理 /v1beta/models/{model}:generateContent 请求(Gemini 原生格式)
func HandleGeminiGenerateContent(ctx context.Context, body []byte, path string, headers http.Header, rc *RelayContext, provider common.DataProvider, billing common.BillingProvider) (*common.Usage, *BillingResult, error) {
return RelayHandler(ctx, body, path, headers, rc, provider, billing)
}
// HandleGeminiModels 处理 GET /v1beta/models 请求(Gemini 原生格式模型列表)
func HandleGeminiModels(ctx context.Context, tenantID, apiKeyID, userID int64, provider common.DataProvider) (*dto.GeminiModelsResponse, error) {
models, err := provider.GetAvailableModels(ctx, tenantID, apiKeyID, userID)
if err != nil {
return nil, err
}
items := make([]dto.GeminiModel, 0, len(models))
for _, m := range models {
items = append(items, dto.GeminiModel{
Name: fmt.Sprintf("models/%s", m.ModelId),
BaseModelId: m.ModelId,
DisplayName: m.ModelName,
SupportedGenerationMethods: []string{"generateContent", "countTokens"},
})
}
return &dto.GeminiModelsResponse{
Models: items,
}, nil
}
// HandleGeminiModelDetail 处理 GET /v1beta/models/{model} 请求(Gemini 原生格式模型详情)
func HandleGeminiModelDetail(ctx context.Context, tenantID int64, modelName string, provider common.DataProvider) (*dto.GeminiModel, error) {
detail, err := provider.GetModelDetail(ctx, tenantID, modelName)
if err != nil {
return nil, err
}
return &dto.GeminiModel{
Name: fmt.Sprintf("models/%s", detail.ID),
BaseModelId: detail.ID,
DisplayName: detail.ModelName,
Description: detail.Description,
InputTokenLimit: detail.MaxContextTokens,
OutputTokenLimit: detail.MaxOutputTokens,
SupportedGenerationMethods: []string{"generateContent", "countTokens"},
}, nil
}
// WriteGeminiRelayError 写入 Gemini 格式的错误响应
func WriteGeminiRelayError(w http.ResponseWriter, err error) {
// adaptor 已直接写入响应体(如 Gemini 原生格式透传),跳过二次写入
var prewritten *constant.RelayError
if errors.As(err, &prewritten) && prewritten.ResponseWritten {
return
}
var relayErr *constant.RelayError
var rateLimitErr *RelayErrorWithRateLimit
statusCode := http.StatusInternalServerError
errMsg := err.Error()
errStatus := "INTERNAL"
if errors.As(err, &rateLimitErr) {
statusCode = rateLimitErr.StatusCode
errMsg = rateLimitErr.Message
errStatus = "RESOURCE_EXHAUSTED"
} else if errors.As(err, &relayErr) {
statusCode = relayErr.StatusCode
errMsg = relayErr.Message
if relayErr.Cause != nil {
errMsg = relayErr.Message + ": " + relayErr.Cause.Error()
}
switch statusCode {
case 401:
errStatus = "UNAUTHENTICATED"
case 403:
errStatus = "PERMISSION_DENIED"
case 400:
errStatus = "INVALID_ARGUMENT"
case 404:
errStatus = "NOT_FOUND"
default:
errStatus = "INTERNAL"
}
}
if statusCode < 100 || statusCode > 599 {
statusCode = http.StatusInternalServerError
}
// 无可用渠道是正常业务条件,已在 handleChannelUnavailable 中以 Warning 记录,此处跳过避免重复日志;
// 其余 5xx 为真实错误,保留 ERROR 但禁用堆栈打印(此处调用栈固定,无调试价值)
if statusCode >= 500 && !errors.Is(err, common.ErrChannelUnavailable) {
g.Log().Stack(false).Errorf(context.Background(), "[GeminiRelayError] statusCode=%d status=%s message=%s originalError=%v",
statusCode, errStatus, errMsg, err)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
errBody, _ := json.Marshal(map[string]any{
"error": map[string]any{
"code": statusCode,
"message": errMsg,
"status": errStatus,
},
})
_, _ = w.Write(errBody)
}