feat(channel): stream LLM responses to Telegram via draft message edits
Wire the existing provider-layer streaming infrastructure through the channel trait and agent loop so Telegram users see tokens arrive progressively via editMessageText, instead of waiting for the full response. Changes: - Add StreamMode enum (off/partial/block) and draft_update_interval_ms to TelegramConfig (backward-compatible defaults: off, 1000ms) - Add supports_draft_updates/send_draft/update_draft/finalize_draft to Channel trait with no-op defaults (zero impact on existing channels) - Implement draft methods on TelegramChannel using sendMessage + editMessageText with rate limiting and Markdown fallback - Add on_delta mpsc::Sender<String> parameter to run_tool_call_loop (None preserves existing behavior) - Wire streaming in process_channel_message: when channel supports drafts, send initial draft, spawn updater task, finalize on completion Edge cases handled: - 4096-char limit: finalize draft and fall back to chunked send - Broken Markdown: use no parse_mode during streaming, apply on finalize - Edit failures: fall back to sending complete response as new message - Rate limiting: configurable draft_update_interval_ms (default 1s)
This commit is contained in:
parent
a0b277b21e
commit
118cd53922
12 changed files with 410 additions and 43 deletions
|
|
@ -337,8 +337,7 @@ impl SqliteMemory {
|
|||
category: Option<&str>,
|
||||
session_id: Option<&str>,
|
||||
) -> anyhow::Result<Vec<(String, f32)>> {
|
||||
let mut sql =
|
||||
"SELECT id, embedding FROM memories WHERE embedding IS NOT NULL".to_string();
|
||||
let mut sql = "SELECT id, embedding FROM memories WHERE embedding IS NOT NULL".to_string();
|
||||
let mut param_values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
|
||||
let mut idx = 1;
|
||||
|
||||
|
|
@ -500,13 +499,11 @@ impl Memory for SqliteMemory {
|
|||
let session_ref = session_id.as_deref();
|
||||
|
||||
// FTS5 BM25 keyword search
|
||||
let keyword_results =
|
||||
Self::fts5_search(&conn, &query, limit * 2).unwrap_or_default();
|
||||
let keyword_results = Self::fts5_search(&conn, &query, limit * 2).unwrap_or_default();
|
||||
|
||||
// Vector similarity search (if embeddings available)
|
||||
let vector_results = if let Some(ref qe) = query_embedding {
|
||||
Self::vector_search(&conn, qe, limit * 2, None, session_ref)
|
||||
.unwrap_or_default()
|
||||
Self::vector_search(&conn, qe, limit * 2, None, session_ref).unwrap_or_default()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
|
@ -604,11 +601,7 @@ impl Memory for SqliteMemory {
|
|||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, _)| {
|
||||
format!(
|
||||
"(content LIKE ?{} OR key LIKE ?{})",
|
||||
i * 2 + 1,
|
||||
i * 2 + 2
|
||||
)
|
||||
format!("(content LIKE ?{} OR key LIKE ?{})", i * 2 + 1, i * 2 + 2)
|
||||
})
|
||||
.collect();
|
||||
let where_clause = conditions.join(" OR ");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue