Deploy public LiteLLM on US DC1 with preserved HK routes

This commit is contained in:
Codex
2026-10-28 19:12:03 -07:00
parent 81c475464e
commit df391cd41f
12 changed files with 741 additions and 0 deletions
@@ -0,0 +1,119 @@
"""Bridge OpenAI file uploads to Beijing Bailian's native ASR API."""
import base64
import io
import json
from pathlib import Path
from urllib.parse import urlsplit
import wave
from litellm.llms.base_llm.audio_transcription.transformation import AudioTranscriptionRequestData
from litellm.llms.openai.transcriptions.whisper_transformation import OpenAIWhisperAudioTranscriptionConfig
from litellm.litellm_core_utils.audio_utils.utils import process_audio_file
from litellm.types.utils import TranscriptionResponse, TranscriptionUsageDurationObject
MODEL = 'qwen-audio-3.1-asr-flash'
FORMATS = {'audio/wav':'wav','audio/x-wav':'wav','audio/wave':'wav',
'audio/mpeg':'mp3','audio/mp3':'mp3','audio/flac':'flac','audio/x-flac':'flac',
'audio/mp4':'m4a','audio/m4a':'m4a','audio/x-m4a':'m4a',
'audio/ogg':'ogg','audio/webm':'webm','video/webm':'webm','audio/aac':'aac'}
VOCABULARY_PATHS = frozenset({'/etc/litellm/personal-vocabulary.json',
'/credentials/personal-vocabulary.json'})
def vocabulary(path):
if path not in VOCABULARY_PATHS:
raise ValueError('Unexpected personal vocabulary path')
words = json.loads(Path(path).read_text())
if not isinstance(words,list) or not 1 <= len(words) <= 100:
raise ValueError('Personal vocabulary must contain 1-100 terms')
if any(not isinstance(w,str) or not w.strip() or len(w)>100 for w in words):
raise ValueError('Invalid personal vocabulary term')
return {w.strip():3 for w in words}
class BailianTranscriptionConfig(OpenAIWhisperAudioTranscriptionConfig):
@property
def has_native_transcription_endpoint(self):
return True
def get_supported_openai_params(self,model):
return ['language','response_format','prompt']
def map_openai_params(self,non_default_params,optional_params,model,drop_params):
result=super().map_openai_params(non_default_params,optional_params,model,drop_params)
if result.get('response_format') not in (None,'json','text'):
raise ValueError('Bailian dictation supports json/text without timestamps')
return result
def validate_environment(self,headers,model,messages,optional_params,litellm_params,
api_key=None,api_base=None):
if not api_key:raise ValueError('Beijing Bailian API key required')
return {**headers,'Authorization':'Bearer '+api_key,'Content-Type':'application/json',
'X-DashScope-SSE':'disable'}
def get_complete_url(self,api_base,api_key,model,optional_params,litellm_params,stream=None):
parsed=urlsplit(api_base or '')
if (model != MODEL or parsed.scheme != 'https' or
not (parsed.hostname or '').endswith('.cn-beijing.maas.aliyuncs.com') or
parsed.path.rstrip('/') != '/api/v1' or parsed.port not in (None,443) or
parsed.username or parsed.password or parsed.query or parsed.fragment):
raise ValueError('Bailian ASR restricted to Beijing workspace native endpoints')
if stream:raise ValueError('Streaming not enabled on this dictation route')
return api_base.rstrip('/')+'/services/aigc/multimodal-generation/generation'
def transform_audio_transcription_request(self,model,audio_file,optional_params,litellm_params):
if model != MODEL or optional_params.get('timestamp_granularities'):
raise ValueError('Unsupported Bailian dictation model or timestamps')
audio=process_audio_file(audio_file)
mime=audio.content_type.split(';',1)[0].lower()
if mime not in FORMATS:raise ValueError('Unsupported Bailian audio MIME type')
if len(audio.file_content)>10_000_000:raise ValueError('Bailian inline audio exceeds 10 MB')
parameters={'format':FORMATS[mime]}
if parameters['format']=='wav':
with wave.open(io.BytesIO(audio.file_content),'rb') as wav:
parameters['sample_rate']=str(wav.getframerate())
language=optional_params.get('language')
if language:parameters['language_hints']=[language]
# LiteLLM classifies DashScope as OpenAI-compatible and nests custom kwargs.
extra=optional_params.get('extra_body') or {}
path=optional_params.get('bailian_vocabulary_path') or extra.get('bailian_vocabulary_path')
if path:parameters['vocabulary']=vocabulary(path)
prompt=optional_params.get('prompt')
messages=[]
if prompt:
if not isinstance(prompt,str) or len(prompt)>400:
raise ValueError('Bailian context prompt must be at most 400 characters')
messages.append({'role':'user','content':[{'type':'input_text','text':prompt}]})
# Always preserve the original bytes; only WAV metadata supplies sample rate.
uri_mime='audio/mp3' if parameters['format']=='mp3' else mime
data_uri='data:'+uri_mime+';base64,'+base64.b64encode(audio.file_content).decode()
if len(data_uri)>10_000_000:
raise ValueError('Bailian inline audio exceeds 10 MB after Base64 encoding')
messages.append({'role':'user','content':[{'type':'input_audio','input_audio':{
'data':data_uri}}]})
return AudioTranscriptionRequestData(data={'model':MODEL,'input':{'messages':messages},
'parameters':parameters})
def transform_audio_transcription_response(self,raw_response):
body=raw_response.json()
text=body.get('output',{}).get('text',body.get('text'))
if not isinstance(text,str):raise ValueError('Bailian returned no transcript')
result=TranscriptionResponse(text=text)
duration=(body.get('usage') or {}).get('duration')
if duration is not None:
result.usage=TranscriptionUsageDurationObject(type='duration',seconds=duration)
result['duration']=duration
return result
def install():
import litellm
from litellm.utils import ProviderConfigManager
original=ProviderConfigManager.get_provider_audio_transcription_config
if getattr(original,'_bailian_asr_adapter',False):return
def select(model,provider):
if provider==litellm.LlmProviders.DASHSCOPE and model==MODEL:
return BailianTranscriptionConfig()
return original(model,provider)
select._bailian_asr_adapter=True
ProviderConfigManager.get_provider_audio_transcription_config=staticmethod(select)