cleaned version

This commit is contained in:
JiriUhlir
2026-07-10 09:54:05 +02:00
parent 7a3f61c125
commit 5d2ba407f4
7 changed files with 104 additions and 180 deletions
+3 -7
View File
@@ -5,7 +5,7 @@ Nepoužíváme těžké SDK — voláme REST endpoint přímo, aby nebyl závisl
import httpx
from ..config import DEEPGRAM_BASE_URL, UPSTREAM_TIMEOUT_SECONDS
from ..errors import UpstreamError
from ..errors import UpstreamError, raise_for_upstream
from ..logging_config import get_logger
log = get_logger("audio-transcription.deepgram")
@@ -43,12 +43,8 @@ async def transcribe(
if resp.status_code >= 400:
# Deepgram vrací chybu v JSON; nikdy nelogujeme klíč (ten je jen v hlavičce).
snippet = resp.text[:500]
log.warning("Deepgram returned %s: %s", resp.status_code, snippet)
raise UpstreamError(
f"Deepgram vrátil chybu {resp.status_code}.",
detail=snippet,
)
log.warning("Deepgram returned %s: %s", resp.status_code, resp.text[:500])
raise_for_upstream("Deepgram", resp.status_code, resp.text)
data = resp.json()
try:
+5 -13
View File
@@ -2,7 +2,7 @@
import httpx
from ..config import OPENAI_BASE_URL, UPSTREAM_TIMEOUT_SECONDS
from ..errors import UpstreamError
from ..errors import UpstreamError, raise_for_upstream
from ..logging_config import get_logger
log = get_logger("audio-transcription.openai")
@@ -33,12 +33,8 @@ async def transcribe(
raise UpstreamError(f"OpenAI Whisper request se nezdařil: {exc}") from exc
if resp.status_code >= 400:
snippet = resp.text[:500]
log.warning("OpenAI transcription returned %s: %s", resp.status_code, snippet)
raise UpstreamError(
f"OpenAI Whisper vrátil chybu {resp.status_code}.",
detail=snippet,
)
log.warning("OpenAI transcription returned %s: %s", resp.status_code, resp.text[:500])
raise_for_upstream("OpenAI Whisper", resp.status_code, resp.text)
data = resp.json()
return data.get("text", "") or ""
@@ -72,12 +68,8 @@ async def merge_transcripts(
raise UpstreamError(f"OpenAI chat (merge) request se nezdařil: {exc}") from exc
if resp.status_code >= 400:
snippet = resp.text[:500]
log.warning("OpenAI chat returned %s: %s", resp.status_code, snippet)
raise UpstreamError(
f"OpenAI chat (merge) vrátil chybu {resp.status_code}.",
detail=snippet,
)
log.warning("OpenAI chat returned %s: %s", resp.status_code, resp.text[:500])
raise_for_upstream("OpenAI chat (merge)", resp.status_code, resp.text)
data = resp.json()
try:
+32 -1
View File
@@ -35,12 +35,43 @@ class BadRequestError(TranscriptionError):
status_code = 400
class RateLimitError(TranscriptionError):
"""Upstream rate limit (HTTP 429)."""
status_code = 429
class UpstreamError(TranscriptionError):
"""Chyba při volání upstream API (Deepgram / OpenAI / stažení audia)."""
"""Chyba při volání upstream API (Deepgram / OpenAI)."""
status_code = 502
def raise_for_upstream(upstream: str, status: int, body: str) -> None:
"""Zmapuje HTTP status z upstreamu (Deepgram/OpenAI) na správnou chybu služby.
- 400/415/422 → 400 (špatný / nepodporovaný audio soubor = chyba klienta)
- 401/403 → 401 (upstream odmítl API klíč volajícího)
- 429 → 429 (rate limit)
- jinak → 502 (výpadek / neočekávaná chyba upstreamu)
"""
snippet = (body or "")[:500]
if status in (400, 415, 422):
raise BadRequestError(
f"{upstream} odmítl audio (HTTP {status}) — pravděpodobně nepodporovaný "
f"formát nebo poškozený soubor.",
detail=snippet,
)
if status in (401, 403):
raise CredentialsError(
f"{upstream} odmítl API klíč (HTTP {status}).",
detail=snippet,
)
if status == 429:
raise RateLimitError(f"{upstream} rate limit (HTTP 429).", detail=snippet)
raise UpstreamError(f"{upstream} vrátil chybu {status}.", detail=snippet)
def register_exception_handlers(app: FastAPI) -> None:
@app.exception_handler(TranscriptionError)
async def _handle_transcription_error(request: Request, exc: TranscriptionError):
+40 -101
View File
@@ -1,11 +1,12 @@
"""Endpointy pro přepis audia.
"""Endpoint pro přepis audia.
- POST /transcribe/dual → paralelní přepis Deepgram + OpenAI Whisper (dva texty)
- POST /transcribe/combined → dual + sloučení do jednoho co nejlepšího přepisu (merge)
POST /transcribe — udělá vše najednou: audio přepíše paralelně Deepgramem i OpenAI
(whisper-1) a oba texty sloučí přes OpenAI Chat podle `combine_prompt` do jednoho
co nejlepšího přepisu.
Audio přichází jako proměnná (upload `file` ve form-data). Všechny ostatní parametry
jsou v POST těle (form fields). API klíče jsou v X- hlavičkách (viz credentials.py).
Výstup je vždy JSON.
Výstup je vždy JSON: {"gpt": ..., "deepgram": ..., "merge": ...}.
"""
import asyncio
@@ -29,17 +30,10 @@ log = get_logger("audio-transcription.transcribe")
router = APIRouter(tags=["transcribe"])
class DualResult(BaseModel):
text1: str = Field(..., description="Přepis z Deepgramu.")
text2: str = Field(..., description="Přepis z OpenAI Whisper.")
deepgram_model: str
whisper_model: str
language: str
class CombinedResult(DualResult):
merged: str = Field(..., description="Sloučený, co nejpřesnější výsledný přepis.")
chat_model: str
class TranscribeResult(BaseModel):
gpt: str = Field(..., description="Přepis z OpenAI (model whisper-1).")
deepgram: str = Field(..., description="Přepis z Deepgramu.")
merge: str = Field(..., description="Sloučený, co nejpřesnější výsledný přepis.")
async def _read_upload(file: UploadFile) -> tuple[bytes, str, str]:
@@ -52,22 +46,36 @@ async def _read_upload(file: UploadFile) -> tuple[bytes, str, str]:
return audio, filename, content_type
async def _transcribe_dual(
creds: Credentials,
audio: bytes,
filename: str,
content_type: str,
deepgram_model: str,
whisper_model: str,
language: str,
diarize: bool,
smart_format: bool,
) -> tuple[str, str]:
"""Spustí oba přepisy paralelně; vrátí (deepgram_text, whisper_text)."""
@router.post(
"/dual-with-merge",
response_model=TranscribeResult,
summary="Přepis audia (Deepgram + OpenAI) a AI sloučení — vše najednou",
description="Přijme audio soubor, vytvoří paralelně přepis z Deepgramu i z OpenAI "
"(whisper-1) a poté je sloučí přes OpenAI Chat podle `combine_prompt` do jediného, "
"co nejpřesnějšího českého přepisu. Vrací JSON `{gpt, deepgram, merge}`.",
)
async def transcribe(
file: UploadFile = File(..., description="Audio soubor k přepisu (proměnná)."),
combine_prompt: str = Form(
DEFAULT_COMBINE_PROMPT,
description="System prompt pro sloučení. Nezadáš-li, použije se výchozí "
"'Czech Transcript Merger'.",
),
deepgram_model: str = Form(DEFAULT_DEEPGRAM_MODEL, description="Deepgram model."),
whisper_model: str = Form(DEFAULT_WHISPER_MODEL, description="OpenAI přepisový model."),
chat_model: str = Form(DEFAULT_CHAT_MODEL, description="OpenAI chat model pro sloučení."),
language: str = Form(DEFAULT_LANGUAGE, description="Jazyk audia (ISO kód, např. cs)."),
diarize: bool = Form(True, description="Deepgram diarizace (rozlišení mluvčích)."),
smart_format: bool = Form(True, description="Deepgram smart formatting."),
creds: Credentials = Depends(get_credentials),
) -> TranscribeResult:
deepgram_key = creds.require_deepgram()
openai_key = creds.require_openai()
text1, text2 = await asyncio.gather(
audio, filename, content_type = await _read_upload(file)
# Oba přepisy paralelně.
text_deepgram, text_gpt = await asyncio.gather(
deepgram_client.transcribe(
api_key=deepgram_key,
audio=audio,
@@ -86,83 +94,14 @@ async def _transcribe_dual(
language=language,
),
)
return text1, text2
@router.post(
"/transcribe/dual",
response_model=DualResult,
summary="Paralelní přepis Deepgram + OpenAI Whisper",
description="Přijme audio soubor a vrátí dva nezávislé přepisy: "
"`text1` z Deepgramu a `text2` z OpenAI Whisper.",
)
async def transcribe_dual(
file: UploadFile = File(..., description="Audio soubor k přepisu (proměnná)."),
deepgram_model: str = Form(DEFAULT_DEEPGRAM_MODEL, description="Deepgram model."),
whisper_model: str = Form(DEFAULT_WHISPER_MODEL, description="OpenAI přepisový model."),
language: str = Form(DEFAULT_LANGUAGE, description="Jazyk audia (ISO kód, např. cs)."),
diarize: bool = Form(True, description="Deepgram diarizace (rozlišení mluvčích)."),
smart_format: bool = Form(True, description="Deepgram smart formatting."),
creds: Credentials = Depends(get_credentials),
) -> DualResult:
audio, filename, content_type = await _read_upload(file)
text1, text2 = await _transcribe_dual(
creds, audio, filename, content_type,
deepgram_model, whisper_model, language, diarize, smart_format,
)
return DualResult(
text1=text1,
text2=text2,
deepgram_model=deepgram_model,
whisper_model=whisper_model,
language=language,
)
@router.post(
"/transcribe/combined",
response_model=CombinedResult,
summary="Dual přepis + AI sloučení do nejlepšího přepisu",
description="Přijme audio soubor, vytvoří přepis z Deepgramu i Whisperu a poté je "
"sloučí přes OpenAI Chat podle `combine_prompt` do jediného, co nejpřesnějšího "
"českého přepisu. Vrací `text1`, `text2` i výsledný `merged`.",
)
async def transcribe_combined(
file: UploadFile = File(..., description="Audio soubor k přepisu (proměnná)."),
combine_prompt: str = Form(
DEFAULT_COMBINE_PROMPT,
description="System prompt pro sloučení. Nezadáš-li, použije se výchozí "
"'Czech Transcript Merger'.",
),
deepgram_model: str = Form(DEFAULT_DEEPGRAM_MODEL, description="Deepgram model."),
whisper_model: str = Form(DEFAULT_WHISPER_MODEL, description="OpenAI přepisový model."),
chat_model: str = Form(DEFAULT_CHAT_MODEL, description="OpenAI chat model pro sloučení."),
language: str = Form(DEFAULT_LANGUAGE, description="Jazyk audia (ISO kód, např. cs)."),
diarize: bool = Form(True, description="Deepgram diarizace (rozlišení mluvčích)."),
smart_format: bool = Form(True, description="Deepgram smart formatting."),
creds: Credentials = Depends(get_credentials),
) -> CombinedResult:
audio, filename, content_type = await _read_upload(file)
text1, text2 = await _transcribe_dual(
creds, audio, filename, content_type,
deepgram_model, whisper_model, language, diarize, smart_format,
)
prompt = combine_prompt.strip() if combine_prompt and combine_prompt.strip() else DEFAULT_COMBINE_PROMPT
merged = await openai_client.merge_transcripts(
api_key=creds.require_openai(),
api_key=openai_key,
model=chat_model,
system_prompt=prompt,
text_deepgram=text1,
text_whisper=text2,
text_deepgram=text_deepgram,
text_whisper=text_gpt,
)
return CombinedResult(
text1=text1,
text2=text2,
merged=merged,
deepgram_model=deepgram_model,
whisper_model=whisper_model,
chat_model=chat_model,
language=language,
)
return TranscribeResult(gpt=text_gpt, deepgram=text_deepgram, merge=merged)