cleaned version
This commit is contained in:
@@ -11,8 +11,7 @@ Twilio bylo vynecháno — audio se posílá přímo jako proměnná (upload).
|
|||||||
|
|
||||||
- `GET /health` — health check
|
- `GET /health` — health check
|
||||||
- `GET /version` — verze + root_path
|
- `GET /version` — verze + root_path
|
||||||
- `POST /transcribe/dual` — Deepgram + Whisper (dva nezávislé texty)
|
- `POST /dual-with-merge` — Deepgram + OpenAI přepis + AI sloučení, vrací `{gpt, deepgram, merge}`
|
||||||
- `POST /transcribe/combined` — dual + sloučený `merged`
|
|
||||||
|
|
||||||
Swagger: `/docs`.
|
Swagger: `/docs`.
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Nepoužíváme těžké SDK — voláme REST endpoint přímo, aby nebyl závisl
|
|||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from ..config import DEEPGRAM_BASE_URL, UPSTREAM_TIMEOUT_SECONDS
|
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
|
from ..logging_config import get_logger
|
||||||
|
|
||||||
log = get_logger("audio-transcription.deepgram")
|
log = get_logger("audio-transcription.deepgram")
|
||||||
@@ -43,12 +43,8 @@ async def transcribe(
|
|||||||
|
|
||||||
if resp.status_code >= 400:
|
if resp.status_code >= 400:
|
||||||
# Deepgram vrací chybu v JSON; nikdy nelogujeme klíč (ten je jen v hlavičce).
|
# 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, resp.text[:500])
|
||||||
log.warning("Deepgram returned %s: %s", resp.status_code, snippet)
|
raise_for_upstream("Deepgram", resp.status_code, resp.text)
|
||||||
raise UpstreamError(
|
|
||||||
f"Deepgram vrátil chybu {resp.status_code}.",
|
|
||||||
detail=snippet,
|
|
||||||
)
|
|
||||||
|
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from ..config import OPENAI_BASE_URL, UPSTREAM_TIMEOUT_SECONDS
|
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
|
from ..logging_config import get_logger
|
||||||
|
|
||||||
log = get_logger("audio-transcription.openai")
|
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
|
raise UpstreamError(f"OpenAI Whisper request se nezdařil: {exc}") from exc
|
||||||
|
|
||||||
if resp.status_code >= 400:
|
if resp.status_code >= 400:
|
||||||
snippet = resp.text[:500]
|
log.warning("OpenAI transcription returned %s: %s", resp.status_code, resp.text[:500])
|
||||||
log.warning("OpenAI transcription returned %s: %s", resp.status_code, snippet)
|
raise_for_upstream("OpenAI Whisper", resp.status_code, resp.text)
|
||||||
raise UpstreamError(
|
|
||||||
f"OpenAI Whisper vrátil chybu {resp.status_code}.",
|
|
||||||
detail=snippet,
|
|
||||||
)
|
|
||||||
|
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
return data.get("text", "") or ""
|
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
|
raise UpstreamError(f"OpenAI chat (merge) request se nezdařil: {exc}") from exc
|
||||||
|
|
||||||
if resp.status_code >= 400:
|
if resp.status_code >= 400:
|
||||||
snippet = resp.text[:500]
|
log.warning("OpenAI chat returned %s: %s", resp.status_code, resp.text[:500])
|
||||||
log.warning("OpenAI chat returned %s: %s", resp.status_code, snippet)
|
raise_for_upstream("OpenAI chat (merge)", resp.status_code, resp.text)
|
||||||
raise UpstreamError(
|
|
||||||
f"OpenAI chat (merge) vrátil chybu {resp.status_code}.",
|
|
||||||
detail=snippet,
|
|
||||||
)
|
|
||||||
|
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
try:
|
try:
|
||||||
|
|||||||
+32
-1
@@ -35,12 +35,43 @@ class BadRequestError(TranscriptionError):
|
|||||||
status_code = 400
|
status_code = 400
|
||||||
|
|
||||||
|
|
||||||
|
class RateLimitError(TranscriptionError):
|
||||||
|
"""Upstream rate limit (HTTP 429)."""
|
||||||
|
|
||||||
|
status_code = 429
|
||||||
|
|
||||||
|
|
||||||
class UpstreamError(TranscriptionError):
|
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
|
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:
|
def register_exception_handlers(app: FastAPI) -> None:
|
||||||
@app.exception_handler(TranscriptionError)
|
@app.exception_handler(TranscriptionError)
|
||||||
async def _handle_transcription_error(request: Request, exc: TranscriptionError):
|
async def _handle_transcription_error(request: Request, exc: TranscriptionError):
|
||||||
|
|||||||
+40
-101
@@ -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 — udělá vše najednou: audio přepíše paralelně Deepgramem i OpenAI
|
||||||
- POST /transcribe/combined → dual + sloučení do jednoho co nejlepšího přepisu (merge)
|
(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
|
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).
|
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
|
import asyncio
|
||||||
|
|
||||||
@@ -29,17 +30,10 @@ log = get_logger("audio-transcription.transcribe")
|
|||||||
router = APIRouter(tags=["transcribe"])
|
router = APIRouter(tags=["transcribe"])
|
||||||
|
|
||||||
|
|
||||||
class DualResult(BaseModel):
|
class TranscribeResult(BaseModel):
|
||||||
text1: str = Field(..., description="Přepis z Deepgramu.")
|
gpt: str = Field(..., description="Přepis z OpenAI (model whisper-1).")
|
||||||
text2: str = Field(..., description="Přepis z OpenAI Whisper.")
|
deepgram: str = Field(..., description="Přepis z Deepgramu.")
|
||||||
deepgram_model: str
|
merge: str = Field(..., description="Sloučený, co nejpřesnější výsledný přepis.")
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
async def _read_upload(file: UploadFile) -> tuple[bytes, str, str]:
|
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
|
return audio, filename, content_type
|
||||||
|
|
||||||
|
|
||||||
async def _transcribe_dual(
|
@router.post(
|
||||||
creds: Credentials,
|
"/dual-with-merge",
|
||||||
audio: bytes,
|
response_model=TranscribeResult,
|
||||||
filename: str,
|
summary="Přepis audia (Deepgram + OpenAI) a AI sloučení — vše najednou",
|
||||||
content_type: str,
|
description="Přijme audio soubor, vytvoří paralelně přepis z Deepgramu i z OpenAI "
|
||||||
deepgram_model: str,
|
"(whisper-1) a poté je sloučí přes OpenAI Chat podle `combine_prompt` do jediného, "
|
||||||
whisper_model: str,
|
"co nejpřesnějšího českého přepisu. Vrací JSON `{gpt, deepgram, merge}`.",
|
||||||
language: str,
|
)
|
||||||
diarize: bool,
|
async def transcribe(
|
||||||
smart_format: bool,
|
file: UploadFile = File(..., description="Audio soubor k přepisu (proměnná)."),
|
||||||
) -> tuple[str, str]:
|
combine_prompt: str = Form(
|
||||||
"""Spustí oba přepisy paralelně; vrátí (deepgram_text, whisper_text)."""
|
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()
|
deepgram_key = creds.require_deepgram()
|
||||||
openai_key = creds.require_openai()
|
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(
|
deepgram_client.transcribe(
|
||||||
api_key=deepgram_key,
|
api_key=deepgram_key,
|
||||||
audio=audio,
|
audio=audio,
|
||||||
@@ -86,83 +94,14 @@ async def _transcribe_dual(
|
|||||||
language=language,
|
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
|
prompt = combine_prompt.strip() if combine_prompt and combine_prompt.strip() else DEFAULT_COMBINE_PROMPT
|
||||||
merged = await openai_client.merge_transcripts(
|
merged = await openai_client.merge_transcripts(
|
||||||
api_key=creds.require_openai(),
|
api_key=openai_key,
|
||||||
model=chat_model,
|
model=chat_model,
|
||||||
system_prompt=prompt,
|
system_prompt=prompt,
|
||||||
text_deepgram=text1,
|
text_deepgram=text_deepgram,
|
||||||
text_whisper=text2,
|
text_whisper=text_gpt,
|
||||||
)
|
)
|
||||||
|
|
||||||
return CombinedResult(
|
return TranscribeResult(gpt=text_gpt, deepgram=text_deepgram, merge=merged)
|
||||||
text1=text1,
|
|
||||||
text2=text2,
|
|
||||||
merged=merged,
|
|
||||||
deepgram_model=deepgram_model,
|
|
||||||
whisper_model=whisper_model,
|
|
||||||
chat_model=chat_model,
|
|
||||||
language=language,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ app/
|
|||||||
openai_client.py # OpenAI Whisper + Chat Completions (httpx)
|
openai_client.py # OpenAI Whisper + Chat Completions (httpx)
|
||||||
routers/
|
routers/
|
||||||
meta.py # /health, /version
|
meta.py # /health, /version
|
||||||
transcribe.py # /transcribe/dual, /transcribe/combined
|
transcribe.py # /transcribe (vše najednou)
|
||||||
```
|
```
|
||||||
|
|
||||||
Žádné SDK — upstream API se volají přímo přes `httpx`, aby služba nebyla závislá na
|
Žádné SDK — upstream API se volají přímo přes `httpx`, aby služba nebyla závislá na
|
||||||
@@ -39,8 +39,7 @@ OpenAPI `servers` i Swagger „Try it out“ míří na správný prefix.
|
|||||||
|--------|-------|-------|
|
|--------|-------|-------|
|
||||||
| GET | `/health` | Health check (200) |
|
| GET | `/health` | Health check (200) |
|
||||||
| GET | `/version` | Verze + root_path |
|
| GET | `/version` | Verze + root_path |
|
||||||
| POST | `/transcribe/dual` | Deepgram + Whisper (dva texty) |
|
| POST | `/dual-with-merge` | Deepgram + OpenAI přepis + AI sloučení (vše najednou) |
|
||||||
| POST | `/transcribe/combined` | dual + sloučený `merged` |
|
|
||||||
|
|
||||||
Detaily viz [transcribe.md](transcribe.md).
|
Detaily viz [transcribe.md](transcribe.md).
|
||||||
|
|
||||||
|
|||||||
+21
-53
@@ -1,6 +1,7 @@
|
|||||||
# Endpointy přepisu
|
# Endpoint přepisu
|
||||||
|
|
||||||
Audio se posílá jako proměnná `file` (multipart/form-data). Všechny ostatní parametry
|
Jediná metoda, která udělá vše najednou: přepis Deepgramem + OpenAI (whisper-1) a jejich
|
||||||
|
AI sloučení. Audio se posílá jako proměnná `file` (multipart/form-data), ostatní parametry
|
||||||
jsou form fields v POST. API klíče jsou v X- hlavičkách. Výstup je vždy JSON.
|
jsou form fields v POST. API klíče jsou v X- hlavičkách. Výstup je vždy JSON.
|
||||||
|
|
||||||
## Hlavičky (secrets)
|
## Hlavičky (secrets)
|
||||||
@@ -8,90 +9,57 @@ jsou form fields v POST. API klíče jsou v X- hlavičkách. Výstup je vždy JS
|
|||||||
| Hlavička | Nutná | Popis |
|
| Hlavička | Nutná | Popis |
|
||||||
|----------|-------|-------|
|
|----------|-------|-------|
|
||||||
| `X-Deepgram-Api-Key` | ano | Deepgram API klíč |
|
| `X-Deepgram-Api-Key` | ano | Deepgram API klíč |
|
||||||
| `X-OpenAI-Api-Key` | ano | OpenAI API klíč (Whisper + slučovací chat) |
|
| `X-OpenAI-Api-Key` | ano | OpenAI API klíč (whisper-1 přepis + slučovací chat) |
|
||||||
|
|
||||||
## POST /transcribe/dual
|
## POST /dual-with-merge
|
||||||
|
|
||||||
Paralelní přepis dvěma engine. Vrací dva nezávislé texty.
|
|
||||||
|
|
||||||
### Form parametry
|
### Form parametry
|
||||||
|
|
||||||
| Pole | Typ | Default | Popis |
|
| Pole | Typ | Default | Popis |
|
||||||
|------|-----|---------|-------|
|
|------|-----|---------|-------|
|
||||||
| `file` | soubor | — | Audio k přepisu (povinné) |
|
| `file` | soubor | — | Audio k přepisu (povinné) |
|
||||||
|
| `combine_prompt` | text | výchozí „Czech Transcript Merger“ | System prompt pro sloučení |
|
||||||
| `deepgram_model` | text | `nova-2` | Deepgram model |
|
| `deepgram_model` | text | `nova-2` | Deepgram model |
|
||||||
| `whisper_model` | text | `whisper-1` | OpenAI přepisový model |
|
| `whisper_model` | text | `whisper-1` | OpenAI přepisový model |
|
||||||
|
| `chat_model` | text | `gpt-4o` | OpenAI chat model pro sloučení |
|
||||||
| `language` | text | `cs` | Jazyk (ISO kód) |
|
| `language` | text | `cs` | Jazyk (ISO kód) |
|
||||||
| `diarize` | bool | `true` | Deepgram diarizace |
|
| `diarize` | bool | `true` | Deepgram diarizace |
|
||||||
| `smart_format` | bool | `true` | Deepgram smart formatting |
|
| `smart_format` | bool | `true` | Deepgram smart formatting |
|
||||||
|
|
||||||
### Odpověď
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"text1": "přepis z Deepgramu",
|
|
||||||
"text2": "přepis z OpenAI Whisper",
|
|
||||||
"deepgram_model": "nova-2",
|
|
||||||
"whisper_model": "whisper-1",
|
|
||||||
"language": "cs"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Příklad (curl)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST https://services.csbot.cz/apps/audio-transcription/transcribe/dual \
|
|
||||||
-H "X-Deepgram-Api-Key: <DEEPGRAM_KEY>" \
|
|
||||||
-H "X-OpenAI-Api-Key: <OPENAI_KEY>" \
|
|
||||||
-F "file=@nahravka.mp3" \
|
|
||||||
-F "language=cs"
|
|
||||||
```
|
|
||||||
|
|
||||||
## POST /transcribe/combined
|
|
||||||
|
|
||||||
Jako `dual`, navíc oba přepisy sloučí přes OpenAI Chat do jednoho výsledku (`merged`).
|
|
||||||
|
|
||||||
### Form parametry
|
|
||||||
|
|
||||||
Vše z `dual`, plus:
|
|
||||||
|
|
||||||
| Pole | Typ | Default | Popis |
|
|
||||||
|------|-----|---------|-------|
|
|
||||||
| `combine_prompt` | text | výchozí „Czech Transcript Merger“ | System prompt pro sloučení |
|
|
||||||
| `chat_model` | text | `gpt-4o` | OpenAI chat model pro sloučení |
|
|
||||||
|
|
||||||
Nezadá-li se `combine_prompt`, použije se výchozí prompt z `app/prompts.py`.
|
Nezadá-li se `combine_prompt`, použije se výchozí prompt z `app/prompts.py`.
|
||||||
|
|
||||||
### Odpověď
|
### Odpověď
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"text1": "přepis z Deepgramu",
|
"gpt": "přepis z OpenAI (whisper-1)",
|
||||||
"text2": "přepis z OpenAI Whisper",
|
"deepgram": "přepis z Deepgramu",
|
||||||
"merged": "sloučený, co nejpřesnější výsledný přepis",
|
"merge": "sloučený, co nejpřesnější výsledný přepis"
|
||||||
"deepgram_model": "nova-2",
|
|
||||||
"whisper_model": "whisper-1",
|
|
||||||
"chat_model": "gpt-4o",
|
|
||||||
"language": "cs"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Příklad (curl)
|
### Příklad (curl)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://services.csbot.cz/apps/audio-transcription/transcribe/combined \
|
curl -X POST https://services.csbot.cz/apps/audio-transcription/dual-with-merge \
|
||||||
-H "X-Deepgram-Api-Key: <DEEPGRAM_KEY>" \
|
-H "X-Deepgram-Api-Key: <DEEPGRAM_KEY>" \
|
||||||
-H "X-OpenAI-Api-Key: <OPENAI_KEY>" \
|
-H "X-OpenAI-Api-Key: <OPENAI_KEY>" \
|
||||||
-F "file=@nahravka.mp3"
|
-F "file=@nahravka.mp3" \
|
||||||
|
-F "language=cs"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Chybové stavy
|
## Chybové stavy
|
||||||
|
|
||||||
|
Chybové odpovědi jsou JSON: `{"error": "<TypChyby>", "message": "...", "detail": "<text z upstreamu>"}`.
|
||||||
|
|
||||||
| HTTP | Kdy |
|
| HTTP | Kdy |
|
||||||
|------|-----|
|
|------|-----|
|
||||||
| 400 | Chybí / prázdný audio soubor |
|
| 400 | Chybí / prázdný audio soubor; **nepodporovaný nebo poškozený audio formát** (Deepgram/OpenAI vrátí 400/415/422) |
|
||||||
| 401 | Chybí `X-Deepgram-Api-Key` nebo `X-OpenAI-Api-Key` |
|
| 401 | Chybí `X-Deepgram-Api-Key` / `X-OpenAI-Api-Key`, **nebo upstream odmítl API klíč** (Deepgram/OpenAI 401/403) |
|
||||||
| 502 | Chyba upstream API (Deepgram / OpenAI) |
|
| 429 | Rate limit na Deepgram nebo OpenAI |
|
||||||
|
| 502 | Výpadek / neočekávaná chyba upstreamu (5xx, síťová chyba, timeout, neočekávaná struktura odpovědi) |
|
||||||
| 500 | Neočekávaná chyba serveru |
|
| 500 | Neočekávaná chyba serveru |
|
||||||
|
|
||||||
|
Mapování dělá `raise_for_upstream` v `app/errors.py`. Špatný typ souboru se pozná až
|
||||||
|
z odpovědi Deepgramu/OpenAI (bytes validují oni) a mapuje se na **400**, ne na 502.
|
||||||
Všechny chyby se logují (bez secrets), žádné tiché selhání.
|
Všechny chyby se logují (bez secrets), žádné tiché selhání.
|
||||||
|
|||||||
Reference in New Issue
Block a user