feat: Add gazetteer annotation to user prompt

This commit introduces functionality to annotate the user prompt with
potential proper name corrections from a gazetteer. The LLM will then
use these annotations to improve the accuracy of transcriptions,
especially for medical terms and names.

The `SYSTEM_PROMPT` has also been updated to inform the LLM about these
new annotations and how to handle them.
This commit is contained in:
2026-04-16 17:28:54 +02:00
parent 5717b8f718
commit e16cb988ed
2 changed files with 10 additions and 7 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ use super::AnalysisInput;
/// System prompt for the consolidation LLM. From docs/projektplan.md:365-369.
/// Temperature 0 is set on the request; the prompt enforces "no interpretation".
pub const SYSTEM_PROMPT: &str = "Du bist ein medizinischer Assistent. Fasse die folgenden diktierten Abschnitte zu einem zusammenhängenden Text zusammen. Bereinigen und strukturieren, nichts hinzufügen, keine medizinische Interpretation, keine Diagnose, keine Arztbrief-Struktur. Entferne Redundanzen. Spätere Aufnahmen haben Vorrang — Korrekturen, Nachträge und Widersprüche zugunsten der chronologisch letzten Aussage auflösen. Antworte auf Deutsch. Nur der zusammengefasste Text, keine Einleitung, kein Abschlusssatz.\n\nWICHTIG: Das Diktat wurde automatisch transkribiert und enthält typische ASR-Fehler — phonetisch ähnliche Verwechslungen (z.B. \"Corona\" statt \"Koronar\", \"Spolade\" statt \"Schokolade\", \"hochgradig in Faust\" statt \"hochgradig infaust\"), falsch geschriebene Medikamentennamen, zerschnittene Komposita. Korrigiere solche Fehler AKTIV: der Arzt hat das Original-Audio zur Verifikation und kann jede Korrektur gegenprüfen. Bei sicherer Korrektur: schreibe das korrigierte Wort. Bei Unsicherheit, wie die Korrektur lauten müsste: behalte das Original bei.\n\nMarkiere jede korrigierte oder zweifelhafte Stelle mit ==text== — das signalisiert dem Arzt \"bitte prüfen\". Markiere zusätzlich: unvollständige oder unsichere Angaben im Diktat (z.B. \"Dosis weiß ich nicht\", abgebrochene Sätze) sowie Zahlen ohne klaren Kontext oder Einheit. Sei sparsam — geläufige, klar gesprochene Fachbegriffe bleiben unmarkiert.";
pub const SYSTEM_PROMPT: &str = "Du bist ein medizinischer Assistent. Fasse die folgenden diktierten Abschnitte zu einem zusammenhängenden Text zusammen. Bereinigen und strukturieren, nichts hinzufügen, keine medizinische Interpretation, keine Diagnose, keine Arztbrief-Struktur. Entferne Redundanzen. Spätere Aufnahmen haben Vorrang — Korrekturen, Nachträge und Widersprüche zugunsten der chronologisch letzten Aussage auflösen. Antworte auf Deutsch. Nur der zusammengefasste Text, keine Einleitung, kein Abschlusssatz.\n\nWICHTIG: Das Diktat wurde automatisch transkribiert und enthält typische ASR-Fehler — phonetisch ähnliche Verwechslungen (z.B. \"Corona\" statt \"Koronar\", \"Spolade\" statt \"Schokolade\", \"hochgradig in Faust\" statt \"hochgradig infaust\"), falsch geschriebene Medikamentennamen, zerschnittene Komposita. Korrigiere solche Fehler AKTIV: der Arzt hat das Original-Audio zur Verifikation und kann jede Korrektur gegenprüfen. Bei sicherer Korrektur: schreibe das korrigierte Wort. Bei Unsicherheit, wie die Korrektur lauten müsste: behalte das Original bei.\n\nMarkiere jede korrigierte oder zweifelhafte Stelle mit ==text== — das signalisiert dem Arzt \"bitte prüfen\". Markiere zusätzlich: unvollständige oder unsichere Angaben im Diktat (z.B. \"Dosis weiß ich nicht\", abgebrochene Sätze) sowie Zahlen ohne klaren Kontext oder Einheit. Sei sparsam — geläufige, klar gesprochene Fachbegriffe bleiben unmarkiert.\n\nDer Text kann Hinweise der Form `Wort [?Kandidat]` enthalten — das sind automatische Vorschläge aus einem Fach-Wörterbuch (Medikamente, Anatomie, Wirkstoffe). Prüfe mit dem Satzkontext: passt der Kandidat, verwende ihn und markiere mit ==. Passt er nicht, behalte das Original-Wort unverändert und ohne Markierung. Die eckige-Klammer-Annotation selbst darf niemals im Output erscheinen.";
/// Render the user-content portion of the chat request from the persisted
/// JSON input. Recordings with blank text are skipped — they carry no signal
+9 -6
View File
@@ -13,25 +13,22 @@ use crate::{BusyGuard, WorkerBusy};
/// Consume analyze jobs sequentially. The external LLM tolerates parallelism,
/// but sequential processing keeps the architecture simple and shields the
/// provider from bursts. Trivial to lift later via `tokio::spawn(process(..))`.
///
/// `_vocab` is the gazetteer for proper-name correction. Currently unused;
/// wired into `process()` in a follow-up step.
pub async fn run(
mut rx: AnalyzeReceiver,
config: Arc<Config>,
client: reqwest::Client,
worker_busy: WorkerBusy,
_vocab: Arc<Gazetteer>,
vocab: Arc<Gazetteer>,
) {
info!(
vocab_entries = _vocab.len(),
vocab_entries = vocab.len(),
"Analyze worker started"
);
let timeout = Duration::from_secs(config.llm_timeout_seconds);
while let Some(job) = rx.recv().await {
let _guard = BusyGuard::new(worker_busy.clone());
process(&job.case_dir, &config, &client, timeout).await;
process(&job.case_dir, &config, &client, &vocab, timeout).await;
}
warn!("Analyze worker stopped (channel closed)");
@@ -41,6 +38,7 @@ async fn process(
case_dir: &Path,
config: &Config,
client: &reqwest::Client,
vocab: &Gazetteer,
timeout: Duration,
) {
let input_path = case_dir.join(ANALYSIS_INPUT_FILE);
@@ -80,7 +78,12 @@ async fn process(
return;
}
// Render the raw transcript bundle, then inject gazetteer hints for
// tokens that phonetically resemble known proper names. The LLM sees
// annotations of the form `Token [?Candidate]` and decides in context
// whether to adopt them.
let user_content = prompt::render_prompt(&input);
let user_content = vocab.annotate(&user_content);
let total_chars = user_content.chars().count();
info!(
case = %case_dir.display(),