swiftide_core/chat_completion/
traits.rs

1use async_trait::async_trait;
2use dyn_clone::DynClone;
3use std::borrow::Cow;
4
5use crate::{AgentContext, CommandOutput};
6
7use super::{
8    chat_completion_request::ChatCompletionRequest,
9    chat_completion_response::ChatCompletionResponse,
10    errors::{ChatCompletionError, ToolError},
11    ToolOutput, ToolSpec,
12};
13
14#[async_trait]
15pub trait ChatCompletion: Send + Sync + DynClone {
16    async fn complete(
17        &self,
18        request: &ChatCompletionRequest,
19    ) -> Result<ChatCompletionResponse, ChatCompletionError>;
20}
21
22#[async_trait]
23impl ChatCompletion for Box<dyn ChatCompletion> {
24    async fn complete(
25        &self,
26        request: &ChatCompletionRequest,
27    ) -> Result<ChatCompletionResponse, ChatCompletionError> {
28        (**self).complete(request).await
29    }
30}
31
32#[async_trait]
33impl ChatCompletion for &dyn ChatCompletion {
34    async fn complete(
35        &self,
36        request: &ChatCompletionRequest,
37    ) -> Result<ChatCompletionResponse, ChatCompletionError> {
38        (**self).complete(request).await
39    }
40}
41
42#[async_trait]
43impl<T> ChatCompletion for &T
44where
45    T: ChatCompletion + Clone + 'static,
46{
47    async fn complete(
48        &self,
49        request: &ChatCompletionRequest,
50    ) -> Result<ChatCompletionResponse, ChatCompletionError> {
51        (**self).complete(request).await
52    }
53}
54
55impl<LLM> From<&LLM> for Box<dyn ChatCompletion>
56where
57    LLM: ChatCompletion + Clone + 'static,
58{
59    fn from(llm: &LLM) -> Self {
60        Box::new(llm.clone()) as Box<dyn ChatCompletion>
61    }
62}
63
64dyn_clone::clone_trait_object!(ChatCompletion);
65
66impl From<CommandOutput> for ToolOutput {
67    fn from(value: CommandOutput) -> Self {
68        ToolOutput::Text(value.output)
69    }
70}
71
72/// The `Tool` trait is the main interface for chat completion and agent tools.
73///
74/// `swiftide-macros` provides a set of macros to generate implementations of this trait. If you
75/// need more control over the implementation, you can implement the trait manually.
76///
77/// The `ToolSpec` is what will end up with the LLM. A builder is provided. The `name` is expected
78/// to be unique, and is used to identify the tool. It should be the same as the name in the
79/// `ToolSpec`.
80#[async_trait]
81pub trait Tool: Send + Sync + DynClone {
82    // tbd
83    async fn invoke(
84        &self,
85        agent_context: &dyn AgentContext,
86        raw_args: Option<&str>,
87    ) -> Result<ToolOutput, ToolError>;
88
89    fn name(&self) -> Cow<'_, str>;
90
91    fn tool_spec(&self) -> ToolSpec;
92
93    fn boxed<'a>(self) -> Box<dyn Tool + 'a>
94    where
95        Self: Sized + 'a,
96    {
97        Box::new(self) as Box<dyn Tool>
98    }
99}
100
101#[async_trait]
102impl Tool for Box<dyn Tool> {
103    async fn invoke(
104        &self,
105        agent_context: &dyn AgentContext,
106        raw_args: Option<&str>,
107    ) -> Result<ToolOutput, ToolError> {
108        (**self).invoke(agent_context, raw_args).await
109    }
110    fn name(&self) -> Cow<'_, str> {
111        (**self).name()
112    }
113    fn tool_spec(&self) -> ToolSpec {
114        (**self).tool_spec()
115    }
116}
117
118dyn_clone::clone_trait_object!(Tool);
119
120/// Tools are identified and unique by name
121/// These allow comparison and lookups
122impl PartialEq for Box<dyn Tool> {
123    fn eq(&self, other: &Self) -> bool {
124        self.name() == other.name()
125    }
126}
127impl Eq for Box<dyn Tool> {}
128impl std::hash::Hash for Box<dyn Tool> {
129    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
130        self.name().hash(state);
131    }
132}