add HumanInterface
add cli
This commit is contained in:
Generated
+44
@@ -287,6 +287,18 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.16.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"unicode-width",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
@@ -425,6 +437,18 @@ version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||
|
||||
[[package]]
|
||||
name = "dialoguer"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96"
|
||||
dependencies = [
|
||||
"console",
|
||||
"shell-words",
|
||||
"tempfile",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@@ -468,6 +492,12 @@ version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
@@ -1517,6 +1547,7 @@ dependencies = [
|
||||
name = "own_assist"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dialoguer",
|
||||
"env_logger",
|
||||
"log",
|
||||
"ollama-rs",
|
||||
@@ -1524,6 +1555,7 @@ dependencies = [
|
||||
"own_mcp",
|
||||
"rmcp",
|
||||
"serde",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
@@ -2271,6 +2303,12 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
@@ -2698,6 +2736,12 @@ version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
|
||||
+3
-1
@@ -12,4 +12,6 @@ env_logger = "0.11.10"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
url = "2.5.8"
|
||||
own_mcp = { path = "own_mcp" }
|
||||
own_assist_common = { path = "common" }
|
||||
own_assist_common = { path = "common" }
|
||||
thiserror = "2.0.18"
|
||||
dialoguer = "0.12.0"
|
||||
@@ -32,7 +32,7 @@ impl DateTimeHandler {
|
||||
Local::now().iso_week().week().to_string()
|
||||
}
|
||||
|
||||
#[tool(description = "return current weekday as 3-character-string (e.g. Mon, Tue, ...)", annotations(read_only_hint = true))]
|
||||
#[tool(description = "return current weekday", annotations(read_only_hint = true))]
|
||||
fn get_weekday() -> String {
|
||||
Local::now().weekday().to_string()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::io::ErrorKind;
|
||||
use std::path::Path;
|
||||
use reqwest::Client;
|
||||
use reqwest::multipart::Form;
|
||||
|
||||
@@ -7,9 +7,10 @@ use ollama_rs::Ollama;
|
||||
use rmcp::model::{CallToolRequestParams, CallToolResult};
|
||||
use rmcp::ServiceError;
|
||||
use std::collections::HashMap;
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq)]
|
||||
pub enum ToolPermission {
|
||||
Allowed,
|
||||
#[default]
|
||||
@@ -186,7 +187,7 @@ impl AgentChat {
|
||||
pub async fn message<C: Future<Output = PermissionAnswer>>(
|
||||
&mut self,
|
||||
user_message: String,
|
||||
permission_request_callback: fn(mcp_server_name: String, tool_name: String) -> C,
|
||||
mut permission_request_callback: impl FnMut(String, String) -> C,
|
||||
) -> Result<ChatMessage, ChatError>
|
||||
{
|
||||
let all_tools: Vec<ToolInfo> = self
|
||||
@@ -213,7 +214,7 @@ 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, permission_request_callback).await?;
|
||||
let result = self.call_tool(tool_call, &mut permission_request_callback).await?;
|
||||
|
||||
let contents = match result {
|
||||
Some(result) => result.content
|
||||
@@ -256,7 +257,7 @@ impl AgentChat {
|
||||
async fn call_tool<C: Future<Output = PermissionAnswer>>(
|
||||
&self,
|
||||
tool_call: &ollama_rs::generation::tools::ToolCall,
|
||||
permission_request_callback: fn(mcp_server_name: String, tool_name: String) -> C,
|
||||
permission_request_callback: &mut impl FnMut(String, String) -> C,
|
||||
) -> Result<Option<CallToolResult>, ChatError> {
|
||||
let arguments_json_object = tool_call
|
||||
.function
|
||||
|
||||
+25
-2
@@ -6,8 +6,9 @@ use own_mcp::audio::{AudioClient, AudioClientTrait};
|
||||
use rmcp::model::Implementation;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
use own_mcp::mcp;
|
||||
use own_mcp::{mcp, AgentChat};
|
||||
use own_mcp::mcp::{guaranteed_mcp_server_name, MCPClient};
|
||||
use own_mcp::mcp::chat::{ChatError, ToolPermission};
|
||||
use own_assist_common::config_from_file;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -16,7 +17,8 @@ pub struct Config {
|
||||
#[serde(rename = "audio-server")]
|
||||
audio_server: AudioServerConfig,
|
||||
#[serde(rename = "mcp-servers")]
|
||||
mcp_servers: Vec<MCPServerConfig>
|
||||
mcp_servers: Vec<MCPServerConfig>,
|
||||
permissions: Option<Vec<PermissionConfig>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -75,6 +77,20 @@ impl Config {
|
||||
|
||||
mcp_clients
|
||||
}
|
||||
|
||||
pub async fn set_tool_permissions(&self, agent_chat: &mut AgentChat) -> Result<(), ChatError> {
|
||||
if let Some(permissions) = &self.permissions && permissions.len() > 0 {
|
||||
for permission_config in permissions {
|
||||
log::info!("set permission for `{}` to {:?}", permission_config.tool_name, permission_config.permission);
|
||||
agent_chat.set_permission(permission_config.tool_name.clone(), permission_config.permission.clone())?;
|
||||
}
|
||||
}
|
||||
else {
|
||||
log::debug!("no permissions were provided.")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -97,4 +113,11 @@ pub struct MCPServerConfig {
|
||||
pub name: Option<String>,
|
||||
pub url: Url,
|
||||
pub authorization: Option<String>, // should probably implement oauth some time // actually, fuck oauth
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PermissionConfig {
|
||||
#[serde(rename = "tool-name")]
|
||||
pub tool_name: String,
|
||||
pub permission: ToolPermission
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
use crate::human_interface::{HumanInterface, HumanInterfaceError};
|
||||
use dialoguer::theme::ColorfulTheme;
|
||||
use dialoguer::{Confirm, Input};
|
||||
use own_mcp::mcp::chat::PermissionAnswer;
|
||||
|
||||
pub struct CommandLine;
|
||||
|
||||
impl HumanInterface for CommandLine {
|
||||
fn agent_message(&self, message: String) {
|
||||
println!("ai: {}", message);
|
||||
}
|
||||
|
||||
async fn expect_user_message(&self) -> Result<String, HumanInterfaceError> {
|
||||
let user_message: String = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("you")
|
||||
.interact_text()
|
||||
.map_err(|e| HumanInterfaceError::IoError(e.into()))?;
|
||||
Ok(user_message)
|
||||
}
|
||||
|
||||
async fn ask_for_permission(&self,
|
||||
mcp_server_name: String,
|
||||
tool_name: String,
|
||||
) -> Result<PermissionAnswer, HumanInterfaceError> {
|
||||
let confirmation = Confirm::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt(format!("Allow usage of {mcp_server_name}:{tool_name}"))
|
||||
.interact()
|
||||
.map_err(|e| HumanInterfaceError::IoError(e.into()))?;
|
||||
match confirmation {
|
||||
true => Ok(PermissionAnswer::Granted),
|
||||
false => Ok(PermissionAnswer::Denied),
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&self) -> Result<(), HumanInterfaceError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
pub mod cli;
|
||||
|
||||
use thiserror::Error;
|
||||
use own_mcp::mcp::chat::PermissionAnswer;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum HumanInterfaceError {
|
||||
#[error(transparent)]
|
||||
IoError(std::io::Error),
|
||||
#[error("other error in human interface: {0}")]
|
||||
Other(String)
|
||||
}
|
||||
|
||||
pub trait HumanInterface {
|
||||
fn agent_message(&self, message: String);
|
||||
|
||||
fn expect_user_message(&self) -> impl Future<Output = Result<String, HumanInterfaceError>>;
|
||||
|
||||
fn ask_for_permission(&self, mcp_server_name: String, tool_name:String) -> impl Future<Output = Result<PermissionAnswer, HumanInterfaceError>>;
|
||||
|
||||
fn run(&self) -> impl Future<Output = Result<(), HumanInterfaceError>>;
|
||||
}
|
||||
+18
-20
@@ -1,14 +1,12 @@
|
||||
mod config;
|
||||
mod model;
|
||||
mod human_interface;
|
||||
|
||||
use own_mcp::audio::{AudioClientTrait};
|
||||
use own_mcp::mcp::chat::{PermissionAnswer, ToolPermission};
|
||||
use crate::config::Config;
|
||||
use crate::model::create_model_from_config;
|
||||
|
||||
async fn permission_callback(mcp_server_name: String, tool_name:String) -> PermissionAnswer {
|
||||
PermissionAnswer::Denied // TODO: implement
|
||||
}
|
||||
use crate::human_interface::HumanInterface;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
@@ -19,45 +17,45 @@ async fn main() {
|
||||
std::process::exit(1);
|
||||
}).unwrap();
|
||||
|
||||
let audio_client = config.audio_client();
|
||||
/*let audio_client = config.audio_client();
|
||||
log::debug!("Audio server status {:?}", audio_client.status().await.inspect_err(|e|{
|
||||
log::error!("audio server error {}", e);
|
||||
std::process::exit(1);
|
||||
}).unwrap());
|
||||
}).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}");
|
||||
std::process::exit(1);
|
||||
}).unwrap();
|
||||
|
||||
let system_prompt = "Du bist ein Assistent, der per Sprache bedient wird. Du erhälst die Transkription \
|
||||
und dein Output wird per Sprache ausgegeben und sollte dementsprechend auch kurz sein. \
|
||||
und dein Output wird per Sprache ausgegeben und sollte dementsprechend auch kurz sein und kein Markdown enthalten. \
|
||||
Wichtiger als deine Antworten sind deine Aktionen.\
|
||||
Nutze bitte die tools, falls du sie brauchst um Informationen zu bekommen (z.B: über das aktuelle Datum oder den aktuellen Wochentag). \
|
||||
Nutze bitte die tools, falls du sie brauchst um Informationen zu bekommen (z.B. über das aktuelle Datum oder den aktuellen Wochentag). \
|
||||
Du bist in einem Agent Loop und kannst mehrere Tools hintereinander nutzen. \
|
||||
Falls der Nutzer das Nutzen eines Tools ablehnt, sag ihm bescheid, dass du es brauchst.".to_string();
|
||||
|
||||
let mcp_clients = config.mcp_clients().await;
|
||||
|
||||
let mut agent_chat = own_mcp::AgentChat::new(ollama, model_name.clone(), mcp_clients, system_prompt).await.inspect_err(
|
||||
|e| {
|
||||
log::error!("error creating agent: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
).unwrap();
|
||||
config.set_tool_permissions(&mut agent_chat).await.unwrap();
|
||||
|
||||
log::info!("all tools: {:#?}", agent_chat.get_all_tools().collect::<Vec<_>>());
|
||||
|
||||
agent_chat.set_permission("datetime:get_weekday".to_string(), ToolPermission::Allowed).unwrap();
|
||||
agent_chat.set_permission("datetime:get_utc_datetime".to_string(), ToolPermission::Allowed).unwrap();
|
||||
agent_chat.set_permission("datetime:get_local_datetime".to_string(), ToolPermission::Allowed).unwrap();
|
||||
agent_chat.set_permission("datetime:get_week".to_string(), ToolPermission::Allowed).unwrap();
|
||||
let human_interface = human_interface::cli::CommandLine{};
|
||||
human_interface.run().await.unwrap();
|
||||
|
||||
let answer = agent_chat.message("In welcher Kalenderwoche befinden wir uns gerade?".to_string(), permission_callback).await;
|
||||
|
||||
dbg!(answer);
|
||||
loop {
|
||||
let user_message = human_interface.expect_user_message().await.unwrap();
|
||||
let agent_message = agent_chat.message(user_message, async |mcp_server_name, tool_name|{
|
||||
human_interface.ask_for_permission(mcp_server_name, tool_name).await.expect("io should not fail")
|
||||
}).await.expect("failed communicating with agent");
|
||||
human_interface.agent_message(agent_message.content);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user