strip_markdown_wrapper robustimmaksi: whitelist-kielitunnisteet + tarkempi ```-poisto

Edelliset heuristiikat olivat hauraita:
- Kielitunniste tunnistettiin "lyhyt alphanumeerinen rivi" → osui koodiin (i, 42)
- rfind("```") poisti koodin sisäisiä backtickejä

Korjaukset:
- Kielitunniste poistetaan VAIN jos se on tunnettu (LANG_TAGS whitelist, 50+ kieltä)
- Sulkeva ``` poistetaan VAIN jos se on omalla rivillään tiedoston lopussa
  (ends_with tarkistus + edeltävä rivinvaihto)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaakko Vanhala
2026-04-05 10:10:48 +03:00
parent 7a1352ead7
commit 7eca426e77
2 changed files with 44 additions and 27 deletions

View File

@@ -234,27 +234,37 @@ impl LlmEngine {
}
}
const LANG_TAGS: &[&str] = &[
"python", "py", "rust", "rs", "javascript", "js", "typescript", "ts",
"java", "kotlin", "scala", "go", "ruby", "rb", "php", "swift",
"c", "cpp", "c++", "c#", "csharp", "r", "sql", "bash", "sh", "zsh",
"html", "css", "json", "yaml", "yml", "toml", "xml", "markdown", "md",
"lua", "perl", "dart", "elixir", "haskell", "hs", "ocaml", "zig",
"plaintext", "text", "txt",
];
/// Siivoa mallin tuottama vastaus (prefill-yhteensopiva).
fn strip_markdown_wrapper(text: &str) -> String {
let mut result = text.trim().to_string();
// Poistetaan kielitunniste ensimmäiseltä riviltä
// 1. Kielitunniste — VAIN tunnettu kieli
if let Some(nl) = result.find('\n') {
let first = result[..nl].trim();
let is_lang = !first.is_empty()
&& first.len() <= 20
&& first.chars().all(|c| c.is_alphanumeric() || c == '+' || c == '#');
if is_lang { result = result[nl + 1..].to_string(); }
}
// Poistetaan sulkeva ``` lopusta
if let Some(pos) = result.rfind("```") {
if result[pos + 3..].trim().is_empty() {
result = result[..pos].to_string();
let first = result[..nl].trim().to_lowercase();
if LANG_TAGS.contains(&first.as_str()) {
result = result[nl + 1..].to_string();
}
}
// Poistetaan johdantolauseet
// 2. Sulkeva ``` — VAIN omalla rivillään lopussa
let trimmed = result.trim_end();
if trimmed.ends_with("```") {
let before = &trimmed[..trimmed.len() - 3];
if before.is_empty() || before.ends_with('\n') {
result = before.trim_end().to_string();
}
}
// 3. Johdantolauseet
let lower = result.trim().to_lowercase();
for prefix in &["sure!", "here is", "here's", "certainly!", "below is"] {
if lower.starts_with(prefix) {
@@ -263,7 +273,7 @@ fn strip_markdown_wrapper(text: &str) -> String {
}
}
// Poistetaan selityskommentit alusta
// 4. Selityskommentit alusta
let mut lines: Vec<&str> = result.trim().lines().collect();
while !lines.is_empty() {
let first = lines[0].trim();