Skip to main content

synaptic_models/
bound_tools.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4use synaptic_core::{
5    ChatModel, ChatRequest, ChatResponse, ChatStream, SynapseError, ToolDefinition,
6};
7
8/// A `ChatModel` wrapper that always includes a set of bound tools in every request.
9///
10/// Created via `BoundToolsChatModel::new(model, tools)`. This is the Rust equivalent
11/// of LangChain's `model.bind_tools(tools)`.
12pub struct BoundToolsChatModel {
13    inner: Arc<dyn ChatModel>,
14    tools: Vec<ToolDefinition>,
15}
16
17impl BoundToolsChatModel {
18    pub fn new(inner: Arc<dyn ChatModel>, tools: Vec<ToolDefinition>) -> Self {
19        Self { inner, tools }
20    }
21
22    fn inject_tools(&self, mut request: ChatRequest) -> ChatRequest {
23        if request.tools.is_empty() {
24            request.tools = self.tools.clone();
25        } else {
26            // Merge: add bound tools that aren't already present
27            for tool in &self.tools {
28                if !request.tools.iter().any(|t| t.name == tool.name) {
29                    request.tools.push(tool.clone());
30                }
31            }
32        }
33        request
34    }
35}
36
37#[async_trait]
38impl ChatModel for BoundToolsChatModel {
39    async fn chat(&self, request: ChatRequest) -> Result<ChatResponse, SynapseError> {
40        self.inner.chat(self.inject_tools(request)).await
41    }
42
43    fn stream_chat(&self, request: ChatRequest) -> ChatStream<'_> {
44        self.inner.stream_chat(self.inject_tools(request))
45    }
46}