use std::collections::{BTreeMap, BTreeSet};
use promptforge_tool_picker::{ToolId as PickerToolId, ToolPicker};
use crate::client::ToolSchema;
use crate::lua::{ToolBindings, ToolScope};
use crate::observe::{Observer, detail};
use crate::tools::{ToolId, ToolRegistry};
use crate::{Error, NearDuplicateDiagnostic, Result};
#[derive(Debug, Clone)]
pub(crate) struct OwnedNearDuplicate {
pub(crate) first_id: ToolId,
pub(crate) second_id: ToolId,
pub(crate) similarity: f32,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ToolAnalysis {
pub(crate) alias_to_id: BTreeMap<String, ToolId>,
pub(crate) id_to_alias: BTreeMap<ToolId, String>,
pub(crate) near_duplicates: Vec<OwnedNearDuplicate>,
}
impl ToolAnalysis {
pub(crate) fn new(bindings: &ToolBindings, picker: &ToolPicker) -> Result<Self> {
let alias_to_id = bindings
.bindings()
.iter()
.map(|binding| (binding.alias().to_owned(), binding.id().clone()))
.collect();
let id_to_alias = bindings
.bindings()
.iter()
.map(|binding| (binding.id().clone(), binding.alias().to_owned()))
.collect();
let ids = bindings
.bindings()
.iter()
.map(|binding| PickerToolId::new(binding.id().server(), binding.id().name()))
.collect::<Vec<_>>();
let near_duplicates = picker
.near_duplicates(&ids)
.map_err(|error| Error::ToolScopeAnalysisSource {
source: Box::new(error),
})?
.iter()
.map(|pair| OwnedNearDuplicate {
first_id: ToolId::from_validated(
pair.first().id().server(),
pair.first().id().name(),
),
second_id: ToolId::from_validated(
pair.second().id().server(),
pair.second().id().name(),
),
similarity: pair.similarity(),
})
.collect();
Ok(Self {
alias_to_id,
id_to_alias,
near_duplicates,
})
}
}
pub(crate) fn prepare_effective_scope(
analysis: &ToolAnalysis,
scope: &ToolScope,
registry: &ToolRegistry<'_>,
execution: &str,
observer: &dyn Observer,
section: &str,
) -> Result<(Vec<ToolSchema>, BTreeMap<String, ToolId>)> {
observer.observe(execution, section, detail::TOOL_SCOPE_VALIDATION_STARTED);
let result = validate_effective_scope_inner(analysis, scope)
.and_then(|()| prepare_scoped_tools(scope, registry));
observer.observe(
execution,
section,
if result.is_ok() {
detail::TOOL_SCOPE_VALIDATION_SUCCEEDED
} else {
detail::TOOL_SCOPE_VALIDATION_FAILED
},
);
result
}
pub(crate) fn validate_effective_scope_inner(
analysis: &ToolAnalysis,
scope: &ToolScope,
) -> Result<()> {
let effective = scope
.bindings()
.iter()
.map(crate::lua::ToolBinding::id)
.collect::<BTreeSet<_>>();
for pair in &analysis.near_duplicates {
if !effective.contains(&pair.first_id) || !effective.contains(&pair.second_id) {
continue;
}
let first_alias = analysis
.id_to_alias
.get(&pair.first_id)
.cloned()
.ok_or_else(|| Error::ToolScopeAnalysis {
detail: "selected identity has no frozen alias".to_owned(),
})?;
let second_alias = analysis
.id_to_alias
.get(&pair.second_id)
.cloned()
.ok_or_else(|| Error::ToolScopeAnalysis {
detail: "selected identity has no frozen alias".to_owned(),
})?;
return Err(Error::NearDuplicateTools {
diagnostic: Box::new(NearDuplicateDiagnostic {
first_alias,
first_id: pair.first_id.clone(),
second_alias,
second_id: pair.second_id.clone(),
similarity: pair.similarity,
}),
});
}
Ok(())
}
pub(crate) fn prepare_scoped_tools(
scope: &ToolScope,
registry: &ToolRegistry<'_>,
) -> Result<(Vec<ToolSchema>, BTreeMap<String, ToolId>)> {
let mut schemas = Vec::with_capacity(scope.bindings().len());
let mut dispatch = BTreeMap::new();
for binding in scope.bindings() {
let tool = registry
.get(binding.id())
.ok_or_else(|| Error::UnknownScopedTool(binding.alias().to_owned()))?;
let description = binding
.model_description()
.unwrap_or_else(|| tool.description())
.to_owned();
let schema = ToolSchema::new(
binding.alias().to_owned(),
description,
tool.parameters_schema(),
)
.map_err(|error| Error::BindSchema {
alias: binding.alias().to_owned(),
source: Box::new(error),
})?;
schemas.push(schema);
dispatch.insert(binding.alias().to_owned(), binding.id().clone());
}
Ok((schemas, dispatch))
}