From 7eca426e779fcd340ba5fed171a0659e35759d9c Mon Sep 17 00:00:00 2001 From: Jaakko Vanhala Date: Sun, 5 Apr 2026 10:10:48 +0300 Subject: [PATCH] strip_markdown_wrapper robustimmaksi: whitelist-kielitunnisteet + tarkempi ```-poisto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- network-poc/native-node/src/inference.rs | 38 +++++++++++++++--------- network-poc/node/src/qwen_coder.rs | 33 ++++++++++++-------- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/network-poc/native-node/src/inference.rs b/network-poc/native-node/src/inference.rs index 5ef2599..d2ea366 100644 --- a/network-poc/native-node/src/inference.rs +++ b/network-poc/native-node/src/inference.rs @@ -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(); diff --git a/network-poc/node/src/qwen_coder.rs b/network-poc/node/src/qwen_coder.rs index 61d3f05..579a0bb 100644 --- a/network-poc/node/src/qwen_coder.rs +++ b/network-poc/node/src/qwen_coder.rs @@ -27,30 +27,37 @@ struct CachedModel { is_3b: bool, } +/// Tunnetut kielitunnisteet joita malli voi tuottaa prefill-backtickien jälkeen. +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-tekniikan vuoksi malli tuottaa: "rust\nfn main() {...}\n```" /// eli kielitunniste alussa + sulkeva ``` lopussa. Molemmat poistetaan. fn strip_markdown_wrapper(text: &str) -> String { let mut result = text.trim().to_string(); - // 1. Poistetaan kielitunniste ensimmäiseltä riviltä (rust, python, javascript jne.) - // Tunnistetaan: rivi jossa vain yksi lyhyt sana ilman välilyöntejä tai erikoismerkkejä + // 1. Poistetaan kielitunniste ensimmäiseltä riviltä — VAIN jos se on tunnettu kieli if let Some(first_newline) = result.find('\n') { - let first_line = result[..first_newline].trim(); - let is_lang_tag = !first_line.is_empty() - && first_line.len() <= 20 - && first_line.chars().all(|c| c.is_alphanumeric() || c == '+' || c == '#'); - if is_lang_tag { + let first_line = result[..first_newline].trim().to_lowercase(); + if LANG_TAGS.contains(&first_line.as_str()) { result = result[first_newline + 1..].to_string(); } } - // 2. Poistetaan sulkeva ``` lopusta - if let Some(pos) = result.rfind("```") { - // Varmistetaan ettei poisteta koodin sisällä olevaa ``` - let after = result[pos + 3..].trim(); - if after.is_empty() { - result = result[..pos].to_string(); + // 2. Poistetaan sulkeva ``` VAIN jos se on omalla rivillään lopussa + let trimmed = result.trim_end(); + if trimmed.ends_with("```") { + let before = &trimmed[..trimmed.len() - 3]; + // Varmistetaan: edellinen merkki on rivinvaihto tai alku (eli ``` on oma rivinsä) + if before.is_empty() || before.ends_with('\n') { + result = before.trim_end().to_string(); } }