新增支持豆包语音合成2.0功能 - #2067
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughImplements Volcengine TTS: adds Volcengine TTS data models and response handling; extends the Volcengine relay adaptor (ConvertAudioRequest, GetRequestURL, SetupRequestHeader, DoResponse) to support audio speech; and updates the UI prompt for Volcengine channel authentication. Changes
Sequence DiagramsequenceDiagram
participant Client
participant Adaptor as Volcengine Adaptor
participant VolcAPI as Volcengine API
participant TTSHandler as TTS Handler
Client->>Adaptor: Audio request (RelayModeAudioSpeech, voice, speed, encoding)
Adaptor->>Adaptor: ConvertAudioRequest -> build VolcengineTTSRequest JSON
Adaptor->>Adaptor: SetupRequestHeader -> extract Bearer, set Content-Type
Adaptor->>VolcAPI: POST /v1/tts or /v1/audio/speech (JSON + Bearer)
VolcAPI-->>Adaptor: JSON response (code, base64 audio, extras)
Adaptor->>TTSHandler: DoResponse -> handleTTSResponse
TTSHandler->>TTSHandler: validate code, decode base64, determine Content-Type
TTSHandler-->>Client: stream audio bytes + usage headers/metrics
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 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 (1)
relay/channel/volcengine/tts.go (1)
156-204: Response handler logic is correct with minor style observation.The function properly:
- Reads and parses the response
- Validates the success code (3000)
- Decodes base64 audio data
- Sets appropriate content type
- Returns usage metrics
Note: Line 165's
defer resp.Body.Close()comes afterio.ReadAll(line 157), which is unusual but not incorrect since the body has already been consumed. Consider moving the defer immediately after the function signature for better clarity.Optional style improvement:
func handleTTSResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo, encoding string) (usage any, err *types.NewAPIError) { + defer resp.Body.Close() body, readErr := io.ReadAll(resp.Body) if readErr != nil { return nil, types.NewErrorWithStatusCode( errors.New("failed to read volcengine response"), types.ErrorCodeReadResponseBodyFailed, http.StatusInternalServerError, ) } - defer resp.Body.Close()
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
relay/channel/volcengine/adaptor.go(4 hunks)relay/channel/volcengine/tts.go(1 hunks)web/src/components/table/channels/modals/EditChannelModal.jsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
relay/channel/volcengine/adaptor.go (3)
relay/constant/relay_mode.go (1)
RelayModeAudioSpeech(35-35)relay/channel/volcengine/tts.go (5)
VolcengineTTSRequest(18-23)VolcengineTTSApp(25-29)VolcengineTTSUser(31-33)VolcengineTTSAudio(35-47)VolcengineTTSReqInfo(49-58)constant/channel.go (2)
ChannelBaseURLs(60-117)ChannelTypeVolcEngine(45-45)
relay/channel/volcengine/tts.go (4)
dto/request_common.go (1)
Request(8-12)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)
🔇 Additional comments (8)
web/src/components/table/channels/modals/EditChannelModal.jsx (1)
110-111: LGTM! Prompt aligns with backend authentication format.The authentication key format
AppId|AccessTokencorrectly matches the backend parsing logic inrelay/channel/volcengine/adaptor.go(line 108), which splits by"|"expecting exactly 2 parts.relay/channel/volcengine/adaptor.go (4)
261-266: LGTM! URL routing logic appropriately handles official and proxy scenarios.The conditional check ensures that official Volcengine channels use the correct TTS endpoint while allowing custom base URLs to use the standard OpenAI-compatible path.
320-323: LGTM! Response routing is clean and delegates appropriately.The early return for audio speech mode correctly routes to the TTS-specific handler with the appropriate encoding parameter.
276-283: Authorization header format is correct.The Volcengine TTS API officially uses
Bearer;{ACCESS_TOKEN}format, confirming that line 279's implementation with"Bearer;" + parts[1]is correct. This is not a typo—it matches the official specification.
55-76: Verify hardcoded values against your Volcengine account requirements and consider documenting their source.The web search found that cluster: "volcano_tts", rate: 24000 (Hz), and uid as a business-defined string are commonly used in Volcengine SDK and community examples. Your implementation aligns with these patterns.
However, ensure these defaults match your specific Volcengine account configuration. Additionally, consider adding code comments documenting why these values are hardcoded (e.g., "Volcengine service defaults per SDK examples") to clarify intent for future maintainers.
relay/channel/volcengine/tts.go (3)
18-87: LGTM! Well-structured data models for Volcengine TTS integration.The type definitions are comprehensive with proper JSON tags and appropriate use of
omitemptyfor optional fields.
115-154: LGTM! Mapping functions have appropriate fallbacks and validation.The helper functions properly handle edge cases:
mapSpeedRatioclamps values to valid range [0.1, 2.0]mapVoiceTypeandmapEncodingprovide sensible fallbacksgetContentTypeByEncodingcorrectly maps to MIME types
206-208: LGTM! Standard UUID generation for request tracking.Using
uuid.New()from the google/uuid package is appropriate for generating unique request IDs.
| var responseFormatToEncodingMap = map[string]string{ | ||
| "mp3": "mp3", | ||
| "opus": "ogg_opus", | ||
| "aac": "mp3", | ||
| "flac": "mp3", | ||
| "wav": "wav", | ||
| "pcm": "pcm", | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
Review format mapping for potential user expectation mismatch.
Lines 101-102 map both "aac" and "flac" to "mp3". When users request these formats, they'll receive MP3 audio instead, which could cause issues if the client expects the requested format. Consider either:
- Returning an error for unsupported formats
- Documenting this limitation clearly
- Checking if Volcengine actually supports these formats
🌐 Web query:
Does Volcengine TTS API support aac and flac output formats?
💡 Result:
Yes. Volcengine's TTS supports AAC and FLAC as output formats (response_format can be mp3, opus, aac, flac, wav, pcm). [1][2]
Sources:
[1] developer.volcengine.com TTS article (response_format list).
[2] TTS API docs (response_format: mp3, opus, aac, flac, wav, pcm).
Fix incorrect format mappings: aac and flac should not map to mp3.
Volcengine's TTS API supports aac and flac as native output formats, but lines 101-102 incorrectly map both to "mp3". This causes users requesting these formats to receive MP3 audio instead, creating a format mismatch.
Correct these mappings:
"aac": "aac""flac": "flac"
🤖 Prompt for AI Agents
In relay/channel/volcengine/tts.go around lines 98 to 105 the mapping for "aac"
and "flac" incorrectly points to "mp3", causing requests for those formats to be
served as MP3; update the map so that "aac" maps to "aac" and "flac" maps to
"flac" (leave other entries unchanged) to ensure the TTS output encoding matches
the requested format.
| func parseVolcengineAuth(apiKey string) (appID, token string, err error) { | ||
| parts := strings.Split(apiKey, "|") | ||
| if len(parts) != 2 { | ||
| return "", "", errors.New("invalid api key format, expected: appid:access_token") | ||
| } | ||
| return parts[0], parts[1], nil | ||
| } |
There was a problem hiding this comment.
Fix inconsistent error message.
Line 110's error message says "expected: appid:access_token" but the actual separator is "|" (pipe), not ":" (colon).
Apply this diff to fix the error message:
func parseVolcengineAuth(apiKey string) (appID, token string, err error) {
parts := strings.Split(apiKey, "|")
if len(parts) != 2 {
- return "", "", errors.New("invalid api key format, expected: appid:access_token")
+ return "", "", errors.New("invalid api key format, expected: AppId|AccessToken")
}
return parts[0], parts[1], nil
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func parseVolcengineAuth(apiKey string) (appID, token string, err error) { | |
| parts := strings.Split(apiKey, "|") | |
| if len(parts) != 2 { | |
| return "", "", errors.New("invalid api key format, expected: appid:access_token") | |
| } | |
| return parts[0], parts[1], nil | |
| } | |
| func parseVolcengineAuth(apiKey string) (appID, token string, err error) { | |
| parts := strings.Split(apiKey, "|") | |
| if len(parts) != 2 { | |
| return "", "", errors.New("invalid api key format, expected: AppId|AccessToken") | |
| } | |
| return parts[0], parts[1], nil | |
| } |
🤖 Prompt for AI Agents
In relay/channel/volcengine/tts.go around lines 107 to 113, the error string
wrongly describes the expected separator as ":" while the code splits on "|" —
update the error message to reflect the actual pipe separator (e.g. "invalid api
key format, expected: appid|access_token") so the message matches the parsing
logic.
豆包音包接口:

https://www.volcengine.com/docs/6561/1257584豆包音色列表:
https://www.volcengine.com/docs/6561/1257544渠道选择:
字节火山方舟,豆包通用渠道豆包语音apiKey官方特殊格式:
AppId|AccessToken模型id:
seed-tts-1.1请求格式:
{
"input": "你是一个好孩子",
"model": "seed-tts-1.1",
"response_format": "mp3",
"speed": 1,
"voice": "zh_female_vv_uranus_bigtts"
}
返回格式:
Summary by CodeRabbit
New Features
Style / UI