use std::collections::BTreeMap;
use super::WorkItem;
#[derive(Debug, Clone)]
pub struct GroupingConfig {
pub context_length_bucket: u32,
}
impl Default for GroupingConfig {
fn default() -> Self {
Self {
context_length_bucket: 4096,
}
}
}
#[derive(Debug)]
pub struct BatchGroup<P> {
pub model: String,
pub prefix_hash: u64,
pub context_bucket: u32,
pub items: Vec<WorkItem<P>>,
}
type GroupKey = (String, u64, u32);
pub fn group_work_items<P>(
items: Vec<WorkItem<P>>,
config: &GroupingConfig,
) -> Vec<BatchGroup<P>> {
let bucket_size = config.context_length_bucket.max(1);
let mut groups: BTreeMap<GroupKey, Vec<WorkItem<P>>> = BTreeMap::new();
for item in items {
let bucket = item.token_count / bucket_size;
let key = (item.model.clone(), item.prefix_hash, bucket);
groups.entry(key).or_default().push(item);
}
groups
.into_iter()
.map(|((model, prefix_hash, context_bucket), items)| BatchGroup {
model,
prefix_hash,
context_bucket,
items,
})
.collect()
}
#[cfg(test)]
mod tests {
use std::time::Instant;
use crate::ids::AgentId;
use super::super::CycleStep;
use super::*;
fn item(model: &str, prefix_hash: u64, token_count: u32) -> WorkItem<()> {
WorkItem {
agent_id: AgentId::new(),
prompt: (),
step: CycleStep::Think,
prefix_hash,
model: model.to_string(),
queued_at: Instant::now(),
token_count,
}
}
#[test]
fn groups_by_model() {
let items = vec![
item("claude", 100, 5000),
item("cogito", 100, 5000),
item("claude", 100, 5000),
];
let groups = group_work_items(items, &GroupingConfig::default());
assert_eq!(groups.len(), 2);
assert_eq!(groups[0].model, "claude");
assert_eq!(groups[0].items.len(), 2);
assert_eq!(groups[1].model, "cogito");
assert_eq!(groups[1].items.len(), 1);
}
#[test]
fn groups_by_prefix_hash() {
let items = vec![
item("claude", 100, 5000),
item("claude", 200, 5000),
item("claude", 100, 5000),
];
let groups = group_work_items(items, &GroupingConfig::default());
assert_eq!(groups.len(), 2);
assert_eq!(groups[0].prefix_hash, 100);
assert_eq!(groups[0].items.len(), 2);
assert_eq!(groups[1].prefix_hash, 200);
}
#[test]
fn groups_by_context_bucket() {
let config = GroupingConfig {
context_length_bucket: 4096,
};
let items = vec![
item("claude", 100, 4000), item("claude", 100, 5000), item("claude", 100, 4500), item("claude", 100, 12000), ];
let groups = group_work_items(items, &config);
assert_eq!(groups.len(), 3);
assert_eq!(groups[0].context_bucket, 0);
assert_eq!(groups[0].items.len(), 1);
assert_eq!(groups[1].context_bucket, 1);
assert_eq!(groups[1].items.len(), 2);
assert_eq!(groups[2].context_bucket, 2);
assert_eq!(groups[2].items.len(), 1);
}
#[test]
fn empty_input() {
let groups = group_work_items::<()>(vec![], &GroupingConfig::default());
assert!(groups.is_empty());
}
}