feat: 添加自定义API参数功能 (#564)
* add custom api parameters * allow more data types for custom api parameters * pass parameter to api payload * add custom parameter settings to sidebar * remove unnecessary object and array types * extract API custom parameter method to BaseProvider * add i18n for custom parameter settings --------- Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
This commit is contained in:
parent
e7545c5a94
commit
ed49066bab
@ -586,7 +586,15 @@
|
||||
"embedding": "Embedding",
|
||||
"embedding_model": "Embedding Model",
|
||||
"embedding_model_tooltip": "Add in Settings->Model Provider->Manage",
|
||||
"dimensions": "Dimensions {{dimensions}}"
|
||||
"dimensions": "Dimensions {{dimensions}}",
|
||||
"custom_parameters": "Custom Parameters",
|
||||
"add_parameter": "Add Parameter",
|
||||
"parameter_name": "Parameter Name",
|
||||
"parameter_type": {
|
||||
"string": "Text",
|
||||
"number": "Number",
|
||||
"boolean": "Boolean"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"summarize": "You are an assistant who is good at conversation. You need to summarize the user's conversation into a title of 10 characters or less, ensuring it matches the user's primary language without using punctuation or other special symbols."
|
||||
|
||||
@ -568,7 +568,16 @@
|
||||
"free": "無料モデル",
|
||||
"embedding": "埋め込みモデル",
|
||||
"embedding_model": "埋め込みモデル",
|
||||
"embedding_model_tooltip": "設定->モデルサービス->管理で追加"
|
||||
"embedding_model_tooltip": "設定->モデルサービス->管理で追加",
|
||||
"dimensions": "{{dimensions}} 次元",
|
||||
"custom_parameters": "カスタムパラメータ",
|
||||
"add_parameter": "パラメータを追加",
|
||||
"parameter_name": "パラメータ名",
|
||||
"parameter_type": {
|
||||
"string": "テキスト",
|
||||
"number": "数値",
|
||||
"boolean": "真偽値"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"summarize": "あなたは会話を得意とするアシスタントです。ユーザーの会話を10文字以内のタイトルに要約し、ユーザーの主言語と一致していることを確認してください。句読点や特殊記号は使用しないでください。"
|
||||
|
||||
@ -586,7 +586,15 @@
|
||||
"embedding": "Встраиваемые модели",
|
||||
"embedding_model": "Встраиваемые модели",
|
||||
"embedding_model_tooltip": "Добавьте в настройки->модель сервиса->управление",
|
||||
"dimensions": "{{dimensions}} мер"
|
||||
"dimensions": "{{dimensions}} мер",
|
||||
"custom_parameters": "Пользовательские параметры",
|
||||
"add_parameter": "Добавить параметр",
|
||||
"parameter_name": "Имя параметра",
|
||||
"parameter_type": {
|
||||
"string": "Текст",
|
||||
"number": "Число",
|
||||
"boolean": "Логическое"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"summarize": "Вы - эксперт в общении, который суммирует разговоры пользователя в 10-символьном заголовке, совпадающем с языком пользователя, без использования знаков препинания и других специальных символов"
|
||||
|
||||
@ -575,7 +575,15 @@
|
||||
"embedding": "嵌入模型",
|
||||
"embedding_model": "嵌入模型",
|
||||
"embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加",
|
||||
"dimensions": "{{dimensions}} 维"
|
||||
"dimensions": "{{dimensions}} 维",
|
||||
"custom_parameters": "自定义参数",
|
||||
"add_parameter": "添加参数",
|
||||
"parameter_name": "参数名称",
|
||||
"parameter_type": {
|
||||
"string": "文本",
|
||||
"number": "数字",
|
||||
"boolean": "布尔值"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"summarize": "你是一名擅长会话的助理,你需要将用户的会话总结为 10 个字以内的标题,标题语言与用户的首要语言一致,不要使用标点符号和其他特殊符号"
|
||||
|
||||
@ -574,7 +574,15 @@
|
||||
"embedding": "嵌入模型",
|
||||
"embedding_model": "嵌入模型",
|
||||
"embedding_model_tooltip": "在设置->模型服务中点击管理按钮添加",
|
||||
"dimensions": "{{dimensions}} 維"
|
||||
"dimensions": "{{dimensions}} 維",
|
||||
"custom_parameters": "自定義參數",
|
||||
"add_parameter": "添加參數",
|
||||
"parameter_name": "參數名稱",
|
||||
"parameter_type": {
|
||||
"string": "文字",
|
||||
"number": "數字",
|
||||
"boolean": "布林值"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"summarize": "你是一名擅長會話的助理,你需要將用戶的會話總結為 10 個字以內的標題,標題語言與用戶的首要語言一致,不要使用標點符號和其他特殊符號"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { CheckOutlined, QuestionCircleOutlined, ReloadOutlined } from '@ant-design/icons'
|
||||
import { CheckOutlined, DeleteOutlined, PlusOutlined, QuestionCircleOutlined, ReloadOutlined } from '@ant-design/icons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import {
|
||||
@ -29,7 +29,7 @@ import {
|
||||
setShowMessageDivider
|
||||
} from '@renderer/store/settings'
|
||||
import { Assistant, AssistantSettings, ThemeMode } from '@renderer/types'
|
||||
import { Col, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
|
||||
import { Button, Col, Input, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
@ -102,7 +102,8 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
maxTokens: DEFAULT_MAX_TOKENS,
|
||||
streamOutput: true,
|
||||
hideMessages: false,
|
||||
autoResetModel: false
|
||||
autoResetModel: false,
|
||||
customParameters: []
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -202,6 +203,106 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
{assistant?.settings?.customParameters?.map((param, index) => (
|
||||
<ParameterCard key={index}>
|
||||
<Row align="middle" gutter={8} style={{ marginBottom: 8 }}>
|
||||
<Col span={14}>
|
||||
<Input
|
||||
placeholder={t('models.parameter_name')}
|
||||
value={param.name}
|
||||
onChange={(e) => {
|
||||
const newParams = [...(assistant?.settings?.customParameters || [])]
|
||||
newParams[index] = { ...param, name: e.target.value }
|
||||
onUpdateAssistantSettings({ customParameters: newParams })
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={10}>
|
||||
<Select
|
||||
value={param.type}
|
||||
onChange={(value: 'string' | 'number' | 'boolean') => {
|
||||
const newParams = [...(assistant?.settings?.customParameters || [])]
|
||||
let defaultValue: any = ''
|
||||
switch (value) {
|
||||
case 'number':
|
||||
defaultValue = 0
|
||||
break
|
||||
case 'boolean':
|
||||
defaultValue = false
|
||||
break
|
||||
default:
|
||||
defaultValue = ''
|
||||
}
|
||||
newParams[index] = { ...param, type: value, value: defaultValue }
|
||||
onUpdateAssistantSettings({ customParameters: newParams })
|
||||
}}
|
||||
style={{ width: '100%' }}>
|
||||
<Select.Option value="string">{t('models.parameter_type.string')}</Select.Option>
|
||||
<Select.Option value="number">{t('models.parameter_type.number')}</Select.Option>
|
||||
<Select.Option value="boolean">{t('models.parameter_type.boolean')}</Select.Option>
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row align="middle" gutter={10}>
|
||||
<Col span={20}>
|
||||
{param.type === 'boolean' ? (
|
||||
<Switch
|
||||
checked={param.value as boolean}
|
||||
onChange={(checked) => {
|
||||
const newParams = [...(assistant?.settings?.customParameters || [])]
|
||||
newParams[index] = { ...param, value: checked }
|
||||
onUpdateAssistantSettings({ customParameters: newParams })
|
||||
}}
|
||||
/>
|
||||
) : param.type === 'number' ? (
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
value={param.value as number}
|
||||
onChange={(value) => {
|
||||
const newParams = [...(assistant?.settings?.customParameters || [])]
|
||||
newParams[index] = { ...param, value: value || 0 }
|
||||
onUpdateAssistantSettings({ customParameters: newParams })
|
||||
}}
|
||||
step={0.01}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
value={typeof param.value === 'string' ? param.value : JSON.stringify(param.value)}
|
||||
onChange={(e) => {
|
||||
const newParams = [...(assistant?.settings?.customParameters || [])]
|
||||
newParams[index] = { ...param, value: e.target.value }
|
||||
onUpdateAssistantSettings({ customParameters: newParams })
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Button
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => {
|
||||
const newParams = [...(assistant?.settings?.customParameters || [])]
|
||||
newParams.splice(index, 1)
|
||||
onUpdateAssistantSettings({ customParameters: newParams })
|
||||
}}
|
||||
danger
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</ParameterCard>
|
||||
))}
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
const newParams = [
|
||||
...(assistant?.settings?.customParameters || []),
|
||||
{ name: '', value: '', type: 'string' as const }
|
||||
]
|
||||
onUpdateAssistantSettings({ customParameters: newParams })
|
||||
}}
|
||||
style={{ marginBottom: 0, width: '100%', borderStyle: 'dashed' }}>
|
||||
{t('models.add_parameter')}
|
||||
</Button>
|
||||
</SettingGroup>
|
||||
<SettingGroup>
|
||||
<SettingSubtitle style={{ marginTop: 0 }}>{t('settings.messages.title')}</SettingSubtitle>
|
||||
@ -418,4 +519,15 @@ export const SettingGroup = styled.div<{ theme?: ThemeMode }>`
|
||||
background: var(--color-group-background);
|
||||
`
|
||||
|
||||
const ParameterCard = styled.div`
|
||||
margin-bottom: 8px;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
background: var(--color-background);
|
||||
&:last-child {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
`
|
||||
|
||||
export default SettingsTab
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
|
||||
import { DeleteOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
|
||||
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
|
||||
import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
|
||||
import { SettingRow } from '@renderer/pages/settings'
|
||||
import { Assistant, AssistantSettings } from '@renderer/types'
|
||||
import { Button, Col, Divider, InputNumber, Row, Slider, Switch, Tooltip } from 'antd'
|
||||
import { Button, Col, Divider, Input, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
@ -25,6 +25,13 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true)
|
||||
const [defaultModel, setDefaultModel] = useState(assistant?.defaultModel)
|
||||
const [topP, setTopP] = useState(assistant?.settings?.topP ?? 1)
|
||||
const [customParameters, setCustomParameters] = useState<
|
||||
Array<{
|
||||
name: string
|
||||
value: string | number | boolean
|
||||
type: 'string' | 'number' | 'boolean'
|
||||
}>
|
||||
>(assistant?.settings?.customParameters ?? [])
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onTemperatureChange = (value) => {
|
||||
@ -51,6 +58,77 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
}
|
||||
}
|
||||
|
||||
const onAddCustomParameter = () => {
|
||||
const newParam = { name: '', value: '', type: 'string' as const }
|
||||
const newParams = [...customParameters, newParam]
|
||||
setCustomParameters(newParams)
|
||||
updateAssistantSettings({ customParameters: newParams })
|
||||
}
|
||||
|
||||
const onUpdateCustomParameter = (
|
||||
index: number,
|
||||
field: 'name' | 'value' | 'type',
|
||||
value: string | number | boolean
|
||||
) => {
|
||||
const newParams = [...customParameters]
|
||||
if (field === 'type') {
|
||||
let defaultValue: any = ''
|
||||
switch (value) {
|
||||
case 'number':
|
||||
defaultValue = 0
|
||||
break
|
||||
case 'boolean':
|
||||
defaultValue = false
|
||||
break
|
||||
default:
|
||||
defaultValue = ''
|
||||
}
|
||||
newParams[index] = {
|
||||
...newParams[index],
|
||||
type: value as any,
|
||||
value: defaultValue
|
||||
}
|
||||
} else {
|
||||
newParams[index] = { ...newParams[index], [field]: value }
|
||||
}
|
||||
setCustomParameters(newParams)
|
||||
updateAssistantSettings({ customParameters: newParams })
|
||||
}
|
||||
|
||||
const renderParameterValueInput = (param: (typeof customParameters)[0], index: number) => {
|
||||
switch (param.type) {
|
||||
case 'number':
|
||||
return (
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
value={param.value as number}
|
||||
onChange={(value) => onUpdateCustomParameter(index, 'value', value || 0)}
|
||||
step={0.01}
|
||||
/>
|
||||
)
|
||||
case 'boolean':
|
||||
return (
|
||||
<Switch
|
||||
checked={param.value as boolean}
|
||||
onChange={(checked) => onUpdateCustomParameter(index, 'value', checked)}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<Input
|
||||
value={param.value as string}
|
||||
onChange={(e) => onUpdateCustomParameter(index, 'value', e.target.value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const onDeleteCustomParameter = (index: number) => {
|
||||
const newParams = customParameters.filter((_, i) => i !== index)
|
||||
setCustomParameters(newParams)
|
||||
updateAssistantSettings({ customParameters: newParams })
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
setTemperature(DEFAULT_TEMPERATURE)
|
||||
setContextCount(DEFAULT_CONTEXTCOUNT)
|
||||
@ -58,13 +136,15 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
setMaxTokens(0)
|
||||
setStreamOutput(true)
|
||||
setTopP(1)
|
||||
setCustomParameters([])
|
||||
updateAssistantSettings({
|
||||
temperature: DEFAULT_TEMPERATURE,
|
||||
contextCount: DEFAULT_CONTEXTCOUNT,
|
||||
enableMaxTokens: false,
|
||||
maxTokens: 0,
|
||||
streamOutput: true,
|
||||
topP: 1
|
||||
topP: 1,
|
||||
customParameters: []
|
||||
})
|
||||
}
|
||||
|
||||
@ -249,6 +329,38 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
}}
|
||||
/>
|
||||
</SettingRow>
|
||||
<Divider style={{ margin: '10px 0' }} />
|
||||
<SettingRow style={{ minHeight: 30 }}>
|
||||
<Label>{t('models.custom_parameters')}</Label>
|
||||
<Button icon={<PlusOutlined />} onClick={onAddCustomParameter}>
|
||||
{t('models.add_parameter')}
|
||||
</Button>
|
||||
</SettingRow>
|
||||
{customParameters.map((param, index) => (
|
||||
<Row key={index} align="middle" gutter={10} style={{ marginTop: 10 }}>
|
||||
<Col span={6}>
|
||||
<Input
|
||||
placeholder={t('models.parameter_name')}
|
||||
value={param.name}
|
||||
onChange={(e) => onUpdateCustomParameter(index, 'name', e.target.value)}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Select
|
||||
value={param.type}
|
||||
onChange={(value) => onUpdateCustomParameter(index, 'type', value)}
|
||||
style={{ width: '100%' }}>
|
||||
<Select.Option value="string">{t('models.parameter_type.string')}</Select.Option>
|
||||
<Select.Option value="number">{t('models.parameter_type.number')}</Select.Option>
|
||||
<Select.Option value="boolean">{t('models.parameter_type.boolean')}</Select.Option>
|
||||
</Select>
|
||||
</Col>
|
||||
<Col span={11}>{renderParameterValueInput(param, index)}</Col>
|
||||
<Col span={3}>
|
||||
<Button icon={<DeleteOutlined />} onClick={() => onDeleteCustomParameter(index)} danger />
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
<Divider style={{ margin: '15px 0' }} />
|
||||
<HStack justifyContent="flex-end">
|
||||
<Button onClick={onReset} style={{ width: 80 }} danger type="primary">
|
||||
|
||||
@ -87,7 +87,8 @@ export default class AnthropicProvider extends BaseProvider {
|
||||
max_tokens: maxTokens || DEFAULT_MAX_TOKENS,
|
||||
temperature: assistant?.settings?.temperature,
|
||||
top_p: assistant?.settings?.topP,
|
||||
system: assistant.prompt
|
||||
system: assistant.prompt,
|
||||
...this.getCustomParameters(assistant)
|
||||
}
|
||||
|
||||
let time_first_token_millsec = 0
|
||||
|
||||
@ -98,4 +98,16 @@ export default abstract class BaseProvider {
|
||||
|
||||
return REFERENCE_PROMPT.replace('{question}', message.content).replace('{references}', references)
|
||||
}
|
||||
|
||||
protected getCustomParameters(assistant: Assistant) {
|
||||
return (
|
||||
assistant?.settings?.customParameters?.reduce(
|
||||
(acc, param) => ({
|
||||
...acc,
|
||||
[param.name]: param.value
|
||||
}),
|
||||
{}
|
||||
) || {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,7 +96,8 @@ export default class GeminiProvider extends BaseProvider {
|
||||
generationConfig: {
|
||||
maxOutputTokens: maxTokens,
|
||||
temperature: assistant?.settings?.temperature,
|
||||
topP: assistant?.settings?.topP
|
||||
topP: assistant?.settings?.topP,
|
||||
...this.getCustomParameters(assistant)
|
||||
},
|
||||
safetySettings: [
|
||||
{ category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, threshold: HarmBlockThreshold.BLOCK_NONE },
|
||||
|
||||
@ -148,7 +148,8 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
top_p: assistant?.settings?.topP,
|
||||
max_tokens: maxTokens,
|
||||
keep_alive: this.keepAliveTime,
|
||||
stream: isSupportStreamOutput
|
||||
stream: isSupportStreamOutput,
|
||||
...this.getCustomParameters(assistant)
|
||||
})
|
||||
|
||||
if (!isSupportStreamOutput) {
|
||||
|
||||
@ -100,7 +100,8 @@ export const getAssistantSettings = (assistant: Assistant): AssistantSettings =>
|
||||
maxTokens: getAssistantMaxTokens(),
|
||||
streamOutput: assistant?.settings?.streamOutput ?? true,
|
||||
hideMessages: assistant?.settings?.hideMessages ?? false,
|
||||
autoResetModel: assistant?.settings?.autoResetModel ?? false
|
||||
autoResetModel: assistant?.settings?.autoResetModel ?? false,
|
||||
customParameters: assistant?.settings?.customParameters ?? []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -30,6 +30,11 @@ export type AssistantSettings = {
|
||||
streamOutput: boolean
|
||||
hideMessages: boolean
|
||||
autoResetModel: boolean
|
||||
customParameters?: {
|
||||
name: string
|
||||
value: string | number | boolean
|
||||
type: 'string' | 'number' | 'boolean'
|
||||
}[]
|
||||
}
|
||||
|
||||
export type Agent = Omit<Assistant, 'model'>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user