Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -751,8 +751,7 @@ mod tests {
skill_listing: Some("<available_skills>\n- pdf\n</available_skills>".to_string()),
agent_listing: Some("<available_agents>\n- Explore\n</available_agents>".to_string()),
collapsed_tool_listing: Some(
"<collapsed_tools>\n- WebFetch: Fetch readable web content.\n</collapsed_tools>"
.to_string(),
"<collapsed_tools>\n- WebFetch\n</collapsed_tools>".to_string(),
),
};
let context = PromptBuilderContext::new(r"workspace\root", None, None)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -562,18 +562,13 @@ mod tests {
.unwrap_or_else(|| panic!("{tool_name} stub should exist"));
assert!(
stub.description.contains(&format!(
"First call `GetToolSpec` with {{\"tool_name\":\"{tool_name}\"}}"
"Call `GetToolSpec` with {{\"tool_name\":\"{tool_name}\"}} before first use."
)),
"collapsed stub must point to the explicit GetToolSpec unlock flow"
);
assert_eq!(stub.parameters["type"], json!("object"));
assert_eq!(stub.parameters["additionalProperties"], json!(false));
assert_eq!(
stub.parameters["properties"]["tool_name"]["description"],
json!(format!(
"Do not supply {tool_name} arguments here while the tool is collapsed. Use GetToolSpec with {{\"tool_name\":\"{tool_name}\"}} first."
))
);
assert_eq!(stub.parameters["properties"], json!({}));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,11 @@ use crate::agentic::tools::tool_context_runtime::ToolUseContext;
use crate::util::errors::{BitFunError, BitFunResult};
use async_trait::async_trait;
use bitfun_agent_tools::{
build_get_tool_spec_collapsed_tool_entry, GetToolSpecCollapsedToolSummary,
GetToolSpecExecutionError, GET_TOOL_SPEC_TOOL_NAME,
GetToolSpecCollapsedToolSummary, GetToolSpecExecutionError, GET_TOOL_SPEC_TOOL_NAME,
};
use serde_json::Value;

const GET_TOOL_SPEC_DESCRIPTION: &str = r#"Read usage instructions for additional tools.

Some tools are collapsed: their names may appear in the tool list, but you must not call them directly until you have loaded their definition with GetToolSpec.

When the current collapsed tool listing includes a <collapsed_tools> section, use the exact tool names from that section. Before using one of those tools, first call GetToolSpec with its exact tool name to read its full description and input schema. After reading the returned definition, call the real tool directly using its own name.
const GET_TOOL_SPEC_DESCRIPTION: &str = r#"Read full schema before first calling a collapsed tool.

Do not call GetToolSpec again for a tool whose definition is already loaded in the current conversation."#;

Expand All @@ -38,9 +33,7 @@ impl GetToolSpecTool {

let collapsed_tools_list = collapsed_tools
.iter()
.map(|tool| {
build_get_tool_spec_collapsed_tool_entry(&tool.name, &tool.short_description)
})
.map(|tool| format!("- {}", tool.name))
.collect::<Vec<_>>()
.join("\n");

Expand Down Expand Up @@ -139,7 +132,7 @@ mod tests {
use std::collections::HashMap;

#[test]
fn collapsed_tools_context_uses_explicit_short_description() {
fn collapsed_tools_context_lists_names_without_short_descriptions() {
let tool_name = format!("CatalogDescriptionTestTool_{}", uuid::Uuid::new_v4());
let description = GetToolSpecTool::build_collapsed_tools_context_section(&[
bitfun_agent_tools::GetToolSpecCollapsedToolSummary {
Expand All @@ -149,8 +142,8 @@ mod tests {
])
.expect("collapsed tools section");

assert!(description.contains(&format!("- {}: Concise catalog entry.", tool_name)));
assert!(!description.contains(&format!("- {}: Verbose description first line.", tool_name)));
assert!(description.contains(&format!("- {}", tool_name)));
assert!(!description.contains("Concise catalog entry."));
}

#[tokio::test]
Expand Down
2 changes: 1 addition & 1 deletion src/crates/execution/agent-runtime/src/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const AGENT_LISTING_TITLE: &str = "# Agent Listing";
const AGENT_LISTING_GUIDANCE: &str = "Available subagent types for the Task tool:";
const COLLAPSED_TOOL_LISTING_TITLE: &str = "# Collapsed Tool Listing";
const COLLAPSED_TOOL_LISTING_GUIDANCE: &str = r#"The folling tools are intentionally collapsed. Their listed descriptions are short summaries rather than full usage instructions.
Before calling a collapsed tool, call `GetToolSpec` with its exact tool name to read its full definition and input schema.
Before calling a collapsed tool, call `GetToolSpec` with its exact tool name to read its full schema.
After reading the returned spec, call the real tool directly by its own name.
If a tool spec is already available in the current conversation, do not call `GetToolSpec` for it again."#;

Expand Down
15 changes: 3 additions & 12 deletions src/crates/execution/tool-contracts/src/framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,22 +309,13 @@ pub fn build_collapsed_tool_stub_definition(
ToolManifestDefinition::new(
tool_name,
format!(
"{} [This tool is collapsed. Do not call `{}` directly yet. First call `GetToolSpec` with {{\"tool_name\":\"{}\"}} to load its full description and input schema, then retry `{}` using the returned schema.]",
short_description, tool_name, tool_name, tool_name
"{} [This tool is collapsed. Call `GetToolSpec` with {{\"tool_name\":\"{}\"}} before first use.]",
short_description, tool_name,
),
serde_json::json!({
"type": "object",
"additionalProperties": false,
"properties": {
"tool_name": {
"type": "string",
"description": format!(
"Do not supply {} arguments here while the tool is collapsed. Use GetToolSpec with {{\"tool_name\":\"{}\"}} first.",
tool_name,
tool_name
)
}
}
"properties": {}
}),
)
}
Expand Down
13 changes: 4 additions & 9 deletions src/crates/execution/tool-contracts/tests/tool_contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1253,18 +1253,13 @@ fn collapsed_tool_stub_definition_preserves_prompt_visible_guardrail() {
assert!(stub.description.contains("Fetch a URL"));
assert!(stub
.description
.contains("First call `GetToolSpec` with {\"tool_name\":\"WebFetch\"}"));
.contains("Call `GetToolSpec` with {\"tool_name\":\"WebFetch\"} before first use."));
assert_eq!(
stub.parameters,
json!({
"type": "object",
"additionalProperties": false,
"properties": {
"tool_name": {
"type": "string",
"description": "Do not supply WebFetch arguments here while the tool is collapsed. Use GetToolSpec with {\"tool_name\":\"WebFetch\"} first."
}
}
"properties": {}
})
);
}
Expand Down Expand Up @@ -1323,7 +1318,7 @@ fn prompt_visible_manifest_builder_preserves_expanded_and_collapsed_contract() {
);
assert!(definitions[2]
.description
.contains("First call `GetToolSpec` with {\"tool_name\":\"WebFetch\"}"));
.contains("Call `GetToolSpec` with {\"tool_name\":\"WebFetch\"} before first use."));
}

#[test]
Expand Down Expand Up @@ -2199,7 +2194,7 @@ async fn contextual_manifest_resolver_preserves_runtime_visible_manifest_contrac
.expect("collapsed WebFetch stub");
assert!(web_fetch
.description
.contains("First call `GetToolSpec` with {\"tool_name\":\"WebFetch\"}"));
.contains("Call `GetToolSpec` with {\"tool_name\":\"WebFetch\"} before first use."));
assert_eq!(web_fetch.parameters["additionalProperties"], false);
}

Expand Down
Loading