mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-20 20:35:14 +08:00
✨ feat(ai): 支持 MCP 少量查数与分服务商思考强度,修复 DeepSeek Anthropic
- MCP HTTP 默认可注册 execute_sql,行数默认 50/上限 200,设置页可切换仅结构模式 - 思考强度按 OpenAI/Anthropic/DeepSeek/Gemini 体系映射档位与请求参数 - 自定义端点 DeepSeek Anthropic 自动纠正 base 路径并回传 thinking 块 - 补充前后端回归测试与多语言文案
This commit is contained in:
@@ -57,7 +57,8 @@ const DEFAULT_MCP_HTTP_SERVER_STATUS: AIMCPHTTPServerStatus = {
|
||||
addr: '127.0.0.1:8765',
|
||||
path: '/mcp',
|
||||
url: 'http://127.0.0.1:8765/mcp',
|
||||
schemaOnly: true,
|
||||
// 默认允许 execute_sql 查少量数据
|
||||
schemaOnly: false,
|
||||
message: '',
|
||||
};
|
||||
|
||||
@@ -65,6 +66,7 @@ const DEFAULT_MCP_HTTP_SERVER_DRAFT: AIMCPHTTPServerDraft = {
|
||||
addr: DEFAULT_MCP_HTTP_SERVER_STATUS.addr,
|
||||
path: DEFAULT_MCP_HTTP_SERVER_STATUS.path,
|
||||
authorizationHeader: '',
|
||||
schemaOnly: false,
|
||||
};
|
||||
|
||||
const buildMCPHTTPServerDraftFromStatus = (
|
||||
@@ -79,6 +81,10 @@ const buildMCPHTTPServerDraftFromStatus = (
|
||||
fallback.authorizationHeader ||
|
||||
'',
|
||||
).trim(),
|
||||
// 运行中用状态;未运行保留草稿选择
|
||||
schemaOnly: typeof status.schemaOnly === 'boolean'
|
||||
? status.schemaOnly
|
||||
: (typeof fallback.schemaOnly === 'boolean' ? fallback.schemaOnly : false),
|
||||
});
|
||||
|
||||
const normalizeMCPHTTPAuthorizationToken = (value: string): string => {
|
||||
@@ -406,6 +412,7 @@ export const AISettingsContent: React.FC<AISettingsContentProps> = ({ active, da
|
||||
models: resolvedModels,
|
||||
baseUrl: finalBaseUrl,
|
||||
apiFormat: resolvedTransport.apiFormat,
|
||||
thinkingIntensity: String(values.thinkingIntensity || '').trim(),
|
||||
};
|
||||
// 后端 AISaveProvider 统一处理新增和更新,返回 void,失败抛异常
|
||||
await Service?.AISaveProvider?.(payload);
|
||||
@@ -549,7 +556,7 @@ export const AISettingsContent: React.FC<AISettingsContentProps> = ({ active, da
|
||||
addr: mcpHTTPServerDraft.addr || DEFAULT_MCP_HTTP_SERVER_STATUS.addr,
|
||||
path: mcpHTTPServerDraft.path || DEFAULT_MCP_HTTP_SERVER_STATUS.path,
|
||||
token: normalizeMCPHTTPAuthorizationToken(mcpHTTPServerDraft.authorizationHeader),
|
||||
schemaOnly: true,
|
||||
schemaOnly: mcpHTTPServerDraft.schemaOnly === true,
|
||||
})
|
||||
: await Service.AIStopMCPHTTPServer();
|
||||
if (nextStatus) {
|
||||
@@ -678,6 +685,7 @@ export const AISettingsContent: React.FC<AISettingsContentProps> = ({ active, da
|
||||
models: resolvedModels,
|
||||
maxTokens: Number(values.maxTokens) || 4096,
|
||||
temperature: Number(values.temperature) ?? 0.7,
|
||||
thinkingIntensity: String(values.thinkingIntensity || '').trim(),
|
||||
apiFormat: resolvedTransport.apiFormat,
|
||||
});
|
||||
if (res?.success) { setTestStatus('success'); void messageApi.success(t('ai_settings.message.test_success')); }
|
||||
|
||||
@@ -32,6 +32,12 @@ const REQUIRED_KEYS = [
|
||||
'ai_settings.mcp_http.panel.stopped_hint',
|
||||
'ai_settings.mcp_http.panel.copy_url',
|
||||
'ai_settings.mcp_http.panel.copy_authorization',
|
||||
'ai_settings.mcp_http.panel.mode.schema_only',
|
||||
'ai_settings.mcp_http.panel.mode.limited_query',
|
||||
'ai_settings.mcp_http.panel.limited_query.label',
|
||||
'ai_settings.mcp_http.panel.limited_query.hint',
|
||||
'ai_settings.mcp_http.panel.limited_query.on',
|
||||
'ai_settings.mcp_http.panel.limited_query.off',
|
||||
];
|
||||
|
||||
const buildPanelProps = () => ({
|
||||
@@ -40,7 +46,7 @@ const buildPanelProps = () => ({
|
||||
addr: '127.0.0.1:8765',
|
||||
path: '/mcp',
|
||||
url: 'http://127.0.0.1:8765/mcp',
|
||||
schemaOnly: true,
|
||||
schemaOnly: false,
|
||||
authorizationHeader: 'Bearer gnv_test',
|
||||
message: '',
|
||||
},
|
||||
@@ -48,6 +54,7 @@ const buildPanelProps = () => ({
|
||||
addr: '127.0.0.1:8765',
|
||||
path: '/mcp',
|
||||
authorizationHeader: 'Bearer gnv_test',
|
||||
schemaOnly: false,
|
||||
},
|
||||
loading: false,
|
||||
cardBg: '#fff',
|
||||
@@ -98,7 +105,7 @@ describe('AIMCPHTTPServerPanel', () => {
|
||||
|
||||
expect(markup).toContain('GoNavi MCP HTTP service');
|
||||
expect(markup).toContain('Running');
|
||||
expect(markup).toContain('schema-only');
|
||||
expect(markup).toContain('Limited query');
|
||||
expect(markup).toContain('Listen address / port');
|
||||
expect(markup).toContain('Authorization');
|
||||
expect(markup).toContain('127.0.0.1:8765');
|
||||
|
||||
@@ -11,6 +11,8 @@ export interface AIMCPHTTPServerDraft {
|
||||
addr: string;
|
||||
path: string;
|
||||
authorizationHeader: string;
|
||||
/** false 时注册 execute_sql,允许查少量样例数据 */
|
||||
schemaOnly: boolean;
|
||||
}
|
||||
|
||||
export interface AIMCPHTTPServerPanelProps {
|
||||
@@ -71,8 +73,10 @@ const AIMCPHTTPServerPanel: React.FC<AIMCPHTTPServerPanelProps> = ({
|
||||
<Tag color={running ? 'success' : 'default'} style={{ marginInlineEnd: 0 }}>
|
||||
{copy(running ? 'ai_settings.mcp_http.panel.status.running' : 'ai_settings.mcp_http.panel.status.stopped')}
|
||||
</Tag>
|
||||
<Tag color="blue" style={{ marginInlineEnd: 0 }}>
|
||||
schema-only
|
||||
<Tag color={draft.schemaOnly || status.schemaOnly ? 'blue' : 'green'} style={{ marginInlineEnd: 0 }}>
|
||||
{draft.schemaOnly || (running && status.schemaOnly)
|
||||
? copy('ai_settings.mcp_http.panel.mode.schema_only')
|
||||
: copy('ai_settings.mcp_http.panel.mode.limited_query')}
|
||||
</Tag>
|
||||
</div>
|
||||
<div style={{ marginTop: 6, fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.7 }}>
|
||||
@@ -126,6 +130,31 @@ const AIMCPHTTPServerPanel: React.FC<AIMCPHTTPServerPanelProps> = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: 12,
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
<div style={{ minWidth: 0, flex: 1 }}>
|
||||
<div style={{ fontSize: 12, fontWeight: 700, color: overlayTheme.titleText }}>
|
||||
{copy('ai_settings.mcp_http.panel.limited_query.label')}
|
||||
</div>
|
||||
<div style={{ marginTop: 2, fontSize: 11, color: overlayTheme.mutedText, lineHeight: 1.6 }}>
|
||||
{copy('ai_settings.mcp_http.panel.limited_query.hint')}
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={!draft.schemaOnly}
|
||||
disabled={running || loading}
|
||||
onChange={(checked) => onDraftChange({ schemaOnly: !checked })}
|
||||
checkedChildren={copy('ai_settings.mcp_http.panel.limited_query.on')}
|
||||
unCheckedChildren={copy('ai_settings.mcp_http.panel.limited_query.off')}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: overlayTheme.mutedText, lineHeight: 1.7 }}>
|
||||
{running
|
||||
? status.message || copy('ai_settings.mcp_http.panel.running_hint')
|
||||
|
||||
@@ -45,13 +45,14 @@ const buildMCPSectionProps = (patch: Partial<AISettingsMCPSectionProps> = {}): A
|
||||
addr: '127.0.0.1:8765',
|
||||
path: '/mcp',
|
||||
url: 'http://127.0.0.1:8765/mcp',
|
||||
schemaOnly: true,
|
||||
schemaOnly: false,
|
||||
message: 'GoNavi MCP HTTP 服务未启动',
|
||||
},
|
||||
mcpHTTPServerDraft: {
|
||||
addr: '127.0.0.1:8765',
|
||||
path: '/mcp',
|
||||
authorizationHeader: 'Bearer gnv_test',
|
||||
schemaOnly: false,
|
||||
},
|
||||
mcpServers: [],
|
||||
mcpTools: [],
|
||||
|
||||
@@ -14,6 +14,12 @@ import {
|
||||
PROVIDER_PRESET_GRID_STYLE,
|
||||
PROVIDER_PRESET_CARD_TITLE_STYLE,
|
||||
} from '../../utils/aiSettingsPresetLayout';
|
||||
import {
|
||||
coerceThinkingIntensityForProfile,
|
||||
resolveThinkingIntensityHintKey,
|
||||
resolveThinkingIntensityOptions,
|
||||
resolveThinkingIntensityProfile,
|
||||
} from '../../utils/aiThinkingIntensity';
|
||||
|
||||
export interface AISettingsProviderPresetOption {
|
||||
key: string;
|
||||
@@ -120,8 +126,44 @@ const AISettingsProvidersSection: React.FC<AISettingsProvidersSectionProps> = ({
|
||||
const currentFieldLabelStyle = fieldLabelStyle(sectionLabelColor);
|
||||
const watchedModel = Form.useWatch('model', form);
|
||||
const watchedModels = Form.useWatch('models', form);
|
||||
const watchedBaseUrl = Form.useWatch('baseUrl', form);
|
||||
const watchedType = Form.useWatch('type', form);
|
||||
const watchedThinkingIntensity = Form.useWatch('thinkingIntensity', form);
|
||||
const watchedInlineCompletionModel = Form.useWatch('inlineCompletionModel', form);
|
||||
const presetFromForm = providerPresets.find((preset) => preset.key === presetKeyFromForm);
|
||||
const thinkingProfile = React.useMemo(() => resolveThinkingIntensityProfile({
|
||||
type: watchedType || editingProvider?.type,
|
||||
apiFormat: watchedApiFormat || editingProvider?.apiFormat,
|
||||
baseUrl: watchedBaseUrl || editingProvider?.baseUrl || presetFromForm?.defaultBaseUrl,
|
||||
model: watchedModel || editingProvider?.model || presetFromForm?.defaultModel,
|
||||
}), [
|
||||
editingProvider?.apiFormat,
|
||||
editingProvider?.baseUrl,
|
||||
editingProvider?.model,
|
||||
editingProvider?.type,
|
||||
presetFromForm?.defaultBaseUrl,
|
||||
presetFromForm?.defaultModel,
|
||||
watchedApiFormat,
|
||||
watchedBaseUrl,
|
||||
watchedModel,
|
||||
watchedType,
|
||||
]);
|
||||
const thinkingIntensityOptions = React.useMemo(
|
||||
() => resolveThinkingIntensityOptions(thinkingProfile).map((item) => ({
|
||||
value: item.value,
|
||||
label: copy(item.labelKey),
|
||||
})),
|
||||
// copy 依赖 i18n 语言,label 随语言刷新即可
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[copy, thinkingProfile, i18n?.language],
|
||||
);
|
||||
React.useEffect(() => {
|
||||
if (!isEditing) return;
|
||||
const next = coerceThinkingIntensityForProfile(watchedThinkingIntensity, thinkingProfile);
|
||||
if (next && next !== String(watchedThinkingIntensity || '').trim()) {
|
||||
form.setFieldsValue({ thinkingIntensity: next });
|
||||
}
|
||||
}, [form, isEditing, thinkingProfile, watchedThinkingIntensity]);
|
||||
const inlineCompletionModelOptions = React.useMemo(() => {
|
||||
const values = [
|
||||
watchedModel,
|
||||
@@ -370,6 +412,24 @@ const AISettingsProvidersSection: React.FC<AISettingsProvidersSectionProps> = ({
|
||||
<Form.Item name="model" hidden><Input /></Form.Item>
|
||||
<Form.Item name="name" hidden><Input /></Form.Item>
|
||||
|
||||
<div style={{ ...currentFieldGroupStyle, marginTop: 16 }}>
|
||||
<div style={currentFieldLabelStyle}>
|
||||
<RobotOutlined style={{ fontSize: 14 }} /> {copy('ai_settings.form.section.thinking')}
|
||||
</div>
|
||||
<Form.Item
|
||||
label={<span style={{ fontWeight: 500, color: overlayTheme.titleText }}>{copy('ai_settings.form.thinking_intensity')}</span>}
|
||||
name="thinkingIntensity"
|
||||
extra={copy(resolveThinkingIntensityHintKey(thinkingProfile))}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<Select
|
||||
size="middle"
|
||||
style={{ width: '100%' }}
|
||||
options={thinkingIntensityOptions}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<div style={{ ...currentFieldGroupStyle, marginTop: 16 }}>
|
||||
<div style={currentFieldLabelStyle}>
|
||||
<RobotOutlined style={{ fontSize: 14 }} /> {copy('ai_settings.form.section.inline_completion')}
|
||||
|
||||
@@ -2149,7 +2149,7 @@ describe("i18n catalog", () => {
|
||||
sliceBetween(
|
||||
source,
|
||||
" const binding = selectCurrentStatementShortcutBinding;",
|
||||
" }, [languagePreference, selectCurrentStatementShortcutBinding, handleSelectCurrentStatement]);",
|
||||
" }, [activeShortcutPlatform, languagePreference, selectCurrentStatementShortcutBinding, handleSelectCurrentStatement]);",
|
||||
),
|
||||
sliceBetween(
|
||||
source,
|
||||
|
||||
@@ -629,6 +629,8 @@ export interface AIProviderConfig {
|
||||
headers?: Record<string, string>;
|
||||
maxTokens: number;
|
||||
temperature: number;
|
||||
/** 思考强度:off | low | medium | high;空表示供应商默认 */
|
||||
thinkingIntensity?: string;
|
||||
}
|
||||
|
||||
export interface AIUserPromptSettings {
|
||||
|
||||
@@ -43,6 +43,7 @@ export const buildAddProviderEditorSession = ({
|
||||
models: [...presetModels],
|
||||
maxTokens: 4096,
|
||||
temperature: 0.7,
|
||||
thinkingIntensity: 'medium',
|
||||
presetKey,
|
||||
};
|
||||
|
||||
|
||||
42
frontend/src/utils/aiThinkingIntensity.test.ts
Normal file
42
frontend/src/utils/aiThinkingIntensity.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
152
frontend/src/utils/aiThinkingIntensity.ts
Normal file
152
frontend/src/utils/aiThinkingIntensity.ts
Normal 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';
|
||||
};
|
||||
@@ -297,6 +297,7 @@ export namespace ai {
|
||||
headers?: Record<string, string>;
|
||||
maxTokens: number;
|
||||
temperature: number;
|
||||
thinkingIntensity?: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ProviderConfig(source);
|
||||
@@ -318,6 +319,7 @@ export namespace ai {
|
||||
this.headers = source["headers"];
|
||||
this.maxTokens = source["maxTokens"];
|
||||
this.temperature = source["temperature"];
|
||||
this.thinkingIntensity = source["thinkingIntensity"];
|
||||
}
|
||||
}
|
||||
export class SafetyResult {
|
||||
|
||||
Reference in New Issue
Block a user