Skip to content

feat: add key-level channel affinity#5006

Open
bubbleatgit wants to merge 3 commits into
QuantumNous:mainfrom
bubbleatgit:channel-affinity-key-index
Open

feat: add key-level channel affinity#5006
bubbleatgit wants to merge 3 commits into
QuantumNous:mainfrom
bubbleatgit:channel-affinity-key-index

Conversation

@bubbleatgit
Copy link
Copy Markdown

@bubbleatgit bubbleatgit commented May 21, 2026

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

  • 渠道亲和性配置支持同渠道多个key的情况下,亲和到 key 级别
  • 新版前端“日志”界面命中亲和的渠道名字上面的🌟在鼠标 hover 的提示中增加了命中的渠道的key id显示
  • 新版前端“日志”界面命中亲和的日志详情增加了渠道亲和性配置的命中详情显示

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

无关联任务

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

测试验收截图

渠道亲和性配置

截图
image
配置示列:

[
  {
    "name": "codex cli trace",
    "model_regex": [
      "^gpt-.*$"
    ],
    "path_regex": [
      "/v1/responses"
    ],
    "key_sources": [
      {
        "type": "gjson",
        "path": "prompt_cache_key"
      }
    ],
    "param_override_template": {
      "operations": [
        {
          "mode": "pass_headers",
          "value": [
            "Originator",
            "Session_id",
            "User-Agent",
            "X-Codex-Beta-Features",
            "X-Codex-Turn-Metadata"
          ],
          "keep_origin": true
        }
      ]
    },
    "value_regex": "",
    "ttl_seconds": 0,
    "skip_retry_on_failure": true,
    "include_using_group": true,
    "include_model_name": false,
    "include_rule_name": true
  },
  {
    "name": "claude cli trace",
    "model_regex": [
      "^claude-.*$"
    ],
    "path_regex": [
      "/v1/messages"
    ],
    "key_sources": [
      {
        "type": "gjson",
        "path": "metadata.user_id"
      }
    ],
    "param_override_template": {
      "operations": [
        {
          "mode": "pass_headers",
          "value": [
            "X-Stainless-Arch",
            "X-Stainless-Lang",
            "X-Stainless-Os",
            "X-Stainless-Package-Version",
            "X-Stainless-Retry-Count",
            "X-Stainless-Runtime",
            "X-Stainless-Runtime-Version",
            "X-Stainless-Timeout",
            "User-Agent",
            "X-App",
            "Anthropic-Beta",
            "Anthropic-Dangerous-Direct-Browser-Access",
            "Anthropic-Version"
          ],
          "keep_origin": true
        }
      ]
    },
    "value_regex": "",
    "ttl_seconds": 0,
    "skip_retry_on_failure": true,
    "include_using_group": true,
    "include_model_name": false,
    "include_rule_name": true
  },
  {
    "name": "domestic codex cache trace",
    "model_regex": [
      "(?i)^(kimi|glm|minimax|deepseek|doubao-seed|qwen3|mimo)([-_/.:].*)?$"
    ],
    "path_regex": [
      "/v1/responses"
    ],
    "user_agent_include": [],
    "key_sources": [
      {
        "type": "gjson",
        "key": "",
        "path": "prompt_cache_key"
      },
      {
        "type": "request_header",
        "key": "Session_id",
        "path": ""
      }
    ],
    "value_regex": "",
    "ttl_seconds": 7200,
    "skip_retry_on_failure": true,
    "include_using_group": true,
    "include_model_name": true,
    "include_rule_name": true,
    "param_override_template": {
      "operations": [
        {
          "mode": "pass_headers",
          "value": [
            "Originator",
            "Session_id",
            "User-Agent",
            "X-Codex-Beta-Features",
            "X-Codex-Turn-Metadata"
          ],
          "keep_origin": true
        }
      ]
    }
  },
  {
    "name": "domestic claude cache trace",
    "model_regex": [
      "(?i)^(kimi|glm|minimax|deepseek|doubao-seed|qwen3|mimo)([-_/.:].*)?$"
    ],
    "path_regex": [
      "/v1/messages"
    ],
    "user_agent_include": [],
    "key_sources": [
      {
        "type": "gjson",
        "key": "",
        "path": "metadata.user_id"
      },
      {
        "type": "gjson",
        "key": "",
        "path": "metadata.session_id"
      },
      {
        "type": "request_header",
        "key": "X-Session-Id",
        "path": ""
      }
    ],
    "value_regex": "",
    "ttl_seconds": 7200,
    "skip_retry_on_failure": true,
    "include_using_group": true,
    "include_model_name": true,
    "include_rule_name": true,
    "param_override_template": {
      "operations": [
        {
          "mode": "pass_headers",
          "value": [
            "X-Stainless-Arch",
            "X-Stainless-Lang",
            "X-Stainless-Os",
            "X-Stainless-Package-Version",
            "X-Stainless-Retry-Count",
            "X-Stainless-Runtime",
            "X-Stainless-Runtime-Version",
            "X-Stainless-Timeout",
            "User-Agent",
            "X-App",
            "Anthropic-Beta",
            "Anthropic-Dangerous-Direct-Browser-Access",
            "Anthropic-Version"
          ],
          "keep_origin": true
        }
      ]
    }
  }
]

日志页面增加显示命中的规则详情

image

日志详情增加显示命中的规则详情

image

redis 缓存命中

image

Summary: Adds channel+key affinity, bumps channel affinity usage-cache stats namespace to v2, and exposes the matched channel key index in the new usage logs UI. Tests: go test ./service ./model ./middleware -count=1; docker buildx build --output type=docker --provenance=false --sbom=false --platform linux/amd64 -t new-api:key-affinity-ui-check .

Summary by CodeRabbit

  • New Features

    • Affinity now records both selected channel and specific key index; UI views show channel and channel-key details and labels.
    • Affinity selection persists/restores a previously chosen key index per channel.
  • Bug Fixes

    • Deterministic, index-based key selection with stricter validation and clearer errors when a key is invalid or disabled.
    • Affinity usage recording updated to include key index.
  • Tests

    • Added and updated tests covering indexed key selection and affinity caching/storage.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Walkthrough

Persist ChannelAffinitySelection (channel_id + key_index) in cache, restore selected key index in middleware via service helpers and GetEnabledKeyByIndex, update affinity context/logging, and surface channel/key metadata in admin UI components.

Changes

Channel Affinity Key Index Tracking

Layer / File(s) Summary
Channel key selection API
model/channel.go, model/channel_key_affinity_test.go
GetEnabledKeyByIndex(index) deterministically returns the key at the requested index with validation and error returns; unit tests cover enabled and disabled cases.
Affinity selection data and cache infrastructure
service/channel_affinity.go
Introduce ChannelAffinitySelection (channel_id, key_index); add Gin context keys and migrate cache from scalar int to structured JSONCodec[ChannelAffinitySelection] with v2 namespace.
Affinity service public API
service/channel_affinity.go
GetPreferredChannelByAffinity now returns a selection struct; MarkChannelAffinityUsed accepts selection and writes channel_id/key_index to Gin context and log info; add GetChannelAffinityKeyIndex and UpdateChannelAffinitySelectedKeyIndex; RecordChannelAffinity caches full selection using context keyIndex.
Affinity service tests
service/channel_affinity_template_test.go, service/channel_affinity_usage_cache_test.go
Tests updated to seed/assert ChannelAffinitySelection; new TestRecordChannelAffinityStoresChannelAndKeyIndex verifies both fields are cached; usage-cache tests use a unique key helper.
Middleware integration
middleware/distributor.go
Distribute consumes selection object and extracts ChannelID; SetupContextForSelectedChannel attempts to restore stored key index via service helpers and GetEnabledKeyByIndex, falls back to prior selection/fallback when needed; affinity usage recorded with the selection.
Frontend types and UI display
web/default/src/features/usage-logs/types.ts, web/default/src/features/usage-logs/index.tsx, web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx, web/default/src/features/system-settings/general/channel-affinity/cache-stats-dialog.tsx, web/default/src/features/usage-logs/components/dialogs/details-dialog.tsx
ChannelAffinityInfo gains optional channel_id and key_index; UI components and dialogs conditionally display formatted channel and channel-key info and include key_index when opening affinity dialogs.

Sequence Diagram

sequenceDiagram
  participant Middleware as middleware/distributor
  participant Service as service/channel_affinity
  participant Model as model/channel
  participant Cache as Redis Cache
  participant Context as Gin Context

  Middleware->>Service: GetPreferredChannelByAffinity(modelName, using_group)
  Service->>Cache: Get ChannelAffinitySelection
  Cache-->>Service: {channel_id, key_index}
  Service-->>Middleware: ChannelAffinitySelection

  Middleware->>Service: GetChannelAffinityKeyIndex(channel_id)
  Service->>Context: read channel_affinity_key_index
  Context-->>Service: key_index
  Service-->>Middleware: key_index

  Middleware->>Model: GetEnabledKeyByIndex(key_index)
  Model-->>Middleware: key or error

  alt key found and enabled
    Middleware->>Service: UpdateChannelAffinitySelectedKeyIndex(channel_id, key_index)
  else error or disabled
    Middleware->>Model: GetNextEnabledKey() (fallback)
  end

  Middleware->>Service: MarkChannelAffinityUsed(selectedGroup, selection)
  Service->>Context: store channel_affinity_channel_id and channel_affinity_key_index
  Service->>Cache: RecordChannelAffinity(selection)
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Suggested Reviewers

  • seefs001

Poem

🐰 I hopped through caches, keys in paw,
Saved the index that I saw,
Middleware asked, the service sighed,
The model gave the key applied,
Affinity kept — hop, cache, and thaw!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding key-level channel affinity support across the entire codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
middleware/distributor.go (1)

383-389: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear the previous multi-key index when switching to a single-key channel.

Line 387 already notes that this context survives retries. With the new affinity recording, leaving ContextKeyChannelMultiKeyIndex untouched lets a stale index from an earlier multi-key attempt get cached for the final single-key channel, so later affinity restores and usage logs carry the wrong key index.

Proposed fix
 	if channel.ChannelInfo.IsMultiKey {
 		common.SetContextKey(c, constant.ContextKeyChannelIsMultiKey, true)
 		common.SetContextKey(c, constant.ContextKeyChannelMultiKeyIndex, index)
 	} else {
 		// 必须设置为 false,否则在重试到单个 key 的时候会导致日志显示错误
 		common.SetContextKey(c, constant.ContextKeyChannelIsMultiKey, false)
+		common.SetContextKey(c, constant.ContextKeyChannelMultiKeyIndex, 0)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@middleware/distributor.go` around lines 383 - 389, When
channel.ChannelInfo.IsMultiKey is false you must clear any previous multi-key
index so stale values don't survive retries; in the else branch (where you
currently call common.SetContextKey(c, constant.ContextKeyChannelIsMultiKey,
false)) also call common.SetContextKey(c,
constant.ContextKeyChannelMultiKeyIndex, nil) (or the sentinel you use for "no
index") to remove the old ContextKeyChannelMultiKeyIndex value so
affinity/restores/logs cannot pick up a stale index.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@web/default/src/features/system-settings/general/channel-affinity/cache-stats-dialog.tsx`:
- Around line 129-132: The displayed label currently concatenates a hardcoded
English fragment `#${keyIndex + 1} (index ${keyIndex})`; replace this with a
translated string using t() and interpolation so both the prefix/number and the
“index” token are localizable. Update the data.push value construction (where
data.push is called) to call t(...) with placeholders (e.g. number and index)
and pass keyIndex and keyIndex+1 as values so translators can render the whole
label.

In
`@web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx`:
- Around line 74-79: formatKeyIndexLabel currently emits user-facing English
text ("index") directly; update it so all display text is i18n-able by either
(a) accepting the translation function t (from useTranslation) as an argument
and using t() to build the label inside formatKeyIndexLabel, or (b) change
formatKeyIndexLabel to return structured data (e.g., {labelNumber: number,
index: number} or null) and perform the final string assembly with t() inside
the React component that renders the value; modify callers of
formatKeyIndexLabel accordingly (e.g., in the component that uses the function)
and ensure the translation key(s) are defined and used via t('...') rather than
hardcoded English.

---

Outside diff comments:
In `@middleware/distributor.go`:
- Around line 383-389: When channel.ChannelInfo.IsMultiKey is false you must
clear any previous multi-key index so stale values don't survive retries; in the
else branch (where you currently call common.SetContextKey(c,
constant.ContextKeyChannelIsMultiKey, false)) also call common.SetContextKey(c,
constant.ContextKeyChannelMultiKeyIndex, nil) (or the sentinel you use for "no
index") to remove the old ContextKeyChannelMultiKeyIndex value so
affinity/restores/logs cannot pick up a stale index.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1b7dd7b6-ec37-4239-8fe8-1a592daba8aa

📥 Commits

Reviewing files that changed from the base of the PR and between 20d3e73 and e4820ce.

📒 Files selected for processing (10)
  • middleware/distributor.go
  • model/channel.go
  • model/channel_key_affinity_test.go
  • service/channel_affinity.go
  • service/channel_affinity_template_test.go
  • service/channel_affinity_usage_cache_test.go
  • web/default/src/features/system-settings/general/channel-affinity/cache-stats-dialog.tsx
  • web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx
  • web/default/src/features/usage-logs/index.tsx
  • web/default/src/features/usage-logs/types.ts

Comment on lines +129 to +132
data.push({
key: t('Channel key'),
value: `#${keyIndex + 1} (index ${keyIndex})`,
})
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 | ⚡ Quick win

Localize the key-index label text.

(index ${keyIndex}) introduces hardcoded English in UI output. Use t() for the full display string (or at least the “index” token) so it can be translated.

As per coding guidelines: web/default/**/*.{tsx,ts}: “All user-facing text content must support i18n using the t() function from useTranslation() in React components”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/default/src/features/system-settings/general/channel-affinity/cache-stats-dialog.tsx`
around lines 129 - 132, The displayed label currently concatenates a hardcoded
English fragment `#${keyIndex + 1} (index ${keyIndex})`; replace this with a
translated string using t() and interpolation so both the prefix/number and the
“index” token are localizable. Update the data.push value construction (where
data.push is called) to call t(...) with placeholders (e.g. number and index)
and pass keyIndex and keyIndex+1 as values so translators can render the whole
label.

Comment on lines +74 to +79
function formatKeyIndexLabel(keyIndex?: number): string | null {
if (keyIndex == null || !Number.isFinite(keyIndex) || keyIndex < 0) {
return null
}
return `#${keyIndex + 1} (index ${keyIndex})`
}
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 | ⚡ Quick win

Move key-index display text under i18n.

formatKeyIndexLabel returns a user-facing English phrase (index) directly. Pass t into this formatter (or return structured values and format in component with t) so the full label is translatable.

As per coding guidelines: web/default/**/*.{tsx,ts}: “All user-facing text content must support i18n using the t() function from useTranslation() in React components”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx`
around lines 74 - 79, formatKeyIndexLabel currently emits user-facing English
text ("index") directly; update it so all display text is i18n-able by either
(a) accepting the translation function t (from useTranslation) as an argument
and using t() to build the label inside formatKeyIndexLabel, or (b) change
formatKeyIndexLabel to return structured data (e.g., {labelNumber: number,
index: number} or null) and perform the final string assembly with t() inside
the React component that renders the value; modify callers of
formatKeyIndexLabel accordingly (e.g., in the component that uses the function)
and ensure the translation key(s) are defined and used via t('...') rather than
hardcoded English.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/default/src/features/usage-logs/components/dialogs/details-dialog.tsx`:
- Around line 138-143: formatKeyIndexLabel currently returns hard-coded English;
update it so the component uses the i18n translation function instead. Either
modify formatKeyIndexLabel to accept a translation function (t: TFunction) and
return t('keyIndexLabel', {n: keyIndex+1, index: keyIndex}) or move the string
construction into the React component that already calls useTranslation() and
call t(...) there (e.g., replace direct calls to formatKeyIndexLabel(...) with
t('...', {n: ..., index: ...})). Ensure you use the existing useTranslation/t()
from the component and add a matching translation key (with interpolation) in
locale files instead of hard-coded English.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7cb3c294-3960-4dd2-83a2-c72636dad3dd

📥 Commits

Reviewing files that changed from the base of the PR and between e4820ce and 610fd56.

📒 Files selected for processing (1)
  • web/default/src/features/usage-logs/components/dialogs/details-dialog.tsx

Comment on lines +138 to +143
function formatKeyIndexLabel(keyIndex?: number): string | null {
if (keyIndex == null || !Number.isFinite(keyIndex) || keyIndex < 0) {
return null
}
return `#${keyIndex + 1} (index ${keyIndex})`
}
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 | ⚡ Quick win

Localize key-index display text instead of hard-coded English.

formatKeyIndexLabel returns a user-facing English string directly, so this part won’t translate with locale changes. Route it through t(...) in the component path.

Suggested patch
-function formatKeyIndexLabel(keyIndex?: number): string | null {
+function formatKeyIndexLabel(
+  keyIndex: number | undefined,
+  t: (key: string, options?: Record<string, unknown>) => string
+): string | null {
   if (keyIndex == null || !Number.isFinite(keyIndex) || keyIndex < 0) {
     return null
   }
-  return `#${keyIndex + 1} (index ${keyIndex})`
+  return t('#{{displayIndex}} (index {{rawIndex}})', {
+    displayIndex: keyIndex + 1,
+    rawIndex: keyIndex,
+  })
 }
-  const affinityKeyDisplay = formatKeyIndexLabel(affinity?.key_index)
+  const affinityKeyDisplay = formatKeyIndexLabel(affinity?.key_index, t)

As per coding guidelines, "All user-facing text content must support i18n using the t() function from useTranslation() in React components".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/usage-logs/components/dialogs/details-dialog.tsx`
around lines 138 - 143, formatKeyIndexLabel currently returns hard-coded
English; update it so the component uses the i18n translation function instead.
Either modify formatKeyIndexLabel to accept a translation function (t:
TFunction) and return t('keyIndexLabel', {n: keyIndex+1, index: keyIndex}) or
move the string construction into the React component that already calls
useTranslation() and call t(...) there (e.g., replace direct calls to
formatKeyIndexLabel(...) with t('...', {n: ..., index: ...})). Ensure you use
the existing useTranslation/t() from the component and add a matching
translation key (with interpolation) in locale files instead of hard-coded
English.

@bubbleatgit bubbleatgit force-pushed the channel-affinity-key-index branch 2 times, most recently from 0b61177 to 798cd98 Compare May 21, 2026 07:03
@bubbleatgit bubbleatgit force-pushed the channel-affinity-key-index branch from 798cd98 to d633de0 Compare May 21, 2026 07:09
@bubbleatgit
Copy link
Copy Markdown
Author

测试验收截图

渠道亲和性配置

截图
image
配置示列:

[
  {
    "name": "codex cli trace",
    "model_regex": [
      "^gpt-.*$"
    ],
    "path_regex": [
      "/v1/responses"
    ],
    "key_sources": [
      {
        "type": "gjson",
        "path": "prompt_cache_key"
      }
    ],
    "param_override_template": {
      "operations": [
        {
          "mode": "pass_headers",
          "value": [
            "Originator",
            "Session_id",
            "User-Agent",
            "X-Codex-Beta-Features",
            "X-Codex-Turn-Metadata"
          ],
          "keep_origin": true
        }
      ]
    },
    "value_regex": "",
    "ttl_seconds": 0,
    "skip_retry_on_failure": true,
    "include_using_group": true,
    "include_model_name": false,
    "include_rule_name": true
  },
  {
    "name": "claude cli trace",
    "model_regex": [
      "^claude-.*$"
    ],
    "path_regex": [
      "/v1/messages"
    ],
    "key_sources": [
      {
        "type": "gjson",
        "path": "metadata.user_id"
      }
    ],
    "param_override_template": {
      "operations": [
        {
          "mode": "pass_headers",
          "value": [
            "X-Stainless-Arch",
            "X-Stainless-Lang",
            "X-Stainless-Os",
            "X-Stainless-Package-Version",
            "X-Stainless-Retry-Count",
            "X-Stainless-Runtime",
            "X-Stainless-Runtime-Version",
            "X-Stainless-Timeout",
            "User-Agent",
            "X-App",
            "Anthropic-Beta",
            "Anthropic-Dangerous-Direct-Browser-Access",
            "Anthropic-Version"
          ],
          "keep_origin": true
        }
      ]
    },
    "value_regex": "",
    "ttl_seconds": 0,
    "skip_retry_on_failure": true,
    "include_using_group": true,
    "include_model_name": false,
    "include_rule_name": true
  },
  {
    "name": "domestic codex cache trace",
    "model_regex": [
      "(?i)^(kimi|glm|minimax|deepseek|doubao-seed|qwen3|mimo)([-_/.:].*)?$"
    ],
    "path_regex": [
      "/v1/responses"
    ],
    "user_agent_include": [],
    "key_sources": [
      {
        "type": "gjson",
        "key": "",
        "path": "prompt_cache_key"
      },
      {
        "type": "request_header",
        "key": "Session_id",
        "path": ""
      }
    ],
    "value_regex": "",
    "ttl_seconds": 7200,
    "skip_retry_on_failure": true,
    "include_using_group": true,
    "include_model_name": true,
    "include_rule_name": true,
    "param_override_template": {
      "operations": [
        {
          "mode": "pass_headers",
          "value": [
            "Originator",
            "Session_id",
            "User-Agent",
            "X-Codex-Beta-Features",
            "X-Codex-Turn-Metadata"
          ],
          "keep_origin": true
        }
      ]
    }
  },
  {
    "name": "domestic claude cache trace",
    "model_regex": [
      "(?i)^(kimi|glm|minimax|deepseek|doubao-seed|qwen3|mimo)([-_/.:].*)?$"
    ],
    "path_regex": [
      "/v1/messages"
    ],
    "user_agent_include": [],
    "key_sources": [
      {
        "type": "gjson",
        "key": "",
        "path": "metadata.user_id"
      },
      {
        "type": "gjson",
        "key": "",
        "path": "metadata.session_id"
      },
      {
        "type": "request_header",
        "key": "X-Session-Id",
        "path": ""
      }
    ],
    "value_regex": "",
    "ttl_seconds": 7200,
    "skip_retry_on_failure": true,
    "include_using_group": true,
    "include_model_name": true,
    "include_rule_name": true,
    "param_override_template": {
      "operations": [
        {
          "mode": "pass_headers",
          "value": [
            "X-Stainless-Arch",
            "X-Stainless-Lang",
            "X-Stainless-Os",
            "X-Stainless-Package-Version",
            "X-Stainless-Retry-Count",
            "X-Stainless-Runtime",
            "X-Stainless-Runtime-Version",
            "X-Stainless-Timeout",
            "User-Agent",
            "X-App",
            "Anthropic-Beta",
            "Anthropic-Dangerous-Direct-Browser-Access",
            "Anthropic-Version"
          ],
          "keep_origin": true
        }
      ]
    }
  }
]

日志页面增加显示命中的规则详情

image

日志详情增加显示命中的规则详情

image

redis 缓存命中

image

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.

1 participant