From 51736a776692c7aaff9dff7269677aceb2cc01c2 Mon Sep 17 00:00:00 2001 From: Lee Jackson <130007945+Imagineer99@users.noreply.github.com> Date: Fri, 22 May 2026 15:03:56 +0100 Subject: [PATCH] Studio: add Anthropic and OpenAI prompt guards for disabled tools (#5674) * Add Anthropic prompt guards for disabled tools * fix: merge Anthropic tool guard into structured system prompts * fix: scope Anthropic disabled-tool guard wording * chore: adjust claude guard prompt * chore: add openai to list of prompt guarded providers * Studio: include web_fetch in the per-turn disabled-tool guard Add webFetchEnabledForThisTurn alongside webSearchEnabledForThisTurn and codeExecEnabledForThisTurn. Use it in the enabled_tools payload so web_fetch follows the Search pill the same way web_search does, and mention "web fetch" in the disabled-tool guard prose on providers that ship the tool (Anthropic today; other providers stay inert via providerSupportsBuiltinWebFetch). --------- Co-authored-by: Roland Tannous <115670425+rolandtannous@users.noreply.github.com> Co-authored-by: Daniel Han --- .../src/features/chat/api/chat-adapter.ts | 131 +++++++++++++----- 1 file changed, 94 insertions(+), 37 deletions(-) diff --git a/studio/frontend/src/features/chat/api/chat-adapter.ts b/studio/frontend/src/features/chat/api/chat-adapter.ts index a5a77b0863..c214c17c0a 100644 --- a/studio/frontend/src/features/chat/api/chat-adapter.ts +++ b/studio/frontend/src/features/chat/api/chat-adapter.ts @@ -869,6 +869,38 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter { throw new Error("Missing connection API key."); } + const webSearchEnabledForThisTurn = + Boolean( + externalProvider && + toolsEnabled && + providerSupportsBuiltinWebSearch(externalProvider.providerType), + ); + const codeExecEnabledForThisTurn = + Boolean( + externalProvider && + externalSelection && + codeToolsEnabled && + providerSupportsBuiltinCodeExecution( + externalProvider.providerType, + externalSelection.modelId, + externalProvider.baseUrl, + ), + ); + // web_fetch shares the Search pill with web_search (no separate + // UI toggle), so it follows toolsEnabled. Anthropic is the only + // provider that ships it today; on others providerSupportsBuiltinWebFetch + // returns false and this stays inert. + const webFetchEnabledForThisTurn = + Boolean( + externalProvider && + toolsEnabled && + providerSupportsBuiltinWebFetch(externalProvider.providerType), + ); + const providerShipsWebFetch = Boolean( + externalProvider && + providerSupportsBuiltinWebFetch(externalProvider.providerType), + ); + const outboundMessages = messages .map(toOpenAIMessage) .filter((message): message is NonNullable => @@ -883,6 +915,62 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter { content: safeSystemPrompt.trim(), }); } + let disabledToolGuard: string | null = null; + const disabledToolGuardProviderType = externalProvider?.providerType; + if ( + disabledToolGuardProviderType === "anthropic" || + disabledToolGuardProviderType === "openai" + ) { + const webLabel = providerShipsWebFetch + ? "web search or web fetch" + : "web search"; + if (!webSearchEnabledForThisTurn && !codeExecEnabledForThisTurn) { + disabledToolGuard = + `You do not have ${webLabel} or code execution tools in this conversation. ` + + "Answer from your own knowledge. " + + "If a request genuinely requires tool use, live data fetch or running code, " + + "inform the user that you do not have access to these capabilities. " + + "Do not return tool-call syntax inside your response."; + } else if (!webSearchEnabledForThisTurn) { + disabledToolGuard = + `You do not have ${webLabel} tools in this conversation. ` + + "You may still use code execution tools when they are available and useful. " + + "If a request genuinely requires live data fetch or web search tool use, " + + "inform the user that you do not have access to these capabilities. " + + "Do not return tool-call syntax inside your response."; + } else if (!codeExecEnabledForThisTurn) { + disabledToolGuard = + "You do not have code execution tools in this conversation. " + + `You may still use ${webLabel} tools when they are available and useful. ` + + "If a request genuinely requires running code or code execution tool use, " + + "inform the user that you do not have access to these capabilities. " + + "Do not return tool-call syntax inside your response."; + } + } + if (disabledToolGuard) { + const firstMessage = outboundMessages[0]; + if (firstMessage?.role === "system") { + if (typeof firstMessage.content === "string") { + outboundMessages[0] = { + ...firstMessage, + content: `${firstMessage.content}\n\n${disabledToolGuard}`, + }; + } else { + outboundMessages[0] = { + ...firstMessage, + content: [ + ...firstMessage.content, + { type: "text", text: `\n\n${disabledToolGuard}` }, + ], + }; + } + } else { + outboundMessages.unshift({ + role: "system", + content: disabledToolGuard, + }); + } + } const imageBase64 = findLatestUserImageBase64(messages); const audioBase64 = findLatestUserAudioBase64(messages); @@ -1137,13 +1225,6 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter { // (sent as `container` on /v1/messages). let openaiCodeExecContainerId: string | null = null; let anthropicCodeExecContainerId: string | null = null; - const codeExecEnabledForThisTurn = - codeToolsEnabled && - providerSupportsBuiltinCodeExecution( - externalProvider.providerType, - externalSelection.modelId, - externalProvider.baseUrl, - ); if (codeExecEnabledForThisTurn && resolvedThreadId) { try { const thread = await getStoredChatThread(resolvedThreadId); @@ -1313,25 +1394,13 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter { // translates enabled_tools into each provider's tool // schema — for Anthropic that's the entries appended to // body["tools"] inside _stream_anthropic. - ...((toolsEnabled && - providerSupportsBuiltinWebSearch( - externalProvider.providerType, - )) || - (codeToolsEnabled && - providerSupportsBuiltinCodeExecution( - externalProvider.providerType, - externalSelection.modelId, - externalProvider.baseUrl, - )) + ...(webSearchEnabledForThisTurn || + webFetchEnabledForThisTurn || + codeExecEnabledForThisTurn ? { enable_tools: true, enabled_tools: [ - ...(toolsEnabled && - providerSupportsBuiltinWebSearch( - externalProvider.providerType, - ) - ? ["web_search"] - : []), + ...(webSearchEnabledForThisTurn ? ["web_search"] : []), // Pair web_fetch with the Search pill on any // provider that ships it (Anthropic today). The // common workflow is "search returns URLs, fetch @@ -1339,20 +1408,8 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter { // surface a citation but cannot quote from the // page body, which is the whole point of the // tool. There is no separate UI toggle yet. - ...(toolsEnabled && - providerSupportsBuiltinWebFetch( - externalProvider.providerType, - ) - ? ["web_fetch"] - : []), - ...(codeToolsEnabled && - providerSupportsBuiltinCodeExecution( - externalProvider.providerType, - externalSelection.modelId, - externalProvider.baseUrl, - ) - ? ["code_execution"] - : []), + ...(webFetchEnabledForThisTurn ? ["web_fetch"] : []), + ...(codeExecEnabledForThisTurn ? ["code_execution"] : []), ], } : {}),