feature/persona-fk-and-guard #90

Merged
cameron merged 3 commits from feature/persona-fk-and-guard into master 2026-05-10 18:42:29 +00:00
Showing only changes of commit b9d9ba0320 - Show all commits

View File

@@ -1558,7 +1558,7 @@ Return ONLY the summary, nothing else."#,
) -> String { ) -> String {
let result = match tool_name { let result = match tool_name {
"search_rag" => self.tool_search_rag(arguments, ollama, cx).await, "search_rag" => self.tool_search_rag(arguments, ollama, cx).await,
"search_messages" => self.tool_search_messages(arguments).await, "search_messages" => self.tool_search_messages(arguments, cx).await,
"get_sms_messages" => self.tool_get_sms_messages(arguments, cx).await, "get_sms_messages" => self.tool_get_sms_messages(arguments, cx).await,
"get_calendar_events" => self.tool_get_calendar_events(arguments, cx).await, "get_calendar_events" => self.tool_get_calendar_events(arguments, cx).await,
"get_location_history" => self.tool_get_location_history(arguments, cx).await, "get_location_history" => self.tool_get_location_history(arguments, cx).await,
@@ -1807,15 +1807,43 @@ Return ONLY the summary, nothing else."#,
/// Tool: search_messages — keyword / semantic / hybrid search over all /// Tool: search_messages — keyword / semantic / hybrid search over all
/// SMS message bodies via the Django FTS5 + embeddings pipeline. Now /// SMS message bodies via the Django FTS5 + embeddings pipeline. Now
/// supports optional `contact_id`, `start_ts`, `end_ts` filters. /// supports optional `contact_id`, `start_ts`, `end_ts` filters.
async fn tool_search_messages(&self, args: &serde_json::Value) -> String { async fn tool_search_messages(&self, args: &serde_json::Value, cx: &opentelemetry::Context) -> String {
let query = match args.get("query").and_then(|v| v.as_str()) { let query = match args.get("query").and_then(|v| v.as_str()) {
Some(q) if !q.trim().is_empty() => q.trim(), Some(q) if !q.trim().is_empty() => q.trim(),
_ => { _ => {
let has_date = args.get("date").is_some() // Common LLM mistake: calling search_messages with
|| args.get("start_ts").is_some() // { date, ... } as if it were date-browsing. The two
|| args.get("end_ts").is_some(); // tools share the "messages" word, and search_messages
let has_contact = args.get("contact").is_some() || args.get("contact_id").is_some(); // sounds like the natural verb. Instead of returning
if has_date || has_contact { // an error and burning a turn, transparently route to
// get_sms_messages when there's a `date` (and a
// contact-name string, optional). The LLM gets real
// data on its first try; the result is logged with a
// routing note so a human reading the trace can see
// what happened.
let has_date_str = args
.get("date")
.and_then(|v| v.as_str())
.map(|s| !s.trim().is_empty())
.unwrap_or(false);
let has_numeric_contact = args.get("contact_id").is_some();
let has_ts_window =
args.get("start_ts").is_some() || args.get("end_ts").is_some();
if has_date_str && !has_numeric_contact && !has_ts_window {
log::info!(
"search_messages with `date` and no `query` — routing to get_sms_messages"
);
let routed = self.tool_get_sms_messages(args, cx).await;
return format!(
"(Note: routed to get_sms_messages — search_messages requires a \
`query`; date-only browsing belongs on get_sms_messages. Prefer \
get_sms_messages directly next time.)\n\n{}",
routed
);
}
let has_contact_name =
args.get("contact").and_then(|v| v.as_str()).is_some();
if has_ts_window || has_numeric_contact || has_contact_name {
return "Error: search_messages needs a 'query' (keywords/phrase). \ return "Error: search_messages needs a 'query' (keywords/phrase). \
To fetch messages around a date or from a contact without keywords, \ To fetch messages around a date or from a contact without keywords, \
call get_sms_messages with { date, contact? } instead." call get_sms_messages with { date, contact? } instead."