增加MiniMax语音合成TTS支持 - #2081
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughAdds MiniMax channel support: new APIType constant and mapping, a minimax adaptor with request/response conversions (including TTS), new TTS types/handlers, updated model list and URL routing, and wiring the adaptor into the relay adaptor dispatcher. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Relay
participant Adaptor as MiniMax Adaptor
participant TTS as TTS Handler
participant MiniMax as MiniMax API
Client->>Relay: Audio request
Relay->>Adaptor: Route by APITypeMiniMax
Adaptor->>Adaptor: ConvertAudioRequest (merge metadata, build TTS payload)
Adaptor->>Adaptor: GetRequestURL (RelayModeAudioSpeech -> /v1/t2a_v2)
Adaptor->>MiniMax: DoRequest (POST payload)
MiniMax-->>Adaptor: HTTP response (redirect / hex / base64)
Adaptor->>TTS: DoResponse -> handleTTSResponse
TTS-->>Client: Stream audio (with proper Content-Type) and usage
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
relay/channel/minimax/relay-minimax.go (2)
12-15: Normalize trailing slash to avoid “//” in URLs.Minor: trim any trailing “/” on baseUrl (both custom and default) before fmt.Sprintf.
Apply:
import ( "fmt" + "strings" channelconstant "github.com/QuantumNous/new-api/constant" relaycommon "github.com/QuantumNous/new-api/relay/common" "github.com/QuantumNous/new-api/relay/constant" ) func GetRequestURL(info *relaycommon.RelayInfo) (string, error) { - baseUrl := info.ChannelBaseUrl + baseUrl := strings.TrimRight(info.ChannelBaseUrl, "/") if baseUrl == "" { - baseUrl = channelconstant.ChannelBaseURLs[channelconstant.ChannelTypeMiniMax] + baseUrl = strings.TrimRight(channelconstant.ChannelBaseURLs[channelconstant.ChannelTypeMiniMax], "/") }
17-24: Endpoints LGTM; consider WS path for streaming TTS.
- Chat: /v1/text/chatcompletion_v2 is correct. (blog.csdn.net)
- TTS (HTTP): /v1/t2a_v2 is correct. (trtc.io)
- Default base https://api.minimax.chat is standard. (docs.folotoy.com)
If info.IsStream, MiniMax also exposes a WS endpoint wss://…/ws/v1/t2a_v2. Consider routing to WS when streaming to reduce latency.
Apply:
switch info.RelayMode { case constant.RelayModeChatCompletions: return fmt.Sprintf("%s/v1/text/chatcompletion_v2", baseUrl), nil case constant.RelayModeAudioSpeech: - return fmt.Sprintf("%s/v1/t2a_v2", baseUrl), nil + if info.IsStream { + wsBase := strings.Replace(strings.Replace(baseUrl, "https://", "wss://", 1), "http://", "ws://", 1) + return fmt.Sprintf("%s/ws/v1/t2a_v2", wsBase), nil + } + return fmt.Sprintf("%s/v1/t2a_v2", baseUrl), nil default: return "", fmt.Errorf("unsupported relay mode: %d", info.RelayMode) }Reference for WS path. (github.com)
relay/channel/openai/adaptor.go (1)
21-21: MiniMax decoupling verified; remove commented code blocks for cleaner state.The routing is correctly configured:
relay/relay_adaptor.go:112-113routesAPITypeMiniMaxto the dedicatedminimax.Adaptor- MiniMax requests will not reach the OpenAI adaptor
- The decoupling is complete and safe
Remove the commented import and case statements at
relay/channel/openai/adaptor.golines 21, 164-165, 602-603, and 619-620 to reduce code drift and maintainability overhead.relay/channel/minimax/adaptor.go (1)
65-69: Add clarifying comment for outputFormat handling.The logic modifying
outputFormatafter marshaling the request can be confusing. This variable is used to track the expected response format (stored in context), not the request format.Apply this diff to clarify:
+ // Track expected response format in context (MiniMax returns "url" for most formats, "hex" for hex encoding) if outputFormat != "hex" { outputFormat = "url" } c.Set("response_format", outputFormat)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
common/api_type.go(1 hunks)constant/api_type.go(1 hunks)relay/channel/minimax/adaptor.go(1 hunks)relay/channel/minimax/constants.go(1 hunks)relay/channel/minimax/relay-minimax.go(1 hunks)relay/channel/minimax/tts.go(1 hunks)relay/channel/openai/adaptor.go(4 hunks)relay/relay_adaptor.go(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
common/api_type.go (2)
constant/channel.go (1)
ChannelTypeMiniMax(35-35)constant/api_type.go (1)
APITypeMiniMax(36-36)
relay/relay_adaptor.go (3)
constant/api_type.go (1)
APITypeMiniMax(36-36)relay/channel/minimax/adaptor.go (1)
Adaptor(21-22)relay/channel/adapter.go (1)
Adaptor(15-32)
relay/channel/minimax/relay-minimax.go (3)
relay/common/relay_info.go (1)
RelayInfo(75-122)constant/channel.go (2)
ChannelBaseURLs(60-117)ChannelTypeMiniMax(35-35)relay/constant/relay_mode.go (2)
RelayModeChatCompletions(10-10)RelayModeAudioSpeech(35-35)
relay/channel/minimax/tts.go (3)
relay/common/relay_info.go (1)
RelayInfo(75-122)types/error.go (5)
NewAPIError(87-95)NewErrorWithStatusCode(259-275)ErrorCodeReadResponseBodyFailed(69-69)ErrorCodeBadResponseBody(72-72)ErrorCodeBadResponse(71-71)dto/openai_response.go (1)
Usage(222-235)
relay/channel/minimax/adaptor.go (8)
relay/channel/openai/adaptor.go (1)
Adaptor(33-36)relay/common/relay_info.go (1)
RelayInfo(75-122)dto/audio.go (1)
AudioRequest(11-20)relay/channel/minimax/tts.go (3)
MiniMaxTTSRequest(18-32)VoiceSetting(38-46)AudioSetting(52-58)relay/channel/minimax/relay-minimax.go (1)
GetRequestURL(11-25)relay/channel/api_request.go (3)
SetupApiRequestHeader(27-39)DoRequest(252-254)DoApiRequest(61-90)types/error.go (1)
NewAPIError(87-95)relay/channel/minimax/constants.go (2)
ModelList(5-17)ChannelName(19-19)
🔇 Additional comments (13)
relay/relay_adaptor.go (2)
21-21: Import addition is correct.Cleanly enables adaptor wiring; no side effects.
112-113: Adaptor selection for MiniMax wired correctly.Returns minimax.Adaptor for APITypeMiniMax; consistent with new mapping.
common/api_type.go (1)
72-73: Mapping LGTM.ChannelTypeMiniMax correctly maps to APITypeMiniMax.
constant/api_type.go (1)
36-36: Enum insertion is safe; Dummy confirmed as sentinel-only.Verification confirms
APITypeDummyis used exclusively as a count/sentinel value in the for loop (controller/model.go:31) and is not persisted. The explicit comment in the enum definition confirms this intent. The insertion ofAPITypeMiniMaxbeforeAPITypeDummyis correct.relay/channel/minimax/constants.go (1)
11-16: LGTM!The six new speech model identifiers are correctly added and match the models documented in the PR description.
relay/channel/minimax/tts.go (3)
18-91: LGTM!The TTS request and response type definitions are well-structured with appropriate JSON tags and field types.
107-172: LGTM with one note.The TTS response handling logic correctly:
- Validates the response structure
- Checks for errors via BaseResp.StatusCode
- Handles both redirect and hex-encoded audio cases
- Computes usage metrics
The hardcoded content type issue at line 160 is already flagged in a separate comment.
174-194: LGTM!This handler correctly passes through the chat completion response by copying headers and returning the raw body.
relay/channel/minimax/adaptor.go (5)
21-30: LGTM!The Adaptor struct and stub implementations for unsupported request types are appropriate.
94-111: LGTM!The conversion methods appropriately pass through supported request types and stub out unsupported ones.
113-124: LGTM!The request/response routing correctly delegates to the TTS handler for audio requests and falls back to the OpenAI adaptor for other relay modes.
126-132: LGTM!The accessor methods correctly return the channel constants.
88-92: Authorization header format is correct—no changes needed.The code sets the Authorization header as
"Bearer "+info.ApiKey, which matches MiniMax API documentation that requiresAuthorization: Bearer <YOUR_API_KEY>. The implementation is accurate and requires no modification.
| func getContentTypeByFormat(format string) string { | ||
| contentTypeMap := map[string]string{ | ||
| "mp3": "audio/mpeg", | ||
| "wav": "audio/wav", | ||
| "flac": "audio/flac", | ||
| "aac": "audio/aac", | ||
| "pcm": "audio/pcm", | ||
| } | ||
| if ct, ok := contentTypeMap[format]; ok { | ||
| return ct | ||
| } | ||
| return "audio/mpeg" // default to mp3 | ||
| } |
There was a problem hiding this comment.
This helper function is never used.
The getContentTypeByFormat function is defined but never called. Line 160 in handleTTSResponse hardcodes contentType = "audio/mpeg" instead of using this function to determine the correct content type based on the audio format.
Apply this diff to use the helper function properly:
} else {
// Handle hex-encoded audio data
audioData, decodeErr := hex.DecodeString(minimaxResp.Data.Audio)
if decodeErr != nil {
return nil, types.NewErrorWithStatusCode(
fmt.Errorf("failed to decode hex audio data: %w", decodeErr),
types.ErrorCodeBadResponse,
http.StatusInternalServerError,
)
}
- // Determine content type - default to mp3
- contentType := "audio/mpeg"
+ // Determine content type from response format
+ format := "mp3" // default
+ if val, exists := c.Get("audio_format"); exists {
+ if f, ok := val.(string); ok {
+ format = f
+ }
+ }
+ contentType := getContentTypeByFormat(format)
c.Data(http.StatusOK, contentType, audioData)
}Note: You'll also need to store the audio format in the Gin context (similar to how response_format is stored at adaptor.go line 69) so it's available when serving the response.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In relay/channel/minimax/tts.go around lines 93-105, the getContentTypeByFormat
helper is defined but unused; update handleTTSResponse (around line 160) to read
the audio format from the Gin context and call getContentTypeByFormat(format)
instead of hardcoding "audio/mpeg", and ensure the audio format is saved into
the Gin context when the response is created (follow the pattern used for
"response_format" at adaptor.go line 69) so the handler can retrieve it; this
makes content type selection dynamic and defaults as implemented in the helper.
Comment out the debug log for MiniMax TTS Request.
增加MiniMax语音合成TTS支持
文档:
https://platform.minimaxi.com/document/t2a_http?key=68ad78146fe587e3fbfe8e03支持模型:
speech-2.5-hd-preview 、 speech-2.5-turbo-preview 、 speech-02-hd 、 speech-02-turbo 、 speech-01-hd 、 speech-01-turbo请求格式:
返回示例:

Summary by CodeRabbit