diff --git a/.github/ISSUE_TEMPLATE/bug_report_en.md b/.github/ISSUE_TEMPLATE/bug_report_en.md new file mode 100644 index 000000000..5c2506180 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report_en.md @@ -0,0 +1,26 @@ +--- +name: Bug Report +about: Describe the issue you encountered with clear and detailed language +title: '' +labels: bug +assignees: '' + +--- + +**Routine Checks** + +[//]: # (Remove the space in the box and fill with an x) ++ [ ] I have confirmed there are no similar issues currently ++ [ ] I have confirmed I have upgraded to the latest version ++ [ ] I have thoroughly read the project README, especially the FAQ section ++ [ ] I understand and am willing to follow up on this issue, assist with testing and provide feedback ++ [ ] I understand and acknowledge the above, and understand that project maintainers have limited time and energy, **issues that do not follow the rules may be ignored or closed directly** + +**Issue Description** + +**Steps to Reproduce** + +**Expected Result** + +**Related Screenshots** +If none, please delete this section. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request_en.md b/.github/ISSUE_TEMPLATE/feature_request_en.md new file mode 100644 index 000000000..cdfc43f0d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request_en.md @@ -0,0 +1,22 @@ +--- +name: Feature Request +about: Describe the new feature you would like to add with clear and detailed language +title: '' +labels: enhancement +assignees: '' + +--- + +**Routine Checks** + +[//]: # (Remove the space in the box and fill with an x) ++ [ ] I have confirmed there are no similar issues currently ++ [ ] I have confirmed I have upgraded to the latest version ++ [ ] I have thoroughly read the project README and confirmed the current version cannot meet my needs ++ [ ] I understand and am willing to follow up on this issue, assist with testing and provide feedback ++ [ ] I understand and acknowledge the above, and understand that project maintainers have limited time and energy, **issues that do not follow the rules may be ignored or closed directly** + +**Feature Description** + +**Use Case** + diff --git a/constant/channel.go b/constant/channel.go index 7d8893c1d..1b5c2724b 100644 --- a/constant/channel.go +++ b/constant/channel.go @@ -113,3 +113,64 @@ var ChannelBaseURLs = []string{ "https://llm.submodel.ai", //53 "https://ark.cn-beijing.volces.com", //54 } + +var ChannelTypeNames = map[int]string{ + ChannelTypeUnknown: "Unknown", + ChannelTypeOpenAI: "OpenAI", + ChannelTypeMidjourney: "Midjourney", + ChannelTypeAzure: "Azure", + ChannelTypeOllama: "Ollama", + ChannelTypeMidjourneyPlus: "MidjourneyPlus", + ChannelTypeOpenAIMax: "OpenAIMax", + ChannelTypeOhMyGPT: "OhMyGPT", + ChannelTypeCustom: "Custom", + ChannelTypeAILS: "AILS", + ChannelTypeAIProxy: "AIProxy", + ChannelTypePaLM: "PaLM", + ChannelTypeAPI2GPT: "API2GPT", + ChannelTypeAIGC2D: "AIGC2D", + ChannelTypeAnthropic: "Anthropic", + ChannelTypeBaidu: "Baidu", + ChannelTypeZhipu: "Zhipu", + ChannelTypeAli: "Ali", + ChannelTypeXunfei: "Xunfei", + ChannelType360: "360", + ChannelTypeOpenRouter: "OpenRouter", + ChannelTypeAIProxyLibrary: "AIProxyLibrary", + ChannelTypeFastGPT: "FastGPT", + ChannelTypeTencent: "Tencent", + ChannelTypeGemini: "Gemini", + ChannelTypeMoonshot: "Moonshot", + ChannelTypeZhipu_v4: "ZhipuV4", + ChannelTypePerplexity: "Perplexity", + ChannelTypeLingYiWanWu: "LingYiWanWu", + ChannelTypeAws: "AWS", + ChannelTypeCohere: "Cohere", + ChannelTypeMiniMax: "MiniMax", + ChannelTypeSunoAPI: "SunoAPI", + ChannelTypeDify: "Dify", + ChannelTypeJina: "Jina", + ChannelCloudflare: "Cloudflare", + ChannelTypeSiliconFlow: "SiliconFlow", + ChannelTypeVertexAi: "VertexAI", + ChannelTypeMistral: "Mistral", + ChannelTypeDeepSeek: "DeepSeek", + ChannelTypeMokaAI: "MokaAI", + ChannelTypeVolcEngine: "VolcEngine", + ChannelTypeBaiduV2: "BaiduV2", + ChannelTypeXinference: "Xinference", + ChannelTypeXai: "xAI", + ChannelTypeCoze: "Coze", + ChannelTypeKling: "Kling", + ChannelTypeJimeng: "Jimeng", + ChannelTypeVidu: "Vidu", + ChannelTypeSubmodel: "Submodel", + ChannelTypeDoubaoVideo: "DoubaoVideo", +} + +func GetChannelTypeName(channelType int) string { + if name, ok := ChannelTypeNames[channelType]; ok { + return name + } + return "Unknown" +} diff --git a/controller/channel-test.go b/controller/channel-test.go index ff1e8cef4..8b0e37dae 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -28,6 +28,7 @@ import ( "time" "github.com/bytedance/gopkg/util/gopool" + "github.com/samber/lo" "github.com/gin-gonic/gin" ) @@ -40,46 +41,19 @@ type testResult struct { func testChannel(channel *model.Channel, testModel string, endpointType string) testResult { tik := time.Now() - if channel.Type == constant.ChannelTypeMidjourney { - return testResult{ - localErr: errors.New("midjourney channel test is not supported"), - newAPIError: nil, - } + var unsupportedTestChannelTypes = []int{ + constant.ChannelTypeMidjourney, + constant.ChannelTypeMidjourneyPlus, + constant.ChannelTypeSunoAPI, + constant.ChannelTypeKling, + constant.ChannelTypeJimeng, + constant.ChannelTypeDoubaoVideo, + constant.ChannelTypeVidu, } - if channel.Type == constant.ChannelTypeMidjourneyPlus { + if lo.Contains(unsupportedTestChannelTypes, channel.Type) { + channelTypeName := constant.GetChannelTypeName(channel.Type) return testResult{ - localErr: errors.New("midjourney plus channel test is not supported"), - newAPIError: nil, - } - } - if channel.Type == constant.ChannelTypeSunoAPI { - return testResult{ - localErr: errors.New("suno channel test is not supported"), - newAPIError: nil, - } - } - if channel.Type == constant.ChannelTypeKling { - return testResult{ - localErr: errors.New("kling channel test is not supported"), - newAPIError: nil, - } - } - if channel.Type == constant.ChannelTypeJimeng { - return testResult{ - localErr: errors.New("jimeng channel test is not supported"), - newAPIError: nil, - } - } - if channel.Type == constant.ChannelTypeDoubaoVideo { - return testResult{ - localErr: errors.New("doubao video channel test is not supported"), - newAPIError: nil, - } - } - if channel.Type == constant.ChannelTypeVidu { - return testResult{ - localErr: errors.New("vidu channel test is not supported"), - newAPIError: nil, + localErr: fmt.Errorf("%s channel test is not supported", channelTypeName), } } w := httptest.NewRecorder() diff --git a/dto/gemini.go b/dto/gemini.go index 80552aade..fdeb2793d 100644 --- a/dto/gemini.go +++ b/dto/gemini.go @@ -2,11 +2,12 @@ package dto import ( "encoding/json" - "github.com/gin-gonic/gin" "one-api/common" "one-api/logger" "one-api/types" "strings" + + "github.com/gin-gonic/gin" ) type GeminiChatRequest struct { @@ -273,6 +274,7 @@ type GeminiChatGenerationConfig struct { ResponseModalities []string `json:"responseModalities,omitempty"` ThinkingConfig *GeminiThinkingConfig `json:"thinkingConfig,omitempty"` SpeechConfig json.RawMessage `json:"speechConfig,omitempty"` // RawMessage to allow flexible speech config + ImageConfig json.RawMessage `json:"imageConfig,omitempty"` // RawMessage to allow flexible image config } type MediaResolution string diff --git a/dto/openai_image.go b/dto/openai_image.go index 5aece25f2..16e9a175d 100644 --- a/dto/openai_image.go +++ b/dto/openai_image.go @@ -74,14 +74,15 @@ func (r ImageRequest) MarshalJSON() ([]byte, error) { return nil, err } + // 不能合并ExtraFields!!!!!!!! // 合并 ExtraFields - for k, v := range r.Extra { - if _, exists := baseMap[k]; !exists { - baseMap[k] = v - } - } + //for k, v := range r.Extra { + // if _, exists := baseMap[k]; !exists { + // baseMap[k] = v + // } + //} - return json.Marshal(baseMap) + return common.Marshal(baseMap) } func GetJSONFieldNames(t reflect.Type) map[string]struct{} { diff --git a/relay/channel/siliconflow/adaptor.go b/relay/channel/siliconflow/adaptor.go index 4c176c088..41ab76327 100644 --- a/relay/channel/siliconflow/adaptor.go +++ b/relay/channel/siliconflow/adaptor.go @@ -61,6 +61,16 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *rel } func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) (any, error) { + // SiliconFlow requires messages array for FIM requests, even if client doesn't send it + if (request.Prefix != nil || request.Suffix != nil) && len(request.Messages) == 0 { + // Add an empty user message to satisfy SiliconFlow's requirement + request.Messages = []dto.Message{ + { + Role: "user", + Content: "", + }, + } + } return request, nil } diff --git a/relay/helper/valid_request.go b/relay/helper/valid_request.go index f4a290ec6..c9d3ed0b7 100644 --- a/relay/helper/valid_request.go +++ b/relay/helper/valid_request.go @@ -275,7 +275,9 @@ func GetAndValidateTextRequest(c *gin.Context, relayMode int) (*dto.GeneralOpenA return nil, errors.New("field prompt is required") } case relayconstant.RelayModeChatCompletions: - if len(textRequest.Messages) == 0 { + // For FIM (Fill-in-the-middle) requests with prefix/suffix, messages is optional + // It will be filled by provider-specific adaptors if needed (e.g., SiliconFlow)。Or it is allowed by model vendor(s) (e.g., DeepSeek) + if len(textRequest.Messages) == 0 && textRequest.Prefix == nil && textRequest.Suffix == nil { return nil, errors.New("field messages is required") } case relayconstant.RelayModeEmbeddings: