Setting Up LLM Proxy for Concurrent Background Tasks in Voice Conversations: A Complete Guide
The LLM Proxy is an OpenAI-compatible passthrough that mounts /v1/chat/completions and /v1/responses endpoints outside the main speech pipeline, allowing the server to forward requests to external LLM providers while continuing to process audio input in parallel.
The huggingface/speech-to-speech repository implements a real-time voice conversation system where blocking operations can disrupt conversational flow. Setting up an LLM proxy for concurrent background tasks in voice conversations enables the server to offload language model inference to external APIs while maintaining continuous speech processing through its VAD → STT → TTS pipeline.
Why the Voice Pipeline Requires an LLM Proxy
Voice interactions follow a strict pipeline: Voice Activity Detection (VAD) → Speech-to-Text (STT) → Language Model (LLM) → Text-to-Speech (TTS). When the pipeline invokes the LLM directly, it blocks on the completion result, preventing the server from listening to new microphone input or processing subsequent conversation turns.
By mounting a proxy endpoint that forwards requests outside the pipeline’s unit queues, the system achieves true concurrency. The speech engine continues accepting audio chunks and performing VAD detection while the proxy handles upstream LLM communication independently.
Architecture of the LLM Proxy System
The proxy architecture separates external API communication from the core audio processing loop through three key components:
Mounting and Configuration
The mount_llm_proxy function in src/speech_to_speech/api/openai_realtime/llm_proxy.py (lines 38-77) inspects the configuration and registers either:
- A passthrough handler for supported backends (
chat-completionsorresponses-api) - A stub handler that returns HTTP 501 when the proxy is disabled or misconfigured
Request Forwarding Flow
The proxy receives JSON requests at /v1/chat/completions or /v1/responses, injects the configured model name, and forces include_usage=True for streaming responses. It forwards requests to the upstream provider using the API key specified in LLMProxyConfig.upstream_api_key, setting the Authorization: Bearer header automatically.
Response Handling Strategies
For non-streaming responses, the proxy returns the upstream JSON verbatim after extracting usage fields to update internal counters. For streaming responses, it relays bytes via StreamingResponse while parsing Server-Sent Events (SSE) in the background to capture token usage without altering the client’s view of the stream.
Usage Accounting
The LLMProxyUsage class tracks requests, response codes, and token counts. These counters integrate into the server’s /v1/usage endpoint under the llm_proxy key, as implemented in src/speech_to_speech/api/openai_realtime/websocket_router.py (lines 537-539).
Enabling the LLM Proxy Configuration
Activating the proxy requires specific CLI arguments and backend selection validated against the registry:
Step 1: Enable the Proxy Flag
Add --enable_llm_proxy to your launch command. This sets ModuleArguments.enable_llm_proxy = True as defined in src/speech_to_speech/arguments_classes/module_arguments.py (lines 69-78).
Step 2: Select a Compatible Backend
Choose --llm_backend chat-completions or --llm_backend responses-api. The BackendCapabilities.supports_llm_proxy property in src/speech_to_speech/backend_registry.py (lines 393-405) validates compatibility. Attempting to enable the proxy with an incompatible backend raises ValueError during pipeline initialization in src/speech_to_speech/s2s_pipeline.py (lines 113-118).
Step 3: Configure Upstream Credentials
Provide --llm_proxy_upstream_api_key or rely on automatic extraction from the backend specification. The build_llm_proxy_config function in src/speech_to_speech/s2s_pipeline.py (lines 106-127) constructs the final LLMProxyConfig object containing upstream_base_url, model_name, and connection timeouts.
Step 4: Initialize the Server
The RealtimeServer class in src/speech_to_speech/api/openai_realtime/server.py (lines 29-41) calls mount_llm_proxy during FastAPI application setup, exposing the proxy endpoints alongside the WebSocket voice interface.
Concurrent Processing and Pipeline Isolation
The LLM proxy operates outside the pipeline’s queue network, meaning it does not consume AudioInItem, AudioOutItem, or LMOutItem queue slots. This isolation ensures that:
- New audio chunks continue processing through VAD while LLM requests are in flight
- Multiple conversation turns can initiate before previous LLM responses complete
- Token usage from upstream providers is accounted for via
LLMProxyUsagewithout introducing latency into the speech synthesis path
Error Handling and Resilience
The proxy implements specific HTTP status codes for failure modes:
- 501 Not Implemented: Returned by
_error_responsewhen the proxy is disabled or the backend lackssupports_llm_proxy=True - 502 Bad Gateway: Returned by
_upstream_unreachablewhen the external LLM provider connection fails - Content preservation: Successful responses pass through unmodified, ensuring full compatibility with OpenAI-compatible clients regardless of the upstream provider
Practical Implementation Examples
Launching with CLI Arguments
python -m speech_to_speech serve \
--enable_llm_proxy \
--llm_backend chat-completions \
--llm_proxy_connect_timeout_s 15.0
Building Configuration Programmatically
from speech_to_speech.s2s_pipeline import build_llm_proxy_config
from speech_to_speech.arguments_classes.module_arguments import ModuleArguments
from speech_to_speech.backend_registry import BackendSelection
module_args = ModuleArguments(enable_llm_proxy=True)
# Assume llm_backend is a BackendSelection instance
proxy_cfg = build_llm_proxy_config(module_args, llm_backend)
print(proxy_cfg.dict())
# Output includes: enabled, upstream_base_url, model_name, connect_timeout_s
Mounting in a Custom FastAPI Application
from fastapi import FastAPI
from speech_to_speech.api.openai_realtime.llm_proxy import LLMProxyConfig, mount_llm_proxy
app = FastAPI()
cfg = LLMProxyConfig(
enabled=True,
llm_backend="chat-completions",
upstream_api_key="sk-my-key",
model_name="gpt-4o",
)
proxy_usage = mount_llm_proxy(app, cfg)
# Inspect accumulated counters
print(f"Requests: {proxy_usage.requests}, Tokens: {proxy_usage.output_tokens}")
Querying Usage Statistics
curl http://localhost:8000/v1/usage
# Response includes:
# "llm_proxy": {
# "requests": 3,
# "responses_2xx": 3,
# "input_tokens": 124,
# "output_tokens": 342
# }
Summary
- The LLM Proxy prevents blocking in the voice pipeline by mounting OpenAI-compatible endpoints outside the main processing queues in
llm_proxy.py - Concurrent background tasks are achieved because the proxy does not consume
AudioInItemorLMOutItemqueue slots, allowing continuous audio processing - Configuration requires
--enable_llm_proxywith a compatible backend (chat-completionsorresponses-api) validated throughbackend_registry.py - Token accounting occurs via
LLMProxyUsagecounters integrated into the/v1/usageendpoint without impacting streaming latency - Error handling distinguishes between configuration errors (501), upstream failures (502), and successful passthrough responses
Frequently Asked Questions
Which LLM backends support proxying in the speech-to-speech server?
Only backends with supports_llm_proxy=True in their BackendCapabilities declaration can operate with the proxy enabled. According to src/speech_to_speech/backend_registry.py (lines 393-405), the chat-completions and responses-api backends support proxying, while others will raise a ValueError during pipeline initialization if the proxy is enabled.
Does the LLM proxy modify the content of streaming responses?
No. The proxy relays streaming responses byte-by-byte via StreamingResponse without altering the content. While forwarding, it asynchronously parses SSE events to extract usage statistics, but the client receives the exact bytes sent by the upstream provider, ensuring compatibility with any OpenAI-compatible client implementation.
How does the server track token usage when using an external LLM provider?
The LLMProxyUsage class accumulates counters for requests, response status codes, input tokens, and output tokens. For streaming responses, the proxy watches for SSE chunks containing usage data. These counters are exposed through the /v1/usage endpoint under the llm_proxy key, as implemented in websocket_router.py (lines 537-539), enabling accurate billing and monitoring without proxying overhead.
Can the LLM proxy operate independently of the speech pipeline?
Yes. While designed for the speech-to-speech architecture, the mount_llm_proxy function can attach to any FastAPI application. The proxy logic in llm_proxy.py is self-contained and only requires an LLMProxyConfig instance to function, making it suitable for standalone OpenAI-compatible proxy deployments or integration into other real-time systems.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →