feat(ai): 支持 MCP 少量查数与分服务商思考强度,修复 DeepSeek Anthropic

- MCP HTTP 默认可注册 execute_sql,行数默认 50/上限 200,设置页可切换仅结构模式
- 思考强度按 OpenAI/Anthropic/DeepSeek/Gemini 体系映射档位与请求参数
- 自定义端点 DeepSeek Anthropic 自动纠正 base 路径并回传 thinking 块
- 补充前后端回归测试与多语言文案
This commit is contained in:
Syngnat
2026-07-09 11:34:38 +08:00
parent f930ffe153
commit 4fcd7ff61f
29 changed files with 1129 additions and 56 deletions

View File

@@ -43,6 +43,7 @@ export const buildAddProviderEditorSession = ({
models: [...presetModels],
maxTokens: 4096,
temperature: 0.7,
thinkingIntensity: 'medium',
presetKey,
};

View File

@@ -0,0 +1,42 @@
import { describe, expect, it } from 'vitest';
import {
coerceThinkingIntensityForProfile,
resolveThinkingIntensityOptions,
resolveThinkingIntensityProfile,
} from './aiThinkingIntensity';
describe('aiThinkingIntensity', () => {
it('detects OpenAI profile for gpt providers', () => {
expect(resolveThinkingIntensityProfile({
type: 'openai',
baseUrl: 'https://api.openai.com/v1',
model: 'gpt-5.2',
})).toBe('openai');
});
it('detects DeepSeek even when api format is anthropic', () => {
expect(resolveThinkingIntensityProfile({
type: 'custom',
apiFormat: 'anthropic',
baseUrl: 'https://api.deepseek.com',
model: 'deepseek-chat',
})).toBe('deepseek');
});
it('exposes openai-style levels including xhigh', () => {
const values = resolveThinkingIntensityOptions('openai').map((item) => item.value);
expect(values).toEqual(['none', 'minimal', 'low', 'medium', 'high', 'xhigh']);
});
it('exposes anthropic-style levels including max', () => {
const values = resolveThinkingIntensityOptions('anthropic').map((item) => item.value);
expect(values).toEqual(['off', 'low', 'medium', 'high', 'xhigh', 'max']);
});
it('coerces openai none when switching away from deepseek off', () => {
expect(coerceThinkingIntensityForProfile('off', 'openai')).toBe('none');
expect(coerceThinkingIntensityForProfile('xhigh', 'deepseek')).toBe('high');
expect(coerceThinkingIntensityForProfile('max', 'openai')).toBe('xhigh');
});
});

View File

@@ -0,0 +1,152 @@
import type { AIProviderType } from '../types';
export type ThinkingIntensityProfile = 'openai' | 'anthropic' | 'deepseek' | 'gemini' | 'generic';
export type ThinkingIntensityOption = {
value: string;
labelKey: string;
};
const OPENAI_OPTIONS: ThinkingIntensityOption[] = [
{ value: 'none', labelKey: 'ai_settings.form.thinking_intensity.none' },
{ value: 'minimal', labelKey: 'ai_settings.form.thinking_intensity.minimal' },
{ value: 'low', labelKey: 'ai_settings.form.thinking_intensity.low' },
{ value: 'medium', labelKey: 'ai_settings.form.thinking_intensity.medium' },
{ value: 'high', labelKey: 'ai_settings.form.thinking_intensity.high' },
{ value: 'xhigh', labelKey: 'ai_settings.form.thinking_intensity.xhigh' },
];
const ANTHROPIC_OPTIONS: ThinkingIntensityOption[] = [
{ value: 'off', labelKey: 'ai_settings.form.thinking_intensity.off' },
{ value: 'low', labelKey: 'ai_settings.form.thinking_intensity.low' },
{ value: 'medium', labelKey: 'ai_settings.form.thinking_intensity.medium' },
{ value: 'high', labelKey: 'ai_settings.form.thinking_intensity.high' },
{ value: 'xhigh', labelKey: 'ai_settings.form.thinking_intensity.xhigh' },
{ value: 'max', labelKey: 'ai_settings.form.thinking_intensity.max' },
];
const DEEPSEEK_OPTIONS: ThinkingIntensityOption[] = [
{ value: 'off', labelKey: 'ai_settings.form.thinking_intensity.off' },
{ value: 'low', labelKey: 'ai_settings.form.thinking_intensity.low' },
{ value: 'medium', labelKey: 'ai_settings.form.thinking_intensity.medium' },
{ value: 'high', labelKey: 'ai_settings.form.thinking_intensity.high' },
];
const GEMINI_OPTIONS: ThinkingIntensityOption[] = [
{ value: 'off', labelKey: 'ai_settings.form.thinking_intensity.off' },
{ value: 'minimal', labelKey: 'ai_settings.form.thinking_intensity.minimal' },
{ value: 'low', labelKey: 'ai_settings.form.thinking_intensity.low' },
{ value: 'medium', labelKey: 'ai_settings.form.thinking_intensity.medium' },
{ value: 'high', labelKey: 'ai_settings.form.thinking_intensity.high' },
];
const GENERIC_OPTIONS: ThinkingIntensityOption[] = [
{ value: 'off', labelKey: 'ai_settings.form.thinking_intensity.off' },
{ value: 'low', labelKey: 'ai_settings.form.thinking_intensity.low' },
{ value: 'medium', labelKey: 'ai_settings.form.thinking_intensity.medium' },
{ value: 'high', labelKey: 'ai_settings.form.thinking_intensity.high' },
];
const getHostname = (raw?: string): string => {
if (!raw) return '';
try {
return new URL(raw).hostname.toLowerCase();
} catch {
return '';
}
};
export const resolveThinkingIntensityProfile = (input: {
type?: AIProviderType | string;
apiFormat?: string;
baseUrl?: string;
model?: string;
}): ThinkingIntensityProfile => {
const type = String(input.type || '').toLowerCase();
const format = String(input.apiFormat || '').toLowerCase();
const base = String(input.baseUrl || '').toLowerCase();
const model = String(input.model || '').toLowerCase();
const host = getHostname(input.baseUrl);
if (host.includes('deepseek') || base.includes('deepseek') || model.includes('deepseek')) {
return 'deepseek';
}
if (type === 'gemini' || format === 'gemini' || host.includes('googleapis.com')) {
return 'gemini';
}
if (type === 'anthropic' || format === 'anthropic') {
return 'anthropic';
}
if (type === 'openai' || format === 'openai' || format === '') {
return 'openai';
}
return 'generic';
};
export const resolveThinkingIntensityOptions = (
profile: ThinkingIntensityProfile,
): ThinkingIntensityOption[] => {
switch (profile) {
case 'openai':
return OPENAI_OPTIONS;
case 'anthropic':
return ANTHROPIC_OPTIONS;
case 'deepseek':
return DEEPSEEK_OPTIONS;
case 'gemini':
return GEMINI_OPTIONS;
default:
return GENERIC_OPTIONS;
}
};
export const resolveThinkingIntensityHintKey = (profile: ThinkingIntensityProfile): string => {
switch (profile) {
case 'openai':
return 'ai_settings.form.thinking_intensity_hint.openai';
case 'anthropic':
return 'ai_settings.form.thinking_intensity_hint.anthropic';
case 'deepseek':
return 'ai_settings.form.thinking_intensity_hint.deepseek';
case 'gemini':
return 'ai_settings.form.thinking_intensity_hint.gemini';
default:
return 'ai_settings.form.thinking_intensity_hint';
}
};
/** 当切换服务商后,若当前值不在新档位集内,映射到最接近的默认值。 */
export const coerceThinkingIntensityForProfile = (
value: string | undefined,
profile: ThinkingIntensityProfile,
): string => {
const options = resolveThinkingIntensityOptions(profile);
const raw = String(value || '').trim().toLowerCase();
if (options.some((item) => item.value === raw)) {
return raw;
}
// 常见跨体系别名
if (raw === 'off' || raw === 'disabled') {
return profile === 'openai' ? 'none' : 'off';
}
if (raw === 'none') {
return profile === 'openai' ? 'none' : 'off';
}
if (raw === 'minimal') {
return options.some((item) => item.value === 'minimal') ? 'minimal' : 'low';
}
if (raw === 'xhigh' || raw === 'max') {
if (options.some((item) => item.value === raw)) return raw;
if (options.some((item) => item.value === 'xhigh')) return 'xhigh';
if (options.some((item) => item.value === 'max')) return 'max';
return 'high';
}
if (options.some((item) => item.value === 'medium')) return 'medium';
return options[0]?.value || 'medium';
};
export const defaultThinkingIntensityForProfile = (profile: ThinkingIntensityProfile): string => {
const options = resolveThinkingIntensityOptions(profile);
if (options.some((item) => item.value === 'medium')) return 'medium';
return options[0]?.value || 'medium';
};