Skip to content

feat: log shows request conversion - #2703

Merged
Calcium-Ion merged 4 commits into
QuantumNous:mainfrom
seefs001:feature/log-conversion-info
Jan 21, 2026
Merged

feat: log shows request conversion#2703
Calcium-Ion merged 4 commits into
QuantumNous:mainfrom
seefs001:feature/log-conversion-info

Conversation

@seefs001

@seefs001 seefs001 commented Jan 20, 2026

Copy link
Copy Markdown
Collaborator
image

Summary by CodeRabbit

  • New Features

    • Track and surface a request conversion chain across the relay pipeline
    • Admins can view a "Request conversion" field in usage logs showing format transformation sequences
  • Refactor

    • Removed admin-only debug fields from serialized log output while preserving conversion history tracking across steps
  • Documentation

    • Added English and Chinese translations for the new request conversion display

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 20, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This 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

Cohort / File(s) Summary
Core request-conversion infra
relay/common/relay_info.go, relay/common/request_conversion.go
Add RelayInfo.RequestConversionChain, Init/Append helpers, GuessRelayFormatFromRequest, and AppendRequestConversionFromRequest; refactor GenRelayInfo for centralized error handling.
Handlers — record conversions
relay/chat_completions_via_responses.go, relay/claude_handler.go, relay/compatible_handler.go, relay/embedding_handler.go, relay/gemini_handler.go, relay/image_handler.go, relay/rerank_handler.go, relay/responses_handler.go
After successful adaptor/request conversion, call relaycommon.AppendRequestConversionFromRequest(info, convertedRequest) to append the detected format to RelayInfo.
Logging / log generation
model/log.go, service/log_info_generate.go
model/log.go: remove admin-only debug fields (admin_info, request_conversion) from serialized Other before continuing. service/log_info_generate.go: add appendRequestConversionChain and populate other["request_conversion"] with human-friendly labels.
Frontend — admin UI & i18n
web/src/hooks/usage-logs/useUsageLogsData.jsx, web/src/i18n/locales/en.json, web/src/i18n/locales/zh.json
Add admin-only expandable row showing request conversion; add three translation keys: "请求转换" -> "Request conversion", "原生格式" -> "Native format", "转换" -> "Convert".

Sequence Diagram

sequenceDiagram
    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)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • Calcium-Ion

Poem

🐰 Through tunnels of code the formats leap and play,
Each conversion logged along the relay,
Admins may peek where the chains align,
Formats traced in order, step by sign,
Hop, nibble, note — transformations on display!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: log shows request conversion' accurately describes the main change—adding request conversion tracking and display to logs across multiple handler files and the frontend.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 454 to +500
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +73 to +103
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
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +309 to +317
const requestConversionDisplayValue = (conversionChain) => {
const chain = Array.isArray(conversionChain)
? conversionChain.filter(Boolean)
: [];
if (chain.length <= 1) {
return t('原生格式');
}
return `${chain.join(' -> ')}`;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, verify the file exists and check the specific lines
fd "useUsageLogsData.jsx" --type f

Repository: 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"
fi

Repository: 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 2

Repository: 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 -20

Repository: QuantumNous/new-api

Length of output: 304


🏁 Script executed:

# Find and examine service/log_info_generate.go
fd "log_info_generate.go" --type f

Repository: 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"
fi

Repository: 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"
fi

Repository: 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 -20

Repository: 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 -30

Repository: 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/null

Repository: 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.

@Calcium-Ion
Calcium-Ion merged commit b28ac71 into QuantumNous:main Jan 21, 2026
1 check passed
ennnnny pushed a commit to ennnnny/new-api that referenced this pull request Mar 17, 2026
…ion-info

feat: log shows request conversion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants