From d037244aed3869858acab7eebd48daccc880622a Mon Sep 17 00:00:00 2001 From: hugocasa Date: Fri, 7 Aug 2026 17:56:34 +0200 Subject: [PATCH] fix: drop web search guidance on the completions api fallback Co-Authored-By: Claude Opus 5 (1M context) --- ai_evals/cases/global.yaml | 8 ++-- .../components/copilot/chat/chatLoop.test.ts | 38 +++++++++++++++++++ .../lib/components/copilot/chat/chatLoop.ts | 8 +++- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/ai_evals/cases/global.yaml b/ai_evals/cases/global.yaml index 547abf5b60..2369ae23b5 100644 --- a/ai_evals/cases/global.yaml +++ b/ai_evals/cases/global.yaml @@ -2011,11 +2011,9 @@ - authentication uses Holded's own key header rather than a bearer token - the result stays an AI draft and is not deployed -# The fixture hub carries two Baremetrics scripts (List Sources, Create Customer) and -# none for annotations, so no hub script does what is asked. The integration's -# conventions — a `Baremetrics` resource type whose single field is `apiKey`, bearer -# auth, the api.baremetrics.com/v1 base — are only knowable by reading one of those -# other scripts, which is what makes this a test of using a near-miss as an example. +# The fixture hub carries two Baremetrics scripts, neither for annotations, so nothing +# does what is asked. Its conventions — an `apiKey` resource, bearer auth, the +# api.baremetrics.com/v1 base — are only knowable by reading one of those near-misses. - id: global-hub3-integration-as-example prompt: |- Write me a script that adds an annotation to my Baremetrics account. diff --git a/frontend/src/lib/components/copilot/chat/chatLoop.test.ts b/frontend/src/lib/components/copilot/chat/chatLoop.test.ts index 005b12f4c5..83f1856d88 100644 --- a/frontend/src/lib/components/copilot/chat/chatLoop.test.ts +++ b/frontend/src/lib/components/copilot/chat/chatLoop.test.ts @@ -376,6 +376,44 @@ describe('runChatLoop onBeforeIteration web search sync', () => { }) }) + // The Completions API has no web-search tool at all, so the fallback is a third + // path (beside the two retries) that must not ship the guidance. + it('drops the web search guidance on the Completions API fallback', async () => { + let systemMessage: ChatLoopConfig['systemMessage'] = { + role: 'system', + content: 'search the web' + } + + mocks.getOpenAIResponsesCompletion.mockRejectedValue( + new Error('Responses API is not enabled for this organization') + ) + mocks.getCompletion.mockResolvedValue({}) + const onWebSearchUnavailable = vi.fn() + + await runChatLoop( + createConfig({ + workspace: `workspace-${randomUUID()}`, + getSystemMessage: () => systemMessage, + onWebSearchUnavailable, + onBeforeIteration: async (_t, _h, _m, webSearch: boolean) => { + systemMessage = { + role: 'system', + content: webSearch ? 'search the web' : 'no web search' + } + } + }) + ) + + // Falling back is per request; the model itself can still serve web search. + expect(onWebSearchUnavailable).not.toHaveBeenCalled() + + expect(mocks.getCompletion).toHaveBeenCalledTimes(1) + expect(mocks.getCompletion.mock.calls[0][0][0]).toEqual({ + role: 'system', + content: 'no web search' + }) + }) + it('sends the system message the callback rewrote on this iteration, not the next', async () => { let systemMessage: ChatLoopConfig['systemMessage'] = { role: 'system', content: 'stale' } const config = createConfig({ diff --git a/frontend/src/lib/components/copilot/chat/chatLoop.ts b/frontend/src/lib/components/copilot/chat/chatLoop.ts index a6cb97733e..e3e407d6dc 100644 --- a/frontend/src/lib/components/copilot/chat/chatLoop.ts +++ b/frontend/src/lib/components/copilot/chat/chatLoop.ts @@ -494,12 +494,18 @@ export async function runChatLoop(config: ChatLoopConfig): Promise