Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adjust the way of obtaining function call results #695

Merged
merged 1 commit into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,12 @@ function_calling: true # Enables or disables function calling (Globall
mapping_tools: # Alias for a tool or toolset
# fs: 'fs_cat,fs_ls,fs_mkdir,fs_rm,fs_write'
use_tools: null # Which tools to use by default
# Regex for seletecting dangerous functions
# User confirmation is required when executing these functions
# e.g. 'execute_command|execute_js_code' 'execute_.*'
dangerously_functions_filter: null
# Per-Agent configuration
agents:
- name: todo-sh
model: null
temperature: null
top_p: null
dangerously_functions_filter: null

# ---- RAG ----
rag_embedding_model: null # Specifies the embedding model to use
Expand Down
6 changes: 0 additions & 6 deletions src/config/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,6 @@ impl Agent {
&self.name
}

pub fn config(&self) -> &AgentConfig {
&self.config
}

pub fn functions(&self) -> &Functions {
&self.functions
}
Expand Down Expand Up @@ -211,8 +207,6 @@ pub struct AgentConfig {
pub top_p: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
use_tools: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dangerously_functions_filter: Option<String>,
}

impl AgentConfig {
Expand Down
23 changes: 0 additions & 23 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use crate::render::{MarkdownRender, RenderOptions};
use crate::utils::*;

use anyhow::{anyhow, bail, Context, Result};
use fancy_regex::Regex;
use indexmap::IndexMap;
use inquire::{Confirm, Select};
use parking_lot::RwLock;
Expand Down Expand Up @@ -107,7 +106,6 @@ pub struct Config {
pub function_calling: bool,
pub mapping_tools: IndexMap<String, String>,
pub use_tools: Option<String>,
pub dangerously_functions_filter: Option<String>,
pub agents: Vec<AgentConfig>,

pub rag_embedding_model: Option<String>,
Expand Down Expand Up @@ -170,7 +168,6 @@ impl Default for Config {
function_calling: true,
mapping_tools: Default::default(),
use_tools: None,
dangerously_functions_filter: None,
agents: vec![],

rag_embedding_model: None,
Expand Down Expand Up @@ -1146,26 +1143,6 @@ impl Config {
}
}

pub fn is_dangerously_function(&self, name: &str) -> bool {
if get_env_bool("no_dangerously_functions") {
return false;
}
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 {
None => false,
Some(regex) => {
let regex = match Regex::new(&format!("^({regex})$")) {
Ok(v) => v,
Err(_) => return false,
};
regex.is_match(name).unwrap_or(false)
}
}
}

pub fn buffer_editor(&self) -> Option<String> {
self.buffer_editor
.clone()
Expand Down
78 changes: 14 additions & 64 deletions src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use crate::{

use anyhow::{anyhow, bail, Context, Result};
use indexmap::IndexMap;
use inquire::{validator::Validation, Text};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::{
Expand Down Expand Up @@ -157,7 +156,6 @@ impl ToolCall {

pub fn eval(&self, config: &GlobalConfig) -> Result<Value> {
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().agent {
Some(agent) => {
if let Some(true) = agent.functions().find(&function_name).map(|v| v.agent) {
Expand Down Expand Up @@ -205,76 +203,28 @@ impl ToolCall {
if bin_dir.exists() {
envs.insert("PATH".into(), prepend_env_path(&bin_dir)?);
}
let temp_file = temp_file("-eval-", "");
envs.insert("LLM_OUTPUT".into(), temp_file.display().to_string());

#[cfg(windows)]
let cmd_name = polyfill_cmd_name(&cmd_name, &bin_dir);

let output = if is_dangerously {
if *IS_STDOUT_TERMINAL {
println!("{}", dimmed_text(&prompt));
let answer = Text::new("[1] Run, [2] Run & Retrieve, [3] Skip:")
.with_default("1")
.with_validator(|input: &str| match matches!(input, "1" | "2" | "3") {
true => Ok(Validation::Valid),
false => Ok(Validation::Invalid(
"Invalid input, please select 1, 2 or 3".into(),
)),
})
.prompt()?;
match answer.as_str() {
"1" => {
let exit_code = run_command(&cmd_name, &cmd_args, Some(envs))?;
if exit_code != 0 {
bail!("Exit {exit_code}");
}
Value::Null
}
"2" => run_and_retrieve(&cmd_name, &cmd_args, envs)?,
_ => Value::Null,
}
} else {
println!("Skipped {prompt}");
Value::Null
}
} else {
println!("{}", dimmed_text(&prompt));
run_and_retrieve(&cmd_name, &cmd_args, envs)?
};

Ok(output)
}
}

fn run_and_retrieve(
cmd_name: &str,
cmd_args: &[String],
envs: HashMap<String, String>,
) -> Result<Value> {
let (success, stdout, stderr) = run_command_with_output(cmd_name, cmd_args, Some(envs))?;

if success {
if !stderr.is_empty() {
eprintln!("{}", warning_text(&stderr));
println!("{}", dimmed_text(&prompt));
let exit_code = run_command(&cmd_name, &cmd_args, Some(envs))?;
if exit_code != 0 {
bail!("Tool call exit with {exit_code}");
}
let value = if !stdout.is_empty() {
serde_json::from_str(&stdout)
let output = if temp_file.exists() {
let contents =
fs::read_to_string(temp_file).context("Failed to retrieve tool call output")?;

serde_json::from_str(&contents)
.ok()
.unwrap_or_else(|| json!({"output": stdout}))
.unwrap_or_else(|| json!({"result": contents}))
} else {
Value::Null
};
Ok(value)
} else {
let err = if stderr.is_empty() {
if stdout.is_empty() {
"Something wrong"
} else {
&stdout
}
} else {
&stderr
};
bail!("{err}");

Ok(output)
}
}

Expand Down
13 changes: 3 additions & 10 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use anyhow::{Context, Result};
use fancy_regex::Regex;
use is_terminal::IsTerminal;
use lazy_static::lazy_static;
use std::{env, path::PathBuf};
use std::{env, path::PathBuf, process};

lazy_static! {
pub static ref CODE_BLOCK_RE: Regex = Regex::new(r"(?ms)```\w*(.*)```").unwrap();
Expand All @@ -42,14 +42,6 @@ pub fn get_env_name(key: &str) -> String {
)
}

pub fn get_env_bool(key: &str) -> bool {
if let Ok(value) = env::var(get_env_name(key)) {
value == "1" || value == "true"
} else {
false
}
}

pub fn tokenize(text: &str) -> Vec<&str> {
if text.is_ascii() {
text.split_inclusive(|c: char| c.is_ascii_whitespace())
Expand Down Expand Up @@ -158,8 +150,9 @@ pub fn dimmed_text(input: &str) -> String {

pub fn temp_file(prefix: &str, suffix: &str) -> PathBuf {
env::temp_dir().join(format!(
"{}{prefix}{}{suffix}",
"{}-{}{prefix}{}{suffix}",
env!("CARGO_CRATE_NAME").to_lowercase(),
process::id(),
uuid::Uuid::new_v4()
))
}
Expand Down