From 847fca874603af2f6a2807b7bcdd4cae0ac46031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=9F=BA=E5=B2=A9?= Date: Sat, 11 Jul 2026 20:37:12 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E6=B8=A0=E9=81=93=20System=20Promp?= =?UTF-8?q?t=20=E6=94=AF=E6=8C=81=E5=8F=98=E9=87=8F=E6=9B=BF=E6=8D=A2=20{m?= =?UTF-8?q?odel=5Fname}=20{site=5Fname}=20{site=5Furl}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在渠道设置的 System Prompt 字段中支持占位符变量: - {model_name} → 当前请求的模型名 - {site_name} → 站点名称 (common.SystemName) - {site_url} → 站点地址 (system_setting.ServerAddress) 复用的渠道 System Prompt 原有处理流程,无需模型表或前端改动。 --- relay/channel/codex/adaptor.go | 3 ++- relay/chat_completions_via_responses.go | 8 +++++--- relay/claude_handler.go | 9 +++++---- relay/compatible_handler.go | 8 ++++---- relay/gemini_handler.go | 9 +++++---- relay/helper/channel_system_prompt.go | 22 ++++++++++++++++++++++ 6 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 relay/helper/channel_system_prompt.go diff --git a/relay/channel/codex/adaptor.go b/relay/channel/codex/adaptor.go index ef4d4fa04125..45c1ea6c0e6d 100644 --- a/relay/channel/codex/adaptor.go +++ b/relay/channel/codex/adaptor.go @@ -13,6 +13,7 @@ import ( "github.com/QuantumNous/new-api/relay/channel/openai" relaycommon "github.com/QuantumNous/new-api/relay/common" relayconstant "github.com/QuantumNous/new-api/relay/constant" + "github.com/QuantumNous/new-api/relay/helper" "github.com/QuantumNous/new-api/types" "github.com/gin-gonic/gin" @@ -56,7 +57,7 @@ func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommo isCompact := info != nil && info.RelayMode == relayconstant.RelayModeResponsesCompact if info != nil && info.ChannelSetting.SystemPrompt != "" { - systemPrompt := info.ChannelSetting.SystemPrompt + systemPrompt := helper.ApplyChannelSystemPromptVariables(info.ChannelSetting.SystemPrompt, info) if len(request.Instructions) == 0 { if b, err := common.Marshal(systemPrompt); err == nil { diff --git a/relay/chat_completions_via_responses.go b/relay/chat_completions_via_responses.go index 80b124759153..35d61327d500 100644 --- a/relay/chat_completions_via_responses.go +++ b/relay/chat_completions_via_responses.go @@ -12,6 +12,7 @@ import ( openaichannel "github.com/QuantumNous/new-api/relay/channel/openai" relaycommon "github.com/QuantumNous/new-api/relay/common" relayconstant "github.com/QuantumNous/new-api/relay/constant" + "github.com/QuantumNous/new-api/relay/helper" "github.com/QuantumNous/new-api/service" "github.com/QuantumNous/new-api/types" @@ -26,6 +27,7 @@ func applySystemPromptIfNeeded(c *gin.Context, info *relaycommon.RelayInfo, requ return } + systemPrompt := helper.ApplyChannelSystemPromptVariables(info.ChannelSetting.SystemPrompt, info) systemRole := request.GetSystemRoleName() containSystemPrompt := false @@ -38,7 +40,7 @@ func applySystemPromptIfNeeded(c *gin.Context, info *relaycommon.RelayInfo, requ if !containSystemPrompt { systemMessage := dto.Message{ Role: systemRole, - Content: info.ChannelSetting.SystemPrompt, + Content: systemPrompt, } request.Messages = append([]dto.Message{systemMessage}, request.Messages...) return @@ -54,14 +56,14 @@ func applySystemPromptIfNeeded(c *gin.Context, info *relaycommon.RelayInfo, requ continue } if message.IsStringContent() { - request.Messages[i].SetStringContent(info.ChannelSetting.SystemPrompt + "\n" + message.StringContent()) + request.Messages[i].SetStringContent(systemPrompt + "\n" + message.StringContent()) return } contents := message.ParseContent() contents = append([]dto.MediaContent{ { Type: dto.ContentTypeText, - Text: info.ChannelSetting.SystemPrompt, + Text: systemPrompt, }, }, contents...) request.Messages[i].Content = contents diff --git a/relay/claude_handler.go b/relay/claude_handler.go index 527363205a1f..0272520478ea 100644 --- a/relay/claude_handler.go +++ b/relay/claude_handler.go @@ -108,21 +108,22 @@ func ClaudeHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ } if info.ChannelSetting.SystemPrompt != "" { + systemPrompt := helper.ApplyChannelSystemPromptVariables(info.ChannelSetting.SystemPrompt, info) if request.System == nil { - request.SetStringSystem(info.ChannelSetting.SystemPrompt) + request.SetStringSystem(systemPrompt) } else if info.ChannelSetting.SystemPromptOverride { common.SetContextKey(c, constant.ContextKeySystemPromptOverride, true) if request.IsStringSystem() { existing := strings.TrimSpace(request.GetStringSystem()) if existing == "" { - request.SetStringSystem(info.ChannelSetting.SystemPrompt) + request.SetStringSystem(systemPrompt) } else { - request.SetStringSystem(info.ChannelSetting.SystemPrompt + "\n" + existing) + request.SetStringSystem(systemPrompt + "\n" + existing) } } else { systemContents := request.ParseSystem() newSystem := dto.ClaudeMediaMessage{Type: dto.ContentTypeText} - newSystem.SetText(info.ChannelSetting.SystemPrompt) + newSystem.SetText(systemPrompt) if len(systemContents) == 0 { request.System = []dto.ClaudeMediaMessage{newSystem} } else { diff --git a/relay/compatible_handler.go b/relay/compatible_handler.go index a68cfe730f60..1e04c7c2bcc8 100644 --- a/relay/compatible_handler.go +++ b/relay/compatible_handler.go @@ -113,7 +113,7 @@ func TextHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *types relaycommon.AppendRequestConversionFromRequest(info, convertedRequest) if info.ChannelSetting.SystemPrompt != "" { - // 如果有系统提示,则将其添加到请求中 + systemPrompt := helper.ApplyChannelSystemPromptVariables(info.ChannelSetting.SystemPrompt, info) request, ok := convertedRequest.(*dto.GeneralOpenAIRequest) if ok { containSystemPrompt := false @@ -127,7 +127,7 @@ func TextHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *types // 如果没有系统提示,则添加系统提示 systemMessage := dto.Message{ Role: request.GetSystemRoleName(), - Content: info.ChannelSetting.SystemPrompt, + Content: systemPrompt, } request.Messages = append([]dto.Message{systemMessage}, request.Messages...) } else if info.ChannelSetting.SystemPromptOverride { @@ -136,13 +136,13 @@ func TextHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *types for i, message := range request.Messages { if message.Role == request.GetSystemRoleName() { if message.IsStringContent() { - request.Messages[i].SetStringContent(info.ChannelSetting.SystemPrompt + "\n" + message.StringContent()) + request.Messages[i].SetStringContent(systemPrompt + "\n" + message.StringContent()) } else { contents := message.ParseContent() contents = append([]dto.MediaContent{ { Type: dto.ContentTypeText, - Text: info.ChannelSetting.SystemPrompt, + Text: systemPrompt, }, }, contents...) request.Messages[i].Content = contents diff --git a/relay/gemini_handler.go b/relay/gemini_handler.go index 8f64552a696d..3e3f8f7334ba 100644 --- a/relay/gemini_handler.go +++ b/relay/gemini_handler.go @@ -96,14 +96,15 @@ func GeminiHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ 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: 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 @@ -111,12 +112,12 @@ func GeminiHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ 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...) } } } diff --git a/relay/helper/channel_system_prompt.go b/relay/helper/channel_system_prompt.go new file mode 100644 index 000000000000..8c8621ae53f9 --- /dev/null +++ b/relay/helper/channel_system_prompt.go @@ -0,0 +1,22 @@ +package helper + +import ( + "strings" + + "github.com/QuantumNous/new-api/common" + relaycommon "github.com/QuantumNous/new-api/relay/common" + "github.com/QuantumNous/new-api/setting/system_setting" +) + +func ApplyChannelSystemPromptVariables(systemPrompt string, info *relaycommon.RelayInfo) string { + if systemPrompt == "" { + return systemPrompt + } + result := systemPrompt + result = strings.ReplaceAll(result, "{site_name}", common.SystemName) + result = strings.ReplaceAll(result, "{site_url}", system_setting.ServerAddress) + if info != nil && info.OriginModelName != "" { + result = strings.ReplaceAll(result, "{model_name}", info.OriginModelName) + } + return result +} From b16b4a24313e48893a7e4282030af4845f0dafae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=9F=BA=E5=B2=A9?= Date: Sat, 11 Jul 2026 20:43:46 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E6=B8=A0=E9=81=93=20System=20Promp?= =?UTF-8?q?t=20=E8=BE=93=E5=85=A5=E6=A1=86=E6=B7=BB=E5=8A=A0=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在渠道配置的 System Prompt 输入框下方显示可用变量提示: {model_name}、{site_name}、{site_url} --- .../channels/components/drawers/channel-mutate-drawer.tsx | 4 ++++ web/default/src/i18n/locales/en.json | 1 + web/default/src/i18n/locales/zh.json | 1 + 3 files changed, 6 insertions(+) diff --git a/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx b/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx index 383d1aee5c2f..5c897fda33bb 100644 --- a/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx +++ b/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx @@ -4157,6 +4157,10 @@ export function ChannelMutateDrawer({ {t( 'Default system prompt for this channel' )} +
+ + {t('Available variables')}: {'{model_name}'}, {'{site_name}'}, {'{site_url}'} + diff --git a/web/default/src/i18n/locales/en.json b/web/default/src/i18n/locales/en.json index 70d3140533e6..3eb353468ed4 100644 --- a/web/default/src/i18n/locales/en.json +++ b/web/default/src/i18n/locales/en.json @@ -520,6 +520,7 @@ "Automatically sync model list when upstream changes are detected": "Automatically sync model list when upstream changes are detected", "Availability (last 24h)": "Availability (last 24h)", "Available": "Available", + "Available variables": "Available variables", "Available credits are ordered by soonest expiration.": "Available credits are ordered by soonest expiration.", "Available disk space": "Available disk space", "Available Models": "Available Models", diff --git a/web/default/src/i18n/locales/zh.json b/web/default/src/i18n/locales/zh.json index 92f52151eefb..c7d28e88c5a8 100644 --- a/web/default/src/i18n/locales/zh.json +++ b/web/default/src/i18n/locales/zh.json @@ -520,6 +520,7 @@ "Automatically sync model list when upstream changes are detected": "检测到上游模型变更时自动同步模型列表", "Availability (last 24h)": "可用率(最近 24 小时)", "Available": "可用", + "Available variables": "可用变量", "Available credits are ordered by soonest expiration.": "可用次数按最早到期排序。", "Available disk space": "可用磁盘空间", "Available Models": "可用模型", From ad5f59a1fc5e3b8e5787a9cd9250b425e5be9eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=9F=BA=E5=B2=A9?= Date: Sat, 11 Jul 2026 21:14:37 +0800 Subject: [PATCH 3/4] =?UTF-8?q?ci:=20=E6=B7=BB=E5=8A=A0=E6=8E=A8=E9=80=81?= =?UTF-8?q?=E5=88=B0=20GHCR=20=E7=9A=84=20Docker=20=E6=9E=84=E5=BB=BA=20wo?= =?UTF-8?q?rkflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docker-build-ghcr.yml | 49 +++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/docker-build-ghcr.yml diff --git a/.github/workflows/docker-build-ghcr.yml b/.github/workflows/docker-build-ghcr.yml new file mode 100644 index 000000000000..fe521b826981 --- /dev/null +++ b/.github/workflows/docker-build-ghcr.yml @@ -0,0 +1,49 @@ +name: Build Docker image to GHCR + +on: + workflow_dispatch: + inputs: + branch: + description: "要构建的分支名" + required: true + type: string + default: "feat/system-prompt-variables" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Check out branch + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + with: + ref: ${{ inputs.branch }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f + + - name: Log in to GitHub Container Registry + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - 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: Build & push + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 + with: + context: . + push: true + tags: | + ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.tag }} + cache-from: type=gha + cache-to: type=gha,mode=max From 5c0765b62ba03856a52bd7ead84efd6d6b5cfbed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=9F=BA=E5=B2=A9?= Date: Sat, 11 Jul 2026 21:18:39 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E9=95=9C=E5=83=8F=E4=BB=93=E5=BA=93?= =?UTF-8?q?=E5=90=8D=E8=BD=AC=E5=B0=8F=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docker-build-ghcr.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build-ghcr.yml b/.github/workflows/docker-build-ghcr.yml index fe521b826981..6043efd6776a 100644 --- a/.github/workflows/docker-build-ghcr.yml +++ b/.github/workflows/docker-build-ghcr.yml @@ -34,9 +34,11 @@ jobs: - name: Resolve image tag id: tag run: | + REPO=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') TAG=$(echo "${{ inputs.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/${{ github.repository }}:$TAG" + echo "Building image: ghcr.io/${REPO}:$TAG" - name: Build & push uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 @@ -44,6 +46,6 @@ jobs: context: . push: true tags: | - ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.tag }} + ghcr.io/${{ steps.tag.outputs.repo }}:${{ steps.tag.outputs.tag }} cache-from: type=gha cache-to: type=gha,mode=max