mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-14 11:37:31 +08:00
fix(llm): let custom OpenAI-compatible providers opt out of API Key requirement (#89)
This commit is contained in:
parent
88fb28a22a
commit
d2886bdaa3
@ -12,5 +12,6 @@ public class CreateCustomProviderRequest {
|
|||||||
private String apiKeyPrefix;
|
private String apiKeyPrefix;
|
||||||
private String protocol;
|
private String protocol;
|
||||||
private String chatModel;
|
private String chatModel;
|
||||||
|
private Boolean requireApiKey;
|
||||||
private List<ModelInfoDTO> models;
|
private List<ModelInfoDTO> models;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ public class ProviderConfigRequest {
|
|||||||
private String protocol;
|
private String protocol;
|
||||||
private String chatModel;
|
private String chatModel;
|
||||||
private Map<String, Object> generateKwargs;
|
private Map<String, Object> generateKwargs;
|
||||||
|
private Boolean requireApiKey;
|
||||||
/**
|
/**
|
||||||
* RFC-009 P3.5: provider's position in the multi-model failover chain.
|
* RFC-009 P3.5: provider's position in the multi-model failover chain.
|
||||||
* {@code 0} = excluded; positive ints define ascending try-order. When
|
* {@code 0} = excluded; positive ints define ascending try-order. When
|
||||||
|
|||||||
@ -129,6 +129,9 @@ public class ModelProviderService {
|
|||||||
provider.setBaseUrl(request.getBaseUrl());
|
provider.setBaseUrl(request.getBaseUrl());
|
||||||
provider.setChatModel(ModelProtocol.resolveChatModel(request.getProtocol(), request.getChatModel()));
|
provider.setChatModel(ModelProtocol.resolveChatModel(request.getProtocol(), request.getChatModel()));
|
||||||
provider.setGenerateKwargs(writeJson(request.getGenerateKwargs()));
|
provider.setGenerateKwargs(writeJson(request.getGenerateKwargs()));
|
||||||
|
if (request.getRequireApiKey() != null) {
|
||||||
|
provider.setRequireApiKey(request.getRequireApiKey());
|
||||||
|
}
|
||||||
// RFC-009 P3.5: only update fallback priority when the caller explicitly
|
// RFC-009 P3.5: only update fallback priority when the caller explicitly
|
||||||
// sends a value. null leaves it untouched (existing chain unchanged).
|
// sends a value. null leaves it untouched (existing chain unchanged).
|
||||||
if (request.getFallbackPriority() != null) {
|
if (request.getFallbackPriority() != null) {
|
||||||
@ -169,7 +172,7 @@ public class ModelProviderService {
|
|||||||
provider.setSupportModelDiscovery(false);
|
provider.setSupportModelDiscovery(false);
|
||||||
provider.setSupportConnectionCheck(false);
|
provider.setSupportConnectionCheck(false);
|
||||||
provider.setFreezeUrl(false);
|
provider.setFreezeUrl(false);
|
||||||
provider.setRequireApiKey(true);
|
provider.setRequireApiKey(request.getRequireApiKey() == null || Boolean.TRUE.equals(request.getRequireApiKey()));
|
||||||
modelProviderMapper.insert(provider);
|
modelProviderMapper.insert(provider);
|
||||||
|
|
||||||
if (request.getModels() != null) {
|
if (request.getModels() != null) {
|
||||||
|
|||||||
@ -608,6 +608,7 @@ export default {
|
|||||||
protocolGemini: 'Gemini Native',
|
protocolGemini: 'Gemini Native',
|
||||||
protocolDashScope: 'DashScope Native',
|
protocolDashScope: 'DashScope Native',
|
||||||
advancedHint: 'Use this for generation options such as temperature, max_tokens, and top_p.',
|
advancedHint: 'Use this for generation options such as temperature, max_tokens, and top_p.',
|
||||||
|
requireApiKeyHint: 'Turn this off for internal or local OpenAI-compatible services that do not require auth. Connection tests will omit the Authorization header.',
|
||||||
fallbackPriorityHint: 'Pool try-order (lower wins): 0 = excluded, 1 = first in line, 2 = second, and so on. Providers sharing the same value are tried in alphabetical order of their ID.',
|
fallbackPriorityHint: 'Pool try-order (lower wins): 0 = excluded, 1 = first in line, 2 = second, and so on. Providers sharing the same value are tried in alphabetical order of their ID.',
|
||||||
fallbackBadge: 'Preferred #{priority}',
|
fallbackBadge: 'Preferred #{priority}',
|
||||||
fallbackBadgeTitle: 'Position in the available pool — lower numbers are tried first',
|
fallbackBadgeTitle: 'Position in the available pool — lower numbers are tried first',
|
||||||
@ -683,6 +684,7 @@ export default {
|
|||||||
providerName: 'Provider Name',
|
providerName: 'Provider Name',
|
||||||
defaultBaseUrl: 'Default Base URL',
|
defaultBaseUrl: 'Default Base URL',
|
||||||
apiKeyPrefix: 'API Key Prefix',
|
apiKeyPrefix: 'API Key Prefix',
|
||||||
|
requireApiKey: 'Requires API Key',
|
||||||
protocol: 'Protocol',
|
protocol: 'Protocol',
|
||||||
generateKwargs: 'Generate Kwargs (JSON)',
|
generateKwargs: 'Generate Kwargs (JSON)',
|
||||||
fallbackPriority: 'Pool Try-Order',
|
fallbackPriority: 'Pool Try-Order',
|
||||||
|
|||||||
@ -494,6 +494,7 @@ export default {
|
|||||||
protocolGemini: 'Gemini 原生',
|
protocolGemini: 'Gemini 原生',
|
||||||
protocolDashScope: 'DashScope 原生',
|
protocolDashScope: 'DashScope 原生',
|
||||||
advancedHint: '用于补充 temperature、max_tokens、top_p 等生成参数。',
|
advancedHint: '用于补充 temperature、max_tokens、top_p 等生成参数。',
|
||||||
|
requireApiKeyHint: '公司内部或本地 OpenAI 兼容服务如果不需要鉴权,可以关闭此项;测试连接时将不会发送 Authorization 头。',
|
||||||
fallbackPriorityHint: '池内尝试顺序(数字越小越先):0 = 不参与;1 = 第一顺位;2 = 第二顺位,依此类推。多个提供商共用同一数字时按 ID 字典序。',
|
fallbackPriorityHint: '池内尝试顺序(数字越小越先):0 = 不参与;1 = 第一顺位;2 = 第二顺位,依此类推。多个提供商共用同一数字时按 ID 字典序。',
|
||||||
fallbackBadge: '偏好 #{priority}',
|
fallbackBadge: '偏好 #{priority}',
|
||||||
fallbackBadgeTitle: '可用池内的尝试顺序,数字越小越先尝试',
|
fallbackBadgeTitle: '可用池内的尝试顺序,数字越小越先尝试',
|
||||||
@ -569,6 +570,7 @@ export default {
|
|||||||
providerName: '提供商名称',
|
providerName: '提供商名称',
|
||||||
defaultBaseUrl: '默认 Base URL',
|
defaultBaseUrl: '默认 Base URL',
|
||||||
apiKeyPrefix: 'API Key 前缀',
|
apiKeyPrefix: 'API Key 前缀',
|
||||||
|
requireApiKey: '需要 API Key',
|
||||||
protocol: '协议',
|
protocol: '协议',
|
||||||
generateKwargs: 'Generate Kwargs (JSON)',
|
generateKwargs: 'Generate Kwargs (JSON)',
|
||||||
fallbackPriority: '池内尝试顺序',
|
fallbackPriority: '池内尝试顺序',
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export function useProviderForm(deps: ListDeps) {
|
|||||||
apiKeyPrefix: 'sk-',
|
apiKeyPrefix: 'sk-',
|
||||||
protocol: 'openai-compatible',
|
protocol: 'openai-compatible',
|
||||||
chatModel: 'OpenAIChatModel',
|
chatModel: 'OpenAIChatModel',
|
||||||
|
requireApiKey: true,
|
||||||
generateKwargsText: '{}',
|
generateKwargsText: '{}',
|
||||||
enableSearch: false,
|
enableSearch: false,
|
||||||
searchStrategy: '',
|
searchStrategy: '',
|
||||||
@ -110,6 +111,7 @@ export function useProviderForm(deps: ListDeps) {
|
|||||||
apiKeyPrefix: 'sk-',
|
apiKeyPrefix: 'sk-',
|
||||||
protocol: 'openai-compatible',
|
protocol: 'openai-compatible',
|
||||||
chatModel: 'OpenAIChatModel',
|
chatModel: 'OpenAIChatModel',
|
||||||
|
requireApiKey: true,
|
||||||
generateKwargsText: '{}',
|
generateKwargsText: '{}',
|
||||||
enableSearch: false,
|
enableSearch: false,
|
||||||
searchStrategy: '',
|
searchStrategy: '',
|
||||||
@ -134,6 +136,7 @@ export function useProviderForm(deps: ListDeps) {
|
|||||||
apiKeyPrefix: provider.apiKeyPrefix || 'sk-',
|
apiKeyPrefix: provider.apiKeyPrefix || 'sk-',
|
||||||
protocol,
|
protocol,
|
||||||
chatModel: provider.chatModel || 'OpenAIChatModel',
|
chatModel: provider.chatModel || 'OpenAIChatModel',
|
||||||
|
requireApiKey: protocol === 'openai-compatible' ? provider.requireApiKey !== false : true,
|
||||||
generateKwargsText: JSON.stringify(kwargs, null, 2),
|
generateKwargsText: JSON.stringify(kwargs, null, 2),
|
||||||
enableSearch: searchDefault,
|
enableSearch: searchDefault,
|
||||||
searchStrategy: (kwargs.searchStrategy as string) || '',
|
searchStrategy: (kwargs.searchStrategy as string) || '',
|
||||||
@ -175,12 +178,16 @@ export function useProviderForm(deps: ListDeps) {
|
|||||||
}
|
}
|
||||||
// RFC-009 P3.5: clamp to non-negative, coerce string input back to integer.
|
// RFC-009 P3.5: clamp to non-negative, coerce string input back to integer.
|
||||||
const fallbackPriority = Math.max(0, Math.floor(Number(providerForm.fallbackPriority) || 0))
|
const fallbackPriority = Math.max(0, Math.floor(Number(providerForm.fallbackPriority) || 0))
|
||||||
|
const requireApiKey = providerForm.protocol === 'openai-compatible'
|
||||||
|
? providerForm.requireApiKey
|
||||||
|
: true
|
||||||
if (editingProvider.value) {
|
if (editingProvider.value) {
|
||||||
await modelApi.updateProviderConfig(editingProvider.value.id, {
|
await modelApi.updateProviderConfig(editingProvider.value.id, {
|
||||||
apiKey: providerForm.apiKey,
|
apiKey: providerForm.apiKey,
|
||||||
baseUrl: providerForm.baseUrl,
|
baseUrl: providerForm.baseUrl,
|
||||||
protocol: providerForm.protocol,
|
protocol: providerForm.protocol,
|
||||||
chatModel: protocolToChatModel(providerForm.protocol),
|
chatModel: protocolToChatModel(providerForm.protocol),
|
||||||
|
requireApiKey,
|
||||||
generateKwargs: kwargs,
|
generateKwargs: kwargs,
|
||||||
fallbackPriority,
|
fallbackPriority,
|
||||||
})
|
})
|
||||||
@ -192,6 +199,7 @@ export function useProviderForm(deps: ListDeps) {
|
|||||||
apiKeyPrefix: providerForm.apiKeyPrefix,
|
apiKeyPrefix: providerForm.apiKeyPrefix,
|
||||||
protocol: providerForm.protocol,
|
protocol: providerForm.protocol,
|
||||||
chatModel: protocolToChatModel(providerForm.protocol),
|
chatModel: protocolToChatModel(providerForm.protocol),
|
||||||
|
requireApiKey,
|
||||||
models: [],
|
models: [],
|
||||||
})
|
})
|
||||||
if (providerForm.apiKey || providerForm.generateKwargsText || fallbackPriority > 0) {
|
if (providerForm.apiKey || providerForm.generateKwargsText || fallbackPriority > 0) {
|
||||||
@ -200,6 +208,7 @@ export function useProviderForm(deps: ListDeps) {
|
|||||||
baseUrl: providerForm.baseUrl,
|
baseUrl: providerForm.baseUrl,
|
||||||
protocol: providerForm.protocol,
|
protocol: providerForm.protocol,
|
||||||
chatModel: protocolToChatModel(providerForm.protocol),
|
chatModel: protocolToChatModel(providerForm.protocol),
|
||||||
|
requireApiKey,
|
||||||
generateKwargs: kwargs,
|
generateKwargs: kwargs,
|
||||||
fallbackPriority,
|
fallbackPriority,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -29,6 +29,16 @@
|
|||||||
/>
|
/>
|
||||||
<div class="field-hint">{{ baseUrlHint }}</div>
|
<div class="field-hint">{{ baseUrlHint }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="editingProvider?.authType !== 'oauth' && form.protocol === 'openai-compatible'" class="form-group">
|
||||||
|
<div class="search-toggle-row">
|
||||||
|
<label class="form-label" style="margin-bottom: 0">{{ t('settings.model.fields.requireApiKey') }}</label>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" v-model="form.requireApiKey" />
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-hint">{{ t('settings.model.requireApiKeyHint') }}</div>
|
||||||
|
</div>
|
||||||
<!-- OAuth 登录区域(auth_type === 'oauth' 时显示) -->
|
<!-- OAuth 登录区域(auth_type === 'oauth' 时显示) -->
|
||||||
<div v-if="editingProvider?.authType === 'oauth'" class="form-group full-width oauth-group">
|
<div v-if="editingProvider?.authType === 'oauth'" class="form-group full-width oauth-group">
|
||||||
<label class="form-label">{{ t('settings.model.oauthTitle') }}</label>
|
<label class="form-label">{{ t('settings.model.oauthTitle') }}</label>
|
||||||
@ -65,7 +75,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- API Key 输入区域(非 OAuth 时显示) -->
|
<!-- API Key 输入区域(非 OAuth 时显示) -->
|
||||||
<div v-else class="form-group">
|
<div v-else-if="form.protocol !== 'openai-compatible' || form.requireApiKey" class="form-group">
|
||||||
<label class="form-label">{{ t('settings.model.apiKey') }}</label>
|
<label class="form-label">{{ t('settings.model.apiKey') }}</label>
|
||||||
<input
|
<input
|
||||||
v-model="form.apiKey"
|
v-model="form.apiKey"
|
||||||
@ -76,7 +86,7 @@
|
|||||||
/>
|
/>
|
||||||
<div class="field-hint">{{ t('settings.model.leaveBlankKeep') }}</div>
|
<div class="field-hint">{{ t('settings.model.leaveBlankKeep') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div v-if="editingProvider?.authType !== 'oauth' && (form.protocol !== 'openai-compatible' || form.requireApiKey)" class="form-group">
|
||||||
<label class="form-label">{{ t('settings.model.fields.apiKeyPrefix') }}</label>
|
<label class="form-label">{{ t('settings.model.fields.apiKeyPrefix') }}</label>
|
||||||
<input v-model="form.apiKeyPrefix" class="form-input" />
|
<input v-model="form.apiKeyPrefix" class="form-input" />
|
||||||
</div>
|
</div>
|
||||||
@ -177,6 +187,7 @@ defineProps<{
|
|||||||
apiKeyPrefix: string
|
apiKeyPrefix: string
|
||||||
protocol: string
|
protocol: string
|
||||||
chatModel: string
|
chatModel: string
|
||||||
|
requireApiKey: boolean
|
||||||
generateKwargsText: string
|
generateKwargsText: string
|
||||||
enableSearch: boolean
|
enableSearch: boolean
|
||||||
searchStrategy: string
|
searchStrategy: string
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user