feat: Added a diagnostic .info tools subcommand to make it easier to see what tools are enabled in all contexts
This commit is contained in:
@@ -671,6 +671,7 @@ bitflags::bitflags! {
|
|||||||
const SESSION = 1 << 2;
|
const SESSION = 1 << 2;
|
||||||
const RAG = 1 << 3;
|
const RAG = 1 << 3;
|
||||||
const AGENT = 1 << 4;
|
const AGENT = 1 << 4;
|
||||||
|
const FUNCTION_CALLING = 1 << 5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -371,6 +371,9 @@ impl RequestContext {
|
|||||||
if self.rag.is_some() {
|
if self.rag.is_some() {
|
||||||
flags |= StateFlags::RAG;
|
flags |= StateFlags::RAG;
|
||||||
}
|
}
|
||||||
|
if self.app.config.function_calling_support {
|
||||||
|
flags |= StateFlags::FUNCTION_CALLING;
|
||||||
|
}
|
||||||
flags
|
flags
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,6 +453,34 @@ impl RequestContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tools_info(&self) -> Result<String> {
|
||||||
|
if !self.app.config.function_calling_support {
|
||||||
|
bail!(
|
||||||
|
"Function calling is disabled. Enable it by setting `function_calling_support: true` in your config or running `.set function_calling_support true`."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let role = self.extract_role(&self.app.config)?;
|
||||||
|
match self.select_functions(&role) {
|
||||||
|
None => Ok("No tools enabled for the next request.\n".to_string()),
|
||||||
|
Some(functions) => {
|
||||||
|
let mut names: Vec<&str> = functions.iter().map(|f| f.name.as_str()).collect();
|
||||||
|
names.sort_unstable();
|
||||||
|
let mut out = format!(
|
||||||
|
"Tools enabled for the next request: {}\n\n",
|
||||||
|
functions.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
for name in names {
|
||||||
|
out.push_str(" ");
|
||||||
|
out.push_str(name);
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn list_sessions(&self) -> Vec<String> {
|
pub fn list_sessions(&self) -> Vec<String> {
|
||||||
list_file_names(self.sessions_dir(), ".yaml")
|
list_file_names(self.sessions_dir(), ".yaml")
|
||||||
}
|
}
|
||||||
@@ -4062,9 +4093,47 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn state_empty_context() {
|
fn state_empty_context_has_no_context_flags() {
|
||||||
let ctx = create_test_ctx();
|
let ctx = create_test_ctx();
|
||||||
assert_eq!(ctx.state(), StateFlags::empty());
|
|
||||||
|
let state = ctx.state();
|
||||||
|
|
||||||
|
assert!(!state.contains(StateFlags::ROLE));
|
||||||
|
assert!(!state.contains(StateFlags::SESSION));
|
||||||
|
assert!(!state.contains(StateFlags::SESSION_EMPTY));
|
||||||
|
assert!(!state.contains(StateFlags::AGENT));
|
||||||
|
assert!(!state.contains(StateFlags::RAG));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn state_includes_function_calling_when_app_enables_it() {
|
||||||
|
let ctx = create_test_ctx();
|
||||||
|
|
||||||
|
assert!(ctx.state().contains(StateFlags::FUNCTION_CALLING));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn state_omits_function_calling_when_app_disables_it() {
|
||||||
|
let app_state = {
|
||||||
|
let config = AppConfig {
|
||||||
|
function_calling_support: false,
|
||||||
|
..AppConfig::default()
|
||||||
|
};
|
||||||
|
Arc::new(AppState {
|
||||||
|
config: Arc::new(config),
|
||||||
|
vault: Arc::new(Vault::default()),
|
||||||
|
mcp_factory: Arc::new(McpFactory::default()),
|
||||||
|
rag_cache: Arc::new(RagCache::default()),
|
||||||
|
mcp_config: None,
|
||||||
|
mcp_log_path: None,
|
||||||
|
mcp_registry: None,
|
||||||
|
functions: Functions::default(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let ctx = RequestContext::new(app_state, WorkingMode::Cmd);
|
||||||
|
|
||||||
|
assert!(!ctx.state().contains(StateFlags::FUNCTION_CALLING));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -4092,6 +4161,89 @@ mod tests {
|
|||||||
assert!(state.contains(StateFlags::SESSION_EMPTY));
|
assert!(state.contains(StateFlags::SESSION_EMPTY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tools_info_returns_message_when_no_tools_enabled() {
|
||||||
|
let ctx = create_test_ctx();
|
||||||
|
|
||||||
|
let info = ctx.tools_info().unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
info.contains("No tools enabled"),
|
||||||
|
"expected 'No tools enabled' message, got: {info}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tools_info_lists_enabled_tool_names_alphabetically() {
|
||||||
|
let mut ctx = create_test_ctx();
|
||||||
|
ctx.tool_scope.functions.append_todo_functions();
|
||||||
|
let mut role = Role::new("r", "p");
|
||||||
|
role.set_enabled_tools(Some(vec!["all".to_string()]));
|
||||||
|
ctx.role = Some(role);
|
||||||
|
|
||||||
|
let info = ctx.tools_info().unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
info.contains("Tools enabled for the next request:"),
|
||||||
|
"expected count line, got: {info}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
info.contains("todo__init"),
|
||||||
|
"expected todo__init in output, got: {info}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let positions: Vec<usize> = info
|
||||||
|
.lines()
|
||||||
|
.filter(|line| line.trim().starts_with("todo__"))
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.collect();
|
||||||
|
assert!(
|
||||||
|
!positions.is_empty(),
|
||||||
|
"expected at least one todo__ entry, got: {info}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let todo_lines: Vec<&str> = info
|
||||||
|
.lines()
|
||||||
|
.filter(|line| line.trim().starts_with("todo__"))
|
||||||
|
.collect();
|
||||||
|
let mut sorted = todo_lines.clone();
|
||||||
|
sorted.sort_unstable();
|
||||||
|
assert_eq!(
|
||||||
|
todo_lines, sorted,
|
||||||
|
"expected todo__ entries to be alphabetically sorted, got: {todo_lines:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tools_info_errors_when_function_calling_disabled() {
|
||||||
|
let app_state = {
|
||||||
|
let config = AppConfig {
|
||||||
|
function_calling_support: false,
|
||||||
|
..AppConfig::default()
|
||||||
|
};
|
||||||
|
Arc::new(AppState {
|
||||||
|
config: Arc::new(config),
|
||||||
|
vault: Arc::new(Vault::default()),
|
||||||
|
mcp_factory: Arc::new(McpFactory::default()),
|
||||||
|
rag_cache: Arc::new(RagCache::default()),
|
||||||
|
mcp_config: None,
|
||||||
|
mcp_log_path: None,
|
||||||
|
mcp_registry: None,
|
||||||
|
functions: Functions::default(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let ctx = RequestContext::new(app_state, WorkingMode::Cmd);
|
||||||
|
|
||||||
|
let err = ctx.tools_info().unwrap_err();
|
||||||
|
|
||||||
|
let msg = err.to_string();
|
||||||
|
assert!(
|
||||||
|
msg.contains("Function calling is disabled"),
|
||||||
|
"expected error to mention function calling, got: {msg}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn role_info_errors_when_no_role() {
|
fn role_info_errors_when_no_role() {
|
||||||
let ctx = create_test_ctx();
|
let ctx = create_test_ctx();
|
||||||
|
|||||||
+12
-3
@@ -49,10 +49,15 @@ pub const DEFAULT_CONTINUATION_PROMPT: &str = indoc! {"
|
|||||||
4. Continue with the next pending item now. Call tools immediately."
|
4. Continue with the next pending item now. Call tools immediately."
|
||||||
};
|
};
|
||||||
|
|
||||||
static REPL_COMMANDS: LazyLock<[ReplCommand; 44]> = LazyLock::new(|| {
|
static REPL_COMMANDS: LazyLock<[ReplCommand; 45]> = LazyLock::new(|| {
|
||||||
[
|
[
|
||||||
ReplCommand::new(".help", "Show this help guide", AssertState::pass()),
|
ReplCommand::new(".help", "Show this help guide", AssertState::pass()),
|
||||||
ReplCommand::new(".info", "Show system info", AssertState::pass()),
|
ReplCommand::new(".info", "Show system info", AssertState::pass()),
|
||||||
|
ReplCommand::new(
|
||||||
|
".info tools",
|
||||||
|
"Show the list of enabled tools to be passed to the LLM",
|
||||||
|
AssertState::True(StateFlags::FUNCTION_CALLING),
|
||||||
|
),
|
||||||
ReplCommand::new(
|
ReplCommand::new(
|
||||||
".authenticate",
|
".authenticate",
|
||||||
"Authenticate the current model client via OAuth (if configured)",
|
"Authenticate the current model client via OAuth (if configured)",
|
||||||
@@ -480,6 +485,10 @@ pub async fn run_repl_command(
|
|||||||
let info = ctx.agent_info()?;
|
let info = ctx.agent_info()?;
|
||||||
print!("{info}");
|
print!("{info}");
|
||||||
}
|
}
|
||||||
|
Some("tools") => {
|
||||||
|
let info = ctx.tools_info()?;
|
||||||
|
print!("{info}");
|
||||||
|
}
|
||||||
Some(_) => unknown_command()?,
|
Some(_) => unknown_command()?,
|
||||||
None => {
|
None => {
|
||||||
let app = Arc::clone(&ctx.app.config);
|
let app = Arc::clone(&ctx.app.config);
|
||||||
@@ -1382,8 +1391,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn repl_commands_has_44_entries() {
|
fn repl_commands_has_45_entries() {
|
||||||
assert_eq!(REPL_COMMANDS.len(), 44);
|
assert_eq!(REPL_COMMANDS.len(), 45);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user