use crate::catalog::{ToolAnnotations, ToolDescriptor};
use crate::config::Config;
use crate::rank::{Candidate, Vectors, comparable};
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Outcome<'a> {
Bind(&'a ToolDescriptor),
Duplicate(CandidateGroup<'a>),
Ambiguous(CandidateGroup<'a>),
Absent,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CandidateGroup<'a> {
tools: Vec<&'a ToolDescriptor>,
}
impl<'a> CandidateGroup<'a> {
fn new(tools: Vec<&'a ToolDescriptor>) -> Self {
debug_assert!(tools.len() >= 2, "a candidate group holds at least two");
Self { tools }
}
#[must_use]
#[expect(
clippy::len_without_is_empty,
reason = "a candidate group always holds at least two candidates"
)]
pub fn len(&self) -> usize {
self.tools.len()
}
#[must_use]
pub fn first(&self) -> &'a ToolDescriptor {
self.tools[0]
}
#[must_use]
pub fn second(&self) -> &'a ToolDescriptor {
self.tools[1]
}
#[must_use]
pub fn get(&self, index: usize) -> Option<&'a ToolDescriptor> {
self.tools.get(index).copied()
}
#[must_use = "iterators are lazy and visit nothing unless consumed"]
pub fn iter(&self) -> CandidateIter<'a, '_> {
CandidateIter {
inner: self.tools.iter(),
}
}
}
impl<'a, 'group> IntoIterator for &'group CandidateGroup<'a> {
type Item = &'a ToolDescriptor;
type IntoIter = CandidateIter<'a, 'group>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug, Clone)]
pub struct CandidateIter<'a, 'group> {
inner: std::slice::Iter<'group, &'a ToolDescriptor>,
}
impl<'a> Iterator for CandidateIter<'a, '_> {
type Item = &'a ToolDescriptor;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().copied()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl DoubleEndedIterator for CandidateIter<'_, '_> {
fn next_back(&mut self) -> Option<Self::Item> {
self.inner.next_back().copied()
}
}
impl ExactSizeIterator for CandidateIter<'_, '_> {}
impl std::iter::FusedIterator for CandidateIter<'_, '_> {}
#[derive(Debug, Clone, PartialEq)]
pub struct Shortlist<'a> {
tools: Vec<&'a ToolDescriptor>,
}
impl<'a> Shortlist<'a> {
#[must_use]
pub fn len(&self) -> usize {
self.tools.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.tools.is_empty()
}
#[must_use]
pub fn first(&self) -> Option<&'a ToolDescriptor> {
self.tools.first().copied()
}
#[must_use]
pub fn get(&self, index: usize) -> Option<&'a ToolDescriptor> {
self.tools.get(index).copied()
}
#[must_use = "iterators are lazy and visit nothing unless consumed"]
pub fn iter(&self) -> CandidateIter<'a, '_> {
CandidateIter {
inner: self.tools.iter(),
}
}
}
impl<'a, 'list> IntoIterator for &'list Shortlist<'a> {
type Item = &'a ToolDescriptor;
type IntoIter = CandidateIter<'a, 'list>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug, Clone, Copy)]
struct Ranked<'a> {
tool: &'a ToolDescriptor,
index: usize,
score: f32,
}
pub(crate) fn decide<'a>(
candidates: &[Candidate],
tools: &'a [ToolDescriptor],
vectors: Vectors<'_>,
config: &Config,
) -> Outcome<'a> {
let ranked = order(candidates, tools);
let Some(leader) = ranked.first().copied() else {
return Outcome::Absent;
};
if leader.score < config.similarity_floor() {
if leader.score >= config.solo_floor() {
let has_peer = ranked
.get(1)
.is_some_and(|runner| runner.score >= config.solo_floor());
if !has_peer {
return Outcome::Bind(leader.tool);
}
}
return Outcome::Absent;
}
let twins: Vec<Ranked<'a>> = std::iter::once(leader)
.chain(ranked[1..].iter().copied().filter(|candidate| {
candidate.tool.server() == leader.tool.server()
&& vectors
.similarity(leader.index, candidate.index)
.is_some_and(|similarity| similarity >= config.duplicate_threshold())
}))
.take(shortlist_bound(config))
.collect();
if twins.len() >= 2 {
return Outcome::Duplicate(CandidateGroup::new(descriptors(&twins)));
}
let tied: Vec<Ranked<'a>> = std::iter::once(leader)
.chain(
ranked[1..]
.iter()
.take_while(|candidate| {
candidate.score >= config.similarity_floor()
&& leader.score - candidate.score < config.margin()
})
.copied(),
)
.take(shortlist_bound(config))
.collect();
if tied.len() < 2 {
return Outcome::Bind(leader.tool);
}
Outcome::Ambiguous(CandidateGroup::new(descriptors(&tied)))
}
pub(crate) fn shortlist<'a>(
candidates: &[Candidate],
tools: &'a [ToolDescriptor],
config: &Config,
) -> Shortlist<'a> {
let ranked = order(candidates, tools);
let above_floor: Vec<&'a ToolDescriptor> = ranked
.iter()
.filter(|candidate| candidate.score >= config.similarity_floor())
.map(|candidate| candidate.tool)
.collect();
if !above_floor.is_empty() {
return Shortlist { tools: above_floor };
}
let solo = ranked
.first()
.filter(|leader| leader.score >= config.solo_floor())
.filter(|_| {
!ranked
.get(1)
.is_some_and(|runner| runner.score >= config.solo_floor())
})
.map(|leader| leader.tool);
Shortlist {
tools: solo.into_iter().collect(),
}
}
fn shortlist_bound(config: &Config) -> usize {
config.top_k().get().max(2)
}
fn order<'a>(candidates: &[Candidate], tools: &'a [ToolDescriptor]) -> Vec<Ranked<'a>> {
let mut ranked: Vec<Ranked<'a>> = candidates
.iter()
.filter_map(|candidate| {
tools.get(candidate.index()).map(|tool| Ranked {
tool,
index: candidate.index(),
score: candidate.score(),
})
})
.collect();
ranked.sort_by(|a, b| {
comparable(b.score)
.total_cmp(&comparable(a.score))
.then_with(|| hint_key(a.tool.annotations()).cmp(&hint_key(b.tool.annotations())))
.then(a.index.cmp(&b.index))
});
ranked
}
fn hint_key(annotations: ToolAnnotations) -> (u8, u8, u8) {
(
u8::from(annotations.read_only() != Some(true)),
u8::from(annotations.destructive() != Some(false)),
u8::from(annotations.idempotent() != Some(true)),
)
}
fn descriptors<'a>(group: &[Ranked<'a>]) -> Vec<&'a ToolDescriptor> {
group.iter().map(|candidate| candidate.tool).collect()
}
#[cfg(test)]
mod tests;