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": "可用模型",