feature/insight-chat-improvements #83
@@ -144,10 +144,13 @@ impl InsightGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the per-call tool gate options by probing each backing
|
/// Compute the per-call tool gate options by probing each backing
|
||||||
/// table. Cheap (`SELECT 1 FROM <t> LIMIT 1` shape via the existing
|
/// table for presence. `daily_summaries_present` uses a `LIMIT 1`
|
||||||
/// count methods); meant to be called once per chat turn / generation.
|
/// existence probe; `calendar_present` and `location_history_present`
|
||||||
/// `has_vision` is supplied by the caller because it depends on the
|
/// use the existing `get_event_count` / `get_location_count`
|
||||||
/// model selected for this turn, not on persistent state.
|
/// methods (small enough that a full `COUNT(*)` is fine). Meant to
|
||||||
|
/// be called once per chat turn / generation. `has_vision` is
|
||||||
|
/// supplied by the caller because it depends on the model selected
|
||||||
|
/// for this turn, not on persistent state.
|
||||||
pub fn current_gate_opts(&self, has_vision: bool) -> ToolGateOpts {
|
pub fn current_gate_opts(&self, has_vision: bool) -> ToolGateOpts {
|
||||||
let cx = opentelemetry::Context::new();
|
let cx = opentelemetry::Context::new();
|
||||||
let calendar_present = {
|
let calendar_present = {
|
||||||
@@ -169,9 +172,7 @@ impl InsightGenerator {
|
|||||||
.daily_summary_dao
|
.daily_summary_dao
|
||||||
.lock()
|
.lock()
|
||||||
.expect("Unable to lock DailySummaryDao");
|
.expect("Unable to lock DailySummaryDao");
|
||||||
dao.get_total_summary_count(&cx)
|
dao.has_any_summaries(&cx).unwrap_or(false)
|
||||||
.map(|n| n > 0)
|
|
||||||
.unwrap_or(false)
|
|
||||||
};
|
};
|
||||||
ToolGateOpts {
|
ToolGateOpts {
|
||||||
has_vision,
|
has_vision,
|
||||||
|
|||||||
@@ -261,38 +261,16 @@ impl SmsApiClient {
|
|||||||
/// - "fts5" keyword-only, supports phrase / prefix / boolean / NEAR
|
/// - "fts5" keyword-only, supports phrase / prefix / boolean / NEAR
|
||||||
/// - "semantic" embedding similarity
|
/// - "semantic" embedding similarity
|
||||||
/// - "hybrid" both merged via reciprocal rank fusion (recommended)
|
/// - "hybrid" both merged via reciprocal rank fusion (recommended)
|
||||||
|
///
|
||||||
|
/// Equivalent to `search_messages_with_contact(query, mode, limit, None)`;
|
||||||
|
/// kept as a convenience for callers that don't filter by contact.
|
||||||
pub async fn search_messages(
|
pub async fn search_messages(
|
||||||
&self,
|
&self,
|
||||||
query: &str,
|
query: &str,
|
||||||
mode: &str,
|
mode: &str,
|
||||||
limit: usize,
|
limit: usize,
|
||||||
) -> Result<Vec<SmsSearchHit>> {
|
) -> Result<Vec<SmsSearchHit>> {
|
||||||
let url = format!(
|
self.search_messages_with_contact(query, mode, limit, None).await
|
||||||
"{}/api/messages/search/?q={}&mode={}&limit={}",
|
|
||||||
self.base_url,
|
|
||||||
urlencoding::encode(query),
|
|
||||||
urlencoding::encode(mode),
|
|
||||||
limit
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut request = self.client.get(&url);
|
|
||||||
if let Some(token) = &self.token {
|
|
||||||
request = request.header("Authorization", format!("Bearer {}", token));
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = request.send().await?;
|
|
||||||
if !response.status().is_success() {
|
|
||||||
let status = response.status();
|
|
||||||
let body = response.text().await.unwrap_or_default();
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"SMS search request failed: {} - {}",
|
|
||||||
status,
|
|
||||||
body
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let data: SmsSearchResponse = response.json().await?;
|
|
||||||
Ok(data.results)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same shape as `search_messages` but with optional `contact_id`. The
|
/// Same shape as `search_messages` but with optional `contact_id`. The
|
||||||
|
|||||||
@@ -76,12 +76,13 @@ pub trait DailySummaryDao: Sync + Send {
|
|||||||
contact: &str,
|
contact: &str,
|
||||||
) -> Result<i64, DbError>;
|
) -> Result<i64, DbError>;
|
||||||
|
|
||||||
/// Get total count of all summaries (across all contacts). Used by
|
/// Cheap presence check — returns true iff at least one daily summary row
|
||||||
/// `current_gate_opts` to check whether daily_summaries are present.
|
/// exists. Used by gating logic that only needs "is the table empty?",
|
||||||
fn get_total_summary_count(
|
/// avoiding a `COUNT(*)` full scan on large corpora.
|
||||||
|
fn has_any_summaries(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &opentelemetry::Context,
|
context: &opentelemetry::Context,
|
||||||
) -> Result<i64, DbError>;
|
) -> Result<bool, DbError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SqliteDailySummaryDao {
|
pub struct SqliteDailySummaryDao {
|
||||||
@@ -462,20 +463,27 @@ impl DailySummaryDao for SqliteDailySummaryDao {
|
|||||||
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_total_summary_count(
|
fn has_any_summaries(&mut self, context: &opentelemetry::Context) -> Result<bool, DbError> {
|
||||||
&mut self,
|
trace_db_call(context, "query", "has_any_summaries", |_span| {
|
||||||
context: &opentelemetry::Context,
|
|
||||||
) -> Result<i64, DbError> {
|
|
||||||
trace_db_call(context, "query", "get_total_summary_count", |_span| {
|
|
||||||
let mut conn = self
|
let mut conn = self
|
||||||
.connection
|
.connection
|
||||||
.lock()
|
.lock()
|
||||||
.expect("Unable to get DailySummaryDao");
|
.expect("Unable to get DailySummaryDao");
|
||||||
|
|
||||||
diesel::sql_query("SELECT COUNT(*) as count FROM daily_conversation_summaries")
|
#[derive(QueryableByName)]
|
||||||
.get_result::<CountResult>(conn.deref_mut())
|
struct ProbeResult {
|
||||||
.map(|r| r.count)
|
#[diesel(sql_type = diesel::sql_types::Integer)]
|
||||||
.map_err(|e| anyhow::anyhow!("Count query error: {:?}", e))
|
#[allow(dead_code)]
|
||||||
|
one: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let rows: Vec<ProbeResult> = diesel::sql_query(
|
||||||
|
"SELECT 1 as one FROM daily_conversation_summaries LIMIT 1",
|
||||||
|
)
|
||||||
|
.load(conn.deref_mut())
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to probe daily summaries: {}", e))?;
|
||||||
|
|
||||||
|
Ok(!rows.is_empty())
|
||||||
})
|
})
|
||||||
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user