[apps] Improve search tool fallback. (#14732)
- [x] Bypass tool search and stuff tool specs directly into model context when either a. Tool search is not available for the model or b. There are not that many tools to search for.
This commit is contained in:
parent
49edf311ac
commit
d4af6053e2
3 changed files with 67 additions and 8 deletions
|
|
@ -377,6 +377,7 @@ pub(crate) const INITIAL_SUBMIT_ID: &str = "";
|
|||
pub(crate) const SUBMISSION_CHANNEL_CAPACITY: usize = 512;
|
||||
const CYBER_VERIFY_URL: &str = "https://chatgpt.com/cyber";
|
||||
const CYBER_SAFETY_URL: &str = "https://developers.openai.com/codex/concepts/cyber-safety";
|
||||
const DIRECT_APP_TOOL_EXPOSURE_THRESHOLD: usize = 100;
|
||||
|
||||
impl Codex {
|
||||
/// Spawn a new [`Codex`] and initialize the session.
|
||||
|
|
@ -6463,8 +6464,6 @@ pub(crate) async fn built_tools(
|
|||
None
|
||||
};
|
||||
|
||||
// Keep the connector-grouped app view around for the router even though
|
||||
// app tools only become prompt-visible after explicit selection/discovery.
|
||||
let app_tools = connectors.as_ref().map(|connectors| {
|
||||
filter_codex_apps_mcp_tools(&mcp_tools, connectors, &turn_context.config)
|
||||
});
|
||||
|
|
@ -6491,6 +6490,21 @@ pub(crate) async fn built_tools(
|
|||
mcp_tools = selected_mcp_tools;
|
||||
}
|
||||
|
||||
// Expose app tools directly when tool_search is disabled, or when tool_search
|
||||
// is enabled but the accessible app tool set stays below the direct-exposure threshold.
|
||||
let expose_app_tools_directly = !turn_context.tools_config.search_tool
|
||||
|| app_tools
|
||||
.as_ref()
|
||||
.is_some_and(|tools| tools.len() < DIRECT_APP_TOOL_EXPOSURE_THRESHOLD);
|
||||
if expose_app_tools_directly && let Some(app_tools) = app_tools.as_ref() {
|
||||
mcp_tools.extend(app_tools.clone());
|
||||
}
|
||||
let app_tools = if expose_app_tools_directly {
|
||||
None
|
||||
} else {
|
||||
app_tools
|
||||
};
|
||||
|
||||
Ok(Arc::new(ToolRouter::from_config(
|
||||
&turn_context.tools_config,
|
||||
ToolRouterParams {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const CONNECTOR_DESCRIPTION: &str = "Plan events and manage your calendar.";
|
|||
const PROTOCOL_VERSION: &str = "2025-11-25";
|
||||
const SERVER_NAME: &str = "codex-apps-test";
|
||||
const SERVER_VERSION: &str = "1.0.0";
|
||||
const SEARCHABLE_TOOL_COUNT: usize = 100;
|
||||
pub const CALENDAR_CREATE_EVENT_RESOURCE_URI: &str =
|
||||
"connector://calendar/tools/calendar_create_event";
|
||||
const CALENDAR_LIST_EVENTS_RESOURCE_URI: &str = "connector://calendar/tools/calendar_list_events";
|
||||
|
|
@ -32,6 +33,21 @@ impl AppsTestServer {
|
|||
Self::mount_with_connector_name(server, CONNECTOR_NAME).await
|
||||
}
|
||||
|
||||
pub async fn mount_searchable(server: &MockServer) -> Result<Self> {
|
||||
mount_oauth_metadata(server).await;
|
||||
mount_connectors_directory(server).await;
|
||||
mount_streamable_http_json_rpc(
|
||||
server,
|
||||
CONNECTOR_NAME.to_string(),
|
||||
CONNECTOR_DESCRIPTION.to_string(),
|
||||
/*searchable*/ true,
|
||||
)
|
||||
.await;
|
||||
Ok(Self {
|
||||
chatgpt_base_url: server.uri(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn mount_with_connector_name(
|
||||
server: &MockServer,
|
||||
connector_name: &str,
|
||||
|
|
@ -42,6 +58,7 @@ impl AppsTestServer {
|
|||
server,
|
||||
connector_name.to_string(),
|
||||
CONNECTOR_DESCRIPTION.to_string(),
|
||||
/*searchable*/ false,
|
||||
)
|
||||
.await;
|
||||
Ok(Self {
|
||||
|
|
@ -97,12 +114,14 @@ async fn mount_streamable_http_json_rpc(
|
|||
server: &MockServer,
|
||||
connector_name: String,
|
||||
connector_description: String,
|
||||
searchable: bool,
|
||||
) {
|
||||
Mock::given(method("POST"))
|
||||
.and(path_regex("^/api/codex/apps/?$"))
|
||||
.respond_with(CodexAppsJsonRpcResponder {
|
||||
connector_name,
|
||||
connector_description,
|
||||
searchable,
|
||||
})
|
||||
.mount(server)
|
||||
.await;
|
||||
|
|
@ -111,6 +130,7 @@ async fn mount_streamable_http_json_rpc(
|
|||
struct CodexAppsJsonRpcResponder {
|
||||
connector_name: String,
|
||||
connector_description: String,
|
||||
searchable: bool,
|
||||
}
|
||||
|
||||
impl Respond for CodexAppsJsonRpcResponder {
|
||||
|
|
@ -157,7 +177,7 @@ impl Respond for CodexAppsJsonRpcResponder {
|
|||
"notifications/initialized" => ResponseTemplate::new(202),
|
||||
"tools/list" => {
|
||||
let id = body.get("id").cloned().unwrap_or(Value::Null);
|
||||
ResponseTemplate::new(200).set_body_json(json!({
|
||||
let mut response = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": id,
|
||||
"result": {
|
||||
|
|
@ -211,7 +231,32 @@ impl Respond for CodexAppsJsonRpcResponder {
|
|||
],
|
||||
"nextCursor": null
|
||||
}
|
||||
}))
|
||||
});
|
||||
if self.searchable
|
||||
&& let Some(tools) = response
|
||||
.pointer_mut("/result/tools")
|
||||
.and_then(Value::as_array_mut)
|
||||
{
|
||||
for index in 2..SEARCHABLE_TOOL_COUNT {
|
||||
tools.push(json!({
|
||||
"name": format!("calendar_timezone_option_{index}"),
|
||||
"description": format!("Read timezone option {index}."),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"timezone": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"_meta": {
|
||||
"connector_id": CONNECTOR_ID,
|
||||
"connector_name": self.connector_name.clone(),
|
||||
"connector_description": self.connector_description.clone()
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
ResponseTemplate::new(200).set_body_json(response)
|
||||
}
|
||||
"tools/call" => {
|
||||
let id = body.get("id").cloned().unwrap_or(Value::Null);
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ async fn search_tool_flag_adds_tool_search() -> Result<()> {
|
|||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let apps_server = AppsTestServer::mount(&server).await?;
|
||||
let apps_server = AppsTestServer::mount_searchable(&server).await?;
|
||||
let mock = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
|
|
@ -212,7 +212,7 @@ async fn search_tool_adds_discovery_instructions_to_tool_description() -> Result
|
|||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let apps_server = AppsTestServer::mount(&server).await?;
|
||||
let apps_server = AppsTestServer::mount_searchable(&server).await?;
|
||||
let mock = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
|
|
@ -254,7 +254,7 @@ async fn search_tool_hides_apps_tools_without_search() -> Result<()> {
|
|||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let apps_server = AppsTestServer::mount(&server).await?;
|
||||
let apps_server = AppsTestServer::mount_searchable(&server).await?;
|
||||
let mock = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
|
|
@ -329,7 +329,7 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() -
|
|||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let apps_server = AppsTestServer::mount(&server).await?;
|
||||
let apps_server = AppsTestServer::mount_searchable(&server).await?;
|
||||
let call_id = "tool-search-1";
|
||||
let mock = mount_sse_sequence(
|
||||
&server,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue