feat: log shows request conversion - #2703
Conversation
WalkthroughThis PR adds request-conversion tracking to RelayInfo, records conversions from converted requests across multiple handlers, surfaces a human-readable conversion chain in logs, strips admin-only debug fields when serializing logs, and exposes conversion info to the admin UI with i18n entries. Changes
Sequence DiagramsequenceDiagram
participant Client
participant Handler
participant RelayCommon as RelayInfo/Common
participant Logger
participant UI
Client->>Handler: Send request
Handler->>Handler: Convert request to target format
Handler->>RelayCommon: AppendRequestConversionFromRequest(info, convertedRequest)
RelayCommon->>RelayCommon: GuessRelayFormatFromRequest / AppendRequestConversion
Handler->>Logger: Include RelayInfo with RequestConversionChain
Logger->>Logger: appendRequestConversionChain -> other["request_conversion"]
Logger->>UI: Emit log data (admin-only request_conversion)
UI->>UI: Format conversion chain for display
UI-->>Client: Render conversion row (admin visible)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@relay/common/relay_info.go`:
- Around line 454-500: The Task and MjProxy branches in GenRelayInfo call
genBaseRelayInfo(c, nil) but do not set RelayInfo.RelayFormat, so
InitRequestConversionChain() has no starting format; update the
types.RelayFormatTask and types.RelayFormatMjProxy cases in GenRelayInfo to
create the base RelayInfo via genBaseRelayInfo and then set info.RelayFormat =
types.RelayFormatTask (or types.RelayFormatMjProxy) respectively before
returning so InitRequestConversionChain can seed the conversion chain correctly.
In `@service/log_info_generate.go`:
- Around line 73-103: appendRequestConversionChain currently maps
relayInfo.RequestConversionChain entries to hard-coded English labels ("OpenAI
Compatible", "Claude Messages", etc.); change it to emit locale-agnostic
canonical identifiers instead (e.g., the enum/string values from
types.RelayFormat*). In function appendRequestConversionChain, replace the
switch that appends human-readable strings with logic that appends the canonical
identifier (for example string(f) or f.String() if available) into the chain
slice, and keep setting other["request_conversion"] = chain so the frontend can
handle localization.
| func GenRelayInfo(c *gin.Context, relayFormat types.RelayFormat, request dto.Request, ws *websocket.Conn) (*RelayInfo, error) { | ||
| var info *RelayInfo | ||
| var err error | ||
| switch relayFormat { | ||
| case types.RelayFormatOpenAI: | ||
| return GenRelayInfoOpenAI(c, request), nil | ||
| info = GenRelayInfoOpenAI(c, request) | ||
| case types.RelayFormatOpenAIAudio: | ||
| return GenRelayInfoOpenAIAudio(c, request), nil | ||
| info = GenRelayInfoOpenAIAudio(c, request) | ||
| case types.RelayFormatOpenAIImage: | ||
| return GenRelayInfoImage(c, request), nil | ||
| info = GenRelayInfoImage(c, request) | ||
| case types.RelayFormatOpenAIRealtime: | ||
| return GenRelayInfoWs(c, ws), nil | ||
| info = GenRelayInfoWs(c, ws) | ||
| case types.RelayFormatClaude: | ||
| return GenRelayInfoClaude(c, request), nil | ||
| info = GenRelayInfoClaude(c, request) | ||
| case types.RelayFormatRerank: | ||
| if request, ok := request.(*dto.RerankRequest); ok { | ||
| return GenRelayInfoRerank(c, request), nil | ||
| info = GenRelayInfoRerank(c, request) | ||
| break | ||
| } | ||
| return nil, errors.New("request is not a RerankRequest") | ||
| err = errors.New("request is not a RerankRequest") | ||
| case types.RelayFormatGemini: | ||
| return GenRelayInfoGemini(c, request), nil | ||
| info = GenRelayInfoGemini(c, request) | ||
| case types.RelayFormatEmbedding: | ||
| return GenRelayInfoEmbedding(c, request), nil | ||
| info = GenRelayInfoEmbedding(c, request) | ||
| case types.RelayFormatOpenAIResponses: | ||
| if request, ok := request.(*dto.OpenAIResponsesRequest); ok { | ||
| return GenRelayInfoResponses(c, request), nil | ||
| info = GenRelayInfoResponses(c, request) | ||
| break | ||
| } | ||
| return nil, errors.New("request is not a OpenAIResponsesRequest") | ||
| err = errors.New("request is not a OpenAIResponsesRequest") | ||
| case types.RelayFormatTask: | ||
| return genBaseRelayInfo(c, nil), nil | ||
| info = genBaseRelayInfo(c, nil) | ||
| case types.RelayFormatMjProxy: | ||
| return genBaseRelayInfo(c, nil), nil | ||
| info = genBaseRelayInfo(c, nil) | ||
| default: | ||
| return nil, errors.New("invalid relay format") | ||
| err = errors.New("invalid relay format") | ||
| } | ||
|
|
||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if info == nil { | ||
| return nil, errors.New("failed to build relay info") | ||
| } | ||
|
|
||
| info.InitRequestConversionChain() | ||
| return info, nil |
There was a problem hiding this comment.
Set RelayFormat for task/mj_proxy to initialize the conversion chain.
InitRequestConversionChain seeds from RelayInfo.RelayFormat, but the Task/MjProxy cases only call genBaseRelayInfo, leaving the chain empty. Assign the format in those cases so logs include the starting format.
🔧 Suggested fix
case types.RelayFormatTask:
info = genBaseRelayInfo(c, nil)
+ info.RelayFormat = types.RelayFormatTask
case types.RelayFormatMjProxy:
info = genBaseRelayInfo(c, nil)
+ info.RelayFormat = types.RelayFormatMjProxy🤖 Prompt for AI Agents
In `@relay/common/relay_info.go` around lines 454 - 500, The Task and MjProxy
branches in GenRelayInfo call genBaseRelayInfo(c, nil) but do not set
RelayInfo.RelayFormat, so InitRequestConversionChain() has no starting format;
update the types.RelayFormatTask and types.RelayFormatMjProxy cases in
GenRelayInfo to create the base RelayInfo via genBaseRelayInfo and then set
info.RelayFormat = types.RelayFormatTask (or types.RelayFormatMjProxy)
respectively before returning so InitRequestConversionChain can seed the
conversion chain correctly.
| appendRequestConversionChain(relayInfo, other) | ||
| return other | ||
| } | ||
|
|
||
| func appendRequestConversionChain(relayInfo *relaycommon.RelayInfo, other map[string]interface{}) { | ||
| if relayInfo == nil || other == nil { | ||
| return | ||
| } | ||
| if len(relayInfo.RequestConversionChain) == 0 { | ||
| return | ||
| } | ||
| chain := make([]string, 0, len(relayInfo.RequestConversionChain)) | ||
| for _, f := range relayInfo.RequestConversionChain { | ||
| switch f { | ||
| case types.RelayFormatOpenAI: | ||
| chain = append(chain, "OpenAI Compatible") | ||
| case types.RelayFormatClaude: | ||
| chain = append(chain, "Claude Messages") | ||
| case types.RelayFormatGemini: | ||
| chain = append(chain, "Google Gemini") | ||
| case types.RelayFormatOpenAIResponses: | ||
| chain = append(chain, "OpenAI Responses") | ||
| default: | ||
| chain = append(chain, string(f)) | ||
| } | ||
| } | ||
| if len(chain) == 0 { | ||
| return | ||
| } | ||
| other["request_conversion"] = chain | ||
| } |
There was a problem hiding this comment.
Consider emitting locale-agnostic format identifiers.
appendRequestConversionChain hard-codes English labels (e.g., “OpenAI Compatible”), which will show untranslated in non-English UIs. Prefer storing canonical relay-format values and localizing in the frontend.
🤖 Prompt for AI Agents
In `@service/log_info_generate.go` around lines 73 - 103,
appendRequestConversionChain currently maps relayInfo.RequestConversionChain
entries to hard-coded English labels ("OpenAI Compatible", "Claude Messages",
etc.); change it to emit locale-agnostic canonical identifiers instead (e.g.,
the enum/string values from types.RelayFormat*). In function
appendRequestConversionChain, replace the switch that appends human-readable
strings with logic that appends the canonical identifier (for example string(f)
or f.String() if available) into the chain slice, and keep setting
other["request_conversion"] = chain so the frontend can handle localization.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@web/src/hooks/usage-logs/useUsageLogsData.jsx`:
- Around line 309-317: The function requestConversionDisplayValue currently
hides single-step conversions by returning "原生格式" when chain.length <= 1; update
its logic so that only empty chains are treated as native by changing the
condition to check chain.length === 0 (preserve the Array.isArray filtering
behavior and the join for non-empty chains) so arrays like ["OpenAI Compatible"]
are displayed rather than labeled native.
| const requestConversionDisplayValue = (conversionChain) => { | ||
| const chain = Array.isArray(conversionChain) | ||
| ? conversionChain.filter(Boolean) | ||
| : []; | ||
| if (chain.length <= 1) { | ||
| return t('原生格式'); | ||
| } | ||
| return `${chain.join(' -> ')}`; | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, verify the file exists and check the specific lines
fd "useUsageLogsData.jsx" --type fRepository: QuantumNous/new-api
Length of output: 108
🏁 Script executed:
# Examine the file around lines 309-317
if [ -f "web/src/hooks/usage-logs/useUsageLogsData.jsx" ]; then
echo "=== Lines 300-330 ===" && sed -n '300,330p' "web/src/hooks/usage-logs/useUsageLogsData.jsx"
echo ""
echo "=== Searching for request_conversion usage ===" && grep -n "request_conversion\|conversionChain\|requestConversionDisplayValue" "web/src/hooks/usage-logs/useUsageLogsData.jsx" | head -20
else
echo "File not found"
fiRepository: QuantumNous/new-api
Length of output: 1308
🏁 Script executed:
# Search for where other.request_conversion comes from (likely API response)
rg "request_conversion|requestConversionDisplayValue" web/src/hooks/usage-logs/ -A 2 -B 2Repository: QuantumNous/new-api
Length of output: 893
🏁 Script executed:
# Look for type definitions or API contracts related to request_conversion
fd -e "ts" -e "tsx" -e "d.ts" | xargs rg "request_conversion" 2>/dev/null | head -20Repository: QuantumNous/new-api
Length of output: 304
🏁 Script executed:
# Find and examine service/log_info_generate.go
fd "log_info_generate.go" --type fRepository: QuantumNous/new-api
Length of output: 91
🏁 Script executed:
# Look at the context around request_conversion assignment in the backend
if [ -f "service/log_info_generate.go" ]; then
# Find lines with request_conversion and show context
rg "request_conversion|chain" "service/log_info_generate.go" -B 5 -A 5 | head -60
else
echo "File not found"
fiRepository: QuantumNous/new-api
Length of output: 1094
🏁 Script executed:
# Also check model/log.go to understand when request_conversion is deleted
if [ -f "model/log.go" ]; then
rg "request_conversion" "model/log.go" -B 3 -A 3
else
echo "File not found"
fiRepository: QuantumNous/new-api
Length of output: 284
🏁 Script executed:
# Search for type definitions or comments about the conversion chain structure
rg "type.*[Cc]onversion|[Cc]onversion.*struct" --type go | head -20Repository: QuantumNous/new-api
Length of output: 45
🏁 Script executed:
# Verify if there are any tests or comments that clarify the expected behavior
rg "RequestConversionChain|request_conversion" --type go -B 2 -A 2 | grep -E "comment|//|len\(chain\)|len\(|if.*==" | head -30Repository: QuantumNous/new-api
Length of output: 880
🏁 Script executed:
# Check if there are any tests related to request_conversion formatting
fd -e "test.go" -e "_test.go" | xargs rg "request_conversion\|requestConversion" 2>/dev/nullRepository: QuantumNous/new-api
Length of output: 47
🏁 Script executed:
# Double-check the exact behavior: when is request_conversion NOT set vs when it IS set
sed -n '1,100p' "service/log_info_generate.go" | grep -A 50 "RequestConversionChain"Repository: QuantumNous/new-api
Length of output: 872
🏁 Script executed:
# Confirm the actual behavior when request_conversion is undefined/missing
sed -n '490,510p' "web/src/hooks/usage-logs/useUsageLogsData.jsx"Repository: QuantumNous/new-api
Length of output: 661
Display single-step conversions instead of hiding them as native format.
The condition chain.length <= 1 incorrectly treats single-element conversion arrays as native format. The backend only sets request_conversion when there are one or more conversions (e.g., ["OpenAI Compatible"]), so admins will see "原生格式" even when a conversion occurred. Change the condition to chain.length === 0 to properly distinguish native requests from converted ones.
Suggested fix
- const requestConversionDisplayValue = (conversionChain) => {
- const chain = Array.isArray(conversionChain)
- ? conversionChain.filter(Boolean)
- : [];
- if (chain.length <= 1) {
- return t('原生格式');
- }
- return `${chain.join(' -> ')}`;
- };
+ const requestConversionDisplayValue = (conversionChain) => {
+ const chain = Array.isArray(conversionChain)
+ ? conversionChain.filter(Boolean)
+ : conversionChain
+ ? [conversionChain]
+ : [];
+ if (chain.length === 0) {
+ return t('原生格式');
+ }
+ return chain.join(' -> ');
+ };🤖 Prompt for AI Agents
In `@web/src/hooks/usage-logs/useUsageLogsData.jsx` around lines 309 - 317, The
function requestConversionDisplayValue currently hides single-step conversions
by returning "原生格式" when chain.length <= 1; update its logic so that only empty
chains are treated as native by changing the condition to check chain.length ===
0 (preserve the Array.isArray filtering behavior and the join for non-empty
chains) so arrays like ["OpenAI Compatible"] are displayed rather than labeled
native.
…ion-info feat: log shows request conversion
Summary by CodeRabbit
New Features
Refactor
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.