From de1b26affadf0464052895d9dd43919d6fe445c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E5=8F=AA=E5=B0=8F=E5=8C=85=E5=AD=90?= <1601302408@qq.com> Date: Sun, 30 Aug 2026 20:53:16 +0800 Subject: [PATCH 1/2] fix(llm): support GPT-5 request parameters --- src/services/llm/providers.test.ts | 44 ++++++++++++++++++++++++++++++ src/services/llm/providers.ts | 18 +++++++++--- 2 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 src/services/llm/providers.test.ts diff --git a/src/services/llm/providers.test.ts b/src/services/llm/providers.test.ts new file mode 100644 index 0000000..6fc8223 --- /dev/null +++ b/src/services/llm/providers.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from 'vitest'; +import { buildOpenAICompatibleRequest } from './providers'; +import type { LLMInvokePayload } from './types'; + +const payload: LLMInvokePayload = { + systemPrompt: 'system prompt', + userPrompt: 'user prompt', + responseFormat: 'text', + maxTokens: 512, + temperature: 0.7, +}; + +describe('buildOpenAICompatibleRequest', () => { + it('uses GPT-5-compatible parameters for GPT-5 models', () => { + const request = buildOpenAICompatibleRequest(payload, ' gpt-5.6-luna '); + + expect(request).toMatchObject({ + model: ' gpt-5.6-luna ', + max_completion_tokens: 512, + }); + expect(request).not.toHaveProperty('max_tokens'); + expect(request).not.toHaveProperty('temperature'); + }); + + it('keeps legacy parameters for non-GPT-5 models', () => { + const request = buildOpenAICompatibleRequest(payload, 'gpt-4.1'); + + expect(request).toMatchObject({ + model: 'gpt-4.1', + max_tokens: 512, + temperature: 0.7, + }); + expect(request).not.toHaveProperty('max_completion_tokens'); + }); + + it('omits temperature for Kimi models', () => { + const request = buildOpenAICompatibleRequest(payload, 'kimi-k2'); + + expect(request).toMatchObject({ + max_tokens: 512, + }); + expect(request).not.toHaveProperty('temperature'); + }); +}); diff --git a/src/services/llm/providers.ts b/src/services/llm/providers.ts index 83ad876..8e2e88e 100644 --- a/src/services/llm/providers.ts +++ b/src/services/llm/providers.ts @@ -12,7 +12,8 @@ import { type OpenAICompatibleRequest = { model: string; messages: Array<{ role: 'system' | 'user'; content: string }>; - max_tokens: number; + max_tokens?: number; + max_completion_tokens?: number; temperature?: number; }; @@ -26,16 +27,25 @@ export function buildOpenAICompatibleRequest( { role: 'system', content: payload.systemPrompt }, { role: 'user', content: payload.userPrompt }, ], - max_tokens: payload.maxTokens, }; - if (!usesLockedKimiTemperature(model)) { - request.temperature = payload.temperature; + if (usesGPT5Parameters(model)) { + request.max_completion_tokens = payload.maxTokens; + } else { + request.max_tokens = payload.maxTokens; + + if (!usesLockedKimiTemperature(model)) { + request.temperature = payload.temperature; + } } return request; } +function usesGPT5Parameters(model: string): boolean { + return /^gpt-5/i.test(model.trim()); +} + function usesLockedKimiTemperature(model: string): boolean { return model.trim().toLowerCase().includes('kimi'); } From 35201947e04bc949c84d2e8288b013037b13f9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E5=8F=AA=E5=B0=8F=E5=8C=85=E5=AD=90?= <1601302408@qq.com> Date: Sun, 30 Aug 2026 21:08:42 +0800 Subject: [PATCH 2/2] fix(llm): omit optional generation parameters --- src/background.ts | 1 - src/services/ai.ts | 1 - src/services/llm/providers.test.ts | 81 ++++++++++++++++++++++-------- src/services/llm/providers.ts | 27 +--------- src/services/llm/types.ts | 1 - 5 files changed, 60 insertions(+), 51 deletions(-) diff --git a/src/background.ts b/src/background.ts index 51709d2..d98957f 100644 --- a/src/background.ts +++ b/src/background.ts @@ -207,7 +207,6 @@ class LLMInvokeHandler { userPrompt: payload.userPrompt, responseFormat: payload.responseFormat === 'text' ? 'text' : 'json', maxTokens: typeof payload.maxTokens === 'number' ? payload.maxTokens : 1024, - temperature: typeof payload.temperature === 'number' ? payload.temperature : 0, }, storedSettings ); diff --git a/src/services/ai.ts b/src/services/ai.ts index a923914..b505403 100644 --- a/src/services/ai.ts +++ b/src/services/ai.ts @@ -30,7 +30,6 @@ export class AIService { userPrompt, responseFormat, maxTokens, - temperature: 0, }; const response = (await chrome.runtime.sendMessage({ diff --git a/src/services/llm/providers.test.ts b/src/services/llm/providers.test.ts index 6fc8223..c643eda 100644 --- a/src/services/llm/providers.test.ts +++ b/src/services/llm/providers.test.ts @@ -1,5 +1,14 @@ -import { describe, expect, it } from 'vitest'; -import { buildOpenAICompatibleRequest } from './providers'; +import { describe, expect, it, vi } from 'vitest'; + +const anthropicCreate = vi.hoisted(() => vi.fn()); + +vi.mock('@anthropic-ai/sdk', () => ({ + default: class Anthropic { + messages = { create: anthropicCreate }; + }, +})); + +import { buildOpenAICompatibleRequest, LLMGateway } from './providers'; import type { LLMInvokePayload } from './types'; const payload: LLMInvokePayload = { @@ -7,38 +16,66 @@ const payload: LLMInvokePayload = { userPrompt: 'user prompt', responseFormat: 'text', maxTokens: 512, - temperature: 0.7, }; describe('buildOpenAICompatibleRequest', () => { - it('uses GPT-5-compatible parameters for GPT-5 models', () => { - const request = buildOpenAICompatibleRequest(payload, ' gpt-5.6-luna '); - - expect(request).toMatchObject({ - model: ' gpt-5.6-luna ', - max_completion_tokens: 512, + it.each(['gpt-5.6-luna', 'gpt-4.1', 'kimi-k2'])('omits optional parameters for %s', (model) => { + expect(buildOpenAICompatibleRequest(payload, model)).toEqual({ + model, + messages: [ + { role: 'system', content: 'system prompt' }, + { role: 'user', content: 'user prompt' }, + ], }); - expect(request).not.toHaveProperty('max_tokens'); - expect(request).not.toHaveProperty('temperature'); }); - it('keeps legacy parameters for non-GPT-5 models', () => { - const request = buildOpenAICompatibleRequest(payload, 'gpt-4.1'); + it('omits temperature from Anthropic Messages requests', async () => { + anthropicCreate.mockResolvedValue({ + id: 'message-1', + content: [{ type: 'text', text: 'response' }], + }); + + await LLMGateway.invoke(payload, { + provider: 'anthropic', + baseUrl: 'https://api.anthropic.com', + apiKey: 'test-key', + model: 'claude-opus-4-6', + }); - expect(request).toMatchObject({ - model: 'gpt-4.1', + expect(anthropicCreate).toHaveBeenCalledWith({ + model: 'claude-opus-4-6', max_tokens: 512, - temperature: 0.7, + system: 'system prompt', + messages: [{ role: 'user', content: 'user prompt' }], }); - expect(request).not.toHaveProperty('max_completion_tokens'); }); - it('omits temperature for Kimi models', () => { - const request = buildOpenAICompatibleRequest(payload, 'kimi-k2'); + it('omits temperature from native custom fetch requests', async () => { + const fetchMock = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ message: { content: 'response' } }), + }); + vi.stubGlobal('fetch', fetchMock); - expect(request).toMatchObject({ - max_tokens: 512, + try { + await LLMGateway.invoke(payload, { + provider: 'custom_fetch', + baseUrl: 'http://localhost:11434', + apiKey: '', + model: 'local-model', + }); + } finally { + vi.unstubAllGlobals(); + } + + const [, options] = fetchMock.mock.calls[0]; + expect(JSON.parse(options.body)).toEqual({ + model: 'local-model', + messages: [ + { role: 'system', content: 'system prompt' }, + { role: 'user', content: 'user prompt' }, + ], + stream: false, }); - expect(request).not.toHaveProperty('temperature'); }); }); diff --git a/src/services/llm/providers.ts b/src/services/llm/providers.ts index 8e2e88e..387e35d 100644 --- a/src/services/llm/providers.ts +++ b/src/services/llm/providers.ts @@ -12,42 +12,19 @@ import { type OpenAICompatibleRequest = { model: string; messages: Array<{ role: 'system' | 'user'; content: string }>; - max_tokens?: number; - max_completion_tokens?: number; - temperature?: number; }; export function buildOpenAICompatibleRequest( payload: LLMInvokePayload, model: string ): OpenAICompatibleRequest { - const request: OpenAICompatibleRequest = { + return { model, messages: [ { role: 'system', content: payload.systemPrompt }, { role: 'user', content: payload.userPrompt }, ], }; - - if (usesGPT5Parameters(model)) { - request.max_completion_tokens = payload.maxTokens; - } else { - request.max_tokens = payload.maxTokens; - - if (!usesLockedKimiTemperature(model)) { - request.temperature = payload.temperature; - } - } - - return request; -} - -function usesGPT5Parameters(model: string): boolean { - return /^gpt-5/i.test(model.trim()); -} - -function usesLockedKimiTemperature(model: string): boolean { - return model.trim().toLowerCase().includes('kimi'); } export class LLMGateway { @@ -122,7 +99,6 @@ export class LLMGateway { const message = await client.messages.create({ model: settings.model, max_tokens: payload.maxTokens, - temperature: payload.temperature, system: payload.systemPrompt, messages: [ { @@ -180,7 +156,6 @@ export class LLMGateway { { role: 'system', content: payload.systemPrompt }, { role: 'user', content: payload.userPrompt }, ], - temperature: payload.temperature, stream: false, } ), diff --git a/src/services/llm/types.ts b/src/services/llm/types.ts index 69eb6b7..5b7fb12 100644 --- a/src/services/llm/types.ts +++ b/src/services/llm/types.ts @@ -7,7 +7,6 @@ export interface LLMInvokePayload { userPrompt: string; responseFormat: LLMResponseFormat; maxTokens: number; - temperature: number; } export interface LLMInvokeResult {