Skip to content

Commit 48d358f

Browse files
committed
feat(adaptor): 新适配百炼多种图片生成模型
- wan2.6系列生图与编辑,适配多图生成计费 - wan2.5系列生图与编辑 - z-image-turbo生图,适配prompt_extend计费
1 parent 8063897 commit 48d358f

File tree

16 files changed

+335
-154
lines changed

16 files changed

+335
-154
lines changed

controller/token.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package controller
22

33
import (
4+
"fmt"
45
"net/http"
56
"strconv"
67
"strings"
@@ -149,6 +150,24 @@ func AddToken(c *gin.Context) {
149150
})
150151
return
151152
}
153+
// 非无限额度时,检查额度值是否超出有效范围
154+
if !token.UnlimitedQuota {
155+
if token.RemainQuota < 0 {
156+
c.JSON(http.StatusOK, gin.H{
157+
"success": false,
158+
"message": "额度值不能为负数",
159+
})
160+
return
161+
}
162+
maxQuotaValue := int((1000000000 * common.QuotaPerUnit))
163+
if token.RemainQuota > maxQuotaValue {
164+
c.JSON(http.StatusOK, gin.H{
165+
"success": false,
166+
"message": fmt.Sprintf("额度值超出有效范围,最大值为 %d", maxQuotaValue),
167+
})
168+
return
169+
}
170+
}
152171
key, err := common.GenerateKey()
153172
if err != nil {
154173
c.JSON(http.StatusOK, gin.H{
@@ -216,6 +235,23 @@ func UpdateToken(c *gin.Context) {
216235
})
217236
return
218237
}
238+
if !token.UnlimitedQuota {
239+
if token.RemainQuota < 0 {
240+
c.JSON(http.StatusOK, gin.H{
241+
"success": false,
242+
"message": "额度值不能为负数",
243+
})
244+
return
245+
}
246+
maxQuotaValue := int((1000000000 * common.QuotaPerUnit))
247+
if token.RemainQuota > maxQuotaValue {
248+
c.JSON(http.StatusOK, gin.H{
249+
"success": false,
250+
"message": fmt.Sprintf("额度值超出有效范围,最大值为 %d", maxQuotaValue),
251+
})
252+
return
253+
}
254+
}
219255
cleanToken, err := model.GetTokenByIds(token.Id, userId)
220256
if err != nil {
221257
common.ApiError(c, err)
@@ -261,7 +297,6 @@ func UpdateToken(c *gin.Context) {
261297
"message": "",
262298
"data": cleanToken,
263299
})
264-
return
265300
}
266301

267302
type TokenBatch struct {

dto/openai_image.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,9 @@ func (i *ImageRequest) SetModelName(modelName string) {
167167
}
168168

169169
type ImageResponse struct {
170-
Data []ImageData `json:"data"`
171-
Created int64 `json:"created"`
172-
Extra any `json:"extra,omitempty"`
170+
Data []ImageData `json:"data"`
171+
Created int64 `json:"created"`
172+
Metadata json.RawMessage `json:"metadata,omitempty"`
173173
}
174174
type ImageData struct {
175175
Url string `json:"url"`

dto/openai_request.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ type FormatJsonSchema struct {
2323
Strict json.RawMessage `json:"strict,omitempty"`
2424
}
2525

26+
// GeneralOpenAIRequest represents a general request structure for OpenAI-compatible APIs.
27+
// 参数增加规范:无引用的参数必须使用json.RawMessage类型,并添加omitempty标签
2628
type GeneralOpenAIRequest struct {
2729
Model string `json:"model,omitempty"`
2830
Messages []Message `json:"messages,omitempty"`
@@ -82,8 +84,9 @@ type GeneralOpenAIRequest struct {
8284
Reasoning json.RawMessage `json:"reasoning,omitempty"`
8385
// Ali Qwen Params
8486
VlHighResolutionImages json.RawMessage `json:"vl_high_resolution_images,omitempty"`
85-
EnableThinking any `json:"enable_thinking,omitempty"`
87+
EnableThinking json.RawMessage `json:"enable_thinking,omitempty"`
8688
ChatTemplateKwargs json.RawMessage `json:"chat_template_kwargs,omitempty"`
89+
EnableSearch json.RawMessage `json:"enable_search,omitempty"`
8790
// ollama Params
8891
Think json.RawMessage `json:"think,omitempty"`
8992
// baidu v2

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ func InjectUmamiAnalytics() {
188188
analyticsInjectBuilder.WriteString(umamiSiteID)
189189
analyticsInjectBuilder.WriteString("\"></script>")
190190
}
191+
analyticsInjectBuilder.WriteString("<!--Umami QuantumNous-->\n")
191192
analyticsInject := analyticsInjectBuilder.String()
192193
indexPage = bytes.ReplaceAll(indexPage, []byte("<!--umami-->\n"), []byte(analyticsInject))
193194
}
@@ -209,6 +210,7 @@ func InjectGoogleAnalytics() {
209210
analyticsInjectBuilder.WriteString("');")
210211
analyticsInjectBuilder.WriteString("</script>")
211212
}
213+
analyticsInjectBuilder.WriteString("<!--Google Analytics QuantumNous-->\n")
212214
analyticsInject := analyticsInjectBuilder.String()
213215
indexPage = bytes.ReplaceAll(indexPage, []byte("<!--Google Analytics-->\n"), []byte(analyticsInject))
214216
}

relay/audio_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func AudioHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *type
7070
if usage.(*dto.Usage).CompletionTokenDetails.AudioTokens > 0 || usage.(*dto.Usage).PromptTokensDetails.AudioTokens > 0 {
7171
service.PostAudioConsumeQuota(c, info, usage.(*dto.Usage), "")
7272
} else {
73-
postConsumeQuota(c, info, usage.(*dto.Usage), "")
73+
postConsumeQuota(c, info, usage.(*dto.Usage))
7474
}
7575

7676
return nil

relay/channel/ali/adaptor.go

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ import (
1919
)
2020

2121
type Adaptor struct {
22+
IsSyncImageModel bool
23+
}
24+
25+
var syncModels = []string{
26+
"z-image",
27+
"qwen-image",
28+
"wan2.6",
29+
}
30+
31+
func isSyncImageModel(modelName string) bool {
32+
for _, m := range syncModels {
33+
if strings.Contains(modelName, m) {
34+
return true
35+
}
36+
}
37+
return false
2238
}
2339

2440
func (a *Adaptor) ConvertGeminiRequest(*gin.Context, *relaycommon.RelayInfo, *dto.GeminiChatRequest) (any, error) {
@@ -45,10 +61,16 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
4561
case constant.RelayModeRerank:
4662
fullRequestURL = fmt.Sprintf("%s/api/v1/services/rerank/text-rerank/text-rerank", info.ChannelBaseUrl)
4763
case constant.RelayModeImagesGenerations:
48-
fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/text2image/image-synthesis", info.ChannelBaseUrl)
64+
if isSyncImageModel(info.OriginModelName) {
65+
fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/multimodal-generation/generation", info.ChannelBaseUrl)
66+
} else {
67+
fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/text2image/image-synthesis", info.ChannelBaseUrl)
68+
}
4969
case constant.RelayModeImagesEdits:
50-
if isWanModel(info.OriginModelName) {
70+
if isOldWanModel(info.OriginModelName) {
5171
fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/image2image/image-synthesis", info.ChannelBaseUrl)
72+
} else if isWanModel(info.OriginModelName) {
73+
fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/image-generation/generation", info.ChannelBaseUrl)
5274
} else {
5375
fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/multimodal-generation/generation", info.ChannelBaseUrl)
5476
}
@@ -72,7 +94,11 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *rel
7294
req.Set("X-DashScope-Plugin", c.GetString("plugin"))
7395
}
7496
if info.RelayMode == constant.RelayModeImagesGenerations {
75-
req.Set("X-DashScope-Async", "enable")
97+
if isSyncImageModel(info.OriginModelName) {
98+
99+
} else {
100+
req.Set("X-DashScope-Async", "enable")
101+
}
76102
}
77103
if info.RelayMode == constant.RelayModeImagesEdits {
78104
if isWanModel(info.OriginModelName) {
@@ -108,15 +134,25 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
108134

109135
func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
110136
if info.RelayMode == constant.RelayModeImagesGenerations {
111-
aliRequest, err := oaiImage2Ali(request)
137+
if isSyncImageModel(info.OriginModelName) {
138+
a.IsSyncImageModel = true
139+
}
140+
aliRequest, err := oaiImage2AliImageRequest(info, request, a.IsSyncImageModel)
112141
if err != nil {
113-
return nil, fmt.Errorf("convert image request failed: %w", err)
142+
return nil, fmt.Errorf("convert image request to async ali image request failed: %w", err)
114143
}
115144
return aliRequest, nil
116145
} else if info.RelayMode == constant.RelayModeImagesEdits {
117-
if isWanModel(info.OriginModelName) {
146+
if isOldWanModel(info.OriginModelName) {
118147
return oaiFormEdit2WanxImageEdit(c, info, request)
119148
}
149+
if isSyncImageModel(info.OriginModelName) {
150+
if isWanModel(info.OriginModelName) {
151+
a.IsSyncImageModel = false
152+
} else {
153+
a.IsSyncImageModel = true
154+
}
155+
}
120156
// ali image edit https://bailian.console.aliyun.com/?tab=api#/api/?type=model&url=2976416
121157
// 如果用户使用表单,则需要解析表单数据
122158
if strings.Contains(c.Request.Header.Get("Content-Type"), "multipart/form-data") {
@@ -126,9 +162,9 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
126162
}
127163
return aliRequest, nil
128164
} else {
129-
aliRequest, err := oaiImage2Ali(request)
165+
aliRequest, err := oaiImage2AliImageRequest(info, request, a.IsSyncImageModel)
130166
if err != nil {
131-
return nil, fmt.Errorf("convert image request failed: %w", err)
167+
return nil, fmt.Errorf("convert image request to async ali image request failed: %w", err)
132168
}
133169
return aliRequest, nil
134170
}
@@ -169,13 +205,9 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
169205
default:
170206
switch info.RelayMode {
171207
case constant.RelayModeImagesGenerations:
172-
err, usage = aliImageHandler(c, resp, info)
208+
err, usage = aliImageHandler(a, c, resp, info)
173209
case constant.RelayModeImagesEdits:
174-
if isWanModel(info.OriginModelName) {
175-
err, usage = aliImageHandler(c, resp, info)
176-
} else {
177-
err, usage = aliImageEditHandler(c, resp, info)
178-
}
210+
err, usage = aliImageHandler(a, c, resp, info)
179211
case constant.RelayModeRerank:
180212
err, usage = RerankHandler(c, resp, info)
181213
default:

relay/channel/ali/dto.go

Lines changed: 98 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package ali
22

3-
import "github.com/QuantumNous/new-api/dto"
3+
import (
4+
"strings"
5+
6+
"github.com/QuantumNous/new-api/dto"
7+
"github.com/QuantumNous/new-api/logger"
8+
"github.com/QuantumNous/new-api/service"
9+
"github.com/gin-gonic/gin"
10+
)
411

512
type AliMessage struct {
613
Content any `json:"content"`
@@ -65,6 +72,7 @@ type AliUsage struct {
6572
InputTokens int `json:"input_tokens"`
6673
OutputTokens int `json:"output_tokens"`
6774
TotalTokens int `json:"total_tokens"`
75+
ImageCount int `json:"image_count,omitempty"`
6876
}
6977

7078
type TaskResult struct {
@@ -75,14 +83,78 @@ type TaskResult struct {
7583
}
7684

7785
type AliOutput struct {
78-
TaskId string `json:"task_id,omitempty"`
79-
TaskStatus string `json:"task_status,omitempty"`
80-
Text string `json:"text"`
81-
FinishReason string `json:"finish_reason"`
82-
Message string `json:"message,omitempty"`
83-
Code string `json:"code,omitempty"`
84-
Results []TaskResult `json:"results,omitempty"`
85-
Choices []map[string]any `json:"choices,omitempty"`
86+
TaskId string `json:"task_id,omitempty"`
87+
TaskStatus string `json:"task_status,omitempty"`
88+
Text string `json:"text"`
89+
FinishReason string `json:"finish_reason"`
90+
Message string `json:"message,omitempty"`
91+
Code string `json:"code,omitempty"`
92+
Results []TaskResult `json:"results,omitempty"`
93+
Choices []struct {
94+
FinishReason string `json:"finish_reason,omitempty"`
95+
Message struct {
96+
Role string `json:"role,omitempty"`
97+
Content []AliMediaContent `json:"content,omitempty"`
98+
ReasoningContent string `json:"reasoning_content,omitempty"`
99+
} `json:"message,omitempty"`
100+
} `json:"choices,omitempty"`
101+
}
102+
103+
func (o *AliOutput) ChoicesToOpenAIImageDate(c *gin.Context, responseFormat string) []dto.ImageData {
104+
var imageData []dto.ImageData
105+
if len(o.Choices) > 0 {
106+
for _, choice := range o.Choices {
107+
var data dto.ImageData
108+
for _, content := range choice.Message.Content {
109+
if content.Image != "" {
110+
if strings.HasPrefix(content.Image, "http") {
111+
var b64Json string
112+
if responseFormat == "b64_json" {
113+
_, b64, err := service.GetImageFromUrl(content.Image)
114+
if err != nil {
115+
logger.LogError(c, "get_image_data_failed: "+err.Error())
116+
continue
117+
}
118+
b64Json = b64
119+
}
120+
data.Url = content.Image
121+
data.B64Json = b64Json
122+
} else {
123+
data.B64Json = content.Image
124+
}
125+
} else if content.Text != "" {
126+
data.RevisedPrompt = content.Text
127+
}
128+
}
129+
imageData = append(imageData, data)
130+
}
131+
}
132+
133+
return imageData
134+
}
135+
136+
func (o *AliOutput) ResultToOpenAIImageDate(c *gin.Context, responseFormat string) []dto.ImageData {
137+
var imageData []dto.ImageData
138+
for _, data := range o.Results {
139+
var b64Json string
140+
if responseFormat == "b64_json" {
141+
_, b64, err := service.GetImageFromUrl(data.Url)
142+
if err != nil {
143+
logger.LogError(c, "get_image_data_failed: "+err.Error())
144+
continue
145+
}
146+
b64Json = b64
147+
} else {
148+
b64Json = data.B64Image
149+
}
150+
151+
imageData = append(imageData, dto.ImageData{
152+
Url: data.Url,
153+
B64Json: b64Json,
154+
RevisedPrompt: "",
155+
})
156+
}
157+
return imageData
86158
}
87159

88160
type AliResponse struct {
@@ -92,18 +164,26 @@ type AliResponse struct {
92164
}
93165

94166
type AliImageRequest struct {
95-
Model string `json:"model"`
96-
Input any `json:"input"`
97-
Parameters any `json:"parameters,omitempty"`
98-
ResponseFormat string `json:"response_format,omitempty"`
167+
Model string `json:"model"`
168+
Input any `json:"input"`
169+
Parameters AliImageParameters `json:"parameters,omitempty"`
170+
ResponseFormat string `json:"response_format,omitempty"`
99171
}
100172

101173
type AliImageParameters struct {
102-
Size string `json:"size,omitempty"`
103-
N int `json:"n,omitempty"`
104-
Steps string `json:"steps,omitempty"`
105-
Scale string `json:"scale,omitempty"`
106-
Watermark *bool `json:"watermark,omitempty"`
174+
Size string `json:"size,omitempty"`
175+
N int `json:"n,omitempty"`
176+
Steps string `json:"steps,omitempty"`
177+
Scale string `json:"scale,omitempty"`
178+
Watermark *bool `json:"watermark,omitempty"`
179+
PromptExtend *bool `json:"prompt_extend,omitempty"`
180+
}
181+
182+
func (p *AliImageParameters) PromptExtendValue() bool {
183+
if p != nil && p.PromptExtend != nil {
184+
return *p.PromptExtend
185+
}
186+
return false
107187
}
108188

109189
type AliImageInput struct {

0 commit comments

Comments
 (0)