mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
fix(kosong): pass through chat reasoning effort (#581)
This commit is contained in:
parent
75a894e9ad
commit
aa3471f5d3
5 changed files with 25 additions and 59 deletions
6
.changeset/openai-compatible-xhigh-reasoning.md
Normal file
6
.changeset/openai-compatible-xhigh-reasoning.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"@moonshot-ai/kosong": patch
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Pass through xhigh reasoning effort for OpenAI-compatible chat completions requests.
|
||||
|
|
@ -8,9 +8,9 @@ import type { TokenUsage } from './usage';
|
|||
*
|
||||
* Values above `high` are provider/model-specific and may be clamped by the
|
||||
* adapter when the native API has no matching level. OpenAI maps `max` to its
|
||||
* `xhigh` ceiling on Responses and selected Chat Completions models; Kimi and
|
||||
* Gemini cap `xhigh`/`max` at `high`; Anthropic supports `xhigh`/`max` only
|
||||
* on selected models and otherwise clamps to `high`.
|
||||
* `xhigh` ceiling; Kimi and Gemini cap `xhigh`/`max` at `high`; Anthropic
|
||||
* supports `xhigh`/`max` only on selected models and otherwise clamps to
|
||||
* `high`.
|
||||
*/
|
||||
export type ThinkingEffort = 'off' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
||||
|
||||
|
|
|
|||
|
|
@ -21,15 +21,6 @@ const OPENAI_RESPONSES_DEVELOPER_ROLE_MODELS = new Set([
|
|||
'o4-mini',
|
||||
]);
|
||||
|
||||
// OpenAI models that document `xhigh` and keep `v1/chat/completions` enabled.
|
||||
// Pro/Codex xhigh models are Responses-only, so the Chat Completions adapter
|
||||
// must clamp them at `high`.
|
||||
const OPENAI_CHAT_COMPLETIONS_XHIGH_REASONING_MODELS = new Set([
|
||||
'gpt-5.2',
|
||||
'gpt-5.4',
|
||||
'gpt-5.5',
|
||||
]);
|
||||
|
||||
const OPENAI_VISION_TOOL_PREFIXES = [
|
||||
'gpt-4o',
|
||||
'gpt-4-turbo',
|
||||
|
|
@ -158,10 +149,6 @@ function normalizeModelName(modelName: string): string {
|
|||
return modelName.toLowerCase();
|
||||
}
|
||||
|
||||
function modelIdLeaf(modelName: string): string {
|
||||
return normalizeModelName(modelName).trim().split('/').at(-1) ?? '';
|
||||
}
|
||||
|
||||
function hasPrefix(modelName: string, prefixes: readonly string[]): boolean {
|
||||
return prefixes.some((prefix) => modelName.startsWith(prefix));
|
||||
}
|
||||
|
|
@ -206,10 +193,6 @@ export function getGoogleGenAIModelCapability(modelName: string): ModelCapabilit
|
|||
return GEMINI_MULTIMODAL_TOOL_CAPABILITY;
|
||||
}
|
||||
|
||||
export function supportsOpenAIChatCompletionsXHighReasoning(modelName: string): boolean {
|
||||
return OPENAI_CHAT_COMPLETIONS_XHIGH_REASONING_MODELS.has(modelIdLeaf(modelName));
|
||||
}
|
||||
|
||||
export function usesOpenAIResponsesDeveloperRole(modelName: string): boolean {
|
||||
const normalized = normalizeModelName(modelName);
|
||||
if (OPENAI_RESPONSES_DEVELOPER_ROLE_MODELS.has(normalized)) return true;
|
||||
|
|
|
|||
|
|
@ -12,10 +12,7 @@ import type { Tool } from '#/tool';
|
|||
import type { TokenUsage } from '#/usage';
|
||||
import OpenAI from 'openai';
|
||||
|
||||
import {
|
||||
getOpenAILegacyModelCapability,
|
||||
supportsOpenAIChatCompletionsXHighReasoning,
|
||||
} from './capability-registry';
|
||||
import { getOpenAILegacyModelCapability } from './capability-registry';
|
||||
import {
|
||||
convertContentPart,
|
||||
convertOpenAIError,
|
||||
|
|
@ -135,16 +132,6 @@ function normalizeGenerationKwargs(
|
|||
return kwargs;
|
||||
}
|
||||
|
||||
function clampChatCompletionsReasoningEffort(
|
||||
reasoningEffort: string | undefined,
|
||||
model: string,
|
||||
): string | undefined {
|
||||
if (reasoningEffort !== 'xhigh') {
|
||||
return reasoningEffort;
|
||||
}
|
||||
return supportsOpenAIChatCompletionsXHighReasoning(model) ? 'xhigh' : 'high';
|
||||
}
|
||||
|
||||
function convertMessage(
|
||||
message: Message,
|
||||
reasoningKey: string | undefined,
|
||||
|
|
@ -518,10 +505,7 @@ export class OpenAILegacyChatProvider implements ChatProvider {
|
|||
}
|
||||
|
||||
withThinking(effort: ThinkingEffort): OpenAILegacyChatProvider {
|
||||
const reasoningEffort = clampChatCompletionsReasoningEffort(
|
||||
thinkingEffortToReasoningEffort(effort),
|
||||
this._model,
|
||||
);
|
||||
const reasoningEffort = thinkingEffortToReasoningEffort(effort);
|
||||
const clone = this._clone();
|
||||
clone._reasoningEffort = reasoningEffort;
|
||||
return clone;
|
||||
|
|
|
|||
|
|
@ -718,8 +718,8 @@ describe('OpenAILegacyChatProvider', () => {
|
|||
expect(body['reasoning_effort']).toBe('high');
|
||||
});
|
||||
|
||||
it.each(['gpt-5.2', 'gpt-5.4', 'gpt-5.5', 'openai/gpt-5.5'])(
|
||||
'.withThinking("xhigh") passes through for Chat Completions xhigh model %s',
|
||||
it.each(['deepseek/deepseek-v4-flash', 'gpt-5.4-pro', 'some-model'])(
|
||||
'.withThinking("xhigh") passes through reasoning_effort for model %s',
|
||||
async (model) => {
|
||||
const provider = createProvider({ model }).withThinking('xhigh');
|
||||
const history: Message[] = [
|
||||
|
|
@ -732,40 +732,33 @@ describe('OpenAILegacyChatProvider', () => {
|
|||
},
|
||||
);
|
||||
|
||||
it.each(['gpt-5.1', 'gpt-5.4-mini', 'gpt-5.4-pro', 'kimi-k2.6', 'some-model'])(
|
||||
'.withThinking("xhigh") clamps to high for Chat Completions model %s',
|
||||
async (model) => {
|
||||
const provider = createProvider({ model }).withThinking('xhigh');
|
||||
const history: Message[] = [
|
||||
{ role: 'user', content: [{ type: 'text', text: 'Think' }], toolCalls: [] },
|
||||
];
|
||||
const body = await captureRequestBody(provider, '', [], history);
|
||||
|
||||
expect(body['reasoning_effort']).toBe('high');
|
||||
expect(provider.thinkingEffort).toBe('high');
|
||||
},
|
||||
);
|
||||
|
||||
it('.withThinking("max") uses xhigh only for Chat Completions xhigh models', async () => {
|
||||
it('.withThinking("max") maps to xhigh without model-specific clamping', async () => {
|
||||
const history: Message[] = [
|
||||
{ role: 'user', content: [{ type: 'text', text: 'Think' }], toolCalls: [] },
|
||||
];
|
||||
|
||||
const supported = await captureRequestBody(
|
||||
const openAIChatModel = await captureRequestBody(
|
||||
createProvider({ model: 'gpt-5.5' }).withThinking('max'),
|
||||
'',
|
||||
[],
|
||||
history,
|
||||
);
|
||||
const unsupported = await captureRequestBody(
|
||||
const openAIProModel = await captureRequestBody(
|
||||
createProvider({ model: 'gpt-5.5-pro' }).withThinking('max'),
|
||||
'',
|
||||
[],
|
||||
history,
|
||||
);
|
||||
const deepSeekModel = await captureRequestBody(
|
||||
createProvider({ model: 'deepseek/deepseek-v4-pro' }).withThinking('max'),
|
||||
'',
|
||||
[],
|
||||
history,
|
||||
);
|
||||
|
||||
expect(supported['reasoning_effort']).toBe('xhigh');
|
||||
expect(unsupported['reasoning_effort']).toBe('high');
|
||||
expect(openAIChatModel['reasoning_effort']).toBe('xhigh');
|
||||
expect(openAIProModel['reasoning_effort']).toBe('xhigh');
|
||||
expect(deepSeekModel['reasoning_effort']).toBe('xhigh');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue