apply formatting
This commit is contained in:
@@ -41,7 +41,10 @@ type AudioResult<T> = Result<T, AudioError>;
|
||||
|
||||
impl AudioClient {
|
||||
fn get(&self, url: Url) -> reqwest::RequestBuilder {
|
||||
self.client.get(url).header("Authorization", self.authorization.clone().unwrap_or(String::new()))
|
||||
self.client.get(url).header(
|
||||
"Authorization",
|
||||
self.authorization.clone().unwrap_or(String::new()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod recording;
|
||||
pub mod client;
|
||||
pub mod models;
|
||||
pub use client::{AudioClient, AudioClientTrait};
|
||||
pub mod recording;
|
||||
pub use client::{AudioClient, AudioClientTrait};
|
||||
|
||||
@@ -20,14 +20,14 @@ pub struct TranscriptionSegment {
|
||||
pub temperature: f32,
|
||||
pub avg_logprob: f32,
|
||||
pub compression_ratio: f32,
|
||||
pub no_speech_prob: f32
|
||||
pub no_speech_prob: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct TranscriptionResponse {
|
||||
pub text: String,
|
||||
pub segments: Vec<TranscriptionSegment>,
|
||||
pub language: String
|
||||
pub language: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
@@ -57,4 +57,4 @@ impl Default for SynthesisConfig {
|
||||
pub struct VoiceRequest {
|
||||
pub text: String,
|
||||
pub config: Option<SynthesisConfig>,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use cpal::traits::{DeviceTrait, StreamTrait};
|
||||
use cpal::{BuildStreamError, Device, SampleFormat, SizedSample, Stream, SupportedStreamConfig};
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{path, thread};
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
use hound::WavSpec;
|
||||
use log::{debug, error, info, trace};
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
use std::{path, thread};
|
||||
use thiserror::Error;
|
||||
|
||||
fn generic_sample_to_i16(sample: impl SizedSample + Into<f64>, format: SampleFormat) -> i16 {
|
||||
@@ -136,7 +136,7 @@ pub fn start(
|
||||
let thread_samples = samples.clone();
|
||||
let thread_recording_stop = recording_should_stop.clone();
|
||||
let thread_device_config = device_config.clone();
|
||||
let record_thread_handle = thread::spawn(move ||
|
||||
let record_thread_handle = thread::spawn(move || {
|
||||
start_recording_blocking_with_parameters(
|
||||
µphone,
|
||||
&thread_device_config,
|
||||
@@ -144,7 +144,7 @@ pub fn start(
|
||||
thread_samples,
|
||||
thread_recording_stop,
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
let spec = WavSpec {
|
||||
channels: device_config.channels(),
|
||||
@@ -163,25 +163,40 @@ pub fn start(
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn stop_and_take_data(handler: Arc<Mutex<Option<RecordingHandler>>>) -> Result<(Vec<i16>, WavSpec), RecordingError> {
|
||||
let handler = handler.lock().map_err(|_|RecordingError::ThreadPoison)?.take().ok_or(RecordingError::RecordingAlreadyStopped)?;
|
||||
pub fn stop_and_take_data(
|
||||
handler: Arc<Mutex<Option<RecordingHandler>>>,
|
||||
) -> Result<(Vec<i16>, WavSpec), RecordingError> {
|
||||
let handler = handler
|
||||
.lock()
|
||||
.map_err(|_| RecordingError::ThreadPoison)?
|
||||
.take()
|
||||
.ok_or(RecordingError::RecordingAlreadyStopped)?;
|
||||
|
||||
let samples = handler.samples.clone();
|
||||
let spec = handler.spec.clone();
|
||||
|
||||
handler.stop_recording()?;
|
||||
|
||||
Ok((samples.lock().map_err(|_|RecordingError::ThreadPoison)?.to_vec(), spec))
|
||||
Ok((
|
||||
samples
|
||||
.lock()
|
||||
.map_err(|_| RecordingError::ThreadPoison)?
|
||||
.to_vec(),
|
||||
spec,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn samples_to_wav(
|
||||
samples: impl IntoIterator<Item = i16>,
|
||||
spec: &WavSpec,
|
||||
filepath: impl AsRef<path::Path>
|
||||
filepath: impl AsRef<path::Path>,
|
||||
) -> Result<Vec<u8>, hound::Error> {
|
||||
let wav_bytes = std::io::Cursor::new(Vec::<u8>::new());
|
||||
|
||||
trace!("creating a wav file writer to {}", filepath.as_ref().display());
|
||||
trace!(
|
||||
"creating a wav file writer to {}",
|
||||
filepath.as_ref().display()
|
||||
);
|
||||
let mut file_writer = hound::WavWriter::create(&filepath, *spec)?;
|
||||
|
||||
trace!("writing samples...");
|
||||
@@ -193,4 +208,4 @@ pub fn samples_to_wav(
|
||||
file_writer.finalize()?;
|
||||
|
||||
Ok(wav_bytes.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
pub mod mcp;
|
||||
pub mod audio;
|
||||
pub mod mcp;
|
||||
|
||||
pub use mcp::chat::AgentChat;
|
||||
pub use mcp::chat::AgentChat;
|
||||
|
||||
+53
-31
@@ -1,13 +1,13 @@
|
||||
use crate::mcp::MCPClient;
|
||||
use ollama_rs::error::OllamaError;
|
||||
use ollama_rs::generation::chat::request::ChatMessageRequest;
|
||||
use ollama_rs::generation::chat::ChatMessage;
|
||||
use ollama_rs::generation::tools::ToolInfo;
|
||||
use ollama_rs::Ollama;
|
||||
use rmcp::model::{CallToolRequestParams, CallToolResult};
|
||||
use ollama_rs::error::OllamaError;
|
||||
use ollama_rs::generation::chat::ChatMessage;
|
||||
use ollama_rs::generation::chat::request::ChatMessageRequest;
|
||||
use ollama_rs::generation::tools::ToolInfo;
|
||||
use rmcp::ServiceError;
|
||||
use std::collections::HashMap;
|
||||
use rmcp::model::{CallToolRequestParams, CallToolResult};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq)]
|
||||
@@ -164,21 +164,29 @@ impl AgentChat {
|
||||
/// * `name`: must be in the format "mcp_server_name:tool_or_resource_name".
|
||||
///
|
||||
/// returns: Option<&RestrictedTool>
|
||||
pub fn get_tool(
|
||||
mpc_server_data: &MCPServerData,
|
||||
name: String,
|
||||
) -> Option<&RestrictedTool> {
|
||||
pub fn get_tool(mpc_server_data: &MCPServerData, name: String) -> Option<&RestrictedTool> {
|
||||
mpc_server_data
|
||||
.translated_tools
|
||||
.iter()
|
||||
.find(|tool| tool.tool_info.function.name == name)
|
||||
}
|
||||
|
||||
pub fn set_permission(&mut self, full_tool_name: String, permission: ToolPermission) -> Result<(), ChatError> {
|
||||
pub fn set_permission(
|
||||
&mut self,
|
||||
full_tool_name: String,
|
||||
permission: ToolPermission,
|
||||
) -> Result<(), ChatError> {
|
||||
let (mcp_server_name, tool_name) = Self::parse_tool_name(full_tool_name.clone())?;
|
||||
|
||||
let mcp_server_data: &mut MCPServerData = self.mcp_servers.get_mut(&mcp_server_name.clone()).ok_or(ChatError::ServiceNotFoundError(mcp_server_name.clone()))?;
|
||||
let tool_index = mcp_server_data.translated_tools.iter().position(|tool| tool.tool_info.function.name == full_tool_name).ok_or(ChatError::ToolNotFoundError(tool_name))?;
|
||||
let mcp_server_data: &mut MCPServerData = self
|
||||
.mcp_servers
|
||||
.get_mut(&mcp_server_name.clone())
|
||||
.ok_or(ChatError::ServiceNotFoundError(mcp_server_name.clone()))?;
|
||||
let tool_index = mcp_server_data
|
||||
.translated_tools
|
||||
.iter()
|
||||
.position(|tool| tool.tool_info.function.name == full_tool_name)
|
||||
.ok_or(ChatError::ToolNotFoundError(tool_name))?;
|
||||
mcp_server_data.translated_tools[tool_index].permission = permission;
|
||||
Ok(())
|
||||
}
|
||||
@@ -187,12 +195,12 @@ impl AgentChat {
|
||||
&mut self,
|
||||
user_message: String,
|
||||
mut permission_request_callback: impl FnMut(String, String) -> C,
|
||||
) -> Result<ChatMessage, ChatError>
|
||||
{
|
||||
) -> Result<ChatMessage, ChatError> {
|
||||
let all_tools: Vec<ToolInfo> = self
|
||||
.get_all_tools()
|
||||
.filter_map(|restricted_tool|{
|
||||
if restricted_tool.permission == ToolPermission::Denied { // filters denied tools to save tokens
|
||||
.filter_map(|restricted_tool| {
|
||||
if restricted_tool.permission == ToolPermission::Denied {
|
||||
// filters denied tools to save tokens
|
||||
None
|
||||
} else {
|
||||
Some(restricted_tool.tool_info.clone())
|
||||
@@ -219,16 +227,22 @@ impl AgentChat {
|
||||
|
||||
for tool_call in &response.message.tool_calls {
|
||||
log::debug!("calling tool {}", tool_call.function.name);
|
||||
let result = self.call_tool(tool_call, &mut permission_request_callback).await?;
|
||||
let result = self
|
||||
.call_tool(tool_call, &mut permission_request_callback)
|
||||
.await?;
|
||||
|
||||
// serialize structured content if it exists
|
||||
if let Some(structured_content) = result.clone().and_then(|result|result.structured_content) {
|
||||
if let Some(structured_content) =
|
||||
result.clone().and_then(|result| result.structured_content)
|
||||
{
|
||||
log::debug!("structured content: {structured_content:#?}");
|
||||
|
||||
self.message_history.push(ChatMessage::tool(structured_content.to_string()));
|
||||
self.message_history
|
||||
.push(ChatMessage::tool(structured_content.to_string()));
|
||||
} else {
|
||||
let contents = match result {
|
||||
Some(result) => result.content
|
||||
Some(result) => result
|
||||
.content
|
||||
.iter()
|
||||
.filter_map(|content| content.as_text())
|
||||
.map(|text_content| text_content.text.clone())
|
||||
@@ -270,7 +284,7 @@ impl AgentChat {
|
||||
&self,
|
||||
tool_call: &ollama_rs::generation::tools::ToolCall,
|
||||
permission_request_callback: &mut impl FnMut(String, String) -> C,
|
||||
) -> Result<Option<CallToolResult>, ChatError> {
|
||||
) -> Result<Option<CallToolResult>, ChatError> {
|
||||
let arguments_json_object = tool_call
|
||||
.function
|
||||
.arguments
|
||||
@@ -286,10 +300,10 @@ impl AgentChat {
|
||||
let mcp_server_data = self
|
||||
.get_mcp_server_by_name(mcp_server_name.clone())
|
||||
.ok_or(ChatError::ServiceNotFoundError(mcp_server_name.clone()))?;
|
||||
let restricted_tool = AgentChat::get_tool(&mcp_server_data, full_unparsed_tool_name.clone())
|
||||
.ok_or(ChatError::ToolNotFoundError(
|
||||
full_unparsed_tool_name.clone(),
|
||||
))?;
|
||||
let restricted_tool =
|
||||
AgentChat::get_tool(&mcp_server_data, full_unparsed_tool_name.clone()).ok_or(
|
||||
ChatError::ToolNotFoundError(full_unparsed_tool_name.clone()),
|
||||
)?;
|
||||
|
||||
match restricted_tool.permission {
|
||||
ToolPermission::Allowed => {
|
||||
@@ -301,7 +315,8 @@ impl AgentChat {
|
||||
}
|
||||
ToolPermission::Ask => {
|
||||
log::trace!("ask restricted tool: {:?}", restricted_tool);
|
||||
let response = permission_request_callback(mcp_server_name, tool_name.clone()).await;
|
||||
let response =
|
||||
permission_request_callback(mcp_server_name, tool_name.clone()).await;
|
||||
match response {
|
||||
PermissionAnswer::Granted => {
|
||||
log::trace!("granted restricted tool: {:?}", restricted_tool);
|
||||
@@ -323,7 +338,9 @@ impl AgentChat {
|
||||
request_params
|
||||
);
|
||||
|
||||
Ok(Some(mcp_server_data.client.call_tool(request_params).await?))
|
||||
Ok(Some(
|
||||
mcp_server_data.client.call_tool(request_params).await?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,16 +373,21 @@ mod tests {
|
||||
MCP_TEST_SERVER_URL,
|
||||
None::<String>,
|
||||
Implementation::new("test", env!("CARGO_PKG_VERSION")),
|
||||
).await.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
)]);
|
||||
|
||||
let mut chat = AgentChat::new(ollama, "lfm2.5-thinking:1.2b".to_string(), mcp_clients).await.unwrap();
|
||||
let mut chat = AgentChat::new(ollama, "lfm2.5-thinking:1.2b".to_string(), mcp_clients)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let tools: Vec<RestrictedTool> = chat.get_all_tools().cloned().collect();
|
||||
assert_eq!(tools.len(), 1);
|
||||
|
||||
assert_eq!(tools[0].permission, ToolPermission::Ask);
|
||||
chat.set_permission("test:echo".to_string(), ToolPermission::Allowed).unwrap();
|
||||
chat.set_permission("test:echo".to_string(), ToolPermission::Allowed)
|
||||
.unwrap();
|
||||
|
||||
let tools: Vec<RestrictedTool> = chat.get_all_tools().cloned().collect();
|
||||
assert_eq!(tools[0].permission, ToolPermission::Allowed);
|
||||
|
||||
+12
-7
@@ -34,24 +34,29 @@ pub async fn get_client(
|
||||
}
|
||||
|
||||
fn generate_random_mcp_server_name() -> String {
|
||||
use rand::distr::Alphanumeric;
|
||||
use rand::RngExt;
|
||||
use rand::distr::Alphanumeric;
|
||||
|
||||
let mut rng = rand::rng();
|
||||
|
||||
(0..5)
|
||||
.map(|_| rng.sample(Alphanumeric) as char)
|
||||
.collect()
|
||||
(0..5).map(|_| rng.sample(Alphanumeric) as char).collect()
|
||||
}
|
||||
|
||||
pub fn guaranteed_mcp_server_name(user_specified_name: Option<String>, client: &MCPClient) -> String {
|
||||
pub fn guaranteed_mcp_server_name(
|
||||
user_specified_name: Option<String>,
|
||||
client: &MCPClient,
|
||||
) -> String {
|
||||
if let Some(user_specified_name) = user_specified_name {
|
||||
user_specified_name
|
||||
} else if let Some(peer_info) = client.peer_info() {
|
||||
peer_info.server_info.name.clone()
|
||||
} else {
|
||||
let random_name = generate_random_mcp_server_name();
|
||||
log::warn!("no name was specified by the user or the client, so a random name had to be generated: `{}` for {:#?}", random_name, client);
|
||||
log::warn!(
|
||||
"no name was specified by the user or the client, so a random name had to be generated: `{}` for {:#?}",
|
||||
random_name,
|
||||
client
|
||||
);
|
||||
random_name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,10 +74,13 @@ fn tool_info_from_mcp_resource(
|
||||
description: format!(
|
||||
"type: {} - {}",
|
||||
mcp_resource
|
||||
.mime_type.clone()
|
||||
.mime_type
|
||||
.clone()
|
||||
.unwrap_or("No type provided".to_string()),
|
||||
mcp_resource.clone()
|
||||
.description.clone()
|
||||
mcp_resource
|
||||
.clone()
|
||||
.description
|
||||
.clone()
|
||||
.unwrap_or("No description".to_string())
|
||||
),
|
||||
parameters: schema,
|
||||
|
||||
Reference in New Issue
Block a user