Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/swarm-controller-api/src/inference.rs

117 lines
4.8 KiB
Rust

//! `/api/agents/{name}/inference`, and the `inference` of `POST /api/agents`.
use serde::{Deserialize, Serialize};
pub use swarm_wire::agent_inference::{Effort, Source};
/// The highest `max_subagents` a `PUT` may set. Far more concurrent claude
/// sessions than one agent container has memory for, so a value above it is a
/// typo rather than a cap.
pub const MAX_SUBAGENTS: u32 = 32;
/// One agent's inference settings. An absent field is not set at swarm level,
/// and the agent keeps its own value for it.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
#[serde(deny_unknown_fields)]
pub struct InferenceSettings {
/// The model the agent's turns run on, as claude's `--model` takes it.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
/// One of `low`, `medium`, `high`, `xhigh`, `max`.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schema", schema(value_type = Option<String>))]
pub effort: Option<Effort>,
/// The models the agent may start its subagents on. At least one.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subagent_models: Option<Vec<String>>,
/// How many subagents the agent may have running at once, from 0 (no
/// subagents) to [`MAX_SUBAGENTS`]. Absent is no cap.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_subagents: Option<u32>,
}
/// What `GET` answers for one agent.
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
pub struct AgentInferenceView {
/// The settings the swarm sets; `null` when it has none for the agent (no
/// key, or a cleared one), `{}` for a stored setting with no fields.
pub configured: Option<InferenceSettings>,
/// What the agent reported running on in its latest turn-state header;
/// `null` when this daemon has received none with these fields since it
/// started.
pub effective: Option<EffectiveInference>,
}
/// The model and effort an agent runs on, and where each comes from.
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
pub struct EffectiveInference {
pub model: String,
pub effort: String,
/// `swarm`, `agent_nix`, `local_file` or `default`.
#[cfg_attr(feature = "schema", schema(value_type = String))]
pub model_source: Source,
/// As `model_source`.
#[cfg_attr(feature = "schema", schema(value_type = String))]
pub effort_source: Source,
}
#[cfg(test)]
mod tests {
use super::{AgentInferenceView, EffectiveInference, InferenceSettings};
use swarm_wire::agent_inference::{Effort, Source};
#[test]
fn settings_round_trip() {
for (value, wire) in [
(InferenceSettings::default(), "{}"),
(
InferenceSettings {
model: Some("opus".to_owned()),
effort: Some(Effort::Xhigh),
subagent_models: Some(vec!["haiku".to_owned()]),
max_subagents: Some(3),
},
r#"{"model":"opus","effort":"xhigh","subagent_models":["haiku"],"max_subagents":3}"#,
),
] {
assert_eq!(serde_json::to_string(&value).unwrap(), wire);
assert_eq!(
serde_json::from_str::<InferenceSettings>(wire).unwrap(),
value
);
}
assert!(serde_json::from_str::<InferenceSettings>(r#"{"effort":"extreme"}"#).is_err());
assert!(serde_json::from_str::<InferenceSettings>(r#"{"modle":"x"}"#).is_err());
}
#[test]
fn a_view_round_trips() {
let wire = r#"{"configured":{"model":"opus"},"effective":{"model":"opus","effort":"medium","model_source":"swarm","effort_source":"local_file"}}"#;
let view = AgentInferenceView {
configured: Some(InferenceSettings {
model: Some("opus".to_owned()),
..InferenceSettings::default()
}),
effective: Some(EffectiveInference {
model: "opus".to_owned(),
effort: "medium".to_owned(),
model_source: Source::Swarm,
effort_source: Source::LocalFile,
}),
};
assert_eq!(serde_json::to_string(&view).unwrap(), wire);
let back: AgentInferenceView = serde_json::from_str(wire).unwrap();
assert_eq!(back.configured, view.configured);
assert_eq!(back.effective, view.effective);
assert_eq!(
serde_json::to_string(&AgentInferenceView {
configured: None,
effective: None
})
.unwrap(),
r#"{"configured":null,"effective":null}"#
);
}
}