Skip to content

Commit f1ffc2c

Browse files
authored
💄 style: support openrouter claude 3.7 sonnet reasoning (lobehub#6806)
* feat: openrouter reasoning * test: add test
1 parent a9eadaf commit f1ffc2c

File tree

4 files changed

+89
-3
lines changed

4 files changed

+89
-3
lines changed

src/config/aiModels/openrouter.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,31 @@ const openrouterChatModels: AIChatModelCard[] = [
137137
releasedAt: '2024-06-20',
138138
type: 'chat',
139139
},
140+
{
141+
abilities: {
142+
functionCall: true,
143+
reasoning: true,
144+
vision: true,
145+
},
146+
contextWindowTokens: 200_000,
147+
description:
148+
'Claude 3.7 Sonnet 是 Anthropic 迄今为止最智能的模型,也是市场上首个混合推理模型。Claude 3.7 Sonnet 可以产生近乎即时的响应或延长的逐步思考,用户可以清晰地看到这些过程。Sonnet 特别擅长编程、数据科学、视觉处理、代理任务。',
149+
displayName: 'Claude 3.7 Sonnet',
150+
enabled: true,
151+
id: 'anthropic/claude-3.7-sonnet',
152+
maxOutput: 8192,
153+
pricing: {
154+
cachedInput: 0.3,
155+
input: 3,
156+
output: 15,
157+
writeCacheInput: 3.75,
158+
},
159+
releasedAt: '2025-02-24',
160+
settings: {
161+
extendParams: ['enableReasoning', 'reasoningBudgetToken'],
162+
},
163+
type: 'chat',
164+
},
140165
{
141166
abilities: {
142167
functionCall: true,
@@ -258,7 +283,7 @@ const openrouterChatModels: AIChatModelCard[] = [
258283
id: 'deepseek/deepseek-r1:free',
259284
releasedAt: '2025-01-20',
260285
type: 'chat',
261-
},
286+
},
262287
{
263288
abilities: {
264289
vision: true,

src/libs/agent-runtime/openrouter/index.test.ts

+33
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,39 @@ describe('LobeOpenRouterAI', () => {
9292
expect(result).toBeInstanceOf(Response);
9393
});
9494

95+
it('should add reasoning field when thinking is enabled', async () => {
96+
// Arrange
97+
const mockStream = new ReadableStream();
98+
const mockResponse = Promise.resolve(mockStream);
99+
100+
(instance['client'].chat.completions.create as Mock).mockResolvedValue(mockResponse);
101+
102+
// Act
103+
const result = await instance.chat({
104+
messages: [{ content: 'Hello', role: 'user' }],
105+
model: 'mistralai/mistral-7b-instruct:free',
106+
temperature: 0.7,
107+
thinking: {
108+
type: 'enabled',
109+
budget_tokens: 1500,
110+
},
111+
});
112+
113+
// Assert
114+
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
115+
expect.objectContaining({
116+
messages: [{ content: 'Hello', role: 'user' }],
117+
model: 'mistralai/mistral-7b-instruct:free',
118+
reasoning: {
119+
max_tokens: 1500,
120+
},
121+
temperature: 0.7,
122+
}),
123+
{ headers: { Accept: '*/*' } },
124+
);
125+
expect(result).toBeInstanceOf(Response);
126+
});
127+
95128
describe('Error', () => {
96129
it('should return OpenRouterBizError with an openai error response when OpenAI.APIError is thrown', async () => {
97130
// Arrange

src/libs/agent-runtime/openrouter/index.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { ChatModelCard } from '@/types/llm';
22

33
import { ModelProvider } from '../types';
44
import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
5-
import { OpenRouterModelCard, OpenRouterModelExtraInfo } from './type';
5+
import { OpenRouterModelCard, OpenRouterModelExtraInfo, OpenRouterReasoning } from './type';
66

77
const formatPrice = (price: string) => {
88
if (price === '-1') return undefined;
@@ -13,10 +13,19 @@ export const LobeOpenRouterAI = LobeOpenAICompatibleFactory({
1313
baseURL: 'https://openrouter.ai/api/v1',
1414
chatCompletion: {
1515
handlePayload: (payload) => {
16+
const { thinking } = payload;
17+
18+
let reasoning: OpenRouterReasoning = {};
19+
if (thinking?.type === 'enabled') {
20+
reasoning = {
21+
max_tokens: thinking.budget_tokens,
22+
};
23+
}
24+
1625
return {
1726
...payload,
18-
include_reasoning: true,
1927
model: payload.enabledSearch ? `${payload.model}:online` : payload.model,
28+
reasoning,
2029
stream: payload.stream ?? true,
2130
} as any;
2231
},

src/libs/agent-runtime/openrouter/type.ts

+19
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,22 @@ export interface OpenRouterModelExtraInfo {
3737
endpoint?: OpenRouterModelEndpoint;
3838
slug: string;
3939
}
40+
41+
interface OpenRouterOpenAIReasoning {
42+
effort: 'high' | 'medium' | 'low';
43+
exclude?: boolean;
44+
}
45+
46+
interface OpenRouterAnthropicReasoning {
47+
exclude?: boolean;
48+
max_tokens: number;
49+
}
50+
51+
interface OpenRouterCommonReasoning {
52+
exclude?: boolean;
53+
}
54+
55+
export type OpenRouterReasoning =
56+
| OpenRouterOpenAIReasoning
57+
| OpenRouterAnthropicReasoning
58+
| OpenRouterCommonReasoning;

0 commit comments

Comments
 (0)