Skip to content

Commit 69551ab

Browse files
authored
Merge pull request #3285 from seefs001/feature/param-override-log
feat: params override log
2 parents a4fd224 + 8aa8b81 commit 69551ab

8 files changed

Lines changed: 704 additions & 3 deletions

File tree

relay/common/override.go

Lines changed: 231 additions & 3 deletions
Large diffs are not rendered by default.

relay/common/override_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"reflect"
77
"testing"
88

9+
common2 "github.com/QuantumNous/new-api/common"
910
"github.com/QuantumNous/new-api/types"
1011

1112
"github.com/QuantumNous/new-api/dto"
@@ -2066,6 +2067,105 @@ func TestRemoveDisabledFieldsAllowInferenceGeo(t *testing.T) {
20662067
assertJSONEqual(t, `{"inference_geo":"eu","store":true}`, string(out))
20672068
}
20682069

2070+
func TestApplyParamOverrideWithRelayInfoRecordsOperationAuditInDebugMode(t *testing.T) {
2071+
originalDebugEnabled := common2.DebugEnabled
2072+
common2.DebugEnabled = true
2073+
t.Cleanup(func() {
2074+
common2.DebugEnabled = originalDebugEnabled
2075+
})
2076+
2077+
info := &RelayInfo{
2078+
ChannelMeta: &ChannelMeta{
2079+
ParamOverride: map[string]interface{}{
2080+
"operations": []interface{}{
2081+
map[string]interface{}{
2082+
"mode": "copy",
2083+
"from": "metadata.target_model",
2084+
"to": "model",
2085+
},
2086+
map[string]interface{}{
2087+
"mode": "set",
2088+
"path": "service_tier",
2089+
"value": "flex",
2090+
},
2091+
map[string]interface{}{
2092+
"mode": "set",
2093+
"path": "temperature",
2094+
"value": 0.1,
2095+
},
2096+
},
2097+
},
2098+
},
2099+
}
2100+
2101+
out, err := ApplyParamOverrideWithRelayInfo([]byte(`{
2102+
"model":"gpt-4.1",
2103+
"temperature":0.7,
2104+
"metadata":{"target_model":"gpt-4.1-mini"}
2105+
}`), info)
2106+
if err != nil {
2107+
t.Fatalf("ApplyParamOverrideWithRelayInfo returned error: %v", err)
2108+
}
2109+
assertJSONEqual(t, `{
2110+
"model":"gpt-4.1-mini",
2111+
"temperature":0.1,
2112+
"service_tier":"flex",
2113+
"metadata":{"target_model":"gpt-4.1-mini"}
2114+
}`, string(out))
2115+
2116+
expected := []string{
2117+
"copy metadata.target_model -> model",
2118+
"set service_tier = flex",
2119+
"set temperature = 0.1",
2120+
}
2121+
if !reflect.DeepEqual(info.ParamOverrideAudit, expected) {
2122+
t.Fatalf("unexpected param override audit, got %#v", info.ParamOverrideAudit)
2123+
}
2124+
}
2125+
2126+
func TestApplyParamOverrideWithRelayInfoRecordsOnlyKeyOperationsWhenDebugDisabled(t *testing.T) {
2127+
originalDebugEnabled := common2.DebugEnabled
2128+
common2.DebugEnabled = false
2129+
t.Cleanup(func() {
2130+
common2.DebugEnabled = originalDebugEnabled
2131+
})
2132+
2133+
info := &RelayInfo{
2134+
ChannelMeta: &ChannelMeta{
2135+
ParamOverride: map[string]interface{}{
2136+
"operations": []interface{}{
2137+
map[string]interface{}{
2138+
"mode": "copy",
2139+
"from": "metadata.target_model",
2140+
"to": "model",
2141+
},
2142+
map[string]interface{}{
2143+
"mode": "set",
2144+
"path": "temperature",
2145+
"value": 0.1,
2146+
},
2147+
},
2148+
},
2149+
},
2150+
}
2151+
2152+
_, err := ApplyParamOverrideWithRelayInfo([]byte(`{
2153+
"model":"gpt-4.1",
2154+
"temperature":0.7,
2155+
"metadata":{"target_model":"gpt-4.1-mini"}
2156+
}`), info)
2157+
if err != nil {
2158+
t.Fatalf("ApplyParamOverrideWithRelayInfo returned error: %v", err)
2159+
}
2160+
2161+
expected := []string{
2162+
"copy metadata.target_model -> model",
2163+
}
2164+
if !reflect.DeepEqual(info.ParamOverrideAudit, expected) {
2165+
t.Fatalf("unexpected param override audit, got %#v", info.ParamOverrideAudit)
2166+
}
2167+
}
2168+
20692169
func assertJSONEqual(t *testing.T, want, got string) {
20702170
t.Helper()
20712171

relay/common/relay_info.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ type RelayInfo struct {
149149
LastError *types.NewAPIError
150150
RuntimeHeadersOverride map[string]interface{}
151151
UseRuntimeHeadersOverride bool
152+
ParamOverrideAudit []string
152153

153154
PriceData types.PriceData
154155

service/log_info_generate.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,17 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m
7474
appendRequestPath(ctx, relayInfo, other)
7575
appendRequestConversionChain(relayInfo, other)
7676
appendBillingInfo(relayInfo, other)
77+
appendParamOverrideInfo(relayInfo, other)
7778
return other
7879
}
7980

81+
func appendParamOverrideInfo(relayInfo *relaycommon.RelayInfo, other map[string]interface{}) {
82+
if relayInfo == nil || other == nil || len(relayInfo.ParamOverrideAudit) == 0 {
83+
return
84+
}
85+
other["po"] = relayInfo.ParamOverrideAudit
86+
}
87+
8088
func appendBillingInfo(relayInfo *relaycommon.RelayInfo, other map[string]interface{}) {
8189
if relayInfo == nil || other == nil {
8290
return
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright (C) 2025 QuantumNous
3+
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU Affero General Public License as
6+
published by the Free Software Foundation, either version 3 of the
7+
License, or (at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Affero General Public License for more details.
13+
14+
You should have received a copy of the GNU Affero General Public License
15+
along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
For commercial licensing, please contact support@quantumnous.com
18+
*/
19+
20+
import React from 'react';
21+
import { Typography } from '@douyinfe/semi-ui';
22+
23+
const { Text } = Typography;
24+
25+
const ParamOverrideEntry = ({ count, onOpen, t }) => {
26+
return (
27+
<div
28+
style={{
29+
display: 'flex',
30+
alignItems: 'center',
31+
gap: 10,
32+
flexWrap: 'wrap',
33+
}}
34+
>
35+
<Text
36+
type='tertiary'
37+
size='small'
38+
style={{ fontVariantNumeric: 'tabular-nums' }}
39+
>
40+
{t('{{count}} 项操作', { count })}
41+
</Text>
42+
<Text
43+
link
44+
size='small'
45+
style={{ fontWeight: 600 }}
46+
onClick={onOpen}
47+
>
48+
{t('查看详情')}
49+
</Text>
50+
</div>
51+
);
52+
};
53+
54+
export default React.memo(ParamOverrideEntry);

web/src/components/table/usage-logs/index.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import LogsFilters from './UsageLogsFilters';
2525
import ColumnSelectorModal from './modals/ColumnSelectorModal';
2626
import UserInfoModal from './modals/UserInfoModal';
2727
import ChannelAffinityUsageCacheModal from './modals/ChannelAffinityUsageCacheModal';
28+
import ParamOverrideModal from './modals/ParamOverrideModal';
2829
import { useLogsData } from '../../../hooks/usage-logs/useUsageLogsData';
2930
import { useIsMobile } from '../../../hooks/common/useIsMobile';
3031
import { createCardProPagination } from '../../../helpers/utils';
@@ -39,6 +40,7 @@ const LogsPage = () => {
3940
<ColumnSelectorModal {...logsData} />
4041
<UserInfoModal {...logsData} />
4142
<ChannelAffinityUsageCacheModal {...logsData} />
43+
<ParamOverrideModal {...logsData} />
4244

4345
{/* Main Content */}
4446
<CardPro

0 commit comments

Comments
 (0)