fix
This commit is contained in:
@@ -21,12 +21,19 @@ async def transcribe(
|
||||
diarize: bool,
|
||||
smart_format: bool,
|
||||
) -> str:
|
||||
"""Přepíše audio Deepgramem a vrátí prostý transkript (channels[0].alternatives[0].transcript)."""
|
||||
"""Přepíše audio Deepgramem.
|
||||
|
||||
Když je zapnutá diarizace, vrátí přepis se štítky mluvčích (z `utterances`),
|
||||
např. `Mluvčí 0: ...`. Bez diarizace vrátí plochý transkript.
|
||||
"""
|
||||
params = {
|
||||
"model": model,
|
||||
"language": language,
|
||||
"diarize": "true" if diarize else "false",
|
||||
"smart_format": "true" if smart_format else "false",
|
||||
# utterances rozseká přepis na promluvy s přiřazeným mluvčím — nutné, aby
|
||||
# v textu bylo vidět rozlišení mluvčích (samotný `transcript` je plochý).
|
||||
"utterances": "true" if diarize else "false",
|
||||
}
|
||||
headers = {
|
||||
"Authorization": f"Token {api_key}",
|
||||
@@ -48,9 +55,36 @@ async def transcribe(
|
||||
|
||||
data = resp.json()
|
||||
try:
|
||||
channels = data["results"]["channels"]
|
||||
alternative = channels[0]["alternatives"][0]
|
||||
results = data["results"]
|
||||
# 1) Preferuj diarizovaný výstup z utterances (se štítky mluvčích).
|
||||
diarized = _format_utterances(results.get("utterances"))
|
||||
if diarized:
|
||||
return diarized
|
||||
# 2) Fallback na plochý transkript.
|
||||
alternative = results["channels"][0]["alternatives"][0]
|
||||
return alternative.get("transcript", "") or ""
|
||||
except (KeyError, IndexError, TypeError) as exc:
|
||||
log.error("Unexpected Deepgram response shape: %s", exc)
|
||||
raise UpstreamError("Neočekávaná struktura odpovědi z Deepgramu.") from exc
|
||||
|
||||
|
||||
def _format_utterances(utterances) -> str:
|
||||
"""Sloučí Deepgram utterances do přepisu se štítky mluvčích (`Mluvčí N: ...`).
|
||||
|
||||
Po sobě jdoucí promluvy stejného mluvčího spojí do jednoho řádku.
|
||||
"""
|
||||
if not utterances:
|
||||
return ""
|
||||
lines: list[str] = []
|
||||
prev_speaker = None
|
||||
for utt in utterances:
|
||||
text = (utt.get("transcript") or "").strip()
|
||||
if not text:
|
||||
continue
|
||||
speaker = utt.get("speaker")
|
||||
if speaker != prev_speaker:
|
||||
lines.append(f"Mluvčí {speaker}: {text}")
|
||||
prev_speaker = speaker
|
||||
else:
|
||||
lines[-1] += " " + text
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -20,7 +20,8 @@ async def transcribe(
|
||||
"""Přepíše audio přes OpenAI Whisper (/audio/transcriptions) a vrátí text."""
|
||||
headers = {"Authorization": f"Bearer {api_key}"}
|
||||
files = {"file": (filename or "audio.mp3", audio, content_type or "application/octet-stream")}
|
||||
form = {"model": model}
|
||||
# temperature=0 => deterministický přepis, méně halucinací na tichu/šumu.
|
||||
form = {"model": model, "temperature": "0"}
|
||||
if language:
|
||||
form["language"] = language
|
||||
url = f"{OPENAI_BASE_URL}/audio/transcriptions"
|
||||
|
||||
+37
-25
@@ -1,31 +1,43 @@
|
||||
"""Defaultní prompt pro slučování přepisů (Czech Transcript Merger).
|
||||
|
||||
Přebráno z původní .NET implementace CallCenterController. Volající může poslat
|
||||
vlastní `combine_prompt` v POST; když ho nepošle, použije se tento.
|
||||
Vychází z původní .NET implementace, ale je upravený tak, aby:
|
||||
- zachoval VŠECHNY repliky obou zdrojů (sjednocení, ne průnik),
|
||||
- využil rozlišení mluvčích z Deepgramu (Text1 chodí se štítky `Mluvčí N:`),
|
||||
- ignoroval typické halucinace Whisperu na tichu/šumu.
|
||||
|
||||
Volající může poslat vlastní `combine_prompt` v POST; jinak se použije tento.
|
||||
"""
|
||||
|
||||
DEFAULT_COMBINE_PROMPT = (
|
||||
"Jsi \"Czech Transcript Merger\". Tvým úkolem je z *Text1* (Deepgram) a *Text2* "
|
||||
"(OpenAI) vytvořit jediný, co nejpřesnější český přepis téhož hovoru."
|
||||
"PRAVIDLA SLOUČENÍ"
|
||||
"1) Plynulost & běžná mluva: upřednostňuj Text1."
|
||||
"2) Strukturované údaje (NEZKRESLUJ, NEVYMÝŠLEJ): upřednostňuj Text2 pro: jména a "
|
||||
"příjmení, firmy/brand, SPZ, čísla (tel., částky, datum/čas), e-maily, URL/domény, "
|
||||
"adresy, čísla objednávek, kódy."
|
||||
"3) Pokud si Text1 a Text2 odporují:"
|
||||
" - pro údaje z bodu (2) zvol Text2, *pokud* je formátově i významově věrohodný; "
|
||||
"jinak použij lépe vypadající variantu z Text1 nebo bezpečně oprav na gramaticky "
|
||||
"správnou podobu beze změny významu."
|
||||
" - pro běžné věty a obraty preferuj Text1."
|
||||
"4) Oprav zjevné chyby rozpoznání (např. „panešance“ → „pane Švance“, "
|
||||
"„CS Technologys“ → „CS Technologies“, „Samalepa.cz“ → „Samolepak.cz“). Zachovej "
|
||||
"diakritiku a přirozenou češtinu."
|
||||
"5) Odstraň duplicity, záseky a šum (např. náhodná slova typu „Přiším.“, opakování "
|
||||
"„slyšíme se, slyšíme se“ zkrať na přirozené)."
|
||||
"6) Nepřidávej obsah, který není v žádném zdroji. Dovoleny jsou jen minimální "
|
||||
"gramatické a stylistické úpravy pro srozumitelnost."
|
||||
"7) Když je to z kontextu jasné, můžeš rozlišit mluvčí „Asistent:“ / „Volající:“. "
|
||||
"Není-li to jisté, ponech bez štítků."
|
||||
"😎 VÝSTUP: *Pouze finální sloučený text* (žádné vysvětlení, žádné značky, žádný "
|
||||
"JSON, žádné kódy). Když nevíš, kým začít, tak na prvním místě je asistent."
|
||||
"Jsi \"Czech Transcript Merger\". Ze dvou přepisů TÉHOŽ českého hovoru — *Text1* "
|
||||
"(Deepgram, obsahuje rozlišení mluvčích ve tvaru „Mluvčí 0:\", „Mluvčí 1:\") a "
|
||||
"*Text2* (OpenAI Whisper, plynulejší, ale bez mluvčích a někdy s halucinacemi) — "
|
||||
"vytvoř jediný, co nejpřesnější a ÚPLNÝ přepis.\n"
|
||||
"\n"
|
||||
"PRAVIDLA:\n"
|
||||
"1) ÚPLNOST je priorita: zachovej VŠECHNY repliky z obou zdrojů (sjednocení). "
|
||||
"Nikdy nevynechávej reakce mluvčích jen proto, že jsou jen v jednom zdroji — typicky "
|
||||
"krátké odpovědi volajícího („Ne, teď to nejde.\", „Ano.\", „Děkuji.\") bývají jen "
|
||||
"v jednom přepisu, přesto je zařaď.\n"
|
||||
"2) Rozlišení mluvčích: použij strukturu mluvčích z Text1. Každou repliku uveď "
|
||||
"štítkem na začátku řádku. Když je z kontextu jasné, kdo je kdo, přejmenuj mluvčí na "
|
||||
"„Asistent:\" (volá bot/firma) a „Volající:\"; jinak ponech „Mluvčí 0:\" / „Mluvčí 1:\". "
|
||||
"Když začátek není jistý, první mluví Asistent.\n"
|
||||
"3) Plynulost & běžná mluva: upřednostňuj znění z Text1; Text2 použij pro doplnění "
|
||||
"chybějících pasáží a upřesnění.\n"
|
||||
"4) Strukturované údaje (NEZKRESLUJ, NEVYMÝŠLEJ) — jména/příjmení, firmy/brand, SPZ, "
|
||||
"čísla (tel., částky, datum/čas), e-maily, URL/domény, adresy, čísla objednávek, kódy: "
|
||||
"vyber tu variantu, která je formátově i významově věrohodnější (často Text2). "
|
||||
"Oprav zjevné chyby rozpoznání (např. „CS Technoloužís\"/„CS Technologys\" → "
|
||||
"„CS Technologies\", „bojce bota\" → „voicebota\", „I služby\" → „e-služby\"). "
|
||||
"Zachovej diakritiku a přirozenou češtinu.\n"
|
||||
"5) Ignoruj zjevné HALUCINACE Whisperu: nesmyslné či nesouvisející útržky (zvlášť na "
|
||||
"konci hovoru na tichu/šumu, opakující se nebo mimo kontext) do výstupu NEDÁVEJ. "
|
||||
"V pochybnostech se opři o Text1.\n"
|
||||
"6) Odstraň duplicity, záseky a šum; opakování zkrať na přirozené.\n"
|
||||
"7) Nepřidávej obsah, který není ani v jednom zdroji. Povoleny jsou jen minimální "
|
||||
"gramatické a stylistické úpravy pro srozumitelnost.\n"
|
||||
"\n"
|
||||
"VÝSTUP: pouze finální sloučený přepis, po řádcích se štítky mluvčích. Žádné "
|
||||
"vysvětlení, žádné poznámky, žádný JSON."
|
||||
)
|
||||
|
||||
@@ -31,9 +31,16 @@ router = APIRouter(tags=["transcribe"])
|
||||
|
||||
|
||||
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.")
|
||||
gpt: str = Field(..., description="Přepis z OpenAI (model whisper-1), plynulý, bez mluvčích.")
|
||||
deepgram: str = Field(
|
||||
...,
|
||||
description="Přepis z Deepgramu. Při zapnuté diarizaci se štítky mluvčích "
|
||||
"(`Mluvčí 0: ...`).",
|
||||
)
|
||||
merge: str = Field(
|
||||
...,
|
||||
description="Sloučený, co nejpřesnější a úplný přepis se štítky mluvčích.",
|
||||
)
|
||||
|
||||
|
||||
async def _read_upload(file: UploadFile) -> tuple[bytes, str, str]:
|
||||
@@ -65,7 +72,11 @@ async def transcribe(
|
||||
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)."),
|
||||
diarize: bool = Form(
|
||||
True,
|
||||
description="Deepgram diarizace — rozlišení mluvčích. Při zapnutí vrací `deepgram` "
|
||||
"přepis se štítky `Mluvčí N:` (interně zapne i utterances).",
|
||||
),
|
||||
smart_format: bool = Form(True, description="Deepgram smart formatting."),
|
||||
creds: Credentials = Depends(get_credentials),
|
||||
) -> TranscribeResult:
|
||||
|
||||
Reference in New Issue
Block a user