add basic i18n

This commit is contained in:
milan
2026-04-12 00:12:45 +02:00
parent 7958275438
commit 01e7da136c
8 changed files with 461 additions and 30 deletions
+326
View File
@@ -11,6 +11,28 @@ dependencies = [
"memchr",
]
[[package]]
name = "alsa"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d"
dependencies = [
"alsa-sys",
"bitflags",
"cfg-if",
"libc",
]
[[package]]
name = "alsa-sys"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad7569085a265dd3f607ebecce7458eaab2132a84393534c95b18dcbc3f31e04"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -164,6 +186,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]]
name = "bumpalo"
version = "3.20.2"
@@ -282,6 +313,50 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "coreaudio-rs"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16dd574a72a021b90c7656c474ea31d11a2f0366a8eff574186e761e0b9e3586"
dependencies = [
"bitflags",
"libc",
"objc2-audio-toolbox",
"objc2-core-audio",
"objc2-core-audio-types",
"objc2-core-foundation",
]
[[package]]
name = "cpal"
version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8942da362c0f0d895d7cac616263f2f9424edc5687364dfd1d25ef7eba506d7"
dependencies = [
"alsa",
"coreaudio-rs",
"dasp_sample",
"jni",
"js-sys",
"libc",
"mach2",
"ndk",
"ndk-context",
"num-derive",
"num-traits",
"objc2",
"objc2-audio-toolbox",
"objc2-avf-audio",
"objc2-core-audio",
"objc2-core-audio-types",
"objc2-core-foundation",
"objc2-foundation",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -344,6 +419,12 @@ dependencies = [
"syn",
]
[[package]]
name = "dasp_sample"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
[[package]]
name = "digest"
version = "0.10.7"
@@ -354,6 +435,16 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "dispatch2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags",
"objc2",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
@@ -658,6 +749,12 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hound"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
[[package]]
name = "http"
version = "1.4.0"
@@ -1080,6 +1177,15 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "mach2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea"
dependencies = [
"libc",
]
[[package]]
name = "memchr"
version = "2.8.0"
@@ -1092,6 +1198,16 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "mio"
version = "1.2.0"
@@ -1120,6 +1236,35 @@ dependencies = [
"tempfile",
]
[[package]]
name = "ndk"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
dependencies = [
"bitflags",
"jni-sys 0.3.1",
"log",
"ndk-sys",
"num_enum",
"thiserror 1.0.69",
]
[[package]]
name = "ndk-context"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
[[package]]
name = "ndk-sys"
version = "0.6.0+11769913"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
dependencies = [
"jni-sys 0.3.1",
]
[[package]]
name = "nix"
version = "0.31.2"
@@ -1132,6 +1277,17 @@ dependencies = [
"libc",
]
[[package]]
name = "num-derive"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -1141,6 +1297,28 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num_enum"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
dependencies = [
"num_enum_derive",
"rustversion",
]
[[package]]
name = "num_enum_derive"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "oauth2"
version = "5.0.0"
@@ -1160,6 +1338,95 @@ dependencies = [
"url",
]
[[package]]
name = "objc2"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-audio-toolbox"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
dependencies = [
"bitflags",
"libc",
"objc2",
"objc2-core-audio",
"objc2-core-audio-types",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-avf-audio"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-audio"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
dependencies = [
"dispatch2",
"objc2",
"objc2-core-audio-types",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-core-audio-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
dependencies = [
"bitflags",
"objc2",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
"block2",
"dispatch2",
"libc",
"objc2",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags",
"block2",
"libc",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "ollama-rs"
version = "0.3.4"
@@ -1250,7 +1517,10 @@ dependencies = [
name = "own_mcp"
version = "0.1.0"
dependencies = [
"bytes",
"cpal",
"env_logger",
"hound",
"log",
"ollama-rs",
"rand 0.10.0",
@@ -1329,6 +1599,15 @@ dependencies = [
"syn",
]
[[package]]
name = "proc-macro-crate"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -1611,6 +1890,7 @@ dependencies = [
"js-sys",
"log",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
"quinn",
@@ -1619,6 +1899,7 @@ dependencies = [
"rustls-platform-verifier",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-rustls",
@@ -2233,6 +2514,36 @@ dependencies = [
"tokio",
]
[[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_edit"
version = "0.25.11+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
dependencies = [
"indexmap",
"toml_datetime",
"toml_parser",
"winnow",
]
[[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 = "tower"
version = "0.5.3"
@@ -2321,6 +2632,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-ident"
version = "1.0.24"
@@ -2902,6 +3219,15 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winnow"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
-1
View File
@@ -7,7 +7,6 @@ use std::thread::JoinHandle;
use std::time::Duration;
use hound::WavSpec;
use log::{debug, error, info, trace};
use thiserror::__private18::AsDisplay;
use thiserror::Error;
fn generic_sample_to_i16(sample: impl SizedSample + Into<f64>, format: SampleFormat) -> i16 {
+9 -4
View File
@@ -103,9 +103,8 @@ impl AgentChat {
ollama_client: Ollama,
model: String,
mcp_clients: HashMap<String, MCPClient>,
system_prompt: String,
) -> Result<Self, ServiceError> {
let history = vec![ChatMessage::system(system_prompt)];
let history = vec![];
let mut servers = HashMap::new();
@@ -192,7 +191,13 @@ impl AgentChat {
{
let all_tools: Vec<ToolInfo> = self
.get_all_tools()
.map(|restricted_tool| restricted_tool.tool_info.clone())
.filter_map(|restricted_tool|{
if restricted_tool.permission == ToolPermission::Denied { // filters denied tools to save tokens
None
} else {
Some(restricted_tool.tool_info.clone())
}
})
.collect();
log::debug!("all tools: {all_tools:#?}");
@@ -347,7 +352,7 @@ mod tests {
).await.unwrap(),
)]);
let mut chat = AgentChat::new(ollama, "lfm2.5-thinking:1.2b".to_string(), mcp_clients, String::new()).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);
+17 -13
View File
@@ -13,12 +13,12 @@ use own_assist_common::config_from_file;
#[derive(Debug, Deserialize)]
pub struct Config {
permissions: Option<Vec<PermissionConfig>>,
ollama: OllamaConfig,
#[serde(rename = "audio-server")]
audio_server: AudioServerConfig,
audio_server: Option<AudioServerConfig>,
#[serde(rename = "mcp-servers")]
mcp_servers: Vec<MCPServerConfig>,
permissions: Option<Vec<PermissionConfig>>,
}
#[derive(Debug, Deserialize)]
@@ -40,7 +40,7 @@ impl Config {
let mut ollama_headers = HeaderMap::new();
if let Some(authorization_header) = &self.ollama.authorization {
ollama_headers.append("Authorization", HeaderValue::from_str(&authorization_header).unwrap());
ollama_headers.append("Authorization", HeaderValue::from_str(authorization_header).unwrap());
}
let ollama_default_url = Url::parse("http://127.0.0.1:11434").unwrap();
@@ -50,16 +50,19 @@ impl Config {
ollama
}
pub fn audio_client(&self) -> AudioClient {
let mut audio_client = AudioClient::new(self.audio_server.url.clone());
if let Some(authorization) = &self.audio_server.authorization {
pub fn audio_client(&self) -> Option<AudioClient> {
if let Some(audio_server) = &self.audio_server {
let mut audio_client = AudioClient::new(audio_server.url.clone());
if let Some(authorization) = &audio_server.authorization {
audio_client = audio_client.with_authorization(authorization.clone());
}
audio_client
Some(audio_client)
} else {
None
}
}
pub async fn mcp_clients(&self) -> HashMap<String, MCPClient> {
pub async fn mcp_clients(&self) -> Result<HashMap<String, MCPClient>, rmcp::service::ClientInitializeError> {
let mut mcp_clients: HashMap<String, MCPClient> = HashMap::new();
for mcp_server in &self.mcp_servers {
@@ -67,7 +70,7 @@ impl Config {
mcp_server.url.as_str(),
mcp_server.authorization.clone(),
Implementation::new("own_assist", env!("CARGO_PKG_VERSION")),
).await.unwrap();
).await?;
let server_name = guaranteed_mcp_server_name(mcp_server.name.clone(), &client);
let server_data = client;
@@ -75,14 +78,14 @@ impl Config {
mcp_clients.insert(server_name.clone(), server_data);
}
mcp_clients
Ok(mcp_clients)
}
pub async fn set_tool_permissions(&self, agent_chat: &mut AgentChat) -> Result<(), ChatError> {
if let Some(permissions) = &self.permissions && permissions.len() > 0 {
if let Some(permissions) = &self.permissions && !permissions.is_empty() {
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())?;
agent_chat.set_permission(permission_config.tool_name.clone(), permission_config.permission)?;
}
}
else {
@@ -104,6 +107,7 @@ pub struct OllamaConfig {
pub struct OllamaModelConfig {
pub from_model: String,
pub name: String,
pub system_prompt: Option<String>,
pub context_size: Option<u64>,
pub temperature: Option<f32>,
}
+87
View File
@@ -0,0 +1,87 @@
use rmcp::serde_json;
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::{LazyLock, OnceLock, RwLock};
#[derive(Debug, Deserialize)]
struct Language {
translations: HashMap<String, String>,
}
static LANGUAGES: OnceLock<HashMap<Locale, Language>> = OnceLock::new();
static LOCALE_SELECTION: LazyLock<RwLock<Locale>> = LazyLock::new(|| RwLock::new(Locale::DE));
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Locale {
DE,
}
#[macro_export]
macro_rules! include_language {
($file:expr) => {
serde_json::from_str::<Language>(include_str!($file)).unwrap()
};
}
fn load_languages() -> &'static HashMap<Locale, Language> {
LANGUAGES
.get_or_init(|| HashMap::from([(Locale::DE, include_language!("translations/de.json"))]))
}
///
/// # Examples
///
/// ```
/// let formatted = format_dynamically("{...} is cool!".to_string(),
/// vec!["Rust".to_string()]);
/// assert_eq!(formatted, "Rust is cool!");
/// ```
fn format_dynamically(mut template: String, arguments: Vec<impl Into<String>>) -> String {
let replaced_str = "{...}";
for arg in arguments {
template = template.replacen(replaced_str, arg.into().as_str(), 1);
}
template
}
pub fn translate(template_name: String, arguments: Vec<impl Into<String>>) -> String {
let locale: Locale = LOCALE_SELECTION.read().unwrap().clone();
let languages = load_languages();
let translation = languages
.get(&locale)
.unwrap()
.translations
.get(&template_name)
.unwrap();
format_dynamically(translation.clone(), arguments)
}
#[macro_export]
macro_rules! tlt {
($template_name:tt) => {
translate($template_name.to_string(), Vec::<String>::new())
};
($template_name:tt, $($args:tt)*) => {
translate($template_name.to_string(), vec![$($args)*])
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dynamic_formatting() {
let formatted = format_dynamically("{...} is cool!".to_string(), vec!["Rust".to_string()]);
assert_eq!(formatted, "Rust is cool!");
let formatted = format_dynamically("{...}, {...} and {...} are three consecutive numbers.".to_string(), vec![
"1".to_string(),
"2".to_string(),
"3".to_string(),
]);
assert_eq!(formatted, "1, 2 and 3 are three consecutive numbers.");
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"translations": {
"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 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). 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."
}
}
+9 -9
View File
@@ -1,9 +1,11 @@
mod config;
mod model;
mod human_interface;
mod i18n;
use crate::i18n::translate;
use own_mcp::audio::{AudioClientTrait};
use own_mcp::mcp::chat::{PermissionAnswer, ToolPermission};
use crate::config::Config;
use crate::model::create_model_from_config;
use crate::human_interface::HumanInterface;
@@ -25,20 +27,18 @@ async fn main() {
let ollama = config.ollama_instance();
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 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). \
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 mcp_clients = config.mcp_clients().await.inspect_err(|e|{
log::error!("failed creating MCP clients: {e}");
std::process::exit(1);
}).unwrap();
let mut agent_chat = own_mcp::AgentChat::new(ollama, model_name.clone(), mcp_clients, system_prompt).await.inspect_err(
let mut agent_chat = own_mcp::AgentChat::new(ollama, model_name.clone(), mcp_clients).await.inspect_err(
|e| {
log::error!("error creating agent: {}", e);
std::process::exit(1);
+7 -2
View File
@@ -1,22 +1,27 @@
use crate::translate;
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};
use crate::tlt;
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.
.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
let system_prompt = config.system_prompt.clone().unwrap_or(tlt!("system_prompt"));
ollama
.create_model(
CreateModelRequest::new(config.name.clone())
.from_model(config.from_model.clone())
.parameters(model_options),
.parameters(model_options)
.system(system_prompt),
)
.await
}