Add contact name filter to SMS search tool + misc improvements

- sms search tool: accept contact name, trim/validate, skip when
  contact_id is set, pass to API client
- sms_client: new contact field in SmsSearchParams, URL-encode on wire
- Tool description clarifies contact_id takes precedence when both given
- Add parse_title_body helper for LLM response parsing
- llamacpp backend improvements
This commit is contained in:
Cameron Cordes committed 2026-05-25 21:46:18 -04:00
1 parent b9175e2718
commit 0a627f4880
4 files changed
+165 -67

No files matched your search

+46
View File
@@ -354,6 +354,8 @@ impl LlamaCppClient {
.and_then(|v| v.as_i64())
.map(|n| n as i32);
log_timings(&parsed, prompt_tokens, completion_tokens);
Ok((chat_msg, prompt_tokens, completion_tokens))
}
}
@@ -456,6 +458,7 @@ impl LlmClient for LlamaCppClient {
let mut role = "assistant".to_string();
let mut prompt_tokens: Option<i32> = None;
let mut completion_tokens: Option<i32> = None;
let mut last_frame: Option<Value> = None;
let mut done_seen = false;
while let Some(chunk) = byte_stream.next().await {
@@ -505,6 +508,7 @@ impl LlmClient for LlamaCppClient {
.get("completion_tokens")
.and_then(|n| n.as_i64())
.map(|n| n as i32);
last_frame = Some(v.clone());
}
let Some(choices) = v.get("choices").and_then(|c| c.as_array())
@@ -587,6 +591,10 @@ impl LlmClient for LlamaCppClient {
Some(v)
};
if let Some(ref frame) = last_frame {
log_timings(frame, prompt_tokens, completion_tokens);
}
let message = ChatMessage {
role,
content: accumulated_content,
@@ -720,6 +728,44 @@ impl LlamaCppClient {
/// Extract a diagnostic fragment from a llama-swap / llama-server response
/// that doesn't match the expected `{choices: [...]}` shape. llama-server
/// returns errors as `{"error": {"message": "...", "code": N, "type": "..."}}`;
fn log_timings(parsed: &Value, prompt_tokens: Option<i32>, completion_tokens: Option<i32>) {
let timings = match parsed.get("timings") {
Some(t) => t,
None => return,
};
let prompt_tps = timings.get("prompt_per_second").and_then(|v| v.as_f64());
let gen_tps = timings.get("predicted_per_second").and_then(|v| v.as_f64());
let prompt_ms = timings.get("prompt_ms").and_then(|v| v.as_f64());
let gen_ms = timings.get("predicted_ms").and_then(|v| v.as_f64());
let mut parts: Vec<String> = Vec::new();
if let Some(c) = prompt_tokens {
let mut s = format!("prompt={} tok", c);
if let Some(ms) = prompt_ms {
s.push_str(&format!(" ({:.0} ms", ms));
if let Some(tps) = prompt_tps {
s.push_str(&format!(", {:.1} tok/s", tps));
}
s.push(')');
}
parts.push(s);
}
if let Some(c) = completion_tokens {
let mut s = format!("gen={} tok", c);
if let Some(ms) = gen_ms {
s.push_str(&format!(" ({:.0} ms", ms));
if let Some(tps) = gen_tps {
s.push_str(&format!(", {:.1} tok/s", tps));
}
s.push(')');
}
parts.push(s);
}
if !parts.is_empty() {
log::info!("llama-swap chat metrics — {}", parts.join(", "));
}
}
/// llama-swap itself sometimes wraps subprocess failures with its own
/// `{"error": "..."}` flat shape. Surface either when present, otherwise fall
/// back to a truncated raw-JSON view.