Merge feat/297-cheaper-ai into feat/299-correlation-simplify
Resolve upload.py (no-scene fallback uses the new _correlate with #298's non_event_chatter reason) and chatter_classifier docstring (gate built, off by default). Fold intelligence._llm_bool into incident_correlator.parse_llm_bool (#88). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,8 +28,39 @@ class Settings(BaseSettings):
|
||||
|
||||
# OpenAI (STT + intelligence)
|
||||
openai_api_key: Optional[str] = None
|
||||
# The OpenAI transcription model. Used when OpenAI is the STT provider AND
|
||||
# as the fallback when another provider fails (server-26#297).
|
||||
stt_model: str = "whisper-1" # whisper-1 | gpt-4o-mini-transcribe | gpt-4o-transcribe
|
||||
|
||||
# ── STT provider (server-26#297) ──────────────────────────────────────────
|
||||
# Any OpenAI-compatible /audio/transcriptions endpoint. Groq serves
|
||||
# whisper-large-v3-turbo at $0.04/audio-hour against whisper-1's $0.36, same
|
||||
# API (min billed length 10s per request, prompt capped at 224 tokens --
|
||||
# console.groq.com/docs/speech-to-text, checked 2026-10-27).
|
||||
# auto -- groq when GROQ_API_KEY is set, compat when STT_BASE_URL is
|
||||
# set, else openai. The default, so a deploy without either
|
||||
# behaves exactly as before.
|
||||
# openai -- OpenAI only (stt_model).
|
||||
# groq -- Groq (stt_compat_model), OpenAI fallback.
|
||||
# compat -- any other OpenAI-compatible server: stt_base_url + stt_api_key
|
||||
# + stt_compat_model (e.g. a self-hosted faster-whisper, #296).
|
||||
stt_provider: str = "auto"
|
||||
stt_base_url: Optional[str] = None # None -> the provider's own default
|
||||
stt_api_key: Optional[str] = None # None -> GROQ_API_KEY / OPENAI_API_KEY
|
||||
groq_api_key: Optional[str] = None
|
||||
stt_compat_model: str = "whisper-large-v3-turbo" # model on a non-OpenAI provider
|
||||
# Retry a failed non-OpenAI request once on OpenAI stt_model. Needs
|
||||
# OPENAI_API_KEY; without it a primary failure just means no transcript.
|
||||
stt_fallback_to_openai: bool = True
|
||||
# Domain vocabulary in the STT prompt (area places, local_knowledge terms,
|
||||
# vocabulary). Applied to the non-OpenAI provider only: whisper-1 echoed
|
||||
# vocabulary into silence before (see transcription.py), so the OpenAI
|
||||
# path -- including the fallback -- keeps today's fixed prompt.
|
||||
stt_vocab_prompt_enabled: bool = True
|
||||
# Whisper keeps only the LAST 224 prompt tokens and silently drops the
|
||||
# rest. The prompt is built against this estimated budget, with headroom.
|
||||
stt_prompt_max_tokens: int = 200
|
||||
|
||||
# AI request timeouts (server-26#153). Without these the OpenAI SDK waits
|
||||
# up to 600s per attempt and retries twice, and the google-genai client
|
||||
# waits forever, so a hung provider call had no upper bound at all — and
|
||||
@@ -48,8 +79,31 @@ class Settings(BaseSettings):
|
||||
# google-genai does not retry unless told to, so this is the whole bound.
|
||||
gemini_timeout_seconds: float = 60.0
|
||||
|
||||
# Scene extraction + embeddings (server-26#298). Were literals in
|
||||
# intelligence.py; any OpenAI chat model that supports JSON mode works.
|
||||
extraction_model: str = "gpt-4o-mini"
|
||||
embedding_model: str = "text-embedding-3-small"
|
||||
# Skip correction + extraction (+ embedding, geocoding) for transcripts
|
||||
# chatter_classifier.gate_chatter calls radio housekeeping. OFF by default:
|
||||
# a gated call carries no units, so it is thin and takes the no-fit-check
|
||||
# thin attach (mechanism A) instead of the fit-tested path or the #115
|
||||
# orphan gate, and server-26#127 set "0 false positives in a live shadow
|
||||
# window" as the bar for going live. Read chatter_classifier_verdict off a
|
||||
# shadow window first, then set CHATTER_GATE_ENABLED=true.
|
||||
chatter_gate_enabled: bool = False
|
||||
|
||||
# Google Maps (geocoding)
|
||||
google_maps_api_key: Optional[str] = None
|
||||
# In-process cache of Geocoding API answers (server-26#298). Dispatch
|
||||
# repeats the same address on every follow-up call of a job, and the
|
||||
# place verifier re-geocodes the same nouns. In-process rather than
|
||||
# Firestore: a Firestore read costs about what it saves, adds latency on
|
||||
# every miss, and would persist Maps content -- the Maps terms allow
|
||||
# caching lat/lng for at most 30 days, and an in-memory TTL can never
|
||||
# outlive that. Lost on restart, which only costs a few repeat lookups.
|
||||
geocode_cache_ttl_seconds: int = 24 * 60 * 60
|
||||
geocode_cache_negative_ttl_seconds: int = 60 * 60 # ZERO_RESULTS etc.
|
||||
geocode_cache_max_entries: int = 5000
|
||||
# 511NY developer key (server-26#183/#198). Issued with the NYSDOT Developer
|
||||
# Access Agreement grant; the keyless legacy API is gone since the 511x cutover.
|
||||
ny511_api_key: Optional[str] = None
|
||||
@@ -105,6 +159,30 @@ class Settings(BaseSettings):
|
||||
# else's PII. Both gates must be open before any snippet leaves.
|
||||
alert_transcript_snippet_enabled: bool = False
|
||||
place_verify_max_per_call: int = 3
|
||||
# ── Alert confidence gate (server-26#300) ─────────────────────────────────
|
||||
# Nothing in the pipeline emits a confidence, so the alerter derives one:
|
||||
# how the rule matched (talkgroup 1.0 / extracted tag 0.8 / raw transcript
|
||||
# text only 0.5 -- the CrimeRadar "fire alarm" -> "firearm" failure was a
|
||||
# transcript match) scaled down when Whisper's own per-token probability
|
||||
# (calls.stt_confidence) is below alert_stt_reference. Below the floor ->
|
||||
# held, never pushed. High-severity matches also need the incident to
|
||||
# have alert_corroboration_min_calls calls, or a confidence at or above
|
||||
# alert_high_severity_min_confidence; held ones are released when a
|
||||
# later call corroborates the incident. Starting values, not measured.
|
||||
alert_min_confidence: float = 0.5
|
||||
alert_high_severity_min_confidence: float = 0.85
|
||||
alert_corroboration_min_calls: int = 2
|
||||
# Low on purpose: exp(avg_logprob) on vocoded radio audio is often well
|
||||
# under 0.5 for speech that is right, and Groq/OpenAI logprobs are not on
|
||||
# a shared scale. Only clearly garbled audio should pull the score down.
|
||||
alert_stt_reference: float = 0.3
|
||||
alert_high_severity_terms: list[str] = [
|
||||
"shots-fired", "shots fired", "shooting", "shot", "gun", "firearm", "weapon",
|
||||
"stabbing", "stabbed", "officer-down", "officer down", "10-13",
|
||||
"officer-needs-assistance", "active-shooter", "hostage", "barricade",
|
||||
"explosion", "structure-fire", "structure fire", "working-fire",
|
||||
"entrapment", "cardiac-arrest", "vehicle-pursuit", "pursuit",
|
||||
]
|
||||
# How close a candidate has to sound before it may rewrite a transcript.
|
||||
# Below this, Places Text Search will confidently hand back the nearest
|
||||
# business for any garbage string.
|
||||
|
||||
Reference in New Issue
Block a user