mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-17 04:55:51 +00:00
💄 style: Support OpenRouter Claude 4 reasoning (#8087)
* Add Claude Sonnet 4 and Claude Opus 4 to OpenRouter provider * Calculate budget_token count
This commit is contained in:
@@ -660,6 +660,50 @@ const openrouterChatModels: AIChatModelCard[] = [
|
||||
},
|
||||
type: 'chat',
|
||||
},
|
||||
{
|
||||
abilities: {
|
||||
functionCall: true,
|
||||
reasoning: true,
|
||||
vision: true,
|
||||
},
|
||||
contextWindowTokens: 200_000,
|
||||
description:
|
||||
'Claude Sonnet 4 可以产生近乎即时的响应或延长的逐步思考,用户可以清晰地看到这些过程。API 用户还可以对模型思考的时间进行细致的控制',
|
||||
displayName: 'Claude Sonnet 4',
|
||||
id: 'anthropic/claude-sonnet-4',
|
||||
maxOutput: 64_000,
|
||||
pricing: {
|
||||
input: 3,
|
||||
output: 15,
|
||||
},
|
||||
releasedAt: '2025-05-23',
|
||||
settings: {
|
||||
extendParams: ['enableReasoning', 'reasoningBudgetToken'],
|
||||
},
|
||||
type: 'chat',
|
||||
},
|
||||
{
|
||||
abilities: {
|
||||
functionCall: true,
|
||||
reasoning: true,
|
||||
vision: true,
|
||||
},
|
||||
contextWindowTokens: 200_000,
|
||||
description:
|
||||
'Claude Opus 4 是 Anthropic 用于处理高度复杂任务的最强大模型。它在性能、智能、流畅性和理解力方面表现卓越。',
|
||||
displayName: 'Claude Opus 4',
|
||||
id: 'anthropic/claude-opus-4',
|
||||
maxOutput: 32_000,
|
||||
pricing: {
|
||||
input: 15,
|
||||
output: 75,
|
||||
},
|
||||
releasedAt: '2025-05-23',
|
||||
settings: {
|
||||
extendParams: ['enableReasoning', 'reasoningBudgetToken'],
|
||||
},
|
||||
type: 'chat',
|
||||
},
|
||||
{
|
||||
abilities: {
|
||||
functionCall: true,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import OpenRouterModels from '@/config/aiModels/openrouter';
|
||||
import type { ChatModelCard } from '@/types/llm';
|
||||
|
||||
import { ModelProvider } from '../types';
|
||||
@@ -14,12 +15,27 @@ export const LobeOpenRouterAI = createOpenAICompatibleRuntime({
|
||||
baseURL: 'https://openrouter.ai/api/v1',
|
||||
chatCompletion: {
|
||||
handlePayload: (payload) => {
|
||||
const { thinking } = payload;
|
||||
const { thinking, model, max_tokens } = payload;
|
||||
|
||||
let reasoning: OpenRouterReasoning = {};
|
||||
|
||||
if (thinking?.type === 'enabled') {
|
||||
const modelConfig = OpenRouterModels.find((m) => m.id === model);
|
||||
const defaultMaxOutput = modelConfig?.maxOutput;
|
||||
|
||||
// 配置优先级:用户设置 > 模型配置 > 硬编码默认值
|
||||
const getMaxTokens = () => {
|
||||
if (max_tokens) return max_tokens;
|
||||
if (defaultMaxOutput) return defaultMaxOutput;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const maxTokens = getMaxTokens() || 32_000; // Claude Opus 4 has minimum maxOutput
|
||||
|
||||
reasoning = {
|
||||
max_tokens: thinking.budget_tokens,
|
||||
max_tokens: thinking?.budget_tokens
|
||||
? Math.min(thinking.budget_tokens, maxTokens - 1)
|
||||
: 1024,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,7 +59,7 @@ export const LobeOpenRouterAI = createOpenAICompatibleRuntime({
|
||||
models: async ({ client }) => {
|
||||
const modelsPage = (await client.models.list()) as any;
|
||||
const modelList: OpenRouterModelCard[] = modelsPage.data;
|
||||
|
||||
|
||||
const modelsExtraInfo: OpenRouterModelExtraInfo[] = [];
|
||||
try {
|
||||
const response = await fetch('https://openrouter.ai/api/frontend/models');
|
||||
@@ -54,49 +70,45 @@ export const LobeOpenRouterAI = createOpenAICompatibleRuntime({
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch OpenRouter frontend models:', error);
|
||||
}
|
||||
|
||||
|
||||
// 解析模型能力
|
||||
const baseModels = await processMultiProviderModelList(modelList);
|
||||
|
||||
|
||||
// 合并 OpenRouter 获取的模型信息
|
||||
return baseModels.map((baseModel) => {
|
||||
const model = modelList.find(m => m.id === baseModel.id);
|
||||
const extraInfo = modelsExtraInfo.find(
|
||||
(m) => m.slug.toLowerCase() === baseModel.id.toLowerCase(),
|
||||
);
|
||||
|
||||
if (!model) return baseModel;
|
||||
|
||||
return {
|
||||
...baseModel,
|
||||
contextWindowTokens: model.context_length,
|
||||
description: model.description,
|
||||
displayName: model.name,
|
||||
functionCall:
|
||||
baseModel.functionCall ||
|
||||
model.description.includes('function calling') ||
|
||||
model.description.includes('tools') ||
|
||||
extraInfo?.endpoint?.supports_tool_parameters ||
|
||||
false,
|
||||
maxTokens:
|
||||
typeof model.top_provider.max_completion_tokens === 'number'
|
||||
? model.top_provider.max_completion_tokens
|
||||
: undefined,
|
||||
pricing: {
|
||||
input: formatPrice(model.pricing.prompt),
|
||||
output: formatPrice(model.pricing.completion),
|
||||
},
|
||||
reasoning:
|
||||
baseModel.reasoning ||
|
||||
extraInfo?.endpoint?.supports_reasoning ||
|
||||
false,
|
||||
releasedAt: new Date(model.created * 1000).toISOString().split('T')[0],
|
||||
vision:
|
||||
baseModel.vision ||
|
||||
model.architecture.modality.includes('image') ||
|
||||
false,
|
||||
};
|
||||
}).filter(Boolean) as ChatModelCard[];
|
||||
return baseModels
|
||||
.map((baseModel) => {
|
||||
const model = modelList.find((m) => m.id === baseModel.id);
|
||||
const extraInfo = modelsExtraInfo.find(
|
||||
(m) => m.slug.toLowerCase() === baseModel.id.toLowerCase(),
|
||||
);
|
||||
|
||||
if (!model) return baseModel;
|
||||
|
||||
return {
|
||||
...baseModel,
|
||||
contextWindowTokens: model.context_length,
|
||||
description: model.description,
|
||||
displayName: model.name,
|
||||
functionCall:
|
||||
baseModel.functionCall ||
|
||||
model.description.includes('function calling') ||
|
||||
model.description.includes('tools') ||
|
||||
extraInfo?.endpoint?.supports_tool_parameters ||
|
||||
false,
|
||||
maxTokens:
|
||||
typeof model.top_provider.max_completion_tokens === 'number'
|
||||
? model.top_provider.max_completion_tokens
|
||||
: undefined,
|
||||
pricing: {
|
||||
input: formatPrice(model.pricing.prompt),
|
||||
output: formatPrice(model.pricing.completion),
|
||||
},
|
||||
reasoning: baseModel.reasoning || extraInfo?.endpoint?.supports_reasoning || false,
|
||||
releasedAt: new Date(model.created * 1000).toISOString().split('T')[0],
|
||||
vision: baseModel.vision || model.architecture.modality.includes('image') || false,
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as ChatModelCard[];
|
||||
},
|
||||
provider: ModelProvider.OpenRouter,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user