Skip to content

feat(api): add native audio transcription and speech endpoints - #8199

Open
axAilotl wants to merge 2 commits into
NousResearch:mainfrom
axAilotl:feature/native-audio-api
Open

axAilotl wants to merge 2 commits into
NousResearch:mainfrom
axAilotl:feature/native-audio-api

Conversation

@axAilotl

Copy link
Copy Markdown

What does this PR do?

This exposes Hermes' existing voice capabilities through the API server by adding OpenAI-style audio endpoints for transcription and speech synthesis.

Today the API server already supports chat and responses, but third-party UIs still cannot use Hermes for voice because /v1/audio/transcriptions and /v1/audio/speech are missing. This change closes that gap by wiring the API surface to the existing STT/TTS helpers, streaming generated audio back to clients, and exposing the Hermes-specific metadata headers browser clients need.

Related Issue

N/A

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • add POST /v1/audio/transcriptions with multipart upload handling, json / text / verbose_json responses, provider metadata headers, and transcript hallucination filtering
  • add POST /v1/audio/speech with streamed audio responses for mp3, wav, opus, and ogg
  • add reusable file-streaming and temp-file cleanup helpers for generated audio responses
  • update API-server CORS headers so browser clients can send X-Hermes-Session-Id and read STT/TTS metadata headers
  • bypass the generic 1 MB request-body limit for /v1/audio/* routes so uploaded audio can be processed
  • register the new audio routes in gateway/platforms/api_server.py

How to Test

  1. Start the Hermes API server with an API key and working STT/TTS configuration.
  2. Run curl -X POST http://localhost:8642/v1/audio/transcriptions -H "Authorization: Bearer $API_SERVER_KEY" -F "file=@sample.webm" -F "model=whisper-1" -F "response_format=verbose_json" and verify you get a transcript payload plus X-Hermes-STT-Provider / X-Hermes-Transcript-Filtered headers.
  3. Run curl -X POST http://localhost:8642/v1/audio/speech -H "Authorization: Bearer $API_SERVER_KEY" -H "Content-Type: application/json" -d '{"input":"hello from Hermes","response_format":"mp3"}' --output speech.mp3 -D headers.txt and verify streamed audio plus X-Hermes-TTS-Provider / X-Hermes-Voice-Compatible headers.
  4. Run /mnt/samesung/ai/util/hermes-agent/.venv/bin/python -m pytest tests/gateway/test_api_server.py -q from the branch checkout and verify the API-server suite stays green.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Linux, using /mnt/samesung/ai/util/hermes-agent/.venv

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

  • python3 -m py_compile gateway/platforms/api_server.py
  • /mnt/samesung/ai/util/hermes-agent/.venv/bin/python -m pytest tests/gateway/test_api_server.py -q107 passed
  • attempted full suite with /mnt/samesung/ai/util/hermes-agent/.venv/bin/python -m pytest tests/ -q, but the repository is not currently full-suite green in this environment

@alt-glitch alt-glitch added type/feature New feature or request P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery tool/tts Text-to-speech and transcription labels Apr 28, 2026
@axAilotl
axAilotl force-pushed the feature/native-audio-api branch from 5d2aada to a2aa9c6 Compare May 10, 2026 20:54

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for wiring the existing STT/TTS helpers into the OpenAI-compatible API surface. The gap is real: current main still advertises audio_api: false at gateway/platforms/api_server.py:1506 and registers no audio routes at gateway/platforms/api_server.py:4767-4809.

Problems

  • gateway/platforms/api_server.py:4368 raises the application-wide aiohttp limit to 100 MB. The new middleware branch only distinguishes audio requests when Content-Length is supplied, so a chunked request to an existing non-audio endpoint bypasses the intended 10 MB limit. Current main relies on the 10 MB client_max_size cap at gateway/platforms/api_server.py:4767.
  • gateway/platforms/api_server.py:1328 labels the response using the requested format, but the underlying helper does not guarantee that format. In particular, Edge TTS saves MP3 bytes to the supplied path (tools/tts_tool.py:939-963), so a .wav temp path can be returned as audio/wav while containing MP3 data.

Suggested changes

  • Preserve the non-audio cap for chunked and Content-Length requests; enforce a separate streaming cap for audio uploads.
  • Either transcode formats or expose only formats the configured provider can produce, and derive MIME from the final artifact.
  • Add coverage for both cases and document the public endpoints.

Automated hermes-sweeper review.

@@ -4099,13 +4365,15 @@ async def connect(self) -> bool:

try:
mws = [mw for mw in (cors_middleware, body_limit_middleware, security_headers_middleware) if mw is not None]
self._app = web.Application(middlewares=mws, client_max_size=MAX_REQUEST_BYTES)
self._app = web.Application(middlewares=mws, client_max_size=AUDIO_MAX_REQUEST_BYTES)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This lifts aiohttp's global body limit for every route. The route-specific middleware only branches when Content-Length is present, so chunked non-audio requests now reach 100 MB instead of the existing 10 MB cap. Keep the global non-audio limit or enforce a route-aware streaming limit that also covers chunked bodies.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 4b235630a. The app keeps the 100 MB outer ceiling required by audio routes, while body_limit_middleware now clones each request with its route-specific limit so request.read()/json() enforce 10 MB for chunked non-audio requests too. Multipart audio bytes are counted explicitly because aiohttp multipart reads bypass client_max_size. Added regression coverage for both chunked paths.

return await self._stream_file_response(
request,
actual_path,
media_type,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The MIME type comes from the requested suffix, not the generated artifact. For example, Edge TTS always writes MP3 bytes (tools/tts_tool.py:_generate_edge_tts), so response_format=wav can return MP3 data as audio/wav. Transcode, restrict formats by provider, or infer the actual output type before sending the response.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 4b235630a. Speech responses now sniff the generated artifact signature and derive Content-Type from the actual bytes rather than the requested suffix. Added an Edge-TTS regression test where response_format=wav produces MP3 bytes and is correctly returned as audio/mpeg. The public audio endpoints are now documented as well.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

also sempai finally noticed me lol (robot sempai)

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 12, 2026
@dliu120

dliu120 commented Aug 4, 2026

Copy link
Copy Markdown

I reproduced the two current review findings on this PR and prepared a hardened continuation while preserving the authorship of the original commits:

https://github.com/dliu120/hermes-agent/tree/feat/api-audio-salvage

The branch is based on the original audio commits and adds:

  • separate streaming limits for audio routes without weakening chunked non-audio request limits;
  • MIME detection from the provider's actual TTS artifact, with Content-Type documented as authoritative;
  • authenticated /v1/capabilities discovery for the whole-file audio contract;
  • profile-route parity, bounded upload/output handling, temp-file/path/symlink cleanup, and sanitized provider errors;
  • regression coverage for disconnect cleanup, profile limits, pathological filename suffixes, and the two review findings.

Verification on 3ddde639d4ed458689e0de46eb2e953a8470ba20:

  • focused audio/capability contract: 22 passed;
  • remaining API-server tests: 111 passed;
  • Ruff lint: passed;
  • one unrelated detailed-health assertion fails identically on the branch base (536754919): it expects ok while the current local readiness probe reports degraded.

I did not open a competing upstream PR. Please cherry-pick any or all of the follow-up commits if this direction matches the contract you want here; I am also happy to rework the branch around maintainer feedback.

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

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data tool/tts Text-to-speech and transcription type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants