Transcription: config-driven OpenAI-compatible STT with OpenAI fallback (server-26#297, server-26#83)
STT_PROVIDER=auto picks Groq whisper-large-v3-turbo only when GROQ_API_KEY is set (else OpenAI, unchanged); compat covers any OpenAI-compatible server via STT_BASE_URL/STT_API_KEY. A failed primary falls back to OpenAI STT_MODEL on the same downloaded audio; the fallback reports to its own ai_health tier so a dead primary stays red. The non-OpenAI provider gets a token-budgeted glossary prompt (area places, local_knowledge, vocabulary; never ten-codes), guarded by a narrow prompt-echo check. A per-30s-window zlib compression check (Whisper's 2.4) adds a noise gate that survives correction being off (#83, partial). Call docs record stt_provider/model/audio_seconds/confidence. The sweep hold-back counts the fallback attempt. gate_chatter added to chatter_classifier (used by transcription to skip correction when CHATTER_GATE_ENABLED). GROQ_API_KEY templated with default ''. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7ecfca7648
commit
d485eae41a
@@ -14,13 +14,17 @@ This module is that classifier. It is a PURE function of the transcript text —
|
||||
no Firestore, no LLM call, no side effects — so it is cheap to run on every
|
||||
transcript and cheap to test against real dumps offline.
|
||||
|
||||
SHADOW MODE ONLY. As of this module's introduction, nothing skips scene
|
||||
extraction based on this verdict. `intelligence.extract_scenes` calls
|
||||
`classify_chatter` purely to record the verdict on the call doc
|
||||
(`chatter_classifier_verdict` / `chatter_classifier_reason`) so it becomes
|
||||
observable in the next `/admin` correlation-debug dump, exactly like
|
||||
`corr_gate_veto` (server-26#115 / PR #126). See the TODO at that call site for
|
||||
what has to be true before this flips live.
|
||||
LIVE AS A COST GATE SINCE server-26#298, through `gate_chatter`, not
|
||||
`classify_chatter`. `classify_chatter` is still the shadow verdict recorded on
|
||||
every call doc (`chatter_classifier_verdict` / `chatter_classifier_reason`),
|
||||
unchanged, so the `/admin` tallies keep measuring the same thing. The gate is
|
||||
stricter on the one shape the #127 review flagged as a live false-positive
|
||||
risk: `roll_call` matches "roll call" anywhere, so "Delayed for roll call, then
|
||||
respond to a structure fire at 12 Main Street" is a roll_call verdict. The gate
|
||||
only skips a roll call whose remaining words are callsigns and roll-call
|
||||
phrasing. Gated transcripts skip transcript correction and scene extraction
|
||||
(and with it embedding and geocoding); the free rule paths (clearance parse,
|
||||
self-initiated/plate backstop) still run.
|
||||
|
||||
Precision over recall, deliberately. A false positive here — flagging a REAL
|
||||
event as chatter — would, once live, silently mean that event never gets a
|
||||
@@ -133,3 +137,36 @@ def classify_chatter(transcript: Optional[str]) -> tuple[bool, Optional[str]]:
|
||||
return False, None
|
||||
|
||||
return True, "bare_acknowledgement"
|
||||
|
||||
|
||||
# Words a roll call is made of, beyond the filler/designator/phonetic sets.
|
||||
_ROLL_CALL_WORDS = frozenset({
|
||||
"roll", "call", "all", "cars", "car", "patrol", "patrols", "troop", "staff",
|
||||
"concludes", "time", "came", "because", "volunteer", "nyc", "kl", "henry",
|
||||
"here", "present", "s", "m", "it",
|
||||
})
|
||||
# A roll call may name a place or two ("Post 4, Ossining"); more than this many
|
||||
# leftover words means something else is being said in the same transmission.
|
||||
_ROLL_CALL_MAX_LEFTOVER = 2
|
||||
|
||||
|
||||
def gate_chatter(transcript: Optional[str]) -> tuple[bool, Optional[str]]:
|
||||
"""
|
||||
Whether to skip paid AI for this transcript (server-26#298).
|
||||
|
||||
`classify_chatter`'s verdict, with roll_call narrowed: the transcript must
|
||||
be roll-call content end to end, not a roll call mentioned on the way to
|
||||
something real. Precision over recall still — a false positive here means
|
||||
a real event never gets a scene.
|
||||
"""
|
||||
is_chatter, reason = classify_chatter(transcript)
|
||||
if not is_chatter:
|
||||
return False, None
|
||||
if reason == "roll_call":
|
||||
leftover = [
|
||||
t for t in _tokenize(transcript or "")
|
||||
if not _is_filler_token(t) and t not in _ROLL_CALL_WORDS
|
||||
]
|
||||
if len(leftover) > _ROLL_CALL_MAX_LEFTOVER:
|
||||
return False, None
|
||||
return True, reason
|
||||
|
||||
Reference in New Issue
Block a user