Search selected plugin apps before falling back (#38484)

## What changed

- When an explicitly selected plugin has apps available, instruct the model to use `tool_search` to discover relevant tools before falling back to unrelated or built-in tools.
- Scope the guidance to the request that selected the plugin and omit it for plugins without available apps.

## Testing

- Cover app-specific rendering, non-app plugins, bounded instruction output, and app enablement in dual-surface plugin requests.

GitOrigin-RevId: 1b9501cc403d2817334a9307e683e99c140a794a
This commit is contained in:
dozaki-openai
2026-08-14 02:50:04 +00:00
committed by copyberry
parent 9d012ca4f5
commit cbe85e117b
3 changed files with 63 additions and 12 deletions

View File

@@ -24,6 +24,17 @@ pub(crate) fn render_explicit_plugin_instructions(
plugin.display_name
)];
if !available_apps.is_empty() {
lines.push(
concat!(
"- For the user request that explicitly selected this plugin, and only for that ",
"request, if `tool_search` is available and an app from this plugin may help, ",
"search for its tools before falling back to unrelated or built-in tools."
)
.to_string(),
);
}
if plugin.has_skills {
let skill_namespace = plugin
.plugin_namespace
@@ -34,17 +45,6 @@ pub(crate) fn render_explicit_plugin_instructions(
));
}
if !available_mcp_servers.is_empty() {
lines.push(format!(
"- MCP servers from this plugin available in this session: {}.",
available_mcp_servers
.iter()
.map(|server| format!("`{server}`"))
.collect::<Vec<_>>()
.join(", ")
));
}
if !available_apps.is_empty() {
lines.push(format!(
"- Apps from this plugin available in this session: {}.",
@@ -56,6 +56,17 @@ pub(crate) fn render_explicit_plugin_instructions(
));
}
if !available_mcp_servers.is_empty() {
lines.push(format!(
"- MCP servers from this plugin available in this session: {}.",
available_mcp_servers
.iter()
.map(|server| format!("`{server}`"))
.collect::<Vec<_>>()
.join(", ")
));
}
if lines.len() == 1 {
return None;
}

View File

@@ -40,6 +40,31 @@ fn explicit_plugin_instructions_use_manifest_namespace_for_skills() {
assert!(rendered.contains("`acme.tools:`"));
assert!(!rendered.contains("`Acme Developer Tools:`"));
assert!(!rendered.contains("tool_search"));
}
#[test]
fn explicit_plugin_instructions_search_available_apps_before_fallback() {
let rendered = render_explicit_plugin_instructions(
&PluginCapabilitySummary {
config_name: "app-adobe@openai-curated-remote".to_string(),
display_name: "Adobe".to_string(),
..PluginCapabilitySummary::default()
},
&[],
&["Adobe".to_string()],
)
.expect("app capability should render");
assert_eq!(
rendered,
"Capabilities from the `Adobe` plugin:\n\
- For the user request that explicitly selected this plugin, and only for that request, \
if `tool_search` is available and an app from this plugin may help, search for its \
tools before falling back to unrelated or built-in tools.\n\
- Apps from this plugin available in this session: `Adobe`.\n\
Use these plugin-associated capabilities to help solve the task."
);
}
#[test]
@@ -47,18 +72,26 @@ fn explicit_plugin_instructions_are_bounded() {
let servers = (0..1_024)
.map(|index| format!("server-{index}"))
.collect::<Vec<_>>();
let apps = (0..1_024)
.map(|index| format!("app-{index}"))
.collect::<Vec<_>>();
let rendered = render_explicit_plugin_instructions(
&PluginCapabilitySummary {
config_name: "sample@test".to_string(),
display_name: "sample".to_string(),
has_skills: true,
..PluginCapabilitySummary::default()
},
&servers,
&[],
&apps,
)
.expect("MCP capability should render");
assert!(rendered.len() <= MAX_EXPLICIT_PLUGIN_INSTRUCTIONS_BYTES);
assert!(rendered.contains("only for that request"));
assert!(rendered.contains("if `tool_search` is available"));
assert!(rendered.contains("Skills from this plugin"));
assert!(rendered.contains("`app-0`"));
assert!(rendered.ends_with(TRUNCATED_PLUGIN_INSTRUCTIONS_SUFFIX));
}

View File

@@ -1134,6 +1134,13 @@ async fn explicit_plugin_mentions_use_apps_for_chatgpt_dual_surface_plugins(
app_enabled,
"plugin app guidance should match app enablement: {developer_messages:?}"
);
assert_eq!(
developer_messages
.iter()
.any(|text| text.contains("if `tool_search` is available")),
app_enabled,
"plugin app search guidance should match app enablement: {developer_messages:?}"
);
assert!(
request
.tool_by_name(SAMPLE_PLUGIN_MCP_NAMESPACE, "echo")