Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
05af05e4
authored
Aug 09, 2025
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add system prompt override functionality in channel settings and request handling #1468
parent
2c4dc446
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
154 additions
and
92 deletions
+154
-92
constant/context_key.go
+2
-0
dto/channel_settings.go
+1
-0
dto/openai_request.go
+2
-0
middleware/distributor.go
+2
-0
relay/relay-text.go
+20
-0
service/log_info_generate.go
+6
-0
web/src/components/table/channels/modals/EditChannelModal.jsx
+17
-0
web/src/components/table/usage-logs/UsageLogsColumnDefs.js
+13
-4
web/src/helpers/render.js
+83
-87
web/src/i18n/locales/en.json
+8
-1
No files found.
constant/context_key.go
View file @
05af05e4
...
...
@@ -40,4 +40,6 @@ const (
ContextKeyUserGroup
ContextKey
=
"user_group"
ContextKeyUsingGroup
ContextKey
=
"group"
ContextKeyUserName
ContextKey
=
"username"
ContextKeySystemPromptOverride
ContextKey
=
"system_prompt_override"
)
dto/channel_settings.go
View file @
05af05e4
...
...
@@ -6,4 +6,5 @@ type ChannelSettings struct {
Proxy
string
`json:"proxy"`
PassThroughBodyEnabled
bool
`json:"pass_through_body_enabled,omitempty"`
SystemPrompt
string
`json:"system_prompt,omitempty"`
SystemPromptOverride
bool
`json:"system_prompt_override,omitempty"`
}
dto/openai_request.go
View file @
05af05e4
...
...
@@ -78,6 +78,8 @@ func (r *GeneralOpenAIRequest) GetSystemRoleName() string {
if
!
strings
.
HasPrefix
(
r
.
Model
,
"o1-mini"
)
&&
!
strings
.
HasPrefix
(
r
.
Model
,
"o1-preview"
)
{
return
"developer"
}
}
else
if
strings
.
HasPrefix
(
r
.
Model
,
"gpt-5"
)
{
return
"developer"
}
return
"system"
}
...
...
middleware/distributor.go
View file @
05af05e4
...
...
@@ -267,6 +267,8 @@ func SetupContextForSelectedChannel(c *gin.Context, channel *model.Channel, mode
common
.
SetContextKey
(
c
,
constant
.
ContextKeyChannelKey
,
key
)
common
.
SetContextKey
(
c
,
constant
.
ContextKeyChannelBaseUrl
,
channel
.
GetBaseURL
())
common
.
SetContextKey
(
c
,
constant
.
ContextKeySystemPromptOverride
,
false
)
// TODO: api_version统一
switch
channel
.
Type
{
case
constant
.
ChannelTypeAzure
:
...
...
relay/relay-text.go
View file @
05af05e4
...
...
@@ -201,6 +201,26 @@ func TextHelper(c *gin.Context) (newAPIError *types.NewAPIError) {
Content
:
relayInfo
.
ChannelSetting
.
SystemPrompt
,
}
request
.
Messages
=
append
([]
dto
.
Message
{
systemMessage
},
request
.
Messages
...
)
}
else
if
relayInfo
.
ChannelSetting
.
SystemPromptOverride
{
common
.
SetContextKey
(
c
,
constant
.
ContextKeySystemPromptOverride
,
true
)
// 如果有系统提示,且允许覆盖,则拼接到前面
for
i
,
message
:=
range
request
.
Messages
{
if
message
.
Role
==
request
.
GetSystemRoleName
()
{
if
message
.
IsStringContent
()
{
request
.
Messages
[
i
]
.
SetStringContent
(
relayInfo
.
ChannelSetting
.
SystemPrompt
+
"
\n
"
+
message
.
StringContent
())
}
else
{
contents
:=
message
.
ParseContent
()
contents
=
append
([]
dto
.
MediaContent
{
{
Type
:
dto
.
ContentTypeText
,
Text
:
relayInfo
.
ChannelSetting
.
SystemPrompt
,
},
},
contents
...
)
request
.
Messages
[
i
]
.
Content
=
contents
}
break
}
}
}
}
...
...
service/log_info_generate.go
View file @
05af05e4
...
...
@@ -28,6 +28,12 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m
other
[
"is_model_mapped"
]
=
true
other
[
"upstream_model_name"
]
=
relayInfo
.
UpstreamModelName
}
isSystemPromptOverwritten
:=
common
.
GetContextKeyBool
(
ctx
,
constant
.
ContextKeySystemPromptOverride
)
if
isSystemPromptOverwritten
{
other
[
"is_system_prompt_overwritten"
]
=
true
}
adminInfo
:=
make
(
map
[
string
]
interface
{})
adminInfo
[
"use_channel"
]
=
ctx
.
GetStringSlice
(
"use_channel"
)
isMultiKey
:=
common
.
GetContextKeyBool
(
ctx
,
constant
.
ContextKeyChannelIsMultiKey
)
...
...
web/src/components/table/channels/modals/EditChannelModal.jsx
View file @
05af05e4
...
...
@@ -131,6 +131,7 @@ const EditChannelModal = (props) => {
proxy
:
''
,
pass_through_body_enabled
:
false
,
system_prompt
:
''
,
system_prompt_override
:
false
,
};
const
[
batch
,
setBatch
]
=
useState
(
false
);
const
[
multiToSingle
,
setMultiToSingle
]
=
useState
(
false
);
...
...
@@ -340,12 +341,15 @@ const EditChannelModal = (props) => {
data
.
proxy
=
parsedSettings
.
proxy
||
''
;
data
.
pass_through_body_enabled
=
parsedSettings
.
pass_through_body_enabled
||
false
;
data
.
system_prompt
=
parsedSettings
.
system_prompt
||
''
;
data
.
system_prompt_override
=
parsedSettings
.
system_prompt_override
||
false
;
}
catch
(
error
)
{
console
.
error
(
'解析渠道设置失败:'
,
error
);
data
.
force_format
=
false
;
data
.
thinking_to_content
=
false
;
data
.
proxy
=
''
;
data
.
pass_through_body_enabled
=
false
;
data
.
system_prompt
=
''
;
data
.
system_prompt_override
=
false
;
}
}
else
{
data
.
force_format
=
false
;
...
...
@@ -353,6 +357,7 @@ const EditChannelModal = (props) => {
data
.
proxy
=
''
;
data
.
pass_through_body_enabled
=
false
;
data
.
system_prompt
=
''
;
data
.
system_prompt_override
=
false
;
}
setInputs
(
data
);
...
...
@@ -372,6 +377,7 @@ const EditChannelModal = (props) => {
proxy
:
data
.
proxy
,
pass_through_body_enabled
:
data
.
pass_through_body_enabled
,
system_prompt
:
data
.
system_prompt
,
system_prompt_override
:
data
.
system_prompt_override
||
false
,
});
// console.log(data);
}
else
{
...
...
@@ -573,6 +579,7 @@ const EditChannelModal = (props) => {
proxy
:
''
,
pass_through_body_enabled
:
false
,
system_prompt
:
''
,
system_prompt_override
:
false
,
});
// 重置密钥模式状态
setKeyMode
(
'append'
);
...
...
@@ -721,6 +728,7 @@ const EditChannelModal = (props) => {
proxy
:
localInputs
.
proxy
||
''
,
pass_through_body_enabled
:
localInputs
.
pass_through_body_enabled
||
false
,
system_prompt
:
localInputs
.
system_prompt
||
''
,
system_prompt_override
:
localInputs
.
system_prompt_override
||
false
,
};
localInputs
.
setting
=
JSON
.
stringify
(
channelExtraSettings
);
...
...
@@ -730,6 +738,7 @@ const EditChannelModal = (props) => {
delete
localInputs
.
proxy
;
delete
localInputs
.
pass_through_body_enabled
;
delete
localInputs
.
system_prompt
;
delete
localInputs
.
system_prompt_override
;
let
res
;
localInputs
.
auto_ban
=
localInputs
.
auto_ban
?
1
:
0
;
...
...
@@ -1722,6 +1731,14 @@ const EditChannelModal = (props) => {
showClear
extraText=
{
t
(
'用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置'
)
}
/>
<
Form
.
Switch
field=
'system_prompt_override'
label=
{
t
(
'系统提示词拼接'
)
}
checkedText=
{
t
(
'开'
)
}
uncheckedText=
{
t
(
'关'
)
}
onChange=
{
(
value
)
=>
handleChannelSettingsChange
(
'system_prompt_override'
,
value
)
}
extraText=
{
t
(
'如果用户请求中包含系统提示词,则使用此设置拼接到用户的系统提示词前面'
)
}
/>
</
Card
>
</
div
>
</
Spin
>
...
...
web/src/components/table/usage-logs/UsageLogsColumnDefs.js
View file @
05af05e4
...
...
@@ -34,7 +34,6 @@ import {
getLogOther
,
renderModelTag
,
renderClaudeLogContent
,
renderClaudeModelPriceSimple
,
renderLogContent
,
renderModelPriceSimple
,
renderAudioModelPrice
,
...
...
@@ -538,7 +537,7 @@ export const getLogsColumns = ({
);
}
let
content
=
other
?.
claude
?
render
Claude
ModelPriceSimple
(
?
renderModelPriceSimple
(
other
.
model_ratio
,
other
.
model_price
,
other
.
group_ratio
,
...
...
@@ -547,6 +546,10 @@ export const getLogsColumns = ({
other
.
cache_ratio
||
1.0
,
other
.
cache_creation_tokens
||
0
,
other
.
cache_creation_ratio
||
1.0
,
false
,
1.0
,
other
?.
is_system_prompt_overwritten
,
'claude'
)
:
renderModelPriceSimple
(
other
.
model_ratio
,
...
...
@@ -555,13 +558,19 @@ export const getLogsColumns = ({
other
?.
user_group_ratio
,
other
.
cache_tokens
||
0
,
other
.
cache_ratio
||
1.0
,
0
,
1.0
,
false
,
1.0
,
other
?.
is_system_prompt_overwritten
,
'openai'
);
return
(
<
Typography
.
Paragraph
ellipsis
=
{{
rows
:
2
,
rows
:
3
,
}}
style
=
{{
maxWidth
:
240
}}
style
=
{{
maxWidth
:
240
,
whiteSpace
:
'pre-line'
}}
>
{
content
}
<
/Typography.Paragraph
>
...
...
web/src/helpers/render.js
View file @
05af05e4
...
...
@@ -953,6 +953,71 @@ function getEffectiveRatio(groupRatio, user_group_ratio) {
};
}
// Shared core for simple price rendering (used by OpenAI-like and Claude-like variants)
function
renderPriceSimpleCore
({
modelRatio
,
modelPrice
=
-
1
,
groupRatio
,
user_group_ratio
,
cacheTokens
=
0
,
cacheRatio
=
1.0
,
cacheCreationTokens
=
0
,
cacheCreationRatio
=
1.0
,
image
=
false
,
imageRatio
=
1.0
,
isSystemPromptOverride
=
false
})
{
const
{
ratio
:
effectiveGroupRatio
,
label
:
ratioLabel
}
=
getEffectiveRatio
(
groupRatio
,
user_group_ratio
,
);
const
finalGroupRatio
=
effectiveGroupRatio
;
if
(
modelPrice
!==
-
1
)
{
return
i18next
.
t
(
'价格:${{price}} * {{ratioType}}:{{ratio}}'
,
{
price
:
modelPrice
,
ratioType
:
ratioLabel
,
ratio
:
finalGroupRatio
,
});
}
const
parts
=
[];
// base: model ratio
parts
.
push
(
i18next
.
t
(
'模型: {{ratio}}'
));
// cache part (label differs when with image)
if
(
cacheTokens
!==
0
)
{
parts
.
push
(
i18next
.
t
(
'缓存: {{cacheRatio}}'
));
}
// cache creation part (Claude specific if passed)
if
(
cacheCreationTokens
!==
0
)
{
parts
.
push
(
i18next
.
t
(
'缓存创建: {{cacheCreationRatio}}'
));
}
// image part
if
(
image
)
{
parts
.
push
(
i18next
.
t
(
'图片输入: {{imageRatio}}'
));
}
parts
.
push
(
`{{ratioType}}: {{groupRatio}}`
);
let
result
=
i18next
.
t
(
parts
.
join
(
' * '
),
{
ratio
:
modelRatio
,
ratioType
:
ratioLabel
,
groupRatio
:
finalGroupRatio
,
cacheRatio
:
cacheRatio
,
cacheCreationRatio
:
cacheCreationRatio
,
imageRatio
:
imageRatio
,
})
if
(
isSystemPromptOverride
)
{
result
+=
'\n\r'
+
i18next
.
t
(
'系统提示覆盖'
);
}
return
result
;
}
export
function
renderModelPrice
(
inputTokens
,
completionTokens
,
...
...
@@ -1245,56 +1310,26 @@ export function renderModelPriceSimple(
user_group_ratio
,
cacheTokens
=
0
,
cacheRatio
=
1.0
,
cacheCreationTokens
=
0
,
cacheCreationRatio
=
1.0
,
image
=
false
,
imageRatio
=
1.0
,
isSystemPromptOverride
=
false
,
provider
=
'openai'
,
)
{
const
{
ratio
:
effectiveGroupRatio
,
label
:
ratioLabel
}
=
getEffectiveRatio
(
groupRatio
,
user_group_ratio
);
groupRatio
=
effectiveGroupRatio
;
if
(
modelPrice
!==
-
1
)
{
return
i18next
.
t
(
'价格:${{price}} * {{ratioType}}:{{ratio}}'
,
{
price
:
modelPrice
,
ratioType
:
ratioLabel
,
ratio
:
groupRatio
,
});
}
else
{
if
(
image
&&
cacheTokens
!==
0
)
{
return
i18next
.
t
(
'模型: {{ratio}} * {{ratioType}}: {{groupRatio}} * 缓存倍率: {{cacheRatio}} * 图片输入倍率: {{imageRatio}}'
,
{
ratio
:
modelRatio
,
ratioType
:
ratioLabel
,
groupRatio
:
groupRatio
,
cacheRatio
:
cacheRatio
,
imageRatio
:
imageRatio
,
},
);
}
else
if
(
image
)
{
return
i18next
.
t
(
'模型: {{ratio}} * {{ratioType}}: {{groupRatio}} * 图片输入倍率: {{imageRatio}}'
,
{
ratio
:
modelRatio
,
ratioType
:
ratioLabel
,
groupRatio
:
groupRatio
,
imageRatio
:
imageRatio
,
},
);
}
else
if
(
cacheTokens
!==
0
)
{
return
i18next
.
t
(
'模型: {{ratio}} * 分组: {{groupRatio}} * 缓存: {{cacheRatio}}'
,
{
ratio
:
modelRatio
,
groupRatio
:
groupRatio
,
cacheRatio
:
cacheRatio
,
},
);
}
else
{
return
i18next
.
t
(
'模型: {{ratio}} * {{ratioType}}:{{groupRatio}}'
,
{
ratio
:
modelRatio
,
ratioType
:
ratioLabel
,
groupRatio
:
groupRatio
,
});
}
}
return
renderPriceSimpleCore
({
modelRatio
,
modelPrice
,
groupRatio
,
user_group_ratio
,
cacheTokens
,
cacheRatio
,
cacheCreationTokens
,
cacheCreationRatio
,
image
,
imageRatio
,
isSystemPromptOverride
});
}
export
function
renderAudioModelPrice
(
...
...
@@ -1635,46 +1670,7 @@ export function renderClaudeLogContent(
}
}
export
function
renderClaudeModelPriceSimple
(
modelRatio
,
modelPrice
=
-
1
,
groupRatio
,
user_group_ratio
,
cacheTokens
=
0
,
cacheRatio
=
1.0
,
cacheCreationTokens
=
0
,
cacheCreationRatio
=
1.0
,
)
{
const
{
ratio
:
effectiveGroupRatio
,
label
:
ratioLabel
}
=
getEffectiveRatio
(
groupRatio
,
user_group_ratio
);
groupRatio
=
effectiveGroupRatio
;
if
(
modelPrice
!==
-
1
)
{
return
i18next
.
t
(
'价格:${{price}} * {{ratioType}}:{{ratio}}'
,
{
price
:
modelPrice
,
ratioType
:
ratioLabel
,
ratio
:
groupRatio
,
});
}
else
{
if
(
cacheTokens
!==
0
||
cacheCreationTokens
!==
0
)
{
return
i18next
.
t
(
'模型: {{ratio}} * {{ratioType}}: {{groupRatio}} * 缓存: {{cacheRatio}}'
,
{
ratio
:
modelRatio
,
ratioType
:
ratioLabel
,
groupRatio
:
groupRatio
,
cacheRatio
:
cacheRatio
,
cacheCreationRatio
:
cacheCreationRatio
,
},
);
}
else
{
return
i18next
.
t
(
'模型: {{ratio}} * {{ratioType}}: {{groupRatio}}'
,
{
ratio
:
modelRatio
,
ratioType
:
ratioLabel
,
groupRatio
:
groupRatio
,
});
}
}
}
// 已统一至 renderModelPriceSimple,若仍有遗留引用,请改为传入 provider='claude'
/**
* rehype 插件:将段落等文本节点拆分为逐词 <span>,并添加淡入动画 class。
...
...
web/src/i18n/locales/en.json
View file @
05af05e4
...
...
@@ -1804,5 +1804,11 @@
"已选择 {{selected}} / {{total}}"
:
"Selected {{selected}} / {{total}}"
,
"新获取的模型"
:
"New models"
,
"已有的模型"
:
"Existing models"
,
"搜索模型"
:
"Search models"
"搜索模型"
:
"Search models"
,
"缓存: {{cacheRatio}}"
:
"Cache: {{cacheRatio}}"
,
"缓存创建: {{cacheCreationRatio}}"
:
"Cache creation: {{cacheCreationRatio}}"
,
"图片输入: {{imageRatio}}"
:
"Image input: {{imageRatio}}"
,
"系统提示覆盖"
:
"System prompt override"
,
"模型: {{ratio}}"
:
"Model: {{ratio}}"
,
"专属倍率"
:
"Exclusive group ratio"
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment