ai: fix UTF-8 byte-slice panics in insight_generator log/truncation paths

Switch four `&s[..N]` / `&s[..s.len().min(N)]` sites to
`chars().take(N).collect::<String>()` so truncation lands on character
boundaries instead of mid-codepoint. The agentic summary preview log
was panicking when generated content hit an em-dash at byte 200; the
few-shot passage cap, brief_json_args debug formatter, and a test
assertion message had the same latent bug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cameron
2026-05-15 15:10:02 -04:00
parent 8503ef7884
commit c30cadde02

View File

@@ -1749,8 +1749,8 @@ Return ONLY the summary, nothing else."#,
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, c)| { .map(|(i, c)| {
let trimmed = if c.len() > 1000 { let trimmed = if c.chars().count() > 1000 {
format!("{}", &c[..1000]) format!("{}", c.chars().take(1000).collect::<String>())
} else { } else {
c.clone() c.clone()
}; };
@@ -3406,8 +3406,8 @@ Return ONLY the summary, nothing else."#,
obj.iter() obj.iter()
.map(|(k, v)| { .map(|(k, v)| {
let rendered = match v { let rendered = match v {
serde_json::Value::String(s) if s.len() > 40 => { serde_json::Value::String(s) if s.chars().count() > 40 => {
format!("\"{}...\"", &s[..40]) format!("\"{}...\"", s.chars().take(40).collect::<String>())
} }
_ => v.to_string(), _ => v.to_string(),
}; };
@@ -4088,10 +4088,11 @@ Return ONLY the summary, nothing else."#,
let title = title_raw.trim().trim_matches('"').to_string(); let title = title_raw.trim().trim_matches('"').to_string();
log::info!("Agentic generated title: {}", title); log::info!("Agentic generated title: {}", title);
let summary_preview: String = final_content.chars().take(200).collect();
log::info!( log::info!(
"Agentic generated summary ({} chars): {}", "Agentic generated summary ({} chars): {}",
final_content.len(), final_content.len(),
&final_content[..final_content.len().min(200)] summary_preview
); );
// 14. Serialize the full message history for training data // 14. Serialize the full message history for training data
@@ -4671,7 +4672,7 @@ mod tests {
assert!( assert!(
out.starts_with("You are a journal writer in first person, warm and reflective."), out.starts_with("You are a journal writer in first person, warm and reflective."),
"custom prompt must lead the system content; got: {}", "custom prompt must lead the system content; got: {}",
&out[..out.len().min(200)], out.chars().take(200).collect::<String>(),
); );
assert!( assert!(
!out.contains("personal photo memory assistant"), !out.contains("personal photo memory assistant"),