Skip to content

Commit

Permalink
💄 style: support openrouter claude 3.7 sonnet reasoning (#6806)
Browse files Browse the repository at this point in the history
* feat: openrouter reasoning

* test: add test
  • Loading branch information
Aloxaf authored Mar 9, 2025
1 parent a9eadaf commit f1ffc2c
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 3 deletions.
27 changes: 26 additions & 1 deletion src/config/aiModels/openrouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,31 @@ const openrouterChatModels: AIChatModelCard[] = [
releasedAt: '2024-06-20',
type: 'chat',
},
{
abilities: {
functionCall: true,
reasoning: true,
vision: true,
},
contextWindowTokens: 200_000,
description:
'Claude 3.7 Sonnet 是 Anthropic 迄今为止最智能的模型,也是市场上首个混合推理模型。Claude 3.7 Sonnet 可以产生近乎即时的响应或延长的逐步思考,用户可以清晰地看到这些过程。Sonnet 特别擅长编程、数据科学、视觉处理、代理任务。',
displayName: 'Claude 3.7 Sonnet',
enabled: true,
id: 'anthropic/claude-3.7-sonnet',
maxOutput: 8192,
pricing: {
cachedInput: 0.3,
input: 3,
output: 15,
writeCacheInput: 3.75,
},
releasedAt: '2025-02-24',
settings: {
extendParams: ['enableReasoning', 'reasoningBudgetToken'],
},
type: 'chat',
},
{
abilities: {
functionCall: true,
Expand Down Expand Up @@ -258,7 +283,7 @@ const openrouterChatModels: AIChatModelCard[] = [
id: 'deepseek/deepseek-r1:free',
releasedAt: '2025-01-20',
type: 'chat',
},
},
{
abilities: {
vision: true,
Expand Down
33 changes: 33 additions & 0 deletions src/libs/agent-runtime/openrouter/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,39 @@ describe('LobeOpenRouterAI', () => {
expect(result).toBeInstanceOf(Response);
});

it('should add reasoning field when thinking is enabled', async () => {
// Arrange
const mockStream = new ReadableStream();
const mockResponse = Promise.resolve(mockStream);

(instance['client'].chat.completions.create as Mock).mockResolvedValue(mockResponse);

// Act
const result = await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: 'mistralai/mistral-7b-instruct:free',
temperature: 0.7,
thinking: {
type: 'enabled',
budget_tokens: 1500,
},
});

// Assert
expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
expect.objectContaining({
messages: [{ content: 'Hello', role: 'user' }],
model: 'mistralai/mistral-7b-instruct:free',
reasoning: {
max_tokens: 1500,
},
temperature: 0.7,
}),
{ headers: { Accept: '*/*' } },
);
expect(result).toBeInstanceOf(Response);
});

describe('Error', () => {
it('should return OpenRouterBizError with an openai error response when OpenAI.APIError is thrown', async () => {
// Arrange
Expand Down
13 changes: 11 additions & 2 deletions src/libs/agent-runtime/openrouter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ChatModelCard } from '@/types/llm';

import { ModelProvider } from '../types';
import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
import { OpenRouterModelCard, OpenRouterModelExtraInfo } from './type';
import { OpenRouterModelCard, OpenRouterModelExtraInfo, OpenRouterReasoning } from './type';

const formatPrice = (price: string) => {
if (price === '-1') return undefined;
Expand All @@ -13,10 +13,19 @@ export const LobeOpenRouterAI = LobeOpenAICompatibleFactory({
baseURL: 'https://openrouter.ai/api/v1',
chatCompletion: {
handlePayload: (payload) => {
const { thinking } = payload;

let reasoning: OpenRouterReasoning = {};
if (thinking?.type === 'enabled') {
reasoning = {
max_tokens: thinking.budget_tokens,
};
}

return {
...payload,
include_reasoning: true,
model: payload.enabledSearch ? `${payload.model}:online` : payload.model,
reasoning,
stream: payload.stream ?? true,
} as any;
},
Expand Down
19 changes: 19 additions & 0 deletions src/libs/agent-runtime/openrouter/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,22 @@ export interface OpenRouterModelExtraInfo {
endpoint?: OpenRouterModelEndpoint;
slug: string;
}

interface OpenRouterOpenAIReasoning {
effort: 'high' | 'medium' | 'low';
exclude?: boolean;
}

interface OpenRouterAnthropicReasoning {
exclude?: boolean;
max_tokens: number;
}

interface OpenRouterCommonReasoning {
exclude?: boolean;
}

export type OpenRouterReasoning =
| OpenRouterOpenAIReasoning
| OpenRouterAnthropicReasoning
| OpenRouterCommonReasoning;

0 comments on commit f1ffc2c

Please sign in to comment.