diff --git a/.github/workflows/docker-build-ghcr.yml b/.github/workflows/docker-build-ghcr.yml
new file mode 100644
index 000000000000..6043efd6776a
--- /dev/null
+++ b/.github/workflows/docker-build-ghcr.yml
@@ -0,0 +1,51 @@
+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: |
+ 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/${REPO}:$TAG"
+
+ - name: Build & push
+ uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8
+ with:
+ context: .
+ push: true
+ tags: |
+ ghcr.io/${{ steps.tag.outputs.repo }}:${{ steps.tag.outputs.tag }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
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
+}
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": "可用模型",