Skip to main content

atomr_agents_tool/
strategies.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4use atomr_agents_callable::CallableHandle;
5use atomr_agents_core::{AgentContext, Result, TokenBudget};
6use atomr_agents_strategy::{ToolRef, ToolStrategy};
7
8use crate::r#trait::{DynTool, ToolCallable};
9
10fn dyn_tool_to_callable(t: DynTool) -> CallableHandle {
11    // Wrap the trait object in a `ToolCallable` (newtype that
12    // implements `Callable`). We need a helper that erases the
13    // generic — a small adapter struct.
14    Arc::new(DynToolCallable { inner: t })
15}
16
17struct DynToolCallable {
18    inner: DynTool,
19}
20
21#[async_trait::async_trait]
22impl atomr_agents_callable::Callable for DynToolCallable {
23    async fn call(
24        &self,
25        input: atomr_agents_core::Value,
26        ctx: atomr_agents_core::CallCtx,
27    ) -> atomr_agents_core::Result<atomr_agents_core::Value> {
28        let invoke_ctx = atomr_agents_core::InvokeCtx {
29            call: ctx,
30            tool_call_id: String::new(),
31            raw_args: input.clone(),
32        };
33        self.inner.invoke(input, &invoke_ctx).await
34    }
35
36    fn label(&self) -> &str {
37        &self.inner.descriptor().name
38    }
39}
40
41/// v0: hand-picked fixed list of tools.
42pub struct StaticToolStrategy {
43    tools: Vec<DynTool>,
44}
45
46impl StaticToolStrategy {
47    pub fn new(tools: Vec<DynTool>) -> Self {
48        Self { tools }
49    }
50}
51
52#[async_trait]
53impl ToolStrategy for StaticToolStrategy {
54    async fn select(&self, _ctx: &AgentContext, _budget: &mut TokenBudget) -> Result<Vec<ToolRef>> {
55        Ok(self
56            .tools
57            .iter()
58            .map(|t| {
59                let d = t.descriptor();
60                ToolRef {
61                    id: d.id.clone(),
62                    name: d.name.clone(),
63                    handle: dyn_tool_to_callable(t.clone()),
64                }
65            })
66            .collect())
67    }
68}
69
70/// v1: lexical filter. Returns tools whose name or description
71/// contains any of the keywords found in the user's turn input.
72/// (Substring match in v0; promote to TF-IDF later.)
73pub struct KeywordToolStrategy {
74    tools: Vec<DynTool>,
75    /// Maximum number of tools to return.
76    max_tools: usize,
77}
78
79impl KeywordToolStrategy {
80    pub fn new(tools: Vec<DynTool>, max_tools: usize) -> Self {
81        Self { tools, max_tools }
82    }
83}
84
85#[async_trait]
86impl ToolStrategy for KeywordToolStrategy {
87    async fn select(&self, ctx: &AgentContext, _budget: &mut TokenBudget) -> Result<Vec<ToolRef>> {
88        let needle = ctx.turn.user.to_lowercase();
89        let words: Vec<&str> = needle
90            .split(|c: char| !c.is_alphanumeric())
91            .filter(|w| !w.is_empty())
92            .collect();
93        let mut scored: Vec<(usize, &DynTool)> = self
94            .tools
95            .iter()
96            .map(|t| {
97                let d = t.descriptor();
98                let hay = format!("{} {}", d.name.to_lowercase(), d.description.to_lowercase());
99                let score = words.iter().filter(|w| hay.contains(*w)).count();
100                (score, t)
101            })
102            .filter(|(s, _)| *s > 0)
103            .collect();
104        scored.sort_by_key(|(score, _)| std::cmp::Reverse(*score));
105        scored.truncate(self.max_tools);
106        Ok(scored
107            .into_iter()
108            .map(|(_, t)| {
109                let d = t.descriptor();
110                ToolRef {
111                    id: d.id.clone(),
112                    name: d.name.clone(),
113                    handle: dyn_tool_to_callable(t.clone()),
114                }
115            })
116            .collect())
117    }
118}
119
120// silence unused-import warning for ToolCallable; it's part of the
121// public surface but only constructed by direct callers.
122#[allow(dead_code)]
123fn _keep_tool_callable_alive<T: crate::r#trait::Tool>(t: T) -> ToolCallable<T> {
124    ToolCallable::new(t)
125}