improved error handling
introduced toml config
This commit is contained in:
Generated
+98
-2
@@ -6,14 +6,17 @@ version = 4
|
|||||||
name = "OwnAssist"
|
name = "OwnAssist"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
"ollama-rs",
|
"ollama-rs",
|
||||||
|
"rand 0.10.0",
|
||||||
"reqwest 0.13.2",
|
"reqwest 0.13.2",
|
||||||
"rmcp",
|
"rmcp",
|
||||||
"serde",
|
"serde",
|
||||||
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -220,6 +223,17 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures 0.3.0",
|
||||||
|
"rand_core 0.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.44"
|
version = "0.4.44"
|
||||||
@@ -294,6 +308,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@@ -607,6 +630,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi 6.0.0",
|
"r-efi 6.0.0",
|
||||||
|
"rand_core 0.10.0",
|
||||||
"wasip2",
|
"wasip2",
|
||||||
"wasip3",
|
"wasip3",
|
||||||
]
|
]
|
||||||
@@ -1160,6 +1184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "f647d8676b95a6b6205e11453c9fac338d73c9cdcc011c94d1ba9c9bfea582cd"
|
checksum = "f647d8676b95a6b6205e11453c9fac338d73c9cdcc011c94d1ba9c9bfea582cd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-stream",
|
"async-stream",
|
||||||
|
"http",
|
||||||
"log",
|
"log",
|
||||||
"ollama-rs-macros",
|
"ollama-rs-macros",
|
||||||
"reqwest 0.12.28",
|
"reqwest 0.12.28",
|
||||||
@@ -1426,6 +1451,17 @@ dependencies = [
|
|||||||
"rand_core 0.9.5",
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
|
||||||
|
dependencies = [
|
||||||
|
"chacha20",
|
||||||
|
"getrandom 0.4.2",
|
||||||
|
"rand_core 0.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -1464,6 +1500,12 @@ dependencies = [
|
|||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ref-cast"
|
name = "ref-cast"
|
||||||
version = "1.0.25"
|
version = "1.0.25"
|
||||||
@@ -1893,6 +1935,15 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@@ -1912,7 +1963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures 0.2.17",
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2192,6 +2243,45 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "1.1.2+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde_core",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_parser",
|
||||||
|
"toml_writer",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "1.1.1+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_parser"
|
||||||
|
version = "1.1.2+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||||
|
dependencies = [
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_writer"
|
||||||
|
version = "1.1.1+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@@ -2787,6 +2877,12 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.51.0"
|
version = "0.51.0"
|
||||||
|
|||||||
+5
-2
@@ -4,11 +4,14 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ollama-rs = {version = "0.3.4", features = ["macros"]}
|
ollama-rs = {version = "0.3.4", features = ["macros", "headers"]}
|
||||||
reqwest = "0.13.2"
|
reqwest = "0.13.2"
|
||||||
tokio = { version = "1.50.0", features = ["rt", "rt-multi-thread", "macros"] }
|
tokio = { version = "1.50.0", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
chrono = "0.4.44"
|
|
||||||
rmcp = {version="1.3.0", features = ["transport-streamable-http-client-reqwest", "reqwest", "client", "auth", "transport-child-process"]}
|
rmcp = {version="1.3.0", features = ["transport-streamable-http-client-reqwest", "reqwest", "client", "auth", "transport-child-process"]}
|
||||||
log = {version = "0.4.29"}
|
log = {version = "0.4.29"}
|
||||||
env_logger = "0.11.10"
|
env_logger = "0.11.10"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
thiserror = "2.0.17"
|
||||||
|
toml = "1.1.2+spec-1.1.0"
|
||||||
|
url = "2.5.8"
|
||||||
|
rand = "0.10.0"
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use log::error;
|
||||||
|
use ollama_rs::headers::{HeaderMap, HeaderValue};
|
||||||
|
use ollama_rs::Ollama;
|
||||||
|
use rmcp::model::Implementation;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use thiserror::Error;
|
||||||
|
use url::Url;
|
||||||
|
use crate::mcp;
|
||||||
|
use crate::mcp::{guaranteed_mcp_server_name, MCPClient};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
ollama: OllamaConfig,
|
||||||
|
#[serde(rename = "mcp-servers")]
|
||||||
|
mcp_servers: Vec<MCPServerConfig>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ConfigLoadingError{
|
||||||
|
#[error(transparent)]
|
||||||
|
IoError(#[from] std::io::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
ParseError(#[from] toml::de::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub async fn from_file() -> Result<Self, ConfigLoadingError> {
|
||||||
|
let toml_string = tokio::fs::read_to_string("assist.toml").await?;
|
||||||
|
Ok(toml::from_str(&toml_string)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ollama_config(&self) -> &OllamaConfig {
|
||||||
|
&self.ollama
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ollama_instance(&self) -> Ollama {
|
||||||
|
let ollama_default_url = Url::parse("http://127.0.0.1:11434").unwrap();
|
||||||
|
|
||||||
|
let mut ollama_headers = HeaderMap::new();
|
||||||
|
|
||||||
|
if let Some(authorization_header) = &self.ollama.authorization {
|
||||||
|
ollama_headers.append("Authorization", HeaderValue::from_str(&authorization_header).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ollama = Ollama::from_url(self.ollama.url.clone().unwrap_or(ollama_default_url));
|
||||||
|
ollama.set_headers(Some(ollama_headers));
|
||||||
|
|
||||||
|
ollama
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn mcp_clients(&self) -> HashMap<String, MCPClient> {
|
||||||
|
let mut mcp_clients: HashMap<String, MCPClient> = HashMap::new();
|
||||||
|
|
||||||
|
for mcp_server in &self.mcp_servers {
|
||||||
|
let client = mcp::get_client(
|
||||||
|
mcp_server.url.as_str(),
|
||||||
|
mcp_server.authorization.clone(),
|
||||||
|
Implementation::new("OwnAssist", "0.0.1"),
|
||||||
|
).await.unwrap();
|
||||||
|
|
||||||
|
let server_name = guaranteed_mcp_server_name(mcp_server.name.clone(), &client);
|
||||||
|
let server_data = client;
|
||||||
|
|
||||||
|
mcp_clients.insert(server_name.clone(), server_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
mcp_clients
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct OllamaConfig {
|
||||||
|
pub url: Option<Url>,
|
||||||
|
pub model: OllamaModelConfig,
|
||||||
|
pub authorization: Option<String>, // TODO: implement
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct OllamaModelConfig {
|
||||||
|
pub from_model: String,
|
||||||
|
pub name: String,
|
||||||
|
pub context_size: Option<u64>,
|
||||||
|
pub temperature: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct MCPServerConfig {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub url: Url,
|
||||||
|
pub authorization: Option<String>, // should probably implement oauth some time
|
||||||
|
}
|
||||||
+36
-65
@@ -1,76 +1,46 @@
|
|||||||
pub mod mcp;
|
pub mod mcp;
|
||||||
|
mod config;
|
||||||
|
mod model;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use chrono::Datelike;
|
|
||||||
use ollama_rs::Ollama;
|
use ollama_rs::Ollama;
|
||||||
use ollama_rs::generation::chat::ChatMessage;
|
use reqwest::header::{HeaderMap, HeaderValue};
|
||||||
use ollama_rs::generation::chat::request::ChatMessageRequest;
|
use rmcp::model::{Implementation};
|
||||||
use rmcp::model::{Implementation, InitializedNotificationMethod};
|
use url::Url;
|
||||||
|
use crate::config::Config;
|
||||||
/// Get the current local datetime as an iso string
|
use crate::mcp::chat::MCPServerData;
|
||||||
///
|
use crate::mcp::guaranteed_mcp_server_name;
|
||||||
/// # Arguments
|
use crate::model::create_model_from_config;
|
||||||
///
|
|
||||||
/// returns: Result<String, Box<dyn Error+Sync+Send, Global>>
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
#[ollama_rs::function]
|
|
||||||
async fn get_current_iso_datetime() -> Result<String, Box<dyn std::error::Error + Sync + Send>> {
|
|
||||||
use chrono::prelude::*;
|
|
||||||
|
|
||||||
let datetime_iso = Local::now().format("%+").to_string();
|
|
||||||
|
|
||||||
println!("iso time requested: {datetime_iso}");
|
|
||||||
|
|
||||||
Ok(datetime_iso)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current weekday as a string in english
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// returns: Result<String, Box<dyn Error+Sync+Send, Global>>
|
|
||||||
#[ollama_rs::function]
|
|
||||||
async fn get_current_weekday() -> Result<String, Box<dyn std::error::Error + Sync + Send>> {
|
|
||||||
let weekday = chrono::offset::Local::now().weekday().to_string();
|
|
||||||
|
|
||||||
println!("Weekday requested: {weekday}");
|
|
||||||
Ok(weekday)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// creates a new task to be completed at given timestamp
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `name`: a short name of the task
|
|
||||||
/// * `description`: optional description, keep empty if not needed
|
|
||||||
/// * `iso_datetime`: must be in ISO 8601, specifies when user should be reminded of task. choose 15:00 if time not specified
|
|
||||||
///
|
|
||||||
/// returns: Empty String
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// create_task("Müll rausbringen".to_string(), None, "2026-11-08T16:23:47.947244200+02:00")
|
|
||||||
/// ```
|
|
||||||
#[ollama_rs::function]
|
|
||||||
async fn create_task(
|
|
||||||
name: String,
|
|
||||||
description: Option<String>,
|
|
||||||
iso_datetime: String,
|
|
||||||
) -> Result<String, Box<dyn std::error::Error + Sync + Send>> {
|
|
||||||
println!("lol, {name}, {description:?}, {iso_datetime}");
|
|
||||||
Ok(String::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
|
let config = Config::from_file().await.inspect_err(|e|{
|
||||||
|
log::error!("error loading assist.toml {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
let ollama = config.ollama_instance();
|
||||||
|
|
||||||
|
let mcp_clients = config.mcp_clients().await;
|
||||||
|
|
||||||
|
let model_name = &config.ollama_config().model.name;
|
||||||
|
|
||||||
|
create_model_from_config(&ollama, &config.ollama_config().model).await.inspect_err(|e|{
|
||||||
|
log::error!("failed creating ollama model {model_name}: {e}")
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
let mut agent_chat = mcp::chat::AgentChat::new(ollama, model_name.clone(), mcp_clients).await.inspect_err(
|
||||||
|
|e| {
|
||||||
|
log::error!("error creating agent: {}", e);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
log::debug!("chat: {:#?}", agent_chat);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
let ollama = Ollama::default();
|
let ollama = Ollama::default();
|
||||||
|
|
||||||
let fetch_client = mcp::get_client(
|
let fetch_client = mcp::get_client(
|
||||||
@@ -83,9 +53,10 @@ async fn main() {
|
|||||||
("fetch".to_string(), fetch_client),
|
("fetch".to_string(), fetch_client),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut agent_chat = mcp::chat::AgentChat::new(ollama, mcp_clients).await.unwrap();
|
let mut agent_chat = mcp::chat::AgentChat::new(ollama, "ollama:e2b".to_string(), mcp_clients).await.unwrap();
|
||||||
log::debug!("chat: {:#?}", agent_chat);
|
log::debug!("chat: {:#?}", agent_chat);
|
||||||
agent_chat.message(String::from("Was ist auf der Website https://uno.mboemer.com zu finden?")).await.unwrap();
|
agent_chat.message(String::from("Was ist auf der Website https://uno.mboemer.com zu finden?")).await.unwrap();
|
||||||
|
*/
|
||||||
|
|
||||||
/*let ollama = Ollama::default();
|
/*let ollama = Ollama::default();
|
||||||
let mut history = vec![
|
let mut history = vec![
|
||||||
|
|||||||
+74
-21
@@ -1,13 +1,14 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use ollama_rs::error::OllamaError;
|
|
||||||
use crate::mcp::MCPClient;
|
use crate::mcp::MCPClient;
|
||||||
use ollama_rs::Ollama;
|
use ollama_rs::Ollama;
|
||||||
|
use ollama_rs::error::OllamaError;
|
||||||
use ollama_rs::generation::chat::ChatMessage;
|
use ollama_rs::generation::chat::ChatMessage;
|
||||||
use ollama_rs::generation::chat::request::ChatMessageRequest;
|
use ollama_rs::generation::chat::request::ChatMessageRequest;
|
||||||
use ollama_rs::generation::tools::ToolInfo;
|
use ollama_rs::generation::tools::ToolInfo;
|
||||||
use rmcp::model::{CallToolRequestParams, CallToolResult};
|
|
||||||
use rmcp::ServiceError;
|
use rmcp::ServiceError;
|
||||||
use serde::__private228::de::content_as_str;
|
use rmcp::model::{CallToolRequestParams, CallToolResult, };
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Default)]
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
pub enum ToolPermission {
|
pub enum ToolPermission {
|
||||||
@@ -52,13 +53,27 @@ impl MCPServerData {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AgentChat {
|
pub struct AgentChat {
|
||||||
ollama_client: Ollama,
|
ollama_client: Ollama,
|
||||||
|
model: String,
|
||||||
mcp_servers: HashMap<String, MCPServerData>,
|
mcp_servers: HashMap<String, MCPServerData>,
|
||||||
message_history: Vec<ChatMessage>,
|
message_history: Vec<ChatMessage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ChatError {
|
||||||
|
#[error(transparent)]
|
||||||
|
OllamaError(#[from] OllamaError),
|
||||||
|
#[error(transparent)]
|
||||||
|
ServiceError(#[from] ServiceError),
|
||||||
|
#[error("the function name could not be parsed")]
|
||||||
|
FunctionParseError(String),
|
||||||
|
#[error("error parsing provided arguments")]
|
||||||
|
ArgumentParsingError,
|
||||||
|
}
|
||||||
|
|
||||||
impl AgentChat {
|
impl AgentChat {
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
ollama_client: Ollama,
|
ollama_client: Ollama,
|
||||||
|
model: String,
|
||||||
mcp_clients: HashMap<String, MCPClient>,
|
mcp_clients: HashMap<String, MCPClient>,
|
||||||
) -> Result<Self, ServiceError> {
|
) -> Result<Self, ServiceError> {
|
||||||
let history = vec![
|
let history = vec![
|
||||||
@@ -78,6 +93,7 @@ impl AgentChat {
|
|||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ollama_client,
|
ollama_client,
|
||||||
|
model,
|
||||||
mcp_servers: servers,
|
mcp_servers: servers,
|
||||||
message_history: history,
|
message_history: history,
|
||||||
})
|
})
|
||||||
@@ -95,19 +111,17 @@ impl AgentChat {
|
|||||||
all_tools
|
all_tools
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn message(&mut self, user_message: String) -> Result<(), OllamaError> {
|
pub async fn message(&mut self, user_message: String) -> Result<(), ChatError> {
|
||||||
let all_tools = self.get_all_tools();
|
let all_tools = self.get_all_tools();
|
||||||
|
|
||||||
log::debug!("all tools: {:#?}", all_tools);
|
log::debug!("all tools: {:#?}", all_tools);
|
||||||
|
|
||||||
let mut response = self.ollama_client
|
let mut response = self
|
||||||
|
.ollama_client
|
||||||
.send_chat_messages_with_history(
|
.send_chat_messages_with_history(
|
||||||
&mut self.message_history,
|
&mut self.message_history,
|
||||||
ChatMessageRequest::new(
|
ChatMessageRequest::new(self.model.clone(), vec![ChatMessage::user(user_message)])
|
||||||
"gemma4:e2b".to_string(),
|
.tools(all_tools.clone()),
|
||||||
vec![ChatMessage::user(user_message)],
|
|
||||||
)
|
|
||||||
.tools(all_tools),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -119,31 +133,70 @@ impl AgentChat {
|
|||||||
|
|
||||||
for tool_call in &response.message.tool_calls {
|
for tool_call in &response.message.tool_calls {
|
||||||
log::debug!("calling tool {}", tool_call.function.name);
|
log::debug!("calling tool {}", tool_call.function.name);
|
||||||
let result = self.call_tool(tool_call).await.unwrap(); // FIXME: nuh uh
|
let result = self.call_tool(tool_call).await?;
|
||||||
let contents = result.content.iter().filter_map(|content| content.as_text()).map(|text_content|text_content.text.clone()).collect::<Vec<_>>();
|
let contents = result
|
||||||
|
.content
|
||||||
|
.iter()
|
||||||
|
.filter_map(|content| content.as_text())
|
||||||
|
.map(|text_content| text_content.text.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
log::debug!("contents: {:#?}", contents);
|
log::debug!("contents: {:#?}", contents);
|
||||||
|
|
||||||
self.message_history.push(ChatMessage::tool(contents.join("\n")));
|
self.message_history
|
||||||
|
.push(ChatMessage::tool(contents.join("\n")));
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.ollama_client.send_chat_messages_with_history(&mut self.message_history, ChatMessageRequest::new("gemma4:e2b".to_string(), Vec::new())).await?;
|
response = self
|
||||||
|
.ollama_client
|
||||||
|
.send_chat_messages_with_history(
|
||||||
|
&mut self.message_history,
|
||||||
|
ChatMessageRequest::new(self.model.clone(), Vec::new()).tools(all_tools.clone()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
log::debug!("received message: {:#?}", response.message);
|
log::debug!("received message: {:#?}", response.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn call_tool(&self, tool_call: &ollama_rs::generation::tools::ToolCall) -> Result<CallToolResult, ServiceError> { // TODO: replace ServiceError handling with something more flexible
|
async fn call_tool(
|
||||||
let arguments_json_object = tool_call.function.arguments.as_object().unwrap(); // FIXME: remove unwrap
|
&self,
|
||||||
|
tool_call: &ollama_rs::generation::tools::ToolCall,
|
||||||
|
) -> Result<CallToolResult, ChatError> {
|
||||||
|
let arguments_json_object = tool_call
|
||||||
|
.function
|
||||||
|
.arguments
|
||||||
|
.as_object()
|
||||||
|
.ok_or(ChatError::ArgumentParsingError)
|
||||||
|
.inspect_err(|_| log::error!("arguments are not of type object"))?;
|
||||||
log::trace!("arguments_json_object: {:?}", arguments_json_object);
|
log::trace!("arguments_json_object: {:?}", arguments_json_object);
|
||||||
|
|
||||||
let (mcp_name, function_name) = tool_call.function.name.split_once("::").unwrap(); // FIXME: this is not how we handle errors in rust
|
let (mcp_name, function_name) =
|
||||||
|
tool_call
|
||||||
|
.function
|
||||||
|
.name
|
||||||
|
.split_once("::")
|
||||||
|
.ok_or(ChatError::FunctionParseError(
|
||||||
|
tool_call.function.name.clone(),
|
||||||
|
))?;
|
||||||
let (mcp_name, function_name) = (mcp_name.to_string(), function_name.to_string());
|
let (mcp_name, function_name) = (mcp_name.to_string(), function_name.to_string());
|
||||||
|
|
||||||
let request_params = CallToolRequestParams::new(function_name).with_arguments(arguments_json_object.clone());
|
let request_params =
|
||||||
|
CallToolRequestParams::new(function_name).with_arguments(arguments_json_object.clone());
|
||||||
|
|
||||||
log::debug!("calling tool {} with request_params: {:#?}", tool_call.function.name, request_params);
|
log::debug!(
|
||||||
self.mcp_servers.get(&mcp_name).unwrap().client.call_tool(request_params).await
|
"calling tool {} with request_params: {:#?}",
|
||||||
|
tool_call.function.name,
|
||||||
|
request_params
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(self
|
||||||
|
.mcp_servers
|
||||||
|
.get(&mcp_name)
|
||||||
|
.unwrap()
|
||||||
|
.client
|
||||||
|
.call_tool(request_params)
|
||||||
|
.await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-1
@@ -13,7 +13,7 @@ use rmcp::{
|
|||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
type MCPClient = RunningService<RoleClient, InitializeRequestParams>;
|
pub type MCPClient = RunningService<RoleClient, InitializeRequestParams>;
|
||||||
|
|
||||||
pub(crate) async fn get_client(
|
pub(crate) async fn get_client(
|
||||||
uri: impl Into<Arc<str>>,
|
uri: impl Into<Arc<str>>,
|
||||||
@@ -34,3 +34,26 @@ pub(crate) async fn get_client(
|
|||||||
log::error!("client error: {:?}", e);
|
log::error!("client error: {:?}", e);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_random_mcp_server_name() -> String {
|
||||||
|
use rand::distr::Alphanumeric;
|
||||||
|
use rand::RngExt;
|
||||||
|
|
||||||
|
let mut rng = rand::rng();
|
||||||
|
|
||||||
|
(0..5)
|
||||||
|
.map(|_| rng.sample(Alphanumeric) as char)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
random_name
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
use crate::config::OllamaModelConfig;
|
||||||
|
use ollama_rs::Ollama;
|
||||||
|
use ollama_rs::error::OllamaError;
|
||||||
|
use ollama_rs::models::ModelOptions;
|
||||||
|
use ollama_rs::models::create::{CreateModelRequest, CreateModelStatus};
|
||||||
|
|
||||||
|
pub async fn create_model_from_config(
|
||||||
|
ollama: &Ollama,
|
||||||
|
config: &OllamaModelConfig,
|
||||||
|
) -> Result<CreateModelStatus, OllamaError> {
|
||||||
|
let model_options = ModelOptions::default()
|
||||||
|
.num_ctx(config.context_size.unwrap_or(2048))// 2048 is the ollama default.
|
||||||
|
.temperature(config.temperature.unwrap_or(0.8)); // 0.8 is the ollama default
|
||||||
|
|
||||||
|
ollama
|
||||||
|
.create_model(
|
||||||
|
CreateModelRequest::new(config.name.clone())
|
||||||
|
.from_model(config.from_model.clone())
|
||||||
|
.parameters(model_options),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user