feat: add beta_supported_tools (#4669)

Gate the new read_file tool behind a new `beta_supported_tools` flag and
only enable it for `gpt-5-codex`
This commit is contained in:
jif-oai
2025-10-03 17:58:03 +01:00
committed by GitHub
parent 153338c20f
commit e0b38bd7a2
3 changed files with 57 additions and 27 deletions

View File

@@ -41,6 +41,9 @@ pub struct ModelFamily {
// Instructions to use for querying the model // Instructions to use for querying the model
pub base_instructions: String, pub base_instructions: String,
/// Names of beta tools that should be exposed to this model family.
pub experimental_supported_tools: Vec<String>,
} }
macro_rules! model_family { macro_rules! model_family {
@@ -57,6 +60,7 @@ macro_rules! model_family {
uses_local_shell_tool: false, uses_local_shell_tool: false,
apply_patch_tool_type: None, apply_patch_tool_type: None,
base_instructions: BASE_INSTRUCTIONS.to_string(), base_instructions: BASE_INSTRUCTIONS.to_string(),
experimental_supported_tools: Vec::new(),
}; };
// apply overrides // apply overrides
$( $(
@@ -105,6 +109,7 @@ pub fn find_family_for_model(slug: &str) -> Option<ModelFamily> {
supports_reasoning_summaries: true, supports_reasoning_summaries: true,
reasoning_summary_format: ReasoningSummaryFormat::Experimental, reasoning_summary_format: ReasoningSummaryFormat::Experimental,
base_instructions: GPT_5_CODEX_INSTRUCTIONS.to_string(), base_instructions: GPT_5_CODEX_INSTRUCTIONS.to_string(),
experimental_supported_tools: vec!["read_file".to_string()],
) )
} else if slug.starts_with("gpt-5") { } else if slug.starts_with("gpt-5") {
model_family!( model_family!(
@@ -127,5 +132,6 @@ pub fn derive_default_model_family(model: &str) -> ModelFamily {
uses_local_shell_tool: false, uses_local_shell_tool: false,
apply_patch_tool_type: None, apply_patch_tool_type: None,
base_instructions: BASE_INSTRUCTIONS.to_string(), base_instructions: BASE_INSTRUCTIONS.to_string(),
experimental_supported_tools: Vec::new(),
} }
} }

View File

@@ -28,6 +28,7 @@ pub(crate) struct ToolsConfig {
pub web_search_request: bool, pub web_search_request: bool,
pub include_view_image_tool: bool, pub include_view_image_tool: bool,
pub experimental_unified_exec_tool: bool, pub experimental_unified_exec_tool: bool,
pub experimental_supported_tools: Vec<String>,
} }
pub(crate) struct ToolsConfigParams<'a> { pub(crate) struct ToolsConfigParams<'a> {
@@ -78,6 +79,7 @@ impl ToolsConfig {
web_search_request: *include_web_search_request, web_search_request: *include_web_search_request,
include_view_image_tool: *include_view_image_tool, include_view_image_tool: *include_view_image_tool,
experimental_unified_exec_tool: *experimental_unified_exec_tool, experimental_unified_exec_tool: *experimental_unified_exec_tool,
experimental_supported_tools: model_family.experimental_supported_tools.clone(),
} }
} }
} }
@@ -515,7 +517,6 @@ pub(crate) fn build_specs(
let exec_stream_handler = Arc::new(ExecStreamHandler); let exec_stream_handler = Arc::new(ExecStreamHandler);
let unified_exec_handler = Arc::new(UnifiedExecHandler); let unified_exec_handler = Arc::new(UnifiedExecHandler);
let plan_handler = Arc::new(PlanHandler); let plan_handler = Arc::new(PlanHandler);
let read_file_handler = Arc::new(ReadFileHandler);
let apply_patch_handler = Arc::new(ApplyPatchHandler); let apply_patch_handler = Arc::new(ApplyPatchHandler);
let view_image_handler = Arc::new(ViewImageHandler); let view_image_handler = Arc::new(ViewImageHandler);
let mcp_handler = Arc::new(McpHandler); let mcp_handler = Arc::new(McpHandler);
@@ -566,8 +567,15 @@ pub(crate) fn build_specs(
builder.register_handler("apply_patch", apply_patch_handler); builder.register_handler("apply_patch", apply_patch_handler);
} }
builder.push_spec(create_read_file_tool()); if config
builder.register_handler("read_file", read_file_handler); .experimental_supported_tools
.iter()
.any(|tool| tool == "read_file")
{
let read_file_handler = Arc::new(ReadFileHandler);
builder.push_spec(create_read_file_tool());
builder.register_handler("read_file", read_file_handler);
}
if config.web_search_request { if config.web_search_request {
builder.push_spec(ToolSpec::WebSearch {}); builder.push_spec(ToolSpec::WebSearch {});
@@ -648,13 +656,7 @@ mod tests {
assert_eq_tool_names( assert_eq_tool_names(
&tools, &tools,
&[ &["unified_exec", "update_plan", "web_search", "view_image"],
"unified_exec",
"update_plan",
"read_file",
"web_search",
"view_image",
],
); );
} }
@@ -674,16 +676,28 @@ mod tests {
assert_eq_tool_names( assert_eq_tool_names(
&tools, &tools,
&[ &["unified_exec", "update_plan", "web_search", "view_image"],
"unified_exec",
"update_plan",
"read_file",
"web_search",
"view_image",
],
); );
} }
#[test]
fn test_build_specs_includes_beta_read_file_tool() {
let model_family = find_family_for_model("gpt-5-codex")
.expect("gpt-5-codex should be a valid model family");
let config = ToolsConfig::new(&ToolsConfigParams {
model_family: &model_family,
include_plan_tool: false,
include_apply_patch_tool: false,
include_web_search_request: false,
use_streamable_shell_tool: false,
include_view_image_tool: false,
experimental_unified_exec_tool: true,
});
let (tools, _) = build_specs(&config, Some(HashMap::new())).build();
assert_eq_tool_names(&tools, &["unified_exec", "read_file"]);
}
#[test] #[test]
fn test_build_specs_mcp_tools() { fn test_build_specs_mcp_tools() {
let model_family = find_family_for_model("o3").expect("o3 should be a valid model family"); let model_family = find_family_for_model("o3").expect("o3 should be a valid model family");
@@ -739,7 +753,6 @@ mod tests {
&tools, &tools,
&[ &[
"unified_exec", "unified_exec",
"read_file",
"web_search", "web_search",
"view_image", "view_image",
"test_server/do_something_cool", "test_server/do_something_cool",
@@ -747,7 +760,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
tools[4], tools[3],
ToolSpec::Function(ResponsesApiTool { ToolSpec::Function(ResponsesApiTool {
name: "test_server/do_something_cool".to_string(), name: "test_server/do_something_cool".to_string(),
parameters: JsonSchema::Object { parameters: JsonSchema::Object {
@@ -858,7 +871,6 @@ mod tests {
&tools, &tools,
&[ &[
"unified_exec", "unified_exec",
"read_file",
"view_image", "view_image",
"test_server/cool", "test_server/cool",
"test_server/do", "test_server/do",
@@ -869,7 +881,8 @@ mod tests {
#[test] #[test]
fn test_mcp_tool_property_missing_type_defaults_to_string() { fn test_mcp_tool_property_missing_type_defaults_to_string() {
let model_family = find_family_for_model("o3").expect("o3 should be a valid model family"); let model_family = find_family_for_model("gpt-5-codex")
.expect("gpt-5-codex should be a valid model family");
let config = ToolsConfig::new(&ToolsConfigParams { let config = ToolsConfig::new(&ToolsConfigParams {
model_family: &model_family, model_family: &model_family,
include_plan_tool: false, include_plan_tool: false,
@@ -937,7 +950,8 @@ mod tests {
#[test] #[test]
fn test_mcp_tool_integer_normalized_to_number() { fn test_mcp_tool_integer_normalized_to_number() {
let model_family = find_family_for_model("o3").expect("o3 should be a valid model family"); let model_family = find_family_for_model("gpt-5-codex")
.expect("gpt-5-codex should be a valid model family");
let config = ToolsConfig::new(&ToolsConfigParams { let config = ToolsConfig::new(&ToolsConfigParams {
model_family: &model_family, model_family: &model_family,
include_plan_tool: false, include_plan_tool: false,
@@ -1000,7 +1014,8 @@ mod tests {
#[test] #[test]
fn test_mcp_tool_array_without_items_gets_default_string_items() { fn test_mcp_tool_array_without_items_gets_default_string_items() {
let model_family = find_family_for_model("o3").expect("o3 should be a valid model family"); let model_family = find_family_for_model("gpt-5-codex")
.expect("gpt-5-codex should be a valid model family");
let config = ToolsConfig::new(&ToolsConfigParams { let config = ToolsConfig::new(&ToolsConfigParams {
model_family: &model_family, model_family: &model_family,
include_plan_tool: false, include_plan_tool: false,
@@ -1066,7 +1081,8 @@ mod tests {
#[test] #[test]
fn test_mcp_tool_anyof_defaults_to_string() { fn test_mcp_tool_anyof_defaults_to_string() {
let model_family = find_family_for_model("o3").expect("o3 should be a valid model family"); let model_family = find_family_for_model("gpt-5-codex")
.expect("gpt-5-codex should be a valid model family");
let config = ToolsConfig::new(&ToolsConfigParams { let config = ToolsConfig::new(&ToolsConfigParams {
model_family: &model_family, model_family: &model_family,
include_plan_tool: false, include_plan_tool: false,
@@ -1144,7 +1160,8 @@ mod tests {
#[test] #[test]
fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() { fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() {
let model_family = find_family_for_model("o3").expect("o3 should be a valid model family"); let model_family = find_family_for_model("gpt-5-codex")
.expect("gpt-5-codex should be a valid model family");
let config = ToolsConfig::new(&ToolsConfigParams { let config = ToolsConfig::new(&ToolsConfigParams {
model_family: &model_family, model_family: &model_family,
include_plan_tool: false, include_plan_tool: false,

View File

@@ -111,14 +111,21 @@ async fn model_selects_expected_tools() {
let codex_tools = collect_tool_identifiers_for_model("codex-mini-latest").await; let codex_tools = collect_tool_identifiers_for_model("codex-mini-latest").await;
assert_eq!( assert_eq!(
codex_tools, codex_tools,
vec!["local_shell".to_string(), "read_file".to_string()], vec!["local_shell".to_string()],
"codex-mini-latest should expose the local shell tool", "codex-mini-latest should expose the local shell tool",
); );
let o3_tools = collect_tool_identifiers_for_model("o3").await; let o3_tools = collect_tool_identifiers_for_model("o3").await;
assert_eq!( assert_eq!(
o3_tools, o3_tools,
vec!["shell".to_string(), "read_file".to_string()], vec!["shell".to_string()],
"o3 should expose the generic shell tool", "o3 should expose the generic shell tool",
); );
let gpt5_codex_tools = collect_tool_identifiers_for_model("gpt-5-codex").await;
assert_eq!(
gpt5_codex_tools,
vec!["shell".to_string(), "read_file".to_string()],
"gpt-5-codex should expose the beta read_file tool",
);
} }