From d645046d2d729a55345e18f97fefcab545086f53 Mon Sep 17 00:00:00 2001 From: sigoden Date: Sat, 22 Jun 2024 06:54:03 +0800 Subject: refactor: rename bot to agent (#628) --- config.example.yaml | 6 +- scripts/completions/aichat.bash | 6 +- scripts/completions/aichat.fish | 4 +- scripts/completions/aichat.nu | 8 +- scripts/completions/aichat.ps1 | 10 +- scripts/completions/aichat.zsh | 8 +- src/cli.rs | 8 +- src/config/agent.rs | 260 ++++++++++++++++++++++++++++++++++++++++ src/config/bot.rs | 260 ---------------------------------------- src/config/input.rs | 16 +-- src/config/mod.rs | 142 +++++++++++----------- src/config/session.rs | 4 +- src/function.rs | 12 +- src/main.rs | 14 +-- src/repl/mod.rs | 26 ++-- 15 files changed, 392 insertions(+), 392 deletions(-) create mode 100644 src/config/agent.rs delete mode 100644 src/config/bot.rs diff --git a/config.example.yaml b/config.example.yaml index 172a6b2..9333f5b 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -24,9 +24,9 @@ function_calling: false # e.g. 'execute_command|execute_js_code' 'execute_.*' dangerously_functions_filter: null -bot_prelude: null # Set a session to use when starting a bot. (e.g. temp, default) +agent_prelude: null # Set a session to use when starting a agent. (e.g. temp, default) -bots: +agents: - name: todo-sh model: null temperature: null @@ -74,7 +74,7 @@ summarize_prompt: 'Summarize the discussion briefly in 200 words or less to use summary_prompt: 'This is a summary of the chat history as a recap: ' # Custom REPL prompt, see https://github.com/sigoden/aichat/wiki/Custom-REPL-Prompt -left_prompt: '{color.green}{?session {?bot {bot}#}{session}{?role /}}{!session {?bot {bot}}}{role}{?rag @{rag}}{color.cyan}{?session )}{!session >}{color.reset} ' +left_prompt: '{color.green}{?session {?agent {agent}#}{session}{?role /}}{!session {?agent {agent}}}{role}{?rag @{rag}}{color.cyan}{?session )}{!session >}{color.reset} ' right_prompt: '{color.purple}{?session {?consume_tokens {consume_tokens}({consume_percent}%)}{!consume_tokens {consume_tokens}}}{color.reset}' clients: diff --git a/scripts/completions/aichat.bash b/scripts/completions/aichat.bash index adfe57d..856c6ba 100644 --- a/scripts/completions/aichat.bash +++ b/scripts/completions/aichat.bash @@ -19,7 +19,7 @@ _aichat() { case "${cmd}" in aichat) - opts="-m -r -s -b -e -c -f -H -S -w -h -V --model --prompt --role --session --save-session --bot --rag --serve --execute --code --file --no-highlight --no-stream --wrap --light-theme --dry-run --info --list-models --list-roles --list-sessions --list-bots --list-rags --help --version" + opts="-m -r -s -b -e -c -f -H -S -w -h -V --model --prompt --role --session --save-session --agent --rag --serve --execute --code --file --no-highlight --no-stream --wrap --light-theme --dry-run --info --list-models --list-roles --list-sessions --list-agents --list-rags --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -61,8 +61,8 @@ _aichat() { COMPREPLY=($(compgen -W "$("$1" --list-sessions)" -- "${cur}")) return 0 ;; - -b|--bot) - COMPREPLY=($(compgen -W "$("$1" --list-bots)" -- "${cur}")) + -b|--agent) + COMPREPLY=($(compgen -W "$("$1" --list-agents)" -- "${cur}")) return 0 ;; --rag) diff --git a/scripts/completions/aichat.fish b/scripts/completions/aichat.fish index 8761b1d..d57f29d 100644 --- a/scripts/completions/aichat.fish +++ b/scripts/completions/aichat.fish @@ -3,7 +3,7 @@ complete -c aichat -l prompt -d 'Use the system prompt' complete -c aichat -s r -l role -x -a "(aichat --list-roles)" -d 'Select a role' -r complete -c aichat -s s -l session -x -a"(aichat --list-sessions)" -d 'Start or join a session' -r complete -c aichat -l save-session -d 'Forces the session to be saved' -complete -c aichat -s b -l bot -x -a"(aichat --list-bots)" -d 'Start a bot' -r +complete -c aichat -s b -l agent -x -a"(aichat --list-agents)" -d 'Start a agent' -r complete -c aichat -l rag -x -a"(aichat --list-rags)" -d 'Start a RAG' -r complete -c aichat -s f -l file -d 'Include files with the message' -r -F complete -c aichat -s w -l wrap -d 'Control text wrapping (no, auto, )' @@ -18,7 +18,7 @@ complete -c aichat -l info -d 'Display information' complete -c aichat -l list-models -d 'List all available chat models' complete -c aichat -l list-roles -d 'List all roles' complete -c aichat -l list-sessions -d 'List all sessions' -complete -c aichat -l list-bots -d 'List all bots' +complete -c aichat -l list-agents -d 'List all agents' complete -c aichat -l list-rags -d 'List all RAGs' complete -c aichat -s h -l help -d 'Print help' complete -c aichat -s V -l version -d 'Print version' diff --git a/scripts/completions/aichat.nu b/scripts/completions/aichat.nu index c09626b..60f45f9 100644 --- a/scripts/completions/aichat.nu +++ b/scripts/completions/aichat.nu @@ -22,8 +22,8 @@ module completions { | parse "{value}" } - def "nu-complete aichat bot" [] { - ^aichat --list-bots | + def "nu-complete aichat agent" [] { + ^aichat --list-agents | | lines | parse "{value}" } @@ -41,7 +41,7 @@ module completions { --role(-r): string@"nu-complete aichat role" # Select a role --session(-s): string@"nu-complete aichat role" # Start or join a session --save-session # Forces the session to be saved - --bot(-b): string@"nu-complete aichat bot" # Start a bot + --agent(-b): string@"nu-complete aichat agent" # Start a agent --rag: string@"nu-complete aichat rag" # Start a RAG --serve # Serve the LLM API and WebAPP --execute(-e) # Execute commands in natural language @@ -56,7 +56,7 @@ module completions { --list-models # List all available chat models --list-roles # List all roles --list-sessions # List all sessions - --list-bots # List all bots + --list-agents # List all agents --list-rags # List all RAGs ...text: string # Input text --help(-h) # Print help diff --git a/scripts/completions/aichat.ps1 b/scripts/completions/aichat.ps1 index cf6cdd4..ac582d7 100644 --- a/scripts/completions/aichat.ps1 +++ b/scripts/completions/aichat.ps1 @@ -28,8 +28,8 @@ Register-ArgumentCompleter -Native -CommandName 'aichat' -ScriptBlock { [CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'Start or join a session') [CompletionResult]::new('--session', '--session', [CompletionResultType]::ParameterName, 'Start or join a session') [CompletionResult]::new('--save-session', '--save-session', [CompletionResultType]::ParameterName, 'Forces the session to be saved') - [CompletionResult]::new('-b', '-b', [CompletionResultType]::ParameterName, 'Start a bot') - [CompletionResult]::new('--bot', '--bot', [CompletionResultType]::ParameterName, 'Start a bot') + [CompletionResult]::new('-b', '-b', [CompletionResultType]::ParameterName, 'Start a agent') + [CompletionResult]::new('--agent', '--agent', [CompletionResultType]::ParameterName, 'Start a agent') [CompletionResult]::new('--rag', '--rag', [CompletionResultType]::ParameterName, 'Start a RAG') [CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'Include files with the message') [CompletionResult]::new('--file', '--file', [CompletionResultType]::ParameterName, 'Include files with the message') @@ -50,7 +50,7 @@ Register-ArgumentCompleter -Native -CommandName 'aichat' -ScriptBlock { [CompletionResult]::new('--list-models', '--list-models', [CompletionResultType]::ParameterName, 'List all available chat models') [CompletionResult]::new('--list-roles', '--list-roles', [CompletionResultType]::ParameterName, 'List all roles') [CompletionResult]::new('--list-sessions', '--list-sessions', [CompletionResultType]::ParameterName, 'List all sessions') - [CompletionResult]::new('--list-bots', '--list-bots', [CompletionResultType]::ParameterName, 'List all bots') + [CompletionResult]::new('--list-agents', '--list-agents', [CompletionResultType]::ParameterName, 'List all agents') [CompletionResult]::new('--list-rags', '--list-rags', [CompletionResultType]::ParameterName, 'List all RAGs') [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help') @@ -76,8 +76,8 @@ Register-ArgumentCompleter -Native -CommandName 'aichat' -ScriptBlock { $completions = Get-AichatValues "--list-roles" } elseif ($flag -eq "-s" -or $flag -eq "--session") { $completions = Get-AichatValues "--list-sessions" - } elseif ($flag -eq "-b" -or $flag -eq "--bot") { - $completions = Get-AichatValues "--list-bots" + } elseif ($flag -eq "-b" -or $flag -eq "--agent") { + $completions = Get-AichatValues "--list-agents" } elseif ($flag -eq "--rag") { $completions = Get-AichatValues "--list-rags" } elseif ($flag -eq "-f" -or $flag -eq "--file") { diff --git a/scripts/completions/aichat.zsh b/scripts/completions/aichat.zsh index b57b575..6d8dbc1 100644 --- a/scripts/completions/aichat.zsh +++ b/scripts/completions/aichat.zsh @@ -23,8 +23,8 @@ _aichat() { '-s+[Start or join a session]:SESSION:->sessions' \ '--session=[Start or join a session]:SESSION:->sessions' \ '--save-session[Forces the session to be saved]' \ -'-b+[Start a bot]:BOT:->bots' \ -'--bot=[Start a bot]:BOT:->bots' \ +'-b+[Start a agent]:BOT:->agents' \ +'--agent=[Start a agent]:BOT:->agents' \ '--rag=[Start a RAG]:RAG:->rags' \ '*-f+[Include files with the message]:FILE:_files' \ '*--file=[Include files with the message]:FILE:_files' \ @@ -45,7 +45,7 @@ _aichat() { '--list-models[List all available chat models]' \ '--list-roles[List all roles]' \ '--list-sessions[List all sessions]' \ -'--list-bots[List all bots]' \ +'--list-agents[List all agents]' \ '--list-rags[List all RAGs]' \ '-h[Print help]' \ '--help[Print help]' \ @@ -58,7 +58,7 @@ _aichat() { _arguments "${_arguments_options[@]}" $common \ && ret=0 case $state in - models|roles|sessions|bots|rags) + models|roles|sessions|agents|rags) local -a values expl values=( ${(f)"$(_call_program values aichat --list-$state)"} ) _wanted values expl $state compadd -a values && ret=0 diff --git a/src/cli.rs b/src/cli.rs index e9e0323..3e52af9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -18,9 +18,9 @@ pub struct Cli { /// Forces the session to be saved #[clap(long)] pub save_session: bool, - /// Start a bot + /// Start a agent #[clap(short = 'b', long)] - pub bot: Option, + pub agent: Option, /// Start a RAG #[clap(long)] pub rag: Option, @@ -63,9 +63,9 @@ pub struct Cli { /// List all sessions #[clap(long)] pub list_sessions: bool, - /// List all bots + /// List all agents #[clap(long)] - pub list_bots: bool, + pub list_agents: bool, /// List all RAGs #[clap(long)] pub list_rags: bool, diff --git a/src/config/agent.rs b/src/config/agent.rs new file mode 100644 index 0000000..91a38b4 --- /dev/null +++ b/src/config/agent.rs @@ -0,0 +1,260 @@ +use super::*; + +use crate::{ + client::Model, + function::{Functions, FunctionsFilter, SELECTED_ALL_FUNCTIONS}, +}; + +use anyhow::{Context, Result}; +use std::{fs::read_to_string, path::Path}; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize)] +pub struct Agent { + name: String, + config: AgentConfig, + definition: AgentDefinition, + #[serde(skip)] + functions: Functions, + #[serde(skip)] + rag: Option>, + #[serde(skip)] + model: Model, +} + +impl Agent { + pub async fn init( + config: &GlobalConfig, + name: &str, + abort_signal: AbortSignal, + ) -> Result { + let definition_path = Config::agent_definition_file(name)?; + let functions_path = Config::agent_functions_file(name)?; + let rag_path = Config::agent_rag_file(name)?; + let embeddings_dir = Config::agent_embeddings_dir(name)?; + let definition = AgentDefinition::load(&definition_path)?; + let functions = if functions_path.exists() { + Functions::init(&functions_path)? + } else { + Functions::default() + }; + let agent_config = config + .read() + .agents + .iter() + .find(|v| v.name == name) + .cloned() + .unwrap_or_else(|| AgentConfig::new(name)); + let model = { + let config = config.read(); + match agent_config.model_id.as_ref() { + Some(model_id) => Model::retrieve_chat(&config, model_id)?, + None => config.current_model().clone(), + } + }; + let rag = if rag_path.exists() { + Some(Arc::new(Rag::load(config, "rag", &rag_path)?)) + } else if embeddings_dir.is_dir() { + println!("The agent uses an embeddings directory, initializing RAG..."); + let doc_path = embeddings_dir.display().to_string(); + Some(Arc::new( + Rag::init(config, "rag", &rag_path, &[doc_path], abort_signal).await?, + )) + } else { + None + }; + + Ok(Self { + name: name.to_string(), + config: agent_config, + definition, + functions, + rag, + model, + }) + } + + pub fn export(&self) -> Result { + let mut value = serde_json::json!(self); + value["functions_dir"] = Config::agent_source_dir(&self.name)? + .display() + .to_string() + .into(); + value["config_dir"] = Config::agent_config_dir(&self.name)? + .display() + .to_string() + .into(); + let data = serde_yaml::to_string(&value)?; + Ok(data) + } + + pub fn banner(&self) -> String { + self.definition.banner() + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn config(&self) -> &AgentConfig { + &self.config + } + + pub fn functions(&self) -> &Functions { + &self.functions + } + + pub fn definition(&self) -> &AgentDefinition { + &self.definition + } + + pub fn rag(&self) -> Option> { + self.rag.clone() + } + + pub fn conversation_staters(&self) -> &[String] { + &self.definition.conversation_starters + } +} + +impl RoleLike for Agent { + fn to_role(&self) -> Role { + let mut role = Role::new("", &self.definition.instructions); + role.sync(self); + role + } + + fn model(&self) -> &Model { + &self.model + } + + fn model_mut(&mut self) -> &mut Model { + &mut self.model + } + + fn temperature(&self) -> Option { + self.config.temperature + } + + fn top_p(&self) -> Option { + self.config.top_p + } + + fn functions_filter(&self) -> Option { + if self.functions.is_empty() { + None + } else { + Some(SELECTED_ALL_FUNCTIONS.into()) + } + } + + fn set_model(&mut self, model: &Model) { + self.config.model_id = Some(model.id()); + self.model = model.clone(); + } + + fn set_temperature(&mut self, value: Option) { + self.config.temperature = value; + } + + fn set_top_p(&mut self, value: Option) { + self.config.top_p = value; + } + + fn set_functions_filter(&mut self, _value: Option) {} +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +pub struct AgentConfig { + pub name: String, + #[serde(rename(serialize = "model", deserialize = "model"))] + pub model_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub temperature: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub top_p: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub dangerously_functions_filter: Option, +} + +impl AgentConfig { + pub fn new(name: &str) -> Self { + Self { + name: name.to_string(), + ..Default::default() + } + } +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +pub struct AgentDefinition { + pub name: String, + #[serde(default)] + pub description: String, + #[serde(default)] + pub version: String, + pub instructions: String, + #[serde(default)] + pub conversation_starters: Vec, +} + +impl AgentDefinition { + pub fn load(path: &Path) -> Result { + let contents = read_to_string(path) + .with_context(|| format!("Failed to read agent index file at '{}'", path.display()))?; + let definition: Self = serde_yaml::from_str(&contents) + .with_context(|| format!("Failed to load agent at '{}'", path.display()))?; + Ok(definition) + } + + fn banner(&self) -> String { + let AgentDefinition { + name, + description, + version, + conversation_starters, + .. + } = self; + let starters = if conversation_starters.is_empty() { + String::new() + } else { + let starters = conversation_starters + .iter() + .map(|v| format!("- {v}")) + .collect::>() + .join("\n"); + format!( + r#" + +## Conversation Starters +{starters}"# + ) + }; + format!( + r#"# {name} {version} +{description}{starters}"# + ) + } +} + +pub fn list_agents() -> Vec { + list_agents_impl().unwrap_or_default() +} + +fn list_agents_impl() -> Result> { + let base_dir = Config::functions_dir()?; + let contents = read_to_string(base_dir.join("agents.txt"))?; + let agents = contents + .split('\n') + .filter_map(|line| { + let line = line.trim(); + if line.is_empty() { + None + } else { + Some(line.to_string()) + } + }) + .collect(); + Ok(agents) +} diff --git a/src/config/bot.rs b/src/config/bot.rs deleted file mode 100644 index 7177f92..0000000 --- a/src/config/bot.rs +++ /dev/null @@ -1,260 +0,0 @@ -use super::*; - -use crate::{ - client::Model, - function::{Functions, FunctionsFilter, SELECTED_ALL_FUNCTIONS}, -}; - -use anyhow::{Context, Result}; -use std::{fs::read_to_string, path::Path}; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize)] -pub struct Bot { - name: String, - config: BotConfig, - definition: BotDefinition, - #[serde(skip)] - functions: Functions, - #[serde(skip)] - rag: Option>, - #[serde(skip)] - model: Model, -} - -impl Bot { - pub async fn init( - config: &GlobalConfig, - name: &str, - abort_signal: AbortSignal, - ) -> Result { - let definition_path = Config::bot_definition_file(name)?; - let functions_path = Config::bot_functions_file(name)?; - let rag_path = Config::bot_rag_file(name)?; - let embeddings_dir = Config::bot_embeddings_dir(name)?; - let definition = BotDefinition::load(&definition_path)?; - let functions = if functions_path.exists() { - Functions::init(&functions_path)? - } else { - Functions::default() - }; - let bot_config = config - .read() - .bots - .iter() - .find(|v| v.name == name) - .cloned() - .unwrap_or_else(|| BotConfig::new(name)); - let model = { - let config = config.read(); - match bot_config.model_id.as_ref() { - Some(model_id) => Model::retrieve_chat(&config, model_id)?, - None => config.current_model().clone(), - } - }; - let rag = if rag_path.exists() { - Some(Arc::new(Rag::load(config, "rag", &rag_path)?)) - } else if embeddings_dir.is_dir() { - println!("The bot uses an embeddings directory, initializing RAG..."); - let doc_path = embeddings_dir.display().to_string(); - Some(Arc::new( - Rag::init(config, "rag", &rag_path, &[doc_path], abort_signal).await?, - )) - } else { - None - }; - - Ok(Self { - name: name.to_string(), - config: bot_config, - definition, - functions, - rag, - model, - }) - } - - pub fn export(&self) -> Result { - let mut value = serde_json::json!(self); - value["functions_dir"] = Config::bot_source_dir(&self.name)? - .display() - .to_string() - .into(); - value["config_dir"] = Config::bot_config_dir(&self.name)? - .display() - .to_string() - .into(); - let data = serde_yaml::to_string(&value)?; - Ok(data) - } - - pub fn banner(&self) -> String { - self.definition.banner() - } - - pub fn name(&self) -> &str { - &self.name - } - - pub fn config(&self) -> &BotConfig { - &self.config - } - - pub fn functions(&self) -> &Functions { - &self.functions - } - - pub fn definition(&self) -> &BotDefinition { - &self.definition - } - - pub fn rag(&self) -> Option> { - self.rag.clone() - } - - pub fn conversation_staters(&self) -> &[String] { - &self.definition.conversation_starters - } -} - -impl RoleLike for Bot { - fn to_role(&self) -> Role { - let mut role = Role::new("", &self.definition.instructions); - role.sync(self); - role - } - - fn model(&self) -> &Model { - &self.model - } - - fn model_mut(&mut self) -> &mut Model { - &mut self.model - } - - fn temperature(&self) -> Option { - self.config.temperature - } - - fn top_p(&self) -> Option { - self.config.top_p - } - - fn functions_filter(&self) -> Option { - if self.functions.is_empty() { - None - } else { - Some(SELECTED_ALL_FUNCTIONS.into()) - } - } - - fn set_model(&mut self, model: &Model) { - self.config.model_id = Some(model.id()); - self.model = model.clone(); - } - - fn set_temperature(&mut self, value: Option) { - self.config.temperature = value; - } - - fn set_top_p(&mut self, value: Option) { - self.config.top_p = value; - } - - fn set_functions_filter(&mut self, _value: Option) {} -} - -#[derive(Debug, Clone, Default, Deserialize, Serialize)] -pub struct BotConfig { - pub name: String, - #[serde(rename(serialize = "model", deserialize = "model"))] - pub model_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub temperature: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub top_p: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub dangerously_functions_filter: Option, -} - -impl BotConfig { - pub fn new(name: &str) -> Self { - Self { - name: name.to_string(), - ..Default::default() - } - } -} - -#[derive(Debug, Clone, Default, Deserialize, Serialize)] -pub struct BotDefinition { - pub name: String, - #[serde(default)] - pub description: String, - #[serde(default)] - pub version: String, - pub instructions: String, - #[serde(default)] - pub conversation_starters: Vec, -} - -impl BotDefinition { - pub fn load(path: &Path) -> Result { - let contents = read_to_string(path) - .with_context(|| format!("Failed to read bot index file at '{}'", path.display()))?; - let definition: Self = serde_yaml::from_str(&contents) - .with_context(|| format!("Failed to load bot at '{}'", path.display()))?; - Ok(definition) - } - - fn banner(&self) -> String { - let BotDefinition { - name, - description, - version, - conversation_starters, - .. - } = self; - let starters = if conversation_starters.is_empty() { - String::new() - } else { - let starters = conversation_starters - .iter() - .map(|v| format!("- {v}")) - .collect::>() - .join("\n"); - format!( - r#" - -## Conversation Starters -{starters}"# - ) - }; - format!( - r#"# {name} {version} -{description}{starters}"# - ) - } -} - -pub fn list_bots() -> Vec { - list_bots_impl().unwrap_or_default() -} - -fn list_bots_impl() -> Result> { - let base_dir = Config::functions_dir()?; - let contents = read_to_string(base_dir.join("bots.txt"))?; - let bots = contents - .split('\n') - .filter_map(|line| { - let line = line.trim(); - if line.is_empty() { - None - } else { - Some(line.to_string()) - } - }) - .collect(); - Ok(bots) -} diff --git a/src/config/input.rs b/src/config/input.rs index b9b1746..b0b0bd9 100644 --- a/src/config/input.rs +++ b/src/config/input.rs @@ -38,12 +38,12 @@ pub struct Input { rag_name: Option, role: Role, with_session: bool, - with_bot: bool, + with_agent: bool, } impl Input { pub fn from_str(config: &GlobalConfig, text: &str, role: Option) -> Self { - let (role, with_session, with_bot) = resolve_role(&config.read(), role); + let (role, with_session, with_agent) = resolve_role(&config.read(), role); Self { config: config.clone(), text: text.to_string(), @@ -56,7 +56,7 @@ impl Input { rag_name: None, role, with_session, - with_bot, + with_agent, } } @@ -102,7 +102,7 @@ impl Input { } } - let (role, with_session, with_bot) = resolve_role(&config.read(), role); + let (role, with_session, with_agent) = resolve_role(&config.read(), role); Ok(Self { config: config.clone(), text: texts.join("\n"), @@ -115,7 +115,7 @@ impl Input { rag_name: None, role, with_session, - with_bot, + with_agent, }) } @@ -289,8 +289,8 @@ impl Input { } } - pub fn with_bot(&self) -> bool { - self.with_bot + pub fn with_agent(&self) -> bool { + self.with_agent } pub fn summary(&self) -> String { @@ -361,7 +361,7 @@ fn resolve_role(config: &Config, role: Option) -> (Role, bool, bool) { None => ( config.extract_role(), config.session.is_some(), - config.bot.is_some(), + config.agent.is_some(), ), } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 97a6017..9cd69c5 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,9 +1,9 @@ -mod bot; +mod agent; mod input; mod role; mod session; -pub use self::bot::{list_bots, Bot, BotConfig}; +pub use self::agent::{list_agents, Agent, AgentConfig}; pub use self::input::Input; pub use self::role::{Role, RoleLike, CODE_ROLE, EXPLAIN_SHELL_ROLE, SHELL_ROLE}; use self::session::Session; @@ -46,7 +46,7 @@ const RAGS_DIR_NAME: &str = "rags"; const FUNCTIONS_DIR_NAME: &str = "functions"; const FUNCTIONS_FILE_NAME: &str = "functions.json"; const FUNCTIONS_BIN_DIR_NAME: &str = "bin"; -const BOTS_DIR_NAME: &str = "bots"; +const BOTS_DIR_NAME: &str = "agents"; const BOT_DEFINITION_FILE_NAME: &str = "index.yaml"; const BOT_EMBEDDINGS_DIR: &str = "embeddings"; const BOT_RAG_FILE_NAME: &str = "rag.bin"; @@ -75,7 +75,7 @@ And answer according to the language of the user's question. Given the context information, answer the query. Query: __INPUT__"#; -const LEFT_PROMPT: &str = "{color.green}{?session {?bot {bot}#}{session}{?role /}}{!session {?bot {bot}}}{role}{?rag @{rag}}{color.cyan}{?session )}{!session >}{color.reset} "; +const LEFT_PROMPT: &str = "{color.green}{?session {?agent {agent}#}{session}{?role /}}{!session {?agent {agent}}}{role}{?rag @{rag}}{color.cyan}{?session )}{!session >}{color.reset} "; const RIGHT_PROMPT: &str = "{color.purple}{?session {?consume_tokens {consume_tokens}({consume_percent}%)}{!consume_tokens {consume_tokens}}}{color.reset}"; #[derive(Debug, Clone, Deserialize)] @@ -99,8 +99,8 @@ pub struct Config { pub buffer_editor: Option, pub function_calling: bool, pub dangerously_functions_filter: Option, - pub bot_prelude: Option, - pub bots: Vec, + pub agent_prelude: Option, + pub agents: Vec, pub rag_embedding_model: Option, pub rag_rerank_model: Option, pub rag_chunk_size: Option, @@ -125,7 +125,7 @@ pub struct Config { #[serde(skip)] pub rag: Option>, #[serde(skip)] - pub bot: Option, + pub agent: Option, #[serde(skip)] pub model: Model, #[serde(skip)] @@ -155,8 +155,8 @@ impl Default for Config { buffer_editor: None, function_calling: false, dangerously_functions_filter: None, - bot_prelude: None, - bots: vec![], + agent_prelude: None, + agents: vec![], rag_embedding_model: None, rag_rerank_model: None, rag_chunk_size: None, @@ -176,7 +176,7 @@ impl Default for Config { role: None, session: None, rag: None, - bot: None, + agent: None, model: Default::default(), functions: Default::default(), working_mode: WorkingMode::Command, @@ -250,22 +250,22 @@ impl Config { } pub fn messages_file(&self) -> Result { - match &self.bot { + match &self.agent { None => match env::var(get_env_name("messages_file")) { Ok(value) => Ok(PathBuf::from(value)), Err(_) => Self::local_path(MESSAGES_FILE_NAME), }, - Some(bot) => Ok(Self::bot_config_dir(bot.name())?.join(MESSAGES_FILE_NAME)), + Some(agent) => Ok(Self::agent_config_dir(agent.name())?.join(MESSAGES_FILE_NAME)), } } pub fn sessions_dir(&self) -> Result { - match &self.bot { + match &self.agent { None => match env::var(get_env_name("sessions_dir")) { Ok(value) => Ok(PathBuf::from(value)), Err(_) => Self::local_path(SESSIONS_DIR_NAME), }, - Some(bot) => Ok(Self::bot_config_dir(bot.name())?.join(SESSIONS_DIR_NAME)), + Some(agent) => Ok(Self::agent_config_dir(agent.name())?.join(SESSIONS_DIR_NAME)), } } @@ -296,7 +296,7 @@ impl Config { } pub fn rag_file(&self, name: &str) -> Result { - let path = if self.bot.is_none() { + let path = if self.agent.is_none() { Self::rags_dir()?.join(format!("{name}.bin")) } else { Self::rags_dir()? @@ -306,35 +306,35 @@ impl Config { Ok(path) } - pub fn bots_dir() -> Result { - match env::var(get_env_name("bots_dir")) { + pub fn agents_dir() -> Result { + match env::var(get_env_name("agents_dir")) { Ok(value) => Ok(PathBuf::from(value)), Err(_) => Self::local_path(BOTS_DIR_NAME), } } - pub fn bot_config_dir(name: &str) -> Result { - Ok(Self::bots_dir()?.join(name)) + pub fn agent_config_dir(name: &str) -> Result { + Ok(Self::agents_dir()?.join(name)) } - pub fn bot_rag_file(name: &str) -> Result { - Ok(Self::bot_config_dir(name)?.join(BOT_RAG_FILE_NAME)) + pub fn agent_rag_file(name: &str) -> Result { + Ok(Self::agent_config_dir(name)?.join(BOT_RAG_FILE_NAME)) } - pub fn bot_source_dir(name: &str) -> Result { + pub fn agent_source_dir(name: &str) -> Result { Ok(Self::functions_dir()?.join(BOTS_DIR_NAME).join(name)) } - pub fn bot_functions_file(name: &str) -> Result { - Ok(Self::bot_source_dir(name)?.join(FUNCTIONS_FILE_NAME)) + pub fn agent_functions_file(name: &str) -> Result { + Ok(Self::agent_source_dir(name)?.join(FUNCTIONS_FILE_NAME)) } - pub fn bot_definition_file(name: &str) -> Result { - Ok(Self::bot_source_dir(name)?.join(BOT_DEFINITION_FILE_NAME)) + pub fn agent_definition_file(name: &str) -> Result { + Ok(Self::agent_source_dir(name)?.join(BOT_DEFINITION_FILE_NAME)) } - pub fn bot_embeddings_dir(name: &str) -> Result { - Ok(Self::bot_source_dir(name)?.join(BOT_EMBEDDINGS_DIR)) + pub fn agent_embeddings_dir(name: &str) -> Result { + Ok(Self::agent_source_dir(name)?.join(BOT_EMBEDDINGS_DIR)) } pub fn state(&self) -> StateFlags { @@ -346,7 +346,7 @@ impl Config { flags |= StateFlags::SESSION; } } - if self.bot.is_some() { + if self.agent.is_some() { flags |= StateFlags::BOT; } if self.role.is_some() { @@ -361,8 +361,8 @@ impl Config { pub fn current_model(&self) -> &Model { if let Some(session) = self.session.as_ref() { session.model() - } else if let Some(bot) = self.bot.as_ref() { - bot.model() + } else if let Some(agent) = self.agent.as_ref() { + agent.model() } else if let Some(role) = self.role.as_ref() { role.model() } else { @@ -373,8 +373,8 @@ impl Config { pub fn role_like_mut(&mut self) -> Option<&mut dyn RoleLike> { if let Some(session) = self.session.as_mut() { Some(session) - } else if let Some(bot) = self.bot.as_mut() { - Some(bot) + } else if let Some(agent) = self.agent.as_mut() { + Some(agent) } else if let Some(role) = self.role.as_mut() { Some(role) } else { @@ -385,8 +385,8 @@ impl Config { pub fn extract_role(&self) -> Role { let mut role = if let Some(session) = self.session.as_ref() { session.to_role() - } else if let Some(bot) = self.bot.as_ref() { - bot.to_role() + } else if let Some(agent) = self.agent.as_ref() { + agent.to_role() } else if let Some(role) = self.role.as_ref() { role.clone() } else { @@ -404,8 +404,8 @@ impl Config { } pub fn info(&self) -> Result { - if let Some(bot) = &self.bot { - let output = bot.export()?; + if let Some(agent) = &self.agent { + let output = agent.export()?; if let Some(session) = &self.session { let session = session .export()? @@ -466,7 +466,7 @@ impl Config { ("roles_file", display_path(&Self::roles_file()?)), ("functions_dir", display_path(&Self::functions_dir()?)), ("rags_dir", display_path(&Self::rags_dir()?)), - ("bots_dir", display_path(&Self::bots_dir()?)), + ("agents_dir", display_path(&Self::agents_dir()?)), ("sessions_dir", display_path(&self.sessions_dir()?)), ("messages_file", display_path(&self.messages_file()?)), ]; @@ -613,8 +613,8 @@ impl Config { } pub fn use_role_obj(&mut self, role: Role) -> Result<()> { - if self.bot.is_some() { - bail!("Cannot perform this action because you are using a bot") + if self.agent.is_some() { + bail!("Cannot perform this action because you are using a agent") } if let Some(session) = self.session.as_mut() { session.guard_empty()?; @@ -696,7 +696,7 @@ impl Config { if let Some(session) = session.as_mut() { if session.is_empty() { if let Some((input, output)) = &self.last_message { - if self.bot.is_some() == input.with_bot() { + if self.agent.is_some() == input.with_agent() { let ans = Confirm::new( "Start a session that incorporates the last question and answer?", ) @@ -840,8 +840,8 @@ impl Config { rag: Option<&str>, abort_signal: AbortSignal, ) -> Result<()> { - if config.read().bot.is_some() { - bail!("Cannot perform this action because you are using a bot") + if config.read().agent.is_some() { + bail!("Cannot perform this action because you are using a agent") } let rag = match rag { None => { @@ -911,49 +911,49 @@ impl Config { .replace("__INPUT__", text) } - pub async fn use_bot( + pub async fn use_agent( config: &GlobalConfig, name: &str, session: Option<&str>, abort_signal: AbortSignal, ) -> Result<()> { if !config.read().function_calling { - bail!("Before using the bot, please configure function calling first."); + bail!("Before using the agent, please configure function calling first."); } - if config.read().bot.is_some() { - bail!("Already in a bot, please run '.exit bot' first to exit the current bot."); + if config.read().agent.is_some() { + bail!("Already in a agent, please run '.exit agent' first to exit the current agent."); } - let bot = Bot::init(config, name, abort_signal).await?; - config.write().rag = bot.rag(); - config.write().bot = Some(bot); + let agent = Agent::init(config, name, abort_signal).await?; + config.write().rag = agent.rag(); + config.write().agent = Some(agent); let session = session .map(|v| v.to_string()) - .or_else(|| config.read().bot_prelude.clone()); + .or_else(|| config.read().agent_prelude.clone()); if let Some(session) = session { config.write().use_session(Some(&session))?; } Ok(()) } - pub fn bot_info(&self) -> Result { - if let Some(bot) = &self.bot { - bot.export() + pub fn agent_info(&self) -> Result { + if let Some(agent) = &self.agent { + agent.export() } else { - bail!("No bot") + bail!("No agent") } } - pub fn bot_banner(&self) -> Result { - if let Some(bot) = &self.bot { - Ok(bot.banner()) + pub fn agent_banner(&self) -> Result { + if let Some(agent) = &self.agent { + Ok(agent.banner()) } else { - bail!("No bot") + bail!("No agent") } } - pub fn exit_bot(&mut self) -> Result<()> { + pub fn exit_agent(&mut self) -> Result<()> { self.exit_session()?; - if self.bot.take().is_some() { + if self.agent.take().is_some() { self.rag.take(); self.last_message = None; } @@ -995,8 +995,8 @@ impl Config { if self.function_calling { let filter = role.functions_filter(); if let Some(filter) = filter { - functions = match &self.bot { - Some(bot) => bot.functions().select(&filter), + functions = match &self.agent { + Some(agent) => agent.functions().select(&filter), None => self.functions.select(&filter), }; if !model.supports_function_calling() { @@ -1014,8 +1014,8 @@ impl Config { if get_env_bool("no_dangerously_functions") { return false; } - let dangerously_functions_filter = match &self.bot { - Some(bot) => bot.config().dangerously_functions_filter.as_ref(), + let dangerously_functions_filter = match &self.agent { + Some(agent) => agent.config().dangerously_functions_filter.as_ref(), None => self.dangerously_functions_filter.as_ref(), }; match dangerously_functions_filter { @@ -1054,9 +1054,9 @@ impl Config { .map(|v| (v, None)) .collect(), ".rag" => self.list_rags().into_iter().map(|v| (v, None)).collect(), - ".bot" => list_bots().into_iter().map(|v| (v, None)).collect(), - ".starter" => match &self.bot { - Some(bot) => bot + ".agent" => list_agents().into_iter().map(|v| (v, None)).collect(), + ".starter" => match &self.agent { + Some(agent) => agent .conversation_staters() .iter() .map(|v| (v.clone(), None)) @@ -1212,8 +1212,8 @@ impl Config { if let Some(rag) = &self.rag { output.insert("rag", rag.name().to_string()); } - if let Some(bot) = &self.bot { - output.insert("bot", bot.name().to_string()); + if let Some(agent) = &self.agent { + output.insert("agent", agent.name().to_string()); } if self.highlight { @@ -1277,7 +1277,7 @@ impl Config { let timestamp = now(); let summary = input.summary(); let input_markdown = input.render(); - let scope = if self.bot.is_none() { + let scope = if self.agent.is_none() { let role_name = if input.role().is_derived() { None } else { diff --git a/src/config/session.rs b/src/config/session.rs index 726249c..3330f22 100644 --- a/src/config/session.rs +++ b/src/config/session.rs @@ -77,10 +77,10 @@ impl Session { session.name = name.to_string(); session.path = Some(path.display().to_string()); - if let Some(bot) = &config.bot { + if let Some(agent) = &config.agent { session .role_prompt - .clone_from(&bot.definition().instructions); + .clone_from(&agent.definition().instructions); } Ok(session) diff --git a/src/function.rs b/src/function.rs index bab8ca9..b91722c 100644 --- a/src/function.rs +++ b/src/function.rs @@ -163,18 +163,18 @@ impl ToolCall { pub fn eval(&self, config: &GlobalConfig) -> Result { let function_name = self.name.clone(); let is_dangerously = config.read().is_dangerously_function(&function_name); - let (call_name, cmd_name, mut cmd_args) = match &config.read().bot { - Some(bot) => { - if !bot.functions().contains(&function_name) { + let (call_name, cmd_name, mut cmd_args) = match &config.read().agent { + Some(agent) => { + if !agent.functions().contains(&function_name) { bail!( "Unexpected call: {} {function_name} {}", - bot.name(), + agent.name(), self.arguments ); } ( - format!("{}:{}", bot.name(), function_name), - bot.name().to_string(), + format!("{}:{}", agent.name(), function_name), + agent.name().to_string(), vec![function_name], ) } diff --git a/src/main.rs b/src/main.rs index d9f258a..871bd0c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,8 +16,8 @@ extern crate log; use crate::cli::Cli; use crate::client::{chat_completion_streaming, list_chat_models, ChatCompletionsOutput}; use crate::config::{ - list_bots, Config, GlobalConfig, Input, WorkingMode, CODE_ROLE, EXPLAIN_SHELL_ROLE, SHELL_ROLE, - TEMP_SESSION_NAME, + list_agents, Config, GlobalConfig, Input, WorkingMode, CODE_ROLE, EXPLAIN_SHELL_ROLE, + SHELL_ROLE, TEMP_SESSION_NAME, }; use crate::function::{eval_tool_calls, need_send_tool_results}; use crate::render::{render_error, MarkdownRender}; @@ -70,9 +70,9 @@ async fn main() -> Result<()> { .for_each(|v| println!("{}", v.name())); return Ok(()); } - if cli.list_bots { - let bots = list_bots().join("\n"); - println!("{bots}"); + if cli.list_agents { + let agents = list_agents().join("\n"); + println!("{agents}"); return Ok(()); } if cli.list_rags { @@ -90,12 +90,12 @@ async fn main() -> Result<()> { config.write().dry_run = true; } - if let Some(bot) = &cli.bot { + if let Some(agent) = &cli.agent { let session = cli.session.as_ref().map(|v| match v { Some(v) => v.as_str(), None => TEMP_SESSION_NAME, }); - Config::use_bot(&config, bot, session, abort_signal.clone()).await? + Config::use_agent(&config, agent, session, abort_signal.clone()).await? } else { if let Some(prompt) = &cli.prompt { config.write().use_prompt(prompt)?; diff --git a/src/repl/mod.rs b/src/repl/mod.rs index 1214fb5..d187956 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -102,10 +102,10 @@ lazy_static! { "Leave the rag", AssertState::TrueFalse(StateFlags::RAG, StateFlags::BOT), ), - ReplCommand::new(".bot", "Use a bot", AssertState::bare()), + ReplCommand::new(".agent", "Use a agent", AssertState::bare()), ReplCommand::new( - ".info bot", - "View bot info", + ".info agent", + "View agent info", AssertState::True(StateFlags::BOT), ), ReplCommand::new( @@ -114,8 +114,8 @@ lazy_static! { AssertState::True(StateFlags::BOT) ), ReplCommand::new( - ".exit bot", - "Leave the bot", + ".exit agent", + "Leave the agent", AssertState::True(StateFlags::BOT) ), ReplCommand::new( @@ -221,8 +221,8 @@ impl Repl { let info = self.config.read().rag_info()?; println!("{}", info); } - Some("bot") => { - let info = self.config.read().bot_info()?; + Some("agent") => { + let info = self.config.read().agent_info()?; println!("{}", info); } Some(_) => unknown_command()?, @@ -262,12 +262,12 @@ impl Repl { ".rag" => { Config::use_rag(&self.config, args, self.abort_signal.clone()).await?; } - ".bot" => match args { + ".agent" => match args { Some(name) => { - Config::use_bot(&self.config, name, None, self.abort_signal.clone()) + Config::use_agent(&self.config, name, None, self.abort_signal.clone()) .await?; } - None => println!(r#"Usage: .bot "#), + None => println!(r#"Usage: .agent "#), }, ".starter" => match args { Some(value) => { @@ -275,7 +275,7 @@ impl Repl { ask(&self.config, self.abort_signal.clone(), input, true).await?; } None => { - let banner = self.config.read().bot_banner()?; + let banner = self.config.read().agent_banner()?; let output = format!( r#"Usage: .starter ... @@ -362,8 +362,8 @@ Tips: use to autocomplete conversation starter text. Some("rag") => { self.config.write().exit_rag()?; } - Some("bot") => { - self.config.write().exit_bot()?; + Some("agent") => { + self.config.write().exit_agent()?; } Some(_) => unknown_command()?, None => { -- cgit v1.2.3