use std::sync::Arc;
use clap::Parser;
use colored::Colorize;
use dotenvy::dotenv_override;
use sapiens::chains::Message;
use sapiens::context::{ChatEntry, ChatEntryFormatter, ContextDump, MessageFormatter};
use sapiens::models::{Role, SupportedModel};
use sapiens::{
models, run_to_the_end, wrap_observer, ChainType, InvocationResultNotification,
ModelNotification, RuntimeObserver, SapiensConfig,
};
use tracing::info;
use tracing_subscriber::EnvFilter;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(long, default_value_t = ChainType::SingleStepOODA, value_enum, env)]
chain: ChainType,
#[arg(long, default_value_t = SupportedModel::GPT3_5Turbo, value_enum, env)]
model: SupportedModel,
#[arg(short, long, default_value_t = 10)]
max_steps: usize,
#[arg(long, default_value_t = 256)]
min_tokens_for_completion: usize,
#[arg(long)]
max_tokens: Option<usize>,
#[arg(short, long, default_value = "Tell me a joke.")]
task: String,
#[arg(long)]
show_warmup_prompt: bool,
#[arg(long, default_value_t = 0.)]
temperature: f32,
}
struct ColorFormatter;
impl ChatEntryFormatter for ColorFormatter {
fn format(&self, entry: &ChatEntry) -> String {
let msg = &entry.msg;
let role = &entry.role;
match role {
Role::System => msg.yellow().to_string(),
Role::User => msg.green().to_string(),
Role::Assistant => msg.blue().to_string(),
Role::Function => msg.red().to_string(),
Role::Tool => msg.magenta().to_string(),
}
}
}
impl MessageFormatter for ColorFormatter {
fn format(&self, msg: &Message) -> String {
msg.to_string().yellow().to_string()
}
}
#[derive(Debug)]
struct Observer {
pub show_warmup_prompt: bool,
}
#[async_trait::async_trait]
impl RuntimeObserver for Observer {
async fn on_start(&mut self, chat_history: ContextDump) {
if self.show_warmup_prompt {
let formatter = ColorFormatter {};
let msgs = chat_history.format(&formatter);
for msg in msgs {
println!("{}", msg);
println!("=============");
}
} else {
let last_msg = chat_history.messages.last().unwrap();
let msg = MessageFormatter::format(&ColorFormatter, last_msg);
println!("{}", msg);
println!("=============");
}
}
async fn on_model_update(&mut self, event: ModelNotification) {
let msg = ChatEntryFormatter::format(&ColorFormatter, &event.chat_entry);
println!("{}", msg);
println!("=============");
}
async fn on_invocation_result(&mut self, event: InvocationResultNotification) {
match event {
InvocationResultNotification::InvocationSuccess(i) => {
println!("{}", i.result.green());
}
InvocationResultNotification::InvocationFailure(i) => {
println!("{}", i.extracted_input.magenta());
println!("{}", i.e.to_string().red());
}
InvocationResultNotification::InvalidInvocation(i) => {
println!("{}", i.e.to_string().yellow());
}
}
println!("=============");
}
}
#[pyo3_asyncio::tokio::main]
async fn main() -> Result<(), pyo3::PyErr> {
let args = Args::parse();
let _ = dotenv_override();
tracing_subscriber::fmt()
.compact()
.with_env_filter(EnvFilter::try_from_default_env().unwrap_or_default())
.init();
info!("Starting sapiens_cli");
let toolbox = sapiens_tools::setup::toolbox_from_env().await;
let model = match args.model {
SupportedModel::ChatBison001 => {
let google_api_key =
std::env::var("GOOGLE_API_KEY").expect("GOOGLE_API_KEY is not set");
models::vertex_ai::build(google_api_key, Some(args.temperature))
.await
.expect("Failed to build model")
}
_ => {
let api_key = std::env::var("OPENAI_API_KEY").ok();
let api_base = std::env::var("OPENAI_API_BASE").ok();
models::openai::build(
args.model.clone(),
api_key,
api_base,
Some(args.temperature),
)
.await
.expect("Failed to build model")
}
};
let task = args.task.clone();
let config = SapiensConfig {
model,
chain_type: args.chain,
max_steps: args.max_steps,
min_tokens_for_completion: args.min_tokens_for_completion,
max_tokens: args.max_tokens,
};
for (k, _) in std::env::vars() {
std::env::remove_var(&k);
}
assert!(
std::env::vars().next().is_none(),
"Environment is not empty"
);
let observer = Observer {
show_warmup_prompt: args.show_warmup_prompt,
};
let observer = wrap_observer(observer);
let w_observer = Arc::downgrade(&observer);
let termination_messages = run_to_the_end(config, toolbox, task, w_observer).await;
if let Err(e) = termination_messages {
println!("{}", e.to_string().red());
return Ok(());
}
let termination_messages = termination_messages.unwrap();
for message in termination_messages {
println!(
"The original question was: {} ",
message.original_question.green()
);
println!("And the conclusion is: {} ", message.conclusion.blue());
}
Ok(())
}