421fbb3e46
The system prompt for the medical assistant LLM has been updated to improve clarity and explicitly state the desired formatting for corrections and uncertainties. This includes: - Consolidating similar correction examples. - Specifying that only "==text==" annotations are allowed for corrections and uncertainties. - Explicitly disallowing other annotation formats like "(unsicher)" or "[TODO]".
98 lines
4.0 KiB
Rust
98 lines
4.0 KiB
Rust
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.\n
|
|
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\n
|
|
WICHTIG: Das Diktat wurde automatisch transkribiert und enthält typische ASR-Fehler wie phonetisch ähnliche Verwechslungen und zerschnittene Komposita (z.B. \"Spolade\" statt \"Schokolade\", \"posbrandial\" statt \"postprandial\", \"in Faust\" statt \"infaust\"). Korrigiere diese Fehler AKTIV. Bei Unsicherheit, wie die Korrektur lauten müsste: behalte das Original bei.\n\n
|
|
MARKIERUNGEN IM FORMAT \"==text==\" signalisieren dem Arzt \"bitte prüfen\".\n
|
|
Markiere jede korrigierte oder zweifelhafte Stelle.\n
|
|
Markiere zusätzlich: Zahlen ohne klaren Kontext oder Einheit.\n
|
|
Markiere sparsam — geläufige, klar gesprochene Fachbegriffe bleiben unmarkiert.\n
|
|
WICHTIG: Es sind keine anderen Arten von Annotationen erlaubt (z.B. \"(unsicher)\", \"[TODO]\").";
|
|
|
|
/// Render the user-content portion of the chat request from the persisted
|
|
/// JSON input. Recordings with blank text are skipped — they carry no signal
|
|
/// for the LLM and only add noise. Chronological order is taken as given;
|
|
/// callers must sort `recordings` before passing in (the handler does).
|
|
pub fn render_prompt(input: &AnalysisInput) -> String {
|
|
let mut out = String::new();
|
|
let mut first = true;
|
|
for r in &input.recordings {
|
|
if r.text.trim().is_empty() {
|
|
continue;
|
|
}
|
|
if !first {
|
|
out.push_str("\n\n---\n\n");
|
|
}
|
|
first = false;
|
|
out.push_str("## ");
|
|
out.push_str(&r.recorded_at);
|
|
out.push_str("\n\n");
|
|
out.push_str(r.text.trim());
|
|
}
|
|
out
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::analyze::RecordingInput;
|
|
|
|
fn mk(input: Vec<(&str, &str)>) -> AnalysisInput {
|
|
AnalysisInput {
|
|
last_recording_mtime: "2026-04-15T10:47:00Z".into(),
|
|
recordings: input
|
|
.into_iter()
|
|
.map(|(ts, text)| RecordingInput {
|
|
recorded_at: ts.into(),
|
|
text: text.into(),
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn renders_single_recording() {
|
|
let out = render_prompt(&mk(vec![(
|
|
"2026-04-15T10:32:00Z",
|
|
"Patient klagt über Knie.",
|
|
)]));
|
|
assert_eq!(out, "## 2026-04-15T10:32:00Z\n\nPatient klagt über Knie.");
|
|
}
|
|
|
|
#[test]
|
|
fn renders_multiple_with_separator() {
|
|
let out = render_prompt(&mk(vec![
|
|
("2026-04-15T10:32:00Z", "Erste Aufnahme."),
|
|
("2026-04-15T10:38:15Z", "Zweite Aufnahme."),
|
|
]));
|
|
assert!(out.contains("\n\n---\n\n"));
|
|
assert!(out.starts_with("## 2026-04-15T10:32:00Z"));
|
|
assert!(out.contains("## 2026-04-15T10:38:15Z"));
|
|
}
|
|
|
|
#[test]
|
|
fn skips_blank_recordings() {
|
|
let out = render_prompt(&mk(vec![
|
|
("2026-04-15T10:32:00Z", "Nur Inhalt."),
|
|
("2026-04-15T10:33:00Z", " "),
|
|
("2026-04-15T10:34:00Z", ""),
|
|
]));
|
|
assert_eq!(out.matches("---").count(), 0);
|
|
assert!(!out.contains("10:33:00"));
|
|
assert!(!out.contains("10:34:00"));
|
|
}
|
|
|
|
#[test]
|
|
fn handles_empty_input() {
|
|
assert_eq!(render_prompt(&mk(vec![])), "");
|
|
}
|
|
|
|
#[test]
|
|
fn trims_per_recording_text() {
|
|
let out = render_prompt(&mk(vec![("2026-04-15T10:32:00Z", " hallo\n\n")]));
|
|
assert!(out.ends_with("hallo"));
|
|
}
|
|
}
|