Skip to content

support POST to voices for custom voice creation - #657

Open
khimaros wants to merge 1 commit into
mostlygeek:mainfrom
khimaros:voices-model
Open

support POST to voices for custom voice creation#657
khimaros wants to merge 1 commit into
mostlygeek:mainfrom
khimaros:voices-model

Conversation

@khimaros

Copy link
Copy Markdown

@coderabbitai

coderabbitai Bot commented Apr 16, 2026

Copy link
Copy Markdown

Walkthrough

A single Gin route handler for POST /v1/audio/voices is updated from proxyInferenceHandler to proxyOAIPostFormHandler, switching the request parsing approach from JSON-body to multipart/form-based OAI-compatible processing.

Changes

Cohort / File(s) Summary
Route Handler Update
proxy/proxymanager.go
Changed POST /v1/audio/voices endpoint handler from pm.proxyInferenceHandler to pm.proxyOAIPostFormHandler for multipart form-based request processing.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~2 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding POST support to the voices endpoint for custom voice creation, which matches the changeset's route handler update.
Description check ✅ Passed The description is related to the changeset, referencing OpenAI voices endpoint conformance which aligns with the handler change from inference to OAI-compatible form parsing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@khimaros

Copy link
Copy Markdown
Author

and this should not regress the existing functionality. i did some pretty thorough testing with my frontend.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@proxy/proxymanager.go`:
- Line 361: The new POST route registration
pm.ginEngine.POST("/v1/audio/voices", pm.apiKeyAuth(), pm.trackInflight(),
pm.proxyOAIPostFormHandler) switched to a multipart/form handler but lacks a
regression test; add a unit/integration test that performs a multipart POST to
"/v1/audio/voices" (modeled after the existing /v1/audio/transcriptions
multipart test) asserting auth, proper proxyOAIPostFormHandler invocation,
expected downstream request/body handling and response codes; update or add the
test to the same test suite that covers proxy handlers and run make test-dev
(and make test-all before finalizing) to ensure no regressions.
- Line 361: The route registration pm.ginEngine.POST("/v1/audio/voices",
pm.apiKeyAuth(), pm.trackInflight(), pm.proxyOAIPostFormHandler) is using
proxyOAIPostFormHandler which rejects requests missing a "model" form field (see
the model extraction in proxyOAIPostFormHandler), but OpenAI's /v1/audio/voices
POST does not include a model field; either register a new handler that mirrors
OpenAI's required fields (accepts audio_sample, consent, name and skips model
validation) or remove/disable this route until such a handler exists; implement
the new handler (e.g., proxyOAIFormVoiceHandler) that forwards form-data to the
upstream without requiring model, update the route to use it instead of
proxyOAIPostFormHandler, and add a regression test that posts
audio_sample/consent/name to /v1/audio/voices and asserts the proxy accepts and
forwards the request.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4798e35f-ee19-437e-9edc-7be640d663cc

📥 Commits

Reviewing files that changed from the base of the PR and between 121fd93 and 4c9899c.

📒 Files selected for processing (1)
  • proxy/proxymanager.go

Comment thread proxy/proxymanager.go
// Support audio/speech endpoint
pm.ginEngine.POST("/v1/audio/speech", pm.apiKeyAuth(), pm.trackInflight(), pm.proxyInferenceHandler)
pm.ginEngine.POST("/v1/audio/voices", pm.apiKeyAuth(), pm.trackInflight(), pm.proxyInferenceHandler)
pm.ginEngine.POST("/v1/audio/voices", pm.apiKeyAuth(), pm.trackInflight(), pm.proxyOAIPostFormHandler)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add regression coverage for the new /v1/audio/voices multipart handler wiring

This route changed handler type, but there is no corresponding test shown (unlike /v1/audio/transcriptions). Please add a multipart POST test for /v1/audio/voices to lock behavior and prevent regressions.

As per coding guidelines proxy/**/*.go: "Use make test-dev after running new tests for a quick overall test run when changes are made to any code under the proxy/ directory", and **/*.go: "Use make test-all before completing work to include long running concurrency tests".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@proxy/proxymanager.go` at line 361, The new POST route registration
pm.ginEngine.POST("/v1/audio/voices", pm.apiKeyAuth(), pm.trackInflight(),
pm.proxyOAIPostFormHandler) switched to a multipart/form handler but lacks a
regression test; add a unit/integration test that performs a multipart POST to
"/v1/audio/voices" (modeled after the existing /v1/audio/transcriptions
multipart test) asserting auth, proper proxyOAIPostFormHandler invocation,
expected downstream request/body handling and response codes; update or add the
test to the same test suite that covers proxy handlers and run make test-dev
(and make test-all before finalizing) to ensure no regressions.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In the official OpenAI API reference, what multipart form fields are required for POST /v1/audio/voices (Create voice)?

💡 Result:

The required multipart form fields for POST /v1/audio/voices (Create voice) are: audio_sample, consent, and name. These are shown in the official curl examples in the OpenAI API reference: curl https://api.openai.com/v1/audio/voices \ -H 'Content-Type: multipart/form-data' \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -F 'audio_sample=@/path/to/audio_sample' \ -F consent=consent \ -F name=name and curl https://api.openai.com/v1/audio/voices \ -X POST \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -F "name=My new voice" \ -F "consent=cons_1234" \ -F "audio_sample=@$HOME/audio_sample.wav;type=audio/x-wav" - audio_sample: The audio file sample for the custom voice (file upload). - consent: The ID of a voice consent recording (obtained via POST /v1/audio/voice_consents). - name: A name for the voice (string).

Citations:


🏁 Script executed:

# First, check the proxyOAIPostFormHandler to see if it requires model field
rg -A 50 "func.*proxyOAIPostFormHandler" proxy/proxymanager.go | head -80

Repository: mostlygeek/llama-swap

Length of output: 2045


🏁 Script executed:

# Check for any existing tests related to /v1/audio/voices
rg "/v1/audio/voices" proxy/ --type go

Repository: mostlygeek/llama-swap

Length of output: 607


🏁 Script executed:

# Search for proxyOAIVoicesCreateHandler or similar handlers
rg "proxyOAIVoices" proxy/ --type go

Repository: mostlygeek/llama-swap

Length of output: 47


🏁 Script executed:

# Check formatting of the file
gofmt -l proxy/proxymanager.go

Repository: mostlygeek/llama-swap

Length of output: 47


🏁 Script executed:

# Search for multipart handlers in the codebase
rg "func.*Handler" proxy/proxymanager.go | grep -i "form\|multipart"

Repository: mostlygeek/llama-swap

Length of output: 130


🏁 Script executed:

# Check for POST /v1/audio/voices test coverage
rg -B 5 -A 15 "POST.*audio/voices\|audio/voices.*POST" proxy/proxymanager_test.go

Repository: mostlygeek/llama-swap

Length of output: 47


🏁 Script executed:

# Search for what other endpoints use proxyOAIPostFormHandler
rg "proxyOAIPostFormHandler" proxy/proxymanager.go

Repository: mostlygeek/llama-swap

Length of output: 450


🏁 Script executed:

# Check all POST handlers to find alternatives
rg "func.*Handler.*\(c \*gin.Context\)" proxy/proxymanager.go | grep -i post

Repository: mostlygeek/llama-swap

Length of output: 130


🏁 Script executed:

# Look for any existing voice-related handlers
rg -i "voice" proxy/proxymanager.go

Repository: mostlygeek/llama-swap

Length of output: 268


🏁 Script executed:

# Check what the transcriptions endpoint actually requires from OpenAI API docs
# and verify the handler is appropriate for it
rg -B 2 -A 2 "audio/transcriptions" proxy/proxymanager.go

Repository: mostlygeek/llama-swap

Length of output: 592


🏁 Script executed:

# Check if proxyOAIPostFormHandler is specifically designed to require model
# and verify OpenAI API requirements for transcriptions and images/edits
rg -B 5 -A 15 "POST /v1/audio/transcriptions\|POST /v1/images/edits" proxy/proxymanager.go

Repository: mostlygeek/llama-swap

Length of output: 47


🏁 Script executed:

# Look for any handler that does NOT require model field
rg "FormValue" proxy/proxymanager.go | head -20

Repository: mostlygeek/llama-swap

Length of output: 112


🏁 Script executed:

# Verify the exact requirements for OpenAI's transcriptions and images/edits endpoints
# to understand if model is expected in those APIs too
web_search "OpenAI API audio transcriptions POST form fields required model"

Repository: mostlygeek/llama-swap

Length of output: 115


🏁 Script executed:

# Check the proxyOAIPostFormHandler more thoroughly - see the full implementation
# to understand if model extraction is conditional or always required
sed -n '847,977p' proxy/proxymanager.go

Repository: mostlygeek/llama-swap

Length of output: 4636


API contract mismatch: /v1/audio/voices POST handler requires model field that OpenAI API does not provide

Line 361 routes POST /v1/audio/voices to pm.proxyOAIPostFormHandler, which rejects requests with a 400 error unless a model form field is present (line 857–858). However, the OpenAI API for this endpoint requires audio_sample, consent, and name fields—it does not include a model field. This routing will break all OpenAI-compatible voice creation requests.

The handler's model extraction is necessary for the proxy's routing logic, but POST /v1/audio/voices is fundamentally incompatible with this design. Either:

  • A new handler is needed that does not require model, or
  • This endpoint should not be exposed in the proxy until a compatible handler exists

Add a regression test for this endpoint if proceeding with the fix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@proxy/proxymanager.go` at line 361, The route registration
pm.ginEngine.POST("/v1/audio/voices", pm.apiKeyAuth(), pm.trackInflight(),
pm.proxyOAIPostFormHandler) is using proxyOAIPostFormHandler which rejects
requests missing a "model" form field (see the model extraction in
proxyOAIPostFormHandler), but OpenAI's /v1/audio/voices POST does not include a
model field; either register a new handler that mirrors OpenAI's required fields
(accepts audio_sample, consent, name and skips model validation) or
remove/disable this route until such a handler exists; implement the new handler
(e.g., proxyOAIFormVoiceHandler) that forwards form-data to the upstream without
requiring model, update the route to use it instead of proxyOAIPostFormHandler,
and add a regression test that posts audio_sample/consent/name to
/v1/audio/voices and asserts the proxy accepts and forwards the request.

@mostlygeek

Copy link
Copy Markdown
Owner

Hi,

Do you have a configuration for how a backend to test this with?

@khimaros

Copy link
Copy Markdown
Author

making use of this: https://github.com/khimaros/qwen3-tts.cpp

  "qwen3-tts-server": |
    qwen3-tts-server
    --port ${PORT}
    --hf-repo-v khimaros/Qwen3-TTS-Tokenizer-12Hz-GGUF:F16
    --threads 16
    --verbose

  "qwen3-tts-1.7b-base:Q8_0":
    macros: {modso: "audio", tuned: "base"}
    metadata: *default_metadata
    cmd: |
      ${qwen3-tts-server}
      --hf-repo khimaros/Qwen3-TTS-12Hz-1.7B-Base-GGUF:Q8_0

  "qwen3-tts-1.7b-customvoice:Q8_0":
    macros: {modso: "audio", tuned: "base"}
    metadata: *default_metadata
    cmd: |
      ${qwen3-tts-server}
      --hf-repo khimaros/Qwen3-TTS-12Hz-1.7B-CustomVoice-GGUF:Q8_0

  "qwen3-tts-1.7b-voicedesign:Q8_0":
    macros: {modso: "audio", tuned: "base"}
    metadata: *default_metadata
    cmd: |
      ${qwen3-tts-server}
      --hf-repo khimaros/Qwen3-TTS-12Hz-1.7B-VoiceDesign-GGUF:Q8_0

@khimaros

khimaros commented Apr 18, 2026

Copy link
Copy Markdown
Author

also with https://github.com/khimaros/s2.cpp

  "s2-server": |
    s2-server
    --port ${PORT}
    --threads 16

  "fish-s2-pro:Q8_0": 
    macros: {modso: "audio", tuned: "base"} 
    metadata: *default_metadata 
    cmd: | 
      ${s2-server} 
      --hf-repo rodrigomt/s2-pro-gguf:Q8_0

@khimaros

Copy link
Copy Markdown
Author

some example clients here: https://github.com/khimaros/flow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants