swiftide_core/chat_completion/
traits.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use dyn_clone::DynClone;
4use std::{borrow::Cow, sync::Arc};
5
6use crate::{AgentContext, CommandOutput};
7
8use super::{
9    chat_completion_request::ChatCompletionRequest,
10    chat_completion_response::ChatCompletionResponse,
11    errors::{ChatCompletionError, ToolError},
12    ToolOutput, ToolSpec,
13};
14
15#[async_trait]
16pub trait ChatCompletion: Send + Sync + DynClone {
17    async fn complete(
18        &self,
19        request: &ChatCompletionRequest,
20    ) -> Result<ChatCompletionResponse, ChatCompletionError>;
21}
22
23#[async_trait]
24impl ChatCompletion for Box<dyn ChatCompletion> {
25    async fn complete(
26        &self,
27        request: &ChatCompletionRequest,
28    ) -> Result<ChatCompletionResponse, ChatCompletionError> {
29        (**self).complete(request).await
30    }
31}
32
33#[async_trait]
34impl ChatCompletion for &dyn ChatCompletion {
35    async fn complete(
36        &self,
37        request: &ChatCompletionRequest,
38    ) -> Result<ChatCompletionResponse, ChatCompletionError> {
39        (**self).complete(request).await
40    }
41}
42
43#[async_trait]
44impl<T> ChatCompletion for &T
45where
46    T: ChatCompletion + Clone + 'static,
47{
48    async fn complete(
49        &self,
50        request: &ChatCompletionRequest,
51    ) -> Result<ChatCompletionResponse, ChatCompletionError> {
52        (**self).complete(request).await
53    }
54}
55
56impl<LLM> From<&LLM> for Box<dyn ChatCompletion>
57where
58    LLM: ChatCompletion + Clone + 'static,
59{
60    fn from(llm: &LLM) -> Self {
61        Box::new(llm.clone()) as Box<dyn ChatCompletion>
62    }
63}
64
65dyn_clone::clone_trait_object!(ChatCompletion);
66
67impl From<CommandOutput> for ToolOutput {
68    fn from(value: CommandOutput) -> Self {
69        ToolOutput::Text(value.output)
70    }
71}
72
73/// The `Tool` trait is the main interface for chat completion and agent tools.
74///
75/// `swiftide-macros` provides a set of macros to generate implementations of this trait. If you
76/// need more control over the implementation, you can implement the trait manually.
77///
78/// The `ToolSpec` is what will end up with the LLM. A builder is provided. The `name` is expected
79/// to be unique, and is used to identify the tool. It should be the same as the name in the
80/// `ToolSpec`.
81#[async_trait]
82pub trait Tool: Send + Sync + DynClone {
83    // tbd
84    async fn invoke(
85        &self,
86        agent_context: &dyn AgentContext,
87        raw_args: Option<&str>,
88    ) -> Result<ToolOutput, ToolError>;
89
90    fn name(&self) -> Cow<'_, str>;
91
92    fn tool_spec(&self) -> ToolSpec;
93
94    fn boxed<'a>(self) -> Box<dyn Tool + 'a>
95    where
96        Self: Sized + 'a,
97    {
98        Box::new(self) as Box<dyn Tool>
99    }
100}
101
102/// A toolbox is a collection of tools
103///
104/// It can be a list, an mcp client, or anything else we can think of.
105///
106/// This allows agents to not know their tools when they are created, and to get them at runtime.
107///
108/// It also allows for tools to be dynamically loaded and unloaded, etc.
109#[async_trait]
110pub trait ToolBox: Send + Sync + DynClone {
111    async fn available_tools(&self) -> Result<Vec<Box<dyn Tool>>>;
112
113    fn name(&self) -> Cow<'_, str> {
114        Cow::Borrowed("Unnamed ToolBox")
115    }
116
117    fn boxed<'a>(self) -> Box<dyn ToolBox + 'a>
118    where
119        Self: Sized + 'a,
120    {
121        Box::new(self) as Box<dyn ToolBox>
122    }
123}
124
125#[async_trait]
126impl ToolBox for Vec<Box<dyn Tool>> {
127    async fn available_tools(&self) -> Result<Vec<Box<dyn Tool>>> {
128        Ok(self.clone())
129    }
130}
131
132#[async_trait]
133impl ToolBox for Box<dyn ToolBox> {
134    async fn available_tools(&self) -> Result<Vec<Box<dyn Tool>>> {
135        (**self).available_tools().await
136    }
137}
138
139#[async_trait]
140impl ToolBox for Arc<dyn ToolBox> {
141    async fn available_tools(&self) -> Result<Vec<Box<dyn Tool>>> {
142        (**self).available_tools().await
143    }
144}
145
146#[async_trait]
147impl ToolBox for &dyn ToolBox {
148    async fn available_tools(&self) -> Result<Vec<Box<dyn Tool>>> {
149        (**self).available_tools().await
150    }
151}
152
153#[async_trait]
154impl ToolBox for &[Box<dyn Tool>] {
155    async fn available_tools(&self) -> Result<Vec<Box<dyn Tool>>> {
156        Ok(self.to_vec())
157    }
158}
159
160#[async_trait]
161impl ToolBox for [Box<dyn Tool>] {
162    async fn available_tools(&self) -> Result<Vec<Box<dyn Tool>>> {
163        Ok(self.to_vec())
164    }
165}
166
167dyn_clone::clone_trait_object!(ToolBox);
168
169#[async_trait]
170impl Tool for Box<dyn Tool> {
171    async fn invoke(
172        &self,
173        agent_context: &dyn AgentContext,
174        raw_args: Option<&str>,
175    ) -> Result<ToolOutput, ToolError> {
176        (**self).invoke(agent_context, raw_args).await
177    }
178    fn name(&self) -> Cow<'_, str> {
179        (**self).name()
180    }
181    fn tool_spec(&self) -> ToolSpec {
182        (**self).tool_spec()
183    }
184}
185
186dyn_clone::clone_trait_object!(Tool);
187
188/// Tools are identified and unique by name
189/// These allow comparison and lookups
190impl PartialEq for Box<dyn Tool> {
191    fn eq(&self, other: &Self) -> bool {
192        self.name() == other.name()
193    }
194}
195impl Eq for Box<dyn Tool> {}
196impl std::hash::Hash for Box<dyn Tool> {
197    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
198        self.name().hash(state);
199    }
200}