豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit 7f62714

Browse files
authored
Merge pull request #5011 from microsoft/dev/bhavyau/fix-effort-guard-0.43
[release/0.43] Guard reasoning effort parameter against unsupported models
2 parents 4b9c9b5 + cb46b3d commit 7f62714

File tree

3 files changed

+175
-6
lines changed

3 files changed

+175
-6
lines changed

src/platform/endpoint/node/messagesApi.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,8 @@ export function createMessagesRequestBody(accessor: ServicesAccessor, options: I
168168
}
169169

170170
const thinkingEnabled = !!thinkingConfig;
171-
172-
// Build output config with effort level for thinking, validating reasoningEffort
173171
let effort: 'low' | 'medium' | 'high' | undefined;
174-
if (thinkingConfig) {
172+
if (thinkingConfig && endpoint.supportsReasoningEffort?.length) {
175173
const candidateEffort = configurationService.getConfig(ConfigKey.TeamInternal.AnthropicThinkingEffort) ?? reasoningEffort;
176174
if (candidateEffort === 'low' || candidateEffort === 'medium' || candidateEffort === 'high') {
177175
effort = candidateEffort;

src/platform/endpoint/node/responsesApi.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ export function createResponsesRequestBody(accessor: ServicesAccessor, options:
7474
const summaryConfig = configService.getExperimentBasedConfig(ConfigKey.ResponsesApiReasoningSummary, expService);
7575
const shouldDisableReasoningSummary = endpoint.family === 'gpt-5.3-codex-spark-preview';
7676
const effortFromSetting = configService.getConfig(ConfigKey.TeamInternal.ResponsesApiReasoningEffort);
77-
const effort = effortFromSetting || options.reasoningEffort || 'medium';
77+
const effort = endpoint.supportsReasoningEffort?.length
78+
? (effortFromSetting || options.reasoningEffort || 'medium')
79+
: undefined;
7880
const summary = summaryConfig === 'off' || shouldDisableReasoningSummary ? undefined : summaryConfig;
7981
if (effort || summary) {
8082
body.reasoning = {

src/platform/endpoint/test/node/messagesApi.spec.ts

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@
55

66
import type { ContentBlockParam, DocumentBlockParam, ImageBlockParam, MessageParam, TextBlockParam, ToolReferenceBlockParam, ToolResultBlockParam } from '@anthropic-ai/sdk/resources';
77
import { Raw } from '@vscode/prompt-tsx';
8-
import { expect, suite, test } from 'vitest';
8+
import { beforeEach, describe, expect, suite, test } from 'vitest';
9+
import { DisposableStore } from '../../../../util/vs/base/common/lifecycle';
10+
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
11+
import { ChatLocation } from '../../../chat/common/commonTypes';
12+
import { ConfigKey, IConfigurationService } from '../../../configuration/common/configurationService';
13+
import { InMemoryConfigurationService } from '../../../configuration/test/common/inMemoryConfigurationService';
914
import { AnthropicMessagesTool, CUSTOM_TOOL_SEARCH_NAME } from '../../../networking/common/anthropic';
10-
import { addToolsAndSystemCacheControl, buildToolInputSchema, rawMessagesToMessagesAPI } from '../../node/messagesApi';
15+
import { IChatEndpoint, ICreateEndpointBodyOptions } from '../../../networking/common/networking';
16+
import { IToolDeferralService } from '../../../networking/common/toolDeferralService';
17+
import { createPlatformServices } from '../../../test/node/services';
18+
import { addToolsAndSystemCacheControl, buildToolInputSchema, createMessagesRequestBody, rawMessagesToMessagesAPI } from '../../node/messagesApi';
1119

1220
function assertContentArray(content: MessageParam['content']): ContentBlockParam[] {
1321
expect(Array.isArray(content)).toBe(true);
@@ -656,3 +664,164 @@ suite('buildToolInputSchema', function () {
656664
expect(result).toEqual(schema);
657665
});
658666
});
667+
668+
describe('createMessagesRequestBody reasoning effort', () => {
669+
let disposables: DisposableStore;
670+
let instantiationService: IInstantiationService;
671+
let mockConfig: InMemoryConfigurationService;
672+
673+
function createMockEndpoint(overrides: Partial<IChatEndpoint> = {}): IChatEndpoint {
674+
return {
675+
model: 'claude-sonnet-4.5',
676+
family: 'claude-sonnet-4.5',
677+
modelProvider: 'Anthropic',
678+
maxOutputTokens: 8192,
679+
modelMaxPromptTokens: 200000,
680+
supportsToolCalls: true,
681+
supportsVision: true,
682+
supportsPrediction: false,
683+
showInModelPicker: true,
684+
isFallback: false,
685+
name: 'test',
686+
version: '1.0',
687+
policy: 'enabled',
688+
urlOrRequestMetadata: 'https://test.com',
689+
tokenizer: 0,
690+
isDefault: false,
691+
processResponseFromChatEndpoint: () => { throw new Error('not implemented'); },
692+
acceptChatPolicy: () => { throw new Error('not implemented'); },
693+
makeChatRequest2: () => { throw new Error('not implemented'); },
694+
createRequestBody: () => { throw new Error('not implemented'); },
695+
cloneWithTokenOverride: () => { throw new Error('not implemented'); },
696+
interceptBody: () => { },
697+
getExtraHeaders: () => ({}),
698+
...overrides,
699+
} as IChatEndpoint;
700+
}
701+
702+
function createMinimalOptions(overrides: Partial<ICreateEndpointBodyOptions> = {}): ICreateEndpointBodyOptions {
703+
return {
704+
debugName: 'test',
705+
requestId: 'test-request-id',
706+
finishedCb: undefined,
707+
messages: [{
708+
role: Raw.ChatRole.User,
709+
content: [{ type: Raw.ChatCompletionContentPartKind.Text, text: 'Hello' }],
710+
}],
711+
postOptions: { max_tokens: 8192 },
712+
location: ChatLocation.Panel,
713+
...overrides,
714+
};
715+
}
716+
717+
beforeEach(() => {
718+
disposables = new DisposableStore();
719+
const services = disposables.add(createPlatformServices(disposables));
720+
services.define(IToolDeferralService, {
721+
_serviceBrand: undefined,
722+
isNonDeferredTool: () => true,
723+
});
724+
const accessor = services.createTestingAccessor();
725+
instantiationService = accessor.get(IInstantiationService);
726+
mockConfig = accessor.get(IConfigurationService) as InMemoryConfigurationService;
727+
});
728+
729+
test('includes effort in output_config when model supports reasoning effort and thinking is adaptive', () => {
730+
const endpoint = createMockEndpoint({
731+
supportsAdaptiveThinking: true,
732+
supportsReasoningEffort: ['low', 'medium', 'high'],
733+
});
734+
const options = createMinimalOptions({
735+
enableThinking: true,
736+
reasoningEffort: 'high',
737+
});
738+
739+
const body = instantiationService.invokeFunction(createMessagesRequestBody, options, endpoint.model, endpoint);
740+
741+
expect(body.thinking).toEqual({ type: 'adaptive' });
742+
expect(body.output_config).toEqual({ effort: 'high' });
743+
});
744+
745+
test('omits effort when model does not declare supportsReasoningEffort', () => {
746+
const endpoint = createMockEndpoint({
747+
supportsAdaptiveThinking: true,
748+
// supportsReasoningEffort is undefined
749+
});
750+
const options = createMinimalOptions({
751+
enableThinking: true,
752+
reasoningEffort: 'high',
753+
});
754+
755+
const body = instantiationService.invokeFunction(createMessagesRequestBody, options, endpoint.model, endpoint);
756+
757+
expect(body.thinking).toEqual({ type: 'adaptive' });
758+
expect(body.output_config).toBeUndefined();
759+
});
760+
761+
test('omits effort when supportsReasoningEffort is an empty array', () => {
762+
const endpoint = createMockEndpoint({
763+
supportsAdaptiveThinking: true,
764+
supportsReasoningEffort: [],
765+
});
766+
const options = createMinimalOptions({
767+
enableThinking: true,
768+
reasoningEffort: 'medium',
769+
});
770+
771+
const body = instantiationService.invokeFunction(createMessagesRequestBody, options, endpoint.model, endpoint);
772+
773+
expect(body.thinking).toEqual({ type: 'adaptive' });
774+
expect(body.output_config).toBeUndefined();
775+
});
776+
777+
test('omits effort when thinking is not enabled', () => {
778+
const endpoint = createMockEndpoint({
779+
supportsAdaptiveThinking: true,
780+
supportsReasoningEffort: ['low', 'medium', 'high'],
781+
});
782+
const options = createMinimalOptions({
783+
enableThinking: false,
784+
reasoningEffort: 'high',
785+
});
786+
787+
const body = instantiationService.invokeFunction(createMessagesRequestBody, options, endpoint.model, endpoint);
788+
789+
expect(body.thinking).toBeUndefined();
790+
expect(body.output_config).toBeUndefined();
791+
});
792+
793+
test('omits effort when reasoningEffort is an invalid value', () => {
794+
const endpoint = createMockEndpoint({
795+
supportsAdaptiveThinking: true,
796+
supportsReasoningEffort: ['low', 'medium', 'high'],
797+
});
798+
const options = createMinimalOptions({
799+
enableThinking: true,
800+
reasoningEffort: 'xhigh' as any,
801+
});
802+
803+
const body = instantiationService.invokeFunction(createMessagesRequestBody, options, endpoint.model, endpoint);
804+
805+
expect(body.thinking).toEqual({ type: 'adaptive' });
806+
expect(body.output_config).toBeUndefined();
807+
});
808+
809+
test('uses budget_tokens thinking when model has maxThinkingBudget but not adaptive', () => {
810+
const endpoint = createMockEndpoint({
811+
supportsAdaptiveThinking: false,
812+
maxThinkingBudget: 32000,
813+
minThinkingBudget: 1024,
814+
supportsReasoningEffort: ['low', 'medium', 'high'],
815+
});
816+
mockConfig.setConfig(ConfigKey.AnthropicThinkingBudget, 10000);
817+
const options = createMinimalOptions({
818+
enableThinking: true,
819+
reasoningEffort: 'low',
820+
});
821+
822+
const body = instantiationService.invokeFunction(createMessagesRequestBody, options, endpoint.model, endpoint);
823+
824+
expect(body.thinking).toEqual({ type: 'enabled', budget_tokens: 8191 });
825+
expect(body.output_config).toEqual({ effort: 'low' });
826+
});
827+
});

0 commit comments

Comments
 (0)