agent: Handle attempts to use hallucinated tools (#29946)
This change: 1. Catches attempts to use missing tools. If this happens, we now send Agent a message listing available tools, after which Agent can gracefully recover. Prior behavior: thread would stop in a broken state. Example of a hallucinated call and a message we send back:  2. Adds evals for hallucinated tool use and imagined edits 3. Adds ability to configure a profile name in evals. Release Notes: - N/A
This commit is contained in:
parent
7dfbe0b908
commit
8199664a5a
14 changed files with 111 additions and 0 deletions
|
@ -20,6 +20,7 @@ path = "src/explorer.rs"
|
|||
[dependencies]
|
||||
agent.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
assistant_tools.workspace = true
|
||||
async-trait.workspace = true
|
||||
|
|
|
@ -12,6 +12,7 @@ use crate::{
|
|||
};
|
||||
use agent::{ContextLoadResult, Thread, ThreadEvent};
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_settings::AgentProfileId;
|
||||
use async_trait::async_trait;
|
||||
use buffer_diff::DiffHunkStatus;
|
||||
use collections::HashMap;
|
||||
|
@ -46,6 +47,7 @@ pub struct ExampleMetadata {
|
|||
pub revision: String,
|
||||
pub language_server: Option<LanguageServer>,
|
||||
pub max_assertions: Option<usize>,
|
||||
pub profile_id: AgentProfileId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -268,6 +270,12 @@ impl ExampleContext {
|
|||
ThreadEvent::InvalidToolInput { .. } => {
|
||||
println!("{log_prefix} invalid tool input");
|
||||
}
|
||||
ThreadEvent::MissingToolUse {
|
||||
tool_use_id: _,
|
||||
ui_text,
|
||||
} => {
|
||||
println!("{log_prefix} {ui_text}");
|
||||
}
|
||||
ThreadEvent::ToolConfirmationNeeded => {
|
||||
panic!(
|
||||
"{}Bug: Tool confirmation should not be required in eval",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use assistant_settings::AgentProfileId;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::example::{Example, ExampleContext, ExampleMetadata, JudgeAssertion, LanguageServer};
|
||||
|
@ -19,6 +20,7 @@ impl Example for AddArgToTraitMethod {
|
|||
allow_preexisting_diagnostics: false,
|
||||
}),
|
||||
max_assertions: None,
|
||||
profile_id: AgentProfileId::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use assistant_settings::AgentProfileId;
|
||||
use async_trait::async_trait;
|
||||
use markdown::PathWithRange;
|
||||
|
||||
|
@ -20,6 +21,7 @@ impl Example for CodeBlockCitations {
|
|||
allow_preexisting_diagnostics: false,
|
||||
}),
|
||||
max_assertions: None,
|
||||
profile_id: AgentProfileId::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::example::{Example, ExampleContext, ExampleMetadata, JudgeAssertion};
|
||||
use anyhow::Result;
|
||||
use assistant_settings::AgentProfileId;
|
||||
use assistant_tools::StreamingEditFileToolInput;
|
||||
use async_trait::async_trait;
|
||||
|
||||
|
@ -14,6 +15,7 @@ impl Example for CommentTranslation {
|
|||
revision: "504d084e29bce4f60614bc702e91af7f7d9e60ad".to_string(),
|
||||
language_server: None,
|
||||
max_assertions: Some(1),
|
||||
profile_id: AgentProfileId::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use assistant_settings::AgentProfileId;
|
||||
use assistant_tools::FindPathToolInput;
|
||||
use async_trait::async_trait;
|
||||
use regex::Regex;
|
||||
|
@ -16,6 +17,7 @@ impl Example for FileSearchExample {
|
|||
revision: "03ecb88fe30794873f191ddb728f597935b3101c".to_string(),
|
||||
language_server: None,
|
||||
max_assertions: Some(3),
|
||||
profile_id: AgentProfileId::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
13
crates/eval/src/examples/hallucinated_tool_calls.toml
Normal file
13
crates/eval/src/examples/hallucinated_tool_calls.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
url = "https://github.com/jlowin/fastmcp"
|
||||
revision = "a2c1e14e5d83af1c32b76280ab368df199c4e860"
|
||||
language_extension = "py"
|
||||
|
||||
prompt = "Write a LICENSE file just saying 'Apache 2.0' and nothing else"
|
||||
|
||||
profile_name = "ask"
|
||||
|
||||
[thread_assertions]
|
||||
|
||||
no_edit_attempts = """The agent should not claim that it edited or created the file. It should not pretend making any changes."""
|
||||
|
||||
mention_insufficient_tools = """Agent should mention that it doesn't have relevant tools needed to make the change."""
|
|
@ -1,4 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use assistant_settings::AgentProfileId;
|
||||
use async_trait::async_trait;
|
||||
use serde::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
|
@ -56,12 +57,19 @@ impl DeclarativeExample {
|
|||
None
|
||||
};
|
||||
|
||||
let profile_id = if let Some(profile_name) = base.profile_name {
|
||||
AgentProfileId(profile_name.into())
|
||||
} else {
|
||||
AgentProfileId::default()
|
||||
};
|
||||
|
||||
let metadata = ExampleMetadata {
|
||||
name,
|
||||
url: base.url,
|
||||
revision: base.revision,
|
||||
language_server,
|
||||
max_assertions: None,
|
||||
profile_id,
|
||||
};
|
||||
|
||||
Ok(DeclarativeExample {
|
||||
|
@ -97,6 +105,8 @@ pub struct ExampleToml {
|
|||
pub allow_preexisting_diagnostics: bool,
|
||||
pub prompt: String,
|
||||
#[serde(default)]
|
||||
pub profile_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub diff_assertions: BTreeMap<String, String>,
|
||||
#[serde(default)]
|
||||
pub thread_assertions: BTreeMap<String, String>,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use assistant_settings::AgentProfileId;
|
||||
use assistant_tool::Tool;
|
||||
use assistant_tools::{OpenTool, TerminalTool};
|
||||
use async_trait::async_trait;
|
||||
|
@ -16,6 +17,7 @@ impl Example for Planets {
|
|||
revision: "59e49c75214f60b4dc4a45092292061c8c26ce27".to_string(), // so effectively a blank project.
|
||||
language_server: None,
|
||||
max_assertions: None,
|
||||
profile_id: AgentProfileId::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -307,9 +307,14 @@ impl ExampleInstance {
|
|||
std::fs::write(&last_diff_file_path, "")?;
|
||||
|
||||
let thread_store = thread_store.await?;
|
||||
|
||||
let profile_id = meta.profile_id.clone();
|
||||
thread_store.update(cx, |thread_store, cx| thread_store.load_profile_by_id(profile_id, cx)).expect("Failed to load profile");
|
||||
|
||||
let thread =
|
||||
thread_store.update(cx, |thread_store, cx| thread_store.create_thread(cx))?;
|
||||
|
||||
|
||||
thread.update(cx, |thread, _cx| {
|
||||
let mut request_count = 0;
|
||||
let previous_diff = Rc::new(RefCell::new("".to_string()));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue