feat(channel): make message timeout configurable via channels_config.message_timeout_secs
Add configurable timeout for processing channel messages (LLM + tools). Default: 300s (optimized for on-device LLMs like Ollama). Can be overridden in config.toml: [channels_config] message_timeout_secs = 600
This commit is contained in:
parent
4ecaf6070c
commit
41a6ed30dd
4 changed files with 38 additions and 24 deletions
|
|
@ -50,6 +50,10 @@ Notes:
|
||||||
|
|
||||||
Top-level channel options are configured under `channels_config`.
|
Top-level channel options are configured under `channels_config`.
|
||||||
|
|
||||||
|
| Key | Default | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `message_timeout_secs` | `300` | Timeout in seconds for processing a single channel message (LLM + tools) |
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- `[channels_config.telegram]`
|
- `[channels_config.telegram]`
|
||||||
|
|
@ -57,6 +61,12 @@ Examples:
|
||||||
- `[channels_config.whatsapp]`
|
- `[channels_config.whatsapp]`
|
||||||
- `[channels_config.email]`
|
- `[channels_config.email]`
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Default `300s` is optimized for on-device LLMs (Ollama) which are slower than cloud APIs.
|
||||||
|
- If using cloud APIs (OpenAI, Anthropic, etc.), you can reduce this to `60` or lower.
|
||||||
|
- When a timeout occurs, users receive: `⚠️ Request timed out while waiting for the model. Please try again.`
|
||||||
|
|
||||||
See detailed channel matrix and allowlist behavior in [channels-reference.md](channels-reference.md).
|
See detailed channel matrix and allowlist behavior in [channels-reference.md](channels-reference.md).
|
||||||
|
|
||||||
## Security-Relevant Defaults
|
## Security-Relevant Defaults
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,8 @@ const BOOTSTRAP_MAX_CHARS: usize = 20_000;
|
||||||
|
|
||||||
const DEFAULT_CHANNEL_INITIAL_BACKOFF_SECS: u64 = 2;
|
const DEFAULT_CHANNEL_INITIAL_BACKOFF_SECS: u64 = 2;
|
||||||
const DEFAULT_CHANNEL_MAX_BACKOFF_SECS: u64 = 60;
|
const DEFAULT_CHANNEL_MAX_BACKOFF_SECS: u64 = 60;
|
||||||
/// Timeout for processing a single channel message (LLM + tools).
|
/// Default timeout for processing a single channel message (LLM + tools).
|
||||||
/// 300s for on-device LLMs (Ollama) which are slower than cloud APIs.
|
/// Used as fallback when not configured in channels_config.message_timeout_secs.
|
||||||
const CHANNEL_MESSAGE_TIMEOUT_SECS: u64 = 300;
|
const CHANNEL_MESSAGE_TIMEOUT_SECS: u64 = 300;
|
||||||
const CHANNEL_PARALLELISM_PER_CHANNEL: usize = 4;
|
const CHANNEL_PARALLELISM_PER_CHANNEL: usize = 4;
|
||||||
const CHANNEL_MIN_IN_FLIGHT_MESSAGES: usize = 8;
|
const CHANNEL_MIN_IN_FLIGHT_MESSAGES: usize = 8;
|
||||||
|
|
@ -120,6 +120,7 @@ struct ChannelRuntimeContext {
|
||||||
reliability: Arc<crate::config::ReliabilityConfig>,
|
reliability: Arc<crate::config::ReliabilityConfig>,
|
||||||
provider_runtime_options: providers::ProviderRuntimeOptions,
|
provider_runtime_options: providers::ProviderRuntimeOptions,
|
||||||
workspace_dir: Arc<PathBuf>,
|
workspace_dir: Arc<PathBuf>,
|
||||||
|
message_timeout_secs: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn conversation_memory_key(msg: &traits::ChannelMessage) -> String {
|
fn conversation_memory_key(msg: &traits::ChannelMessage) -> String {
|
||||||
|
|
@ -696,7 +697,7 @@ async fn process_channel_message(ctx: Arc<ChannelRuntimeContext>, msg: traits::C
|
||||||
};
|
};
|
||||||
|
|
||||||
let llm_result = tokio::time::timeout(
|
let llm_result = tokio::time::timeout(
|
||||||
Duration::from_secs(CHANNEL_MESSAGE_TIMEOUT_SECS),
|
Duration::from_secs(ctx.message_timeout_secs),
|
||||||
run_tool_call_loop(
|
run_tool_call_loop(
|
||||||
active_provider.as_ref(),
|
active_provider.as_ref(),
|
||||||
&mut history,
|
&mut history,
|
||||||
|
|
@ -787,10 +788,7 @@ async fn process_channel_message(ctx: Arc<ChannelRuntimeContext>, msg: traits::C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let timeout_msg = format!(
|
let timeout_msg = format!("LLM response timed out after {}s", ctx.message_timeout_secs);
|
||||||
"LLM response timed out after {}s",
|
|
||||||
CHANNEL_MESSAGE_TIMEOUT_SECS
|
|
||||||
);
|
|
||||||
eprintln!(
|
eprintln!(
|
||||||
" ❌ {} (elapsed: {}ms)",
|
" ❌ {} (elapsed: {}ms)",
|
||||||
timeout_msg,
|
timeout_msg,
|
||||||
|
|
@ -1835,6 +1833,7 @@ pub async fn start_channels(config: Config) -> Result<()> {
|
||||||
reliability: Arc::new(config.reliability.clone()),
|
reliability: Arc::new(config.reliability.clone()),
|
||||||
provider_runtime_options,
|
provider_runtime_options,
|
||||||
workspace_dir: Arc::new(config.workspace_dir.clone()),
|
workspace_dir: Arc::new(config.workspace_dir.clone()),
|
||||||
|
message_timeout_secs: config.channels_config.message_timeout_secs,
|
||||||
});
|
});
|
||||||
|
|
||||||
run_message_dispatch_loop(rx, runtime_ctx, max_in_flight_messages).await;
|
run_message_dispatch_loop(rx, runtime_ctx, max_in_flight_messages).await;
|
||||||
|
|
@ -2225,6 +2224,7 @@ mod tests {
|
||||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||||
|
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||||
});
|
});
|
||||||
|
|
||||||
process_channel_message(
|
process_channel_message(
|
||||||
|
|
@ -2277,6 +2277,7 @@ mod tests {
|
||||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||||
|
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||||
});
|
});
|
||||||
|
|
||||||
process_channel_message(
|
process_channel_message(
|
||||||
|
|
@ -2338,6 +2339,7 @@ mod tests {
|
||||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||||
|
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||||
});
|
});
|
||||||
|
|
||||||
process_channel_message(
|
process_channel_message(
|
||||||
|
|
@ -2420,6 +2422,7 @@ mod tests {
|
||||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||||
|
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||||
});
|
});
|
||||||
|
|
||||||
process_channel_message(
|
process_channel_message(
|
||||||
|
|
@ -2478,6 +2481,7 @@ mod tests {
|
||||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||||
|
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||||
});
|
});
|
||||||
|
|
||||||
process_channel_message(
|
process_channel_message(
|
||||||
|
|
@ -2531,6 +2535,7 @@ mod tests {
|
||||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||||
|
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||||
});
|
});
|
||||||
|
|
||||||
process_channel_message(
|
process_channel_message(
|
||||||
|
|
@ -2635,6 +2640,7 @@ mod tests {
|
||||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||||
|
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||||
});
|
});
|
||||||
|
|
||||||
let (tx, rx) = tokio::sync::mpsc::channel::<traits::ChannelMessage>(4);
|
let (tx, rx) = tokio::sync::mpsc::channel::<traits::ChannelMessage>(4);
|
||||||
|
|
@ -2705,6 +2711,7 @@ mod tests {
|
||||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||||
|
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||||
});
|
});
|
||||||
|
|
||||||
process_channel_message(
|
process_channel_message(
|
||||||
|
|
@ -3095,6 +3102,7 @@ mod tests {
|
||||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||||
|
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||||
});
|
});
|
||||||
|
|
||||||
process_channel_message(
|
process_channel_message(
|
||||||
|
|
|
||||||
|
|
@ -1979,6 +1979,14 @@ pub struct ChannelsConfig {
|
||||||
pub lark: Option<LarkConfig>,
|
pub lark: Option<LarkConfig>,
|
||||||
pub dingtalk: Option<DingTalkConfig>,
|
pub dingtalk: Option<DingTalkConfig>,
|
||||||
pub qq: Option<QQConfig>,
|
pub qq: Option<QQConfig>,
|
||||||
|
/// Timeout in seconds for processing a single channel message (LLM + tools).
|
||||||
|
/// Default: 300s for on-device LLMs (Ollama) which are slower than cloud APIs.
|
||||||
|
#[serde(default = "default_channel_message_timeout_secs")]
|
||||||
|
pub message_timeout_secs: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_channel_message_timeout_secs() -> u64 {
|
||||||
|
300
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ChannelsConfig {
|
impl Default for ChannelsConfig {
|
||||||
|
|
@ -1999,6 +2007,7 @@ impl Default for ChannelsConfig {
|
||||||
lark: None,
|
lark: None,
|
||||||
dingtalk: None,
|
dingtalk: None,
|
||||||
qq: None,
|
qq: None,
|
||||||
|
message_timeout_secs: default_channel_message_timeout_secs(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3242,6 +3251,7 @@ default_temperature = 0.7
|
||||||
lark: None,
|
lark: None,
|
||||||
dingtalk: None,
|
dingtalk: None,
|
||||||
qq: None,
|
qq: None,
|
||||||
|
message_timeout_secs: 300,
|
||||||
},
|
},
|
||||||
memory: MemoryConfig::default(),
|
memory: MemoryConfig::default(),
|
||||||
storage: StorageConfig::default(),
|
storage: StorageConfig::default(),
|
||||||
|
|
@ -3746,6 +3756,7 @@ allowed_users = ["@ops:matrix.org"]
|
||||||
lark: None,
|
lark: None,
|
||||||
dingtalk: None,
|
dingtalk: None,
|
||||||
qq: None,
|
qq: None,
|
||||||
|
message_timeout_secs: 300,
|
||||||
};
|
};
|
||||||
let toml_str = toml::to_string_pretty(&c).unwrap();
|
let toml_str = toml::to_string_pretty(&c).unwrap();
|
||||||
let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap();
|
let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap();
|
||||||
|
|
@ -3909,6 +3920,7 @@ channel_id = "C123"
|
||||||
lark: None,
|
lark: None,
|
||||||
dingtalk: None,
|
dingtalk: None,
|
||||||
qq: None,
|
qq: None,
|
||||||
|
message_timeout_secs: 300,
|
||||||
};
|
};
|
||||||
let toml_str = toml::to_string_pretty(&c).unwrap();
|
let toml_str = toml::to_string_pretty(&c).unwrap();
|
||||||
let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap();
|
let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -2452,23 +2452,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
|
||||||
print_bullet("CLI is always available. Connect more channels now.");
|
print_bullet("CLI is always available. Connect more channels now.");
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
let mut config = ChannelsConfig {
|
let mut config = ChannelsConfig::default();
|
||||||
cli: true,
|
|
||||||
telegram: None,
|
|
||||||
discord: None,
|
|
||||||
slack: None,
|
|
||||||
mattermost: None,
|
|
||||||
webhook: None,
|
|
||||||
imessage: None,
|
|
||||||
matrix: None,
|
|
||||||
signal: None,
|
|
||||||
whatsapp: None,
|
|
||||||
email: None,
|
|
||||||
irc: None,
|
|
||||||
lark: None,
|
|
||||||
dingtalk: None,
|
|
||||||
qq: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let options = vec![
|
let options = vec![
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue