feat: Add reasoning effort control for Claude 3.7 (#2540)
* feat: Add reasoning effort control for Anthropic models with Anthropic Provider and OpenAI Provider - Add reasoning effort settings with low/medium/high options - Implement reasoning effort for Claude 3.7 Sonnet models - Update localization tips for reasoning effort - Enhance provider handling of reasoning effort parameters * fix: Extract o1-mini and o1-preview * fix: Add OpenAI o-series model to ReasoningModel * fix: Improve OpenAI o-series model detection * style: Reduce font size * fix: Add default token handling using DEFAULT_MAX_TOKENS * fix: Add beta parameter for Anthropic reasoning models
This commit is contained in:
parent
956c2f683d
commit
ae11490f87
@ -81,7 +81,7 @@
|
|||||||
"webdav": "4.11.4"
|
"webdav": "4.11.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.24.3",
|
"@anthropic-ai/sdk": "^0.38.0",
|
||||||
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
|
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
|
||||||
"@electron-toolkit/eslint-config-ts": "^1.0.1",
|
"@electron-toolkit/eslint-config-ts": "^1.0.1",
|
||||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||||
|
|||||||
@ -1839,6 +1839,10 @@ export function isVisionModel(model: Model): boolean {
|
|||||||
return VISION_REGEX.test(model.id) || model.type?.includes('vision') || false
|
return VISION_REGEX.test(model.id) || model.type?.includes('vision') || false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isOpenAIoSeries(model: Model): boolean {
|
||||||
|
return ['o1', 'o1-2024-12-17'].includes(model.id) || model.id.includes('o3')
|
||||||
|
}
|
||||||
|
|
||||||
export function isReasoningModel(model: Model): boolean {
|
export function isReasoningModel(model: Model): boolean {
|
||||||
if (!model) {
|
if (!model) {
|
||||||
return false
|
return false
|
||||||
@ -1848,6 +1852,10 @@ export function isReasoningModel(model: Model): boolean {
|
|||||||
return REASONING_REGEX.test(model.name) || model.type?.includes('reasoning') || false
|
return REASONING_REGEX.test(model.name) || model.type?.includes('reasoning') || false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (model.id.includes('claude-3-7-sonnet') || model.id.includes('claude-3.7-sonnet') || isOpenAIoSeries(model)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
return REASONING_REGEX.test(model.id) || model.type?.includes('reasoning') || false
|
return REASONING_REGEX.test(model.id) || model.type?.includes('reasoning') || false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -52,7 +52,7 @@
|
|||||||
"settings.reasoning_effort.low": "low",
|
"settings.reasoning_effort.low": "low",
|
||||||
"settings.reasoning_effort.medium": "medium",
|
"settings.reasoning_effort.medium": "medium",
|
||||||
"settings.reasoning_effort.off": "off",
|
"settings.reasoning_effort.off": "off",
|
||||||
"settings.reasoning_effort.tip": "Only supports reasoning models",
|
"settings.reasoning_effort.tip": "Only supports OpenAI o-series and Anthropic reasoning models",
|
||||||
"title": "Assistants"
|
"title": "Assistants"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
|
|||||||
@ -52,7 +52,7 @@
|
|||||||
"settings.reasoning_effort.low": "短い",
|
"settings.reasoning_effort.low": "短い",
|
||||||
"settings.reasoning_effort.medium": "中程度",
|
"settings.reasoning_effort.medium": "中程度",
|
||||||
"settings.reasoning_effort.off": "オフ",
|
"settings.reasoning_effort.off": "オフ",
|
||||||
"settings.reasoning_effort.tip": "この設定は推論モデルのみサポートしています",
|
"settings.reasoning_effort.tip": "OpenAIのoシリーズとAnthropicの推論モデルのみサポートしています",
|
||||||
"title": "アシスタント"
|
"title": "アシスタント"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
|
|||||||
@ -52,7 +52,7 @@
|
|||||||
"settings.reasoning_effort.low": "Короткая",
|
"settings.reasoning_effort.low": "Короткая",
|
||||||
"settings.reasoning_effort.medium": "Средняя",
|
"settings.reasoning_effort.medium": "Средняя",
|
||||||
"settings.reasoning_effort.off": "Выключено",
|
"settings.reasoning_effort.off": "Выключено",
|
||||||
"settings.reasoning_effort.tip": "Эта настройка поддерживается только моделями с рассуждением",
|
"settings.reasoning_effort.tip": "Поддерживается только моделями с рассуждением OpenAI o-series и Anthropic",
|
||||||
"title": "Ассистенты"
|
"title": "Ассистенты"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
|
|||||||
@ -52,7 +52,7 @@
|
|||||||
"settings.reasoning_effort.low": "短",
|
"settings.reasoning_effort.low": "短",
|
||||||
"settings.reasoning_effort.medium": "中",
|
"settings.reasoning_effort.medium": "中",
|
||||||
"settings.reasoning_effort.off": "关",
|
"settings.reasoning_effort.off": "关",
|
||||||
"settings.reasoning_effort.tip": "该设置仅支持推理模型",
|
"settings.reasoning_effort.tip": "仅支持 OpenAI o-series 和 Anthropic 推理模型",
|
||||||
"title": "助手"
|
"title": "助手"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
|
|||||||
@ -52,7 +52,7 @@
|
|||||||
"settings.reasoning_effort.low": "短",
|
"settings.reasoning_effort.low": "短",
|
||||||
"settings.reasoning_effort.medium": "中",
|
"settings.reasoning_effort.medium": "中",
|
||||||
"settings.reasoning_effort.off": "關",
|
"settings.reasoning_effort.off": "關",
|
||||||
"settings.reasoning_effort.tip": "該設置僅支持推理模型",
|
"settings.reasoning_effort.tip": "僅支持OpenAI o系列和Anthropic推理模型",
|
||||||
"title": "助手"
|
"title": "助手"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
|
|||||||
@ -32,7 +32,7 @@ import {
|
|||||||
} from '@renderer/store/settings'
|
} from '@renderer/store/settings'
|
||||||
import { Assistant, AssistantSettings, ThemeMode, TranslateLanguageVarious } from '@renderer/types'
|
import { Assistant, AssistantSettings, ThemeMode, TranslateLanguageVarious } from '@renderer/types'
|
||||||
import { modalConfirm } from '@renderer/utils'
|
import { modalConfirm } from '@renderer/utils'
|
||||||
import { Col, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
|
import { Col, InputNumber, Row, Segmented, Select, Slider, Switch, Tooltip } from 'antd'
|
||||||
import { FC, useEffect, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -51,6 +51,7 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
const [maxTokens, setMaxTokens] = useState(assistant?.settings?.maxTokens ?? 0)
|
const [maxTokens, setMaxTokens] = useState(assistant?.settings?.maxTokens ?? 0)
|
||||||
const [fontSizeValue, setFontSizeValue] = useState(fontSize)
|
const [fontSizeValue, setFontSizeValue] = useState(fontSize)
|
||||||
const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true)
|
const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true)
|
||||||
|
const [reasoningEffort, setReasoningEffort] = useState(assistant?.settings?.reasoning_effort)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
@ -96,9 +97,14 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onReasoningEffortChange = (value) => {
|
||||||
|
updateAssistantSettings({ reasoning_effort: value })
|
||||||
|
}
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
setTemperature(DEFAULT_TEMPERATURE)
|
setTemperature(DEFAULT_TEMPERATURE)
|
||||||
setContextCount(DEFAULT_CONTEXTCOUNT)
|
setContextCount(DEFAULT_CONTEXTCOUNT)
|
||||||
|
setReasoningEffort(undefined)
|
||||||
updateAssistant({
|
updateAssistant({
|
||||||
...assistant,
|
...assistant,
|
||||||
settings: {
|
settings: {
|
||||||
@ -109,6 +115,7 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
maxTokens: DEFAULT_MAX_TOKENS,
|
maxTokens: DEFAULT_MAX_TOKENS,
|
||||||
streamOutput: true,
|
streamOutput: true,
|
||||||
hideMessages: false,
|
hideMessages: false,
|
||||||
|
reasoning_effort: undefined,
|
||||||
customParameters: []
|
customParameters: []
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -120,6 +127,7 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
setEnableMaxTokens(assistant?.settings?.enableMaxTokens ?? false)
|
setEnableMaxTokens(assistant?.settings?.enableMaxTokens ?? false)
|
||||||
setMaxTokens(assistant?.settings?.maxTokens ?? DEFAULT_MAX_TOKENS)
|
setMaxTokens(assistant?.settings?.maxTokens ?? DEFAULT_MAX_TOKENS)
|
||||||
setStreamOutput(assistant?.settings?.streamOutput ?? true)
|
setStreamOutput(assistant?.settings?.streamOutput ?? true)
|
||||||
|
setReasoningEffort(assistant?.settings?.reasoning_effort)
|
||||||
}, [assistant])
|
}, [assistant])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -223,6 +231,45 @@ const SettingsTab: FC<Props> = (props) => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
|
<SettingDivider />
|
||||||
|
<Row align="middle">
|
||||||
|
<Label>{t('assistants.settings.reasoning_effort')}</Label>
|
||||||
|
<Tooltip title={t('assistants.settings.reasoning_effort.tip')}>
|
||||||
|
<QuestionIcon />
|
||||||
|
</Tooltip>
|
||||||
|
</Row>
|
||||||
|
<Row align="middle" gutter={10}>
|
||||||
|
<Col span={24}>
|
||||||
|
<SegmentedContainer>
|
||||||
|
<Segmented<'low' | 'medium' | 'high' | undefined>
|
||||||
|
value={reasoningEffort}
|
||||||
|
onChange={(value) => {
|
||||||
|
setReasoningEffort(value)
|
||||||
|
onReasoningEffortChange(value)
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
value: 'low',
|
||||||
|
label: t('assistants.settings.reasoning_effort.low')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'medium',
|
||||||
|
label: t('assistants.settings.reasoning_effort.medium')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'high',
|
||||||
|
label: t('assistants.settings.reasoning_effort.high')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: undefined,
|
||||||
|
label: t('assistants.settings.reasoning_effort.off')
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
block
|
||||||
|
/>
|
||||||
|
</SegmentedContainer>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
<SettingGroup>
|
<SettingGroup>
|
||||||
<SettingSubtitle style={{ marginTop: 0 }}>{t('settings.messages.title')}</SettingSubtitle>
|
<SettingSubtitle style={{ marginTop: 0 }}>{t('settings.messages.title')}</SettingSubtitle>
|
||||||
@ -485,4 +532,24 @@ export const SettingGroup = styled.div<{ theme?: ThemeMode }>`
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// Define the styled component with hover state styling
|
||||||
|
const SegmentedContainer = styled.div`
|
||||||
|
.ant-segmented-item {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.ant-segmented-item-selected {
|
||||||
|
background-color: var(--color-primary) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-segmented-item:hover:not(.ant-segmented-item-selected) {
|
||||||
|
background-color: var(--color-primary-bg) !important;
|
||||||
|
color: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-segmented-thumb {
|
||||||
|
background-color: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export default SettingsTab
|
export default SettingsTab
|
||||||
|
|||||||
@ -154,6 +154,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
|||||||
setMaxTokens(0)
|
setMaxTokens(0)
|
||||||
setStreamOutput(true)
|
setStreamOutput(true)
|
||||||
setTopP(1)
|
setTopP(1)
|
||||||
|
setReasoningEffort(undefined)
|
||||||
setCustomParameters([])
|
setCustomParameters([])
|
||||||
updateAssistantSettings({
|
updateAssistantSettings({
|
||||||
temperature: DEFAULT_TEMPERATURE,
|
temperature: DEFAULT_TEMPERATURE,
|
||||||
@ -162,6 +163,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
|||||||
maxTokens: 0,
|
maxTokens: 0,
|
||||||
streamOutput: true,
|
streamOutput: true,
|
||||||
topP: 1,
|
topP: 1,
|
||||||
|
reasoning_effort: undefined,
|
||||||
customParameters: []
|
customParameters: []
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import Anthropic from '@anthropic-ai/sdk'
|
import Anthropic from '@anthropic-ai/sdk'
|
||||||
import { MessageCreateParamsNonStreaming, MessageParam } from '@anthropic-ai/sdk/resources'
|
import { MessageCreateParamsNonStreaming, MessageParam } from '@anthropic-ai/sdk/resources'
|
||||||
import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
|
import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
|
||||||
|
import { isReasoningModel } from '@renderer/config/models'
|
||||||
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService'
|
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService'
|
||||||
@ -18,7 +19,11 @@ export default class AnthropicProvider extends BaseProvider {
|
|||||||
|
|
||||||
constructor(provider: Provider) {
|
constructor(provider: Provider) {
|
||||||
super(provider)
|
super(provider)
|
||||||
this.sdk = new Anthropic({ apiKey: this.apiKey, baseURL: this.getBaseURL() })
|
this.sdk = new Anthropic({
|
||||||
|
apiKey: this.apiKey,
|
||||||
|
baseURL: this.getBaseURL(),
|
||||||
|
dangerouslyAllowBrowser: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public getBaseURL(): string {
|
public getBaseURL(): string {
|
||||||
@ -60,6 +65,47 @@ export default class AnthropicProvider extends BaseProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getTemperature(assistant: Assistant, model: Model) {
|
||||||
|
if (isReasoningModel(model)) return undefined
|
||||||
|
|
||||||
|
return assistant?.settings?.temperature
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTopP(assistant: Assistant, model: Model) {
|
||||||
|
if (isReasoningModel(model)) return undefined
|
||||||
|
|
||||||
|
return assistant?.settings?.topP
|
||||||
|
}
|
||||||
|
|
||||||
|
private getReasoningEffort(assistant: Assistant, model: Model) {
|
||||||
|
if (isReasoningModel(model)) {
|
||||||
|
const effort_ratio =
|
||||||
|
assistant?.settings?.reasoning_effort === 'high'
|
||||||
|
? 0.8
|
||||||
|
: assistant?.settings?.reasoning_effort === 'medium'
|
||||||
|
? 0.5
|
||||||
|
: assistant?.settings?.reasoning_effort === 'low'
|
||||||
|
? 0.2
|
||||||
|
: undefined
|
||||||
|
if (!effort_ratio)
|
||||||
|
return {
|
||||||
|
type: 'disabled'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.id.includes('claude-3.7-sonnet') || model.id.includes('claude-3-7-sonnet')) {
|
||||||
|
return {
|
||||||
|
type: 'enabled',
|
||||||
|
budget_tokens: Math.max(
|
||||||
|
Math.min((assistant?.settings?.maxTokens || DEFAULT_MAX_TOKENS) * effort_ratio, 32000),
|
||||||
|
1024
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
public async completions({ messages, assistant, onChunk, onFilterMessages }: CompletionsParams) {
|
public async completions({ messages, assistant, onChunk, onFilterMessages }: CompletionsParams) {
|
||||||
const defaultModel = getDefaultModel()
|
const defaultModel = getDefaultModel()
|
||||||
const model = assistant.model || defaultModel
|
const model = assistant.model || defaultModel
|
||||||
@ -84,20 +130,43 @@ export default class AnthropicProvider extends BaseProvider {
|
|||||||
model: model.id,
|
model: model.id,
|
||||||
messages: userMessages,
|
messages: userMessages,
|
||||||
max_tokens: maxTokens || DEFAULT_MAX_TOKENS,
|
max_tokens: maxTokens || DEFAULT_MAX_TOKENS,
|
||||||
temperature: assistant?.settings?.temperature,
|
temperature: this.getTemperature(assistant, model),
|
||||||
top_p: assistant?.settings?.topP,
|
top_p: this.getTopP(assistant, model),
|
||||||
system: assistant.prompt,
|
system: assistant.prompt,
|
||||||
...this.getCustomParameters(assistant)
|
...this.getCustomParameters(assistant)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isReasoningModel(model)) {
|
||||||
|
;(body as any).thinking = this.getReasoningEffort(assistant, model)
|
||||||
|
;(body as any).betas = ['output-128k-2025-02-19']
|
||||||
|
}
|
||||||
|
|
||||||
let time_first_token_millsec = 0
|
let time_first_token_millsec = 0
|
||||||
|
let time_first_content_millsec = 0
|
||||||
const start_time_millsec = new Date().getTime()
|
const start_time_millsec = new Date().getTime()
|
||||||
|
|
||||||
if (!streamOutput) {
|
if (!streamOutput) {
|
||||||
const message = await this.sdk.messages.create({ ...body, stream: false })
|
const message = await this.sdk.messages.create({ ...body, stream: false })
|
||||||
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
||||||
|
|
||||||
|
let text = ''
|
||||||
|
let reasoning_content = ''
|
||||||
|
|
||||||
|
if (message.content && message.content.length > 0) {
|
||||||
|
const thinkingBlock = message.content.find((block) => block.type === 'thinking')
|
||||||
|
const textBlock = message.content.find((block) => block.type === 'text')
|
||||||
|
|
||||||
|
if (thinkingBlock && 'thinking' in thinkingBlock) {
|
||||||
|
reasoning_content = thinkingBlock.thinking
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textBlock && 'text' in textBlock) {
|
||||||
|
text = textBlock.text
|
||||||
|
}
|
||||||
|
}
|
||||||
return onChunk({
|
return onChunk({
|
||||||
text: message.content[0].type === 'text' ? message.content[0].text : '',
|
text,
|
||||||
|
reasoning_content,
|
||||||
usage: message.usage,
|
usage: message.usage,
|
||||||
metrics: {
|
metrics: {
|
||||||
completion_tokens: message.usage.output_tokens,
|
completion_tokens: message.usage.output_tokens,
|
||||||
@ -113,6 +182,7 @@ export default class AnthropicProvider extends BaseProvider {
|
|||||||
const { signal } = abortController
|
const { signal } = abortController
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
let hasThinkingContent = false
|
||||||
const stream = this.sdk.messages
|
const stream = this.sdk.messages
|
||||||
.stream({ ...body, stream: true }, { signal })
|
.stream({ ...body, stream: true }, { signal })
|
||||||
.on('text', (text) => {
|
.on('text', (text) => {
|
||||||
@ -123,9 +193,34 @@ export default class AnthropicProvider extends BaseProvider {
|
|||||||
if (time_first_token_millsec == 0) {
|
if (time_first_token_millsec == 0) {
|
||||||
time_first_token_millsec = new Date().getTime() - start_time_millsec
|
time_first_token_millsec = new Date().getTime() - start_time_millsec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasThinkingContent && time_first_content_millsec === 0) {
|
||||||
|
time_first_content_millsec = new Date().getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
const time_thinking_millsec = time_first_content_millsec ? time_first_content_millsec - start_time_millsec : 0
|
||||||
|
|
||||||
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
||||||
onChunk({
|
onChunk({
|
||||||
text,
|
text,
|
||||||
|
metrics: {
|
||||||
|
completion_tokens: undefined,
|
||||||
|
time_completion_millsec,
|
||||||
|
time_first_token_millsec,
|
||||||
|
time_thinking_millsec
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.on('thinking', (thinking) => {
|
||||||
|
hasThinkingContent = true
|
||||||
|
if (time_first_token_millsec == 0) {
|
||||||
|
time_first_token_millsec = new Date().getTime() - start_time_millsec
|
||||||
|
}
|
||||||
|
|
||||||
|
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
||||||
|
onChunk({
|
||||||
|
reasoning_content: thinking,
|
||||||
|
text: '',
|
||||||
metrics: {
|
metrics: {
|
||||||
completion_tokens: undefined,
|
completion_tokens: undefined,
|
||||||
time_completion_millsec,
|
time_completion_millsec,
|
||||||
@ -134,6 +229,8 @@ export default class AnthropicProvider extends BaseProvider {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.on('finalMessage', (message) => {
|
.on('finalMessage', (message) => {
|
||||||
|
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
||||||
|
const time_thinking_millsec = time_first_content_millsec ? time_first_content_millsec - start_time_millsec : 0
|
||||||
onChunk({
|
onChunk({
|
||||||
text: '',
|
text: '',
|
||||||
usage: {
|
usage: {
|
||||||
@ -143,8 +240,9 @@ export default class AnthropicProvider extends BaseProvider {
|
|||||||
},
|
},
|
||||||
metrics: {
|
metrics: {
|
||||||
completion_tokens: message.usage.output_tokens,
|
completion_tokens: message.usage.output_tokens,
|
||||||
time_completion_millsec: new Date().getTime() - start_time_millsec,
|
time_completion_millsec,
|
||||||
time_first_token_millsec
|
time_first_token_millsec,
|
||||||
|
time_thinking_millsec
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
resolve()
|
resolve()
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
import { getOpenAIWebSearchParams, isReasoningModel, isSupportedModel, isVisionModel } from '@renderer/config/models'
|
import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
|
||||||
|
import {
|
||||||
|
getOpenAIWebSearchParams,
|
||||||
|
isOpenAIoSeries,
|
||||||
|
isReasoningModel,
|
||||||
|
isSupportedModel,
|
||||||
|
isVisionModel
|
||||||
|
} from '@renderer/config/models'
|
||||||
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService'
|
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService'
|
||||||
@ -156,9 +163,46 @@ export default class OpenAIProvider extends BaseProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isReasoningModel(model)) {
|
if (isReasoningModel(model)) {
|
||||||
return {
|
if (model.provider === 'openrouter') {
|
||||||
reasoning_effort: assistant?.settings?.reasoning_effort
|
return {
|
||||||
|
reasoning: {
|
||||||
|
effort: assistant?.settings?.reasoning_effort
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isOpenAIoSeries(model)) {
|
||||||
|
return {
|
||||||
|
reasoning_effort: assistant?.settings?.reasoning_effort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const effort_ratio =
|
||||||
|
assistant?.settings?.reasoning_effort === 'high'
|
||||||
|
? 0.8
|
||||||
|
: assistant?.settings?.reasoning_effort === 'medium'
|
||||||
|
? 0.5
|
||||||
|
: assistant?.settings?.reasoning_effort === 'low'
|
||||||
|
? 0.2
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
if (model.id.includes('claude-3.7-sonnet') || model.id.includes('claude-3-7-sonnet')) {
|
||||||
|
if (!effort_ratio) {
|
||||||
|
return {
|
||||||
|
type: 'disabled'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
thinking: {
|
||||||
|
budget_tokens: Math.max(
|
||||||
|
Math.min((assistant?.settings?.maxTokens || DEFAULT_MAX_TOKENS) * effort_ratio, 32000),
|
||||||
|
1024
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
@ -175,7 +219,7 @@ export default class OpenAIProvider extends BaseProvider {
|
|||||||
|
|
||||||
let systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
|
let systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
|
||||||
|
|
||||||
if (['o1', 'o1-2024-12-17'].includes(model.id) || model.id.startsWith('o3')) {
|
if (isOpenAIoSeries(model)) {
|
||||||
systemMessage = {
|
systemMessage = {
|
||||||
role: 'developer',
|
role: 'developer',
|
||||||
content: `Formatting re-enabled${systemMessage ? '\n' + systemMessage.content : ''}`
|
content: `Formatting re-enabled${systemMessage ? '\n' + systemMessage.content : ''}`
|
||||||
@ -212,6 +256,7 @@ export default class OpenAIProvider extends BaseProvider {
|
|||||||
delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta & {
|
delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta & {
|
||||||
reasoning_content?: string
|
reasoning_content?: string
|
||||||
reasoning?: string
|
reasoning?: string
|
||||||
|
thinking?: string
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
if (!delta?.content) return false
|
if (!delta?.content) return false
|
||||||
@ -226,7 +271,7 @@ export default class OpenAIProvider extends BaseProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果有reasoning_content或reasoning,说明是在思考中
|
// 如果有reasoning_content或reasoning,说明是在思考中
|
||||||
if (delta?.reasoning_content || delta?.reasoning) {
|
if (delta?.reasoning_content || delta?.reasoning || delta?.thinking) {
|
||||||
hasReasoningContent = true
|
hasReasoningContent = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
yarn.lock
13
yarn.lock
@ -110,9 +110,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@anthropic-ai/sdk@npm:^0.24.3":
|
"@anthropic-ai/sdk@npm:^0.38.0":
|
||||||
version: 0.24.3
|
version: 0.38.0
|
||||||
resolution: "@anthropic-ai/sdk@npm:0.24.3"
|
resolution: "@anthropic-ai/sdk@npm:0.38.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node": "npm:^18.11.18"
|
"@types/node": "npm:^18.11.18"
|
||||||
"@types/node-fetch": "npm:^2.6.4"
|
"@types/node-fetch": "npm:^2.6.4"
|
||||||
@ -121,8 +121,7 @@ __metadata:
|
|||||||
form-data-encoder: "npm:1.7.2"
|
form-data-encoder: "npm:1.7.2"
|
||||||
formdata-node: "npm:^4.3.2"
|
formdata-node: "npm:^4.3.2"
|
||||||
node-fetch: "npm:^2.6.7"
|
node-fetch: "npm:^2.6.7"
|
||||||
web-streams-polyfill: "npm:^3.2.1"
|
checksum: 10c0/accd003cbe314d32d4d36f5fd7fd743c32e2a896c9ea57190966eda20b8c46e00f542bf03ec3603d1274a7ac18e902bed4158ff5980e4e248a6d5c75e3fd891a
|
||||||
checksum: 10c0/1c73c3df9637522da548d2cddfaf89513dac935c5cdb7c0b3db1c427c069a0de76df935bd189e477822063e9f944360e2d059827d5be4dca33bd388c61e97a30
|
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -2996,7 +2995,7 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "CherryStudio@workspace:."
|
resolution: "CherryStudio@workspace:."
|
||||||
dependencies:
|
dependencies:
|
||||||
"@anthropic-ai/sdk": "npm:^0.24.3"
|
"@anthropic-ai/sdk": "npm:^0.38.0"
|
||||||
"@electron-toolkit/eslint-config-prettier": "npm:^2.0.0"
|
"@electron-toolkit/eslint-config-prettier": "npm:^2.0.0"
|
||||||
"@electron-toolkit/eslint-config-ts": "npm:^1.0.1"
|
"@electron-toolkit/eslint-config-ts": "npm:^1.0.1"
|
||||||
"@electron-toolkit/preload": "npm:^3.0.0"
|
"@electron-toolkit/preload": "npm:^3.0.0"
|
||||||
@ -14363,7 +14362,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"web-streams-polyfill@npm:^3.0.3, web-streams-polyfill@npm:^3.2.1":
|
"web-streams-polyfill@npm:^3.0.3":
|
||||||
version: 3.3.3
|
version: 3.3.3
|
||||||
resolution: "web-streams-polyfill@npm:3.3.3"
|
resolution: "web-streams-polyfill@npm:3.3.3"
|
||||||
checksum: 10c0/64e855c47f6c8330b5436147db1c75cb7e7474d924166800e8e2aab5eb6c76aac4981a84261dd2982b3e754490900b99791c80ae1407a9fa0dcff74f82ea3a7f
|
checksum: 10c0/64e855c47f6c8330b5436147db1c75cb7e7474d924166800e8e2aab5eb6c76aac4981a84261dd2982b3e754490900b99791c80ae1407a9fa0dcff74f82ea3a7f
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user