Commit fd6cd838 by feitianbubu

refactor: clean up doubao tts code

parent 828bb17d
...@@ -24,7 +24,6 @@ import ( ...@@ -24,7 +24,6 @@ import (
) )
const ( const (
// Context keys for passing data between methods
contextKeyTTSRequest = "volcengine_tts_request" contextKeyTTSRequest = "volcengine_tts_request"
contextKeyResponseFormat = "response_format" contextKeyResponseFormat = "response_format"
) )
...@@ -76,27 +75,23 @@ func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInf ...@@ -76,27 +75,23 @@ func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInf
Request: VolcengineTTSReqInfo{ Request: VolcengineTTSReqInfo{
ReqID: generateRequestID(), ReqID: generateRequestID(),
Text: request.Input, Text: request.Input,
Operation: "submit", // default WebSocket uses "submit" Operation: "submit",
Model: info.OriginModelName, Model: info.OriginModelName,
}, },
} }
// 同步扩展字段的厂商自定义metadata
if len(request.Metadata) > 0 { if len(request.Metadata) > 0 {
if err = json.Unmarshal(request.Metadata, &volcRequest); err != nil { if err = json.Unmarshal(request.Metadata, &volcRequest); err != nil {
return nil, fmt.Errorf("error unmarshalling metadata to volcengine request: %w", err) return nil, fmt.Errorf("error unmarshalling metadata to volcengine request: %w", err)
} }
} }
// Store the request in context for WebSocket handler
c.Set(contextKeyTTSRequest, volcRequest) c.Set(contextKeyTTSRequest, volcRequest)
// https://www.volcengine.com/docs/6561/1257584
// operation需要设置为submit才是流式返回
if volcRequest.Request.Operation == "submit" { if volcRequest.Request.Operation == "submit" {
info.IsStream = true info.IsStream = true
} }
// Return nil as WebSocket doesn't use traditional request body
jsonData, err := json.Marshal(volcRequest) jsonData, err := json.Marshal(volcRequest)
if err != nil { if err != nil {
return nil, fmt.Errorf("error marshalling volcengine request: %w", err) return nil, fmt.Errorf("error marshalling volcengine request: %w", err)
...@@ -115,9 +110,8 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf ...@@ -115,9 +110,8 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
writer := multipart.NewWriter(&requestBody) writer := multipart.NewWriter(&requestBody)
writer.WriteField("model", request.Model) writer.WriteField("model", request.Model)
// 获取所有表单字段
formData := c.Request.PostForm formData := c.Request.PostForm
// 遍历表单字段并打印输出
for key, values := range formData { for key, values := range formData {
if key == "model" { if key == "model" {
continue continue
...@@ -127,21 +121,16 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf ...@@ -127,21 +121,16 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
} }
} }
// Parse the multipart form to handle both single image and multiple images if err := c.Request.ParseMultipartForm(32 << 20); err != nil {
if err := c.Request.ParseMultipartForm(32 << 20); err != nil { // 32MB max memory
return nil, errors.New("failed to parse multipart form") return nil, errors.New("failed to parse multipart form")
} }
if c.Request.MultipartForm != nil && c.Request.MultipartForm.File != nil { if c.Request.MultipartForm != nil && c.Request.MultipartForm.File != nil {
// Check if "image" field exists in any form, including array notation
var imageFiles []*multipart.FileHeader var imageFiles []*multipart.FileHeader
var exists bool var exists bool
// First check for standard "image" field
if imageFiles, exists = c.Request.MultipartForm.File["image"]; !exists || len(imageFiles) == 0 { if imageFiles, exists = c.Request.MultipartForm.File["image"]; !exists || len(imageFiles) == 0 {
// If not found, check for "image[]" field
if imageFiles, exists = c.Request.MultipartForm.File["image[]"]; !exists || len(imageFiles) == 0 { if imageFiles, exists = c.Request.MultipartForm.File["image[]"]; !exists || len(imageFiles) == 0 {
// If still not found, iterate through all fields to find any that start with "image["
foundArrayImages := false foundArrayImages := false
for fieldName, files := range c.Request.MultipartForm.File { for fieldName, files := range c.Request.MultipartForm.File {
if strings.HasPrefix(fieldName, "image[") && len(files) > 0 { if strings.HasPrefix(fieldName, "image[") && len(files) > 0 {
...@@ -152,14 +141,12 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf ...@@ -152,14 +141,12 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
} }
} }
// If no image fields found at all
if !foundArrayImages && (len(imageFiles) == 0) { if !foundArrayImages && (len(imageFiles) == 0) {
return nil, errors.New("image is required") return nil, errors.New("image is required")
} }
} }
} }
// Process all image files
for i, fileHeader := range imageFiles { for i, fileHeader := range imageFiles {
file, err := fileHeader.Open() file, err := fileHeader.Open()
if err != nil { if err != nil {
...@@ -167,16 +154,13 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf ...@@ -167,16 +154,13 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
} }
defer file.Close() defer file.Close()
// If multiple images, use image[] as the field name
fieldName := "image" fieldName := "image"
if len(imageFiles) > 1 { if len(imageFiles) > 1 {
fieldName = "image[]" fieldName = "image[]"
} }
// Determine MIME type based on file extension
mimeType := detectImageMimeType(fileHeader.Filename) mimeType := detectImageMimeType(fileHeader.Filename)
// Create a form file with the appropriate content type
h := make(textproto.MIMEHeader) h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldName, fileHeader.Filename)) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldName, fileHeader.Filename))
h.Set("Content-Type", mimeType) h.Set("Content-Type", mimeType)
...@@ -191,7 +175,6 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf ...@@ -191,7 +175,6 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
} }
} }
// Handle mask file if present
if maskFiles, exists := c.Request.MultipartForm.File["mask"]; exists && len(maskFiles) > 0 { if maskFiles, exists := c.Request.MultipartForm.File["mask"]; exists && len(maskFiles) > 0 {
maskFile, err := maskFiles[0].Open() maskFile, err := maskFiles[0].Open()
if err != nil { if err != nil {
...@@ -199,10 +182,8 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf ...@@ -199,10 +182,8 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
} }
defer maskFile.Close() defer maskFile.Close()
// Determine MIME type for mask file
mimeType := detectImageMimeType(maskFiles[0].Filename) mimeType := detectImageMimeType(maskFiles[0].Filename)
// Create a form file with the appropriate content type
h := make(textproto.MIMEHeader) h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="mask"; filename="%s"`, maskFiles[0].Filename)) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="mask"; filename="%s"`, maskFiles[0].Filename))
h.Set("Content-Type", mimeType) h.Set("Content-Type", mimeType)
...@@ -220,7 +201,6 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf ...@@ -220,7 +201,6 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
return nil, errors.New("no multipart form data found") return nil, errors.New("no multipart form data found")
} }
// 关闭 multipart 编写器以设置分界线
writer.Close() writer.Close()
c.Request.Header.Set("Content-Type", writer.FormDataContentType()) c.Request.Header.Set("Content-Type", writer.FormDataContentType())
return bytes.NewReader(requestBody.Bytes()), nil return bytes.NewReader(requestBody.Bytes()), nil
...@@ -230,7 +210,6 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf ...@@ -230,7 +210,6 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
} }
} }
// detectImageMimeType determines the MIME type based on the file extension
func detectImageMimeType(filename string) string { func detectImageMimeType(filename string) string {
ext := strings.ToLower(filepath.Ext(filename)) ext := strings.ToLower(filepath.Ext(filename))
switch ext { switch ext {
...@@ -241,11 +220,9 @@ func detectImageMimeType(filename string) string { ...@@ -241,11 +220,9 @@ func detectImageMimeType(filename string) string {
case ".webp": case ".webp":
return "image/webp" return "image/webp"
default: default:
// Try to detect from extension if possible
if strings.HasPrefix(ext, ".jp") { if strings.HasPrefix(ext, ".jp") {
return "image/jpeg" return "image/jpeg"
} }
// Default to png as a fallback
return "image/png" return "image/png"
} }
} }
...@@ -281,7 +258,6 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { ...@@ -281,7 +258,6 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
case constant.RelayModeRerank: case constant.RelayModeRerank:
return fmt.Sprintf("%s/api/v3/rerank", baseUrl), nil return fmt.Sprintf("%s/api/v3/rerank", baseUrl), nil
case constant.RelayModeAudioSpeech: case constant.RelayModeAudioSpeech:
// 只有当 baseUrl 是火山默认的官方Url时才改为官方的的TTS接口,否则走透传的New接口
if baseUrl == channelconstant.ChannelBaseURLs[channelconstant.ChannelTypeVolcEngine] { if baseUrl == channelconstant.ChannelBaseURLs[channelconstant.ChannelTypeVolcEngine] {
return "wss://openspeech.bytedance.com/api/v1/tts/ws_binary", nil return "wss://openspeech.bytedance.com/api/v1/tts/ws_binary", nil
} }
...@@ -312,7 +288,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn ...@@ -312,7 +288,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
if request == nil { if request == nil {
return nil, errors.New("request is nil") return nil, errors.New("request is nil")
} }
// 适配 方舟deepseek混合模型 的 thinking 后缀
if strings.HasSuffix(info.UpstreamModelName, "-thinking") && strings.HasPrefix(info.UpstreamModelName, "deepseek") { if strings.HasSuffix(info.UpstreamModelName, "-thinking") && strings.HasPrefix(info.UpstreamModelName, "deepseek") {
info.UpstreamModelName = strings.TrimSuffix(info.UpstreamModelName, "-thinking") info.UpstreamModelName = strings.TrimSuffix(info.UpstreamModelName, "-thinking")
request.Model = info.UpstreamModelName request.Model = info.UpstreamModelName
...@@ -330,18 +306,16 @@ func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.Rela ...@@ -330,18 +306,16 @@ func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.Rela
} }
func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.OpenAIResponsesRequest) (any, error) { func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.OpenAIResponsesRequest) (any, error) {
// TODO implement me
return nil, errors.New("not implemented") return nil, errors.New("not implemented")
} }
func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) { func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
// For TTS with WebSocket, skip traditional HTTP request
if info.RelayMode == constant.RelayModeAudioSpeech { if info.RelayMode == constant.RelayModeAudioSpeech {
baseUrl := info.ChannelBaseUrl baseUrl := info.ChannelBaseUrl
if baseUrl == "" { if baseUrl == "" {
baseUrl = channelconstant.ChannelBaseURLs[channelconstant.ChannelTypeVolcEngine] baseUrl = channelconstant.ChannelBaseURLs[channelconstant.ChannelTypeVolcEngine]
} }
// Only use WebSocket for official Volcengine endpoint
if baseUrl == channelconstant.ChannelBaseURLs[channelconstant.ChannelTypeVolcEngine] { if baseUrl == channelconstant.ChannelBaseURLs[channelconstant.ChannelTypeVolcEngine] {
if info.IsStream { if info.IsStream {
return nil, nil return nil, nil
......
...@@ -196,9 +196,7 @@ func generateRequestID() string { ...@@ -196,9 +196,7 @@ func generateRequestID() string {
return uuid.New().String() return uuid.New().String()
} }
// handleTTSWebSocketResponse handles streaming TTS response via WebSocket
func handleTTSWebSocketResponse(c *gin.Context, requestURL string, volcRequest VolcengineTTSRequest, info *relaycommon.RelayInfo, encoding string) (usage any, err *types.NewAPIError) { func handleTTSWebSocketResponse(c *gin.Context, requestURL string, volcRequest VolcengineTTSRequest, info *relaycommon.RelayInfo, encoding string) (usage any, err *types.NewAPIError) {
// Parse API key for auth
_, token, parseErr := parseVolcengineAuth(info.ApiKey) _, token, parseErr := parseVolcengineAuth(info.ApiKey)
if parseErr != nil { if parseErr != nil {
return nil, types.NewErrorWithStatusCode( return nil, types.NewErrorWithStatusCode(
...@@ -208,11 +206,9 @@ func handleTTSWebSocketResponse(c *gin.Context, requestURL string, volcRequest V ...@@ -208,11 +206,9 @@ func handleTTSWebSocketResponse(c *gin.Context, requestURL string, volcRequest V
) )
} }
// Setup WebSocket headers
header := http.Header{} header := http.Header{}
header.Set("Authorization", fmt.Sprintf("Bearer;%s", token)) header.Set("Authorization", fmt.Sprintf("Bearer;%s", token))
// Dial WebSocket connection
conn, resp, dialErr := websocket.DefaultDialer.DialContext(context.Background(), requestURL, header) conn, resp, dialErr := websocket.DefaultDialer.DialContext(context.Background(), requestURL, header)
if dialErr != nil { if dialErr != nil {
if resp != nil { if resp != nil {
...@@ -239,7 +235,6 @@ func handleTTSWebSocketResponse(c *gin.Context, requestURL string, volcRequest V ...@@ -239,7 +235,6 @@ func handleTTSWebSocketResponse(c *gin.Context, requestURL string, volcRequest V
) )
} }
// Send full client request
if sendErr := FullClientRequest(conn, payload); sendErr != nil { if sendErr := FullClientRequest(conn, payload); sendErr != nil {
return nil, types.NewErrorWithStatusCode( return nil, types.NewErrorWithStatusCode(
fmt.Errorf("failed to send request: %w", sendErr), fmt.Errorf("failed to send request: %w", sendErr),
...@@ -248,13 +243,10 @@ func handleTTSWebSocketResponse(c *gin.Context, requestURL string, volcRequest V ...@@ -248,13 +243,10 @@ func handleTTSWebSocketResponse(c *gin.Context, requestURL string, volcRequest V
) )
} }
// Set response headers
contentType := getContentTypeByEncoding(encoding) contentType := getContentTypeByEncoding(encoding)
c.Header("Content-Type", contentType) c.Header("Content-Type", contentType)
c.Header("Transfer-Encoding", "chunked") c.Header("Transfer-Encoding", "chunked")
// Stream audio data
var audioBuffer []byte
for { for {
msg, recvErr := ReceiveMessage(conn) msg, recvErr := ReceiveMessage(conn)
if recvErr != nil { if recvErr != nil {
...@@ -279,7 +271,6 @@ func handleTTSWebSocketResponse(c *gin.Context, requestURL string, volcRequest V ...@@ -279,7 +271,6 @@ func handleTTSWebSocketResponse(c *gin.Context, requestURL string, volcRequest V
continue continue
case MsgTypeAudioOnlyServer: case MsgTypeAudioOnlyServer:
if len(msg.Payload) > 0 { if len(msg.Payload) > 0 {
audioBuffer = append(audioBuffer, msg.Payload...)
if _, writeErr := c.Writer.Write(msg.Payload); writeErr != nil { if _, writeErr := c.Writer.Write(msg.Payload); writeErr != nil {
return nil, types.NewErrorWithStatusCode( return nil, types.NewErrorWithStatusCode(
fmt.Errorf("failed to write audio data: %w", writeErr), fmt.Errorf("failed to write audio data: %w", writeErr),
...@@ -287,7 +278,6 @@ func handleTTSWebSocketResponse(c *gin.Context, requestURL string, volcRequest V ...@@ -287,7 +278,6 @@ func handleTTSWebSocketResponse(c *gin.Context, requestURL string, volcRequest V
http.StatusInternalServerError, http.StatusInternalServerError,
) )
} }
//logger.Infof("write audio chunk size: %d", len(msg.Payload))
c.Writer.Flush() c.Writer.Flush()
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment