217 lines
5.5 KiB
Rust
217 lines
5.5 KiB
Rust
//! `loopd` configuration: read `config.toml` into a typed `Config`.
|
|
//!
|
|
//! This is our own format, so unknown keys are errors in every table: a
|
|
//! misspelt `liveness_ms` that silently fell back to its default would be a
|
|
//! limit the owner believes is set and is not.
|
|
|
|
use serde::Deserialize;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct Infer {
|
|
pub socket: std::path::PathBuf,
|
|
pub model: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct Slots {
|
|
pub main: u32,
|
|
pub background: u32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct Expect {
|
|
pub template_sha256: proto::Hash32,
|
|
pub n_ctx: u64,
|
|
pub slots: u32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
|
|
#[serde(deny_unknown_fields, default)]
|
|
pub struct Sampling {
|
|
pub temperature: f64,
|
|
pub top_p: f64,
|
|
pub top_k: u32,
|
|
}
|
|
|
|
impl Default for Sampling {
|
|
fn default() -> Self {
|
|
Self {
|
|
temperature: 0.6,
|
|
top_p: 0.95,
|
|
top_k: 20,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
#[serde(deny_unknown_fields, default)]
|
|
pub struct Limits {
|
|
pub poll_ms: u64,
|
|
pub busy_wait_ms: u64,
|
|
pub load_wait_ms: u64,
|
|
pub idle_grace_ms: u64,
|
|
pub liveness_ms: u64,
|
|
pub thinking_cap: u64,
|
|
pub thinking_overrun: u64,
|
|
pub max_tokens: u64,
|
|
pub queue_len: usize,
|
|
pub retry_attempts: u32,
|
|
pub retry_backoff_ms: Vec<u64>,
|
|
pub retry_window_ms: u64,
|
|
}
|
|
|
|
impl Default for Limits {
|
|
fn default() -> Self {
|
|
Self {
|
|
poll_ms: 5_000,
|
|
busy_wait_ms: 600_000,
|
|
load_wait_ms: 180_000,
|
|
idle_grace_ms: 30_000,
|
|
liveness_ms: 30_000,
|
|
thinking_cap: 4_096,
|
|
thinking_overrun: 256,
|
|
max_tokens: 8_192,
|
|
queue_len: 8,
|
|
retry_attempts: 4,
|
|
retry_backoff_ms: vec![2_000, 8_000, 30_000],
|
|
retry_window_ms: 300_000,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
|
|
#[serde(deny_unknown_fields, default)]
|
|
pub struct Channel {
|
|
pub socket: std::path::PathBuf,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
#[serde(deny_unknown_fields, default)]
|
|
pub struct Paths {
|
|
pub home: std::path::PathBuf,
|
|
}
|
|
|
|
impl Default for Paths {
|
|
fn default() -> Self {
|
|
Self {
|
|
home: std::env::var_os("BOXMAKER_HOME")
|
|
.map(std::path::PathBuf::from)
|
|
.unwrap_or_else(|| std::path::PathBuf::from("/var/lib/boxmaker")),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
#[serde(deny_unknown_fields, default)]
|
|
pub struct Loop {
|
|
pub tool_iterations: u32,
|
|
pub repeat_detection: bool,
|
|
pub tool_result_cap: usize,
|
|
}
|
|
|
|
impl Default for Loop {
|
|
fn default() -> Self {
|
|
Self {
|
|
tool_iterations: 8,
|
|
repeat_detection: true,
|
|
tool_result_cap: 16_384,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
#[serde(deny_unknown_fields, default)]
|
|
pub struct Baseline {
|
|
pub system: std::path::PathBuf,
|
|
}
|
|
|
|
impl Default for Baseline {
|
|
fn default() -> Self {
|
|
Self {
|
|
system: std::path::PathBuf::from("system.md"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The tool broker (`brokerd`). No socket means no broker: every non-core tool call fails.
|
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
#[serde(deny_unknown_fields, default)]
|
|
pub struct Broker {
|
|
pub socket: Option<std::path::PathBuf>,
|
|
pub timeout_ms: u64,
|
|
}
|
|
|
|
impl Default for Broker {
|
|
fn default() -> Self {
|
|
Self {
|
|
socket: None,
|
|
timeout_ms: 120_000,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct Config {
|
|
pub infer: Infer,
|
|
pub slots: Slots,
|
|
pub expect: Expect,
|
|
#[serde(default)]
|
|
pub sampling: Sampling,
|
|
#[serde(default)]
|
|
pub limits: Limits,
|
|
#[serde(default)]
|
|
pub paths: Paths,
|
|
#[serde(default)]
|
|
pub channel: Channel,
|
|
#[serde(default)]
|
|
pub r#loop: Loop,
|
|
#[serde(default)]
|
|
pub baseline: Baseline,
|
|
#[serde(default)]
|
|
pub broker: Broker,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum ConfigError {
|
|
Read(std::path::PathBuf, std::io::Error),
|
|
Parse(std::path::PathBuf, toml::de::Error),
|
|
}
|
|
|
|
impl std::fmt::Display for ConfigError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
match self {
|
|
ConfigError::Read(path, err) => write!(f, "{}: {err}", path.display()),
|
|
ConfigError::Parse(path, err) => write!(f, "{}: {err}", path.display()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for ConfigError {}
|
|
|
|
impl Config {
|
|
pub fn parse(text: &str) -> Result<Config, toml::de::Error> {
|
|
toml::from_str(text)
|
|
}
|
|
pub fn load(path: &std::path::Path) -> Result<Config, ConfigError> {
|
|
let text =
|
|
std::fs::read_to_string(path).map_err(|e| ConfigError::Read(path.to_path_buf(), e))?;
|
|
let mut config: Config =
|
|
toml::from_str(&text).map_err(|e| ConfigError::Parse(path.to_path_buf(), e))?;
|
|
if let Some(parent) = path.parent() {
|
|
config.baseline.system = parent.join(&config.baseline.system);
|
|
}
|
|
Ok(config)
|
|
}
|
|
pub fn channel_socket(&self) -> std::path::PathBuf {
|
|
if self.channel.socket.as_os_str().is_empty() {
|
|
self.paths.home.join("run/loop/loop.sock")
|
|
} else {
|
|
self.channel.socket.clone()
|
|
}
|
|
}
|
|
}
|