From ea6ff6997f3052faebdf87b1a7b5ed0bdc500f1b Mon Sep 17 00:00:00 2001 From: chyoo Date: Tue, 20 Oct 2026 10:17:40 +0900 Subject: [PATCH] perf: request low reasoning effort for OpenAI agents --- src/ai/ai-provider.service.spec.ts | 27 +++++++++++++++++++++++++++ src/ai/ai-provider.service.ts | 2 ++ src/ai/copilot-backend.spec.ts | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/ai/ai-provider.service.spec.ts b/src/ai/ai-provider.service.spec.ts index 82fc5a3..678b50f 100644 --- a/src/ai/ai-provider.service.spec.ts +++ b/src/ai/ai-provider.service.spec.ts @@ -39,6 +39,31 @@ describe('AiProviderService', () => { expect(fetchMock).not.toHaveBeenCalled(); }); + it.each([ + 'gpt-6.1-sol', 'gpt-6-astra', 'gpt-6-sol', 'gpt-6-luna', 'gpt-6.1-sol-2026-10-06', + 'gpt-5', 'gpt-5-mini', 'gpt-5.1', 'gpt-5.5', 'gpt-5.6-sol', 'o1', 'o3', 'o4-mini', + ])('requests low reasoning effort for OpenAI model %s', async model => { + const fetchMock = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ choices: [{ message: { content: '{"ok":true}' } }] }) }); + global.fetch = fetchMock as typeof fetch; + const service = new AiProviderService({ get: jest.fn() } as unknown as ConfigService); + await service.complete({ provider: 'OPENAI', model, apiKey: 'fixture', system: 'Return JSON', json: true, messages: [{ role: 'user', content: 'Find the steering configuration' }] }); + const body = JSON.parse(String(fetchMock.mock.calls[0][1].body)); + expect(body).toMatchObject({ model, reasoning_effort: 'low', response_format: { type: 'json_object' } }); + expect(body).not.toHaveProperty('temperature'); + expect(body.messages).toEqual([{ role: 'system', content: 'Return JSON' }, { role: 'user', content: 'Find the steering configuration' }]); + }); + + it.each([ + 'gpt-4.1-mini', 'gpt-4o', 'custom-model', 'gpt-5-pro', 'gpt-5.2-pro-2025-12-11', + 'gpt-5-chat-latest', 'gpt-5.1-chat-latest', 'o1-mini', 'o1-preview-2024-09-12', 'o3-deep-research', + ])('preserves default reasoning settings for unsupported OpenAI variant %s', async model => { + const fetchMock = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ choices: [{ message: { content: 'answer' } }] }) }); + global.fetch = fetchMock as typeof fetch; + const service = new AiProviderService({ get: jest.fn() } as unknown as ConfigService); + await service.complete({ provider: 'OPENAI', model, apiKey: 'fixture', system: 'system', messages: [{ role: 'user', content: 'test' }] }); + expect(JSON.parse(String(fetchMock.mock.calls[0][1].body))).not.toHaveProperty('reasoning_effort'); + }); + it('prefers the course Qwen endpoint over the server default', async () => { const fetchMock = jest.fn().mockResolvedValue({ ok: true, @@ -89,6 +114,7 @@ describe('AiProviderService', () => { const body = JSON.parse(String(fetchMock.mock.calls[0][1].body)); expect(provider === 'GEMINI' ? body.generationConfig : body).not.toHaveProperty('temperature'); expect(body).not.toHaveProperty('enable_thinking'); + if (provider !== 'OPENAI') expect(body).not.toHaveProperty('reasoning_effort'); if (provider !== 'GEMINI') expect(body.model).toBe(model); if (provider === 'OPENAI') expect(body.response_format).toEqual({ type: 'json_object' }); if (provider === 'GEMINI') expect(body.generationConfig.responseMimeType).toBe('application/json'); @@ -108,6 +134,7 @@ describe('AiProviderService', () => { await service.complete({ provider, model, apiKey: 'fixture', system: 'system', messages: [{ role: 'user', content: 'test' }] }); const body = JSON.parse(String(fetchMock.mock.calls[0][1].body)); expect(provider === 'GEMINI' ? body.generationConfig.temperature : body.temperature).toBe(0.2); + expect(body).not.toHaveProperty('reasoning_effort'); if (provider !== 'QWEN') expect(body).not.toHaveProperty('enable_thinking'); if (provider !== 'GEMINI') expect(body.model).toBe(model); }); diff --git a/src/ai/ai-provider.service.ts b/src/ai/ai-provider.service.ts index 4ac1411..c617ab7 100644 --- a/src/ai/ai-provider.service.ts +++ b/src/ai/ai-provider.service.ts @@ -54,12 +54,14 @@ export class AiProviderService { private async openAi(input: CompletionInput): Promise { const reasoningModel = /^gpt-(?:5|6)(?:[.-]|$)|^o[1-9](?:-|$)/.test(input.model); + const lowReasoningModel = reasoningModel && !/-(?:pro|chat|deep-research)(?:-|$)|^o1-(?:mini|preview)(?:-|$)/.test(input.model); const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { Authorization: `Bearer ${input.apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: input.model, temperature: reasoningModel ? undefined : 0.2, + reasoning_effort: lowReasoningModel ? 'low' : undefined, response_format: input.json ? { type: 'json_object' } : undefined, messages: [{ role: 'system', content: input.system }, ...input.messages], }), diff --git a/src/ai/copilot-backend.spec.ts b/src/ai/copilot-backend.spec.ts index 410a422..d42685f 100644 --- a/src/ai/copilot-backend.spec.ts +++ b/src/ai/copilot-backend.spec.ts @@ -48,7 +48,7 @@ describe('managed A-Copilot backend', () => { await provider.complete({ provider: 'A_COPILOT', model: 'ignored', apiKey: 'ignored', endpointUrl: 'https://untrusted.test', system: 'agent instructions', json: true, messages: [{ role: 'user', content: 'test' }] }); expect(fetchMock.mock.calls[0][0]).toBe('https://api.openai.com/v1/chat/completions'); expect(fetchMock.mock.calls[0][1].headers.Authorization).toBe('Bearer shared-openai-key'); - expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toMatchObject({ model: 'gpt-6-astra', response_format: { type: 'json_object' }, messages: [{ role: 'system', content: 'agent instructions' }, { role: 'user', content: 'test' }] }); + expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toMatchObject({ model: 'gpt-6-astra', reasoning_effort: 'low', response_format: { type: 'json_object' }, messages: [{ role: 'system', content: 'agent instructions' }, { role: 'user', content: 'test' }] }); }); it('never falls back to a different server provider when its configured key is missing', async () => {