選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 

217 行
7.9 KiB

  1. package relay
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "strings"
  9. "github.com/QuantumNous/new-api/common"
  10. "github.com/QuantumNous/new-api/constant"
  11. "github.com/QuantumNous/new-api/dto"
  12. relaycommon "github.com/QuantumNous/new-api/relay/common"
  13. "github.com/QuantumNous/new-api/relay/helper"
  14. "github.com/QuantumNous/new-api/service"
  15. "github.com/QuantumNous/new-api/setting/model_setting"
  16. "github.com/QuantumNous/new-api/setting/reasoning"
  17. "github.com/QuantumNous/new-api/types"
  18. "github.com/gin-gonic/gin"
  19. "github.com/tidwall/gjson"
  20. "github.com/tidwall/sjson"
  21. )
  22. func ClaudeHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *types.NewAPIError) {
  23. info.InitChannelMeta(c)
  24. claudeReq, ok := info.Request.(*dto.ClaudeRequest)
  25. if !ok {
  26. return types.NewErrorWithStatusCode(fmt.Errorf("invalid request type, expected *dto.ClaudeRequest, got %T", info.Request), types.ErrorCodeInvalidRequest, http.StatusBadRequest, types.ErrOptionWithSkipRetry())
  27. }
  28. request, err := common.DeepCopy(claudeReq)
  29. if err != nil {
  30. return types.NewError(fmt.Errorf("failed to copy request to ClaudeRequest: %w", err), types.ErrorCodeInvalidRequest, types.ErrOptionWithSkipRetry())
  31. }
  32. err = helper.ModelMappedHelper(c, info, request)
  33. if err != nil {
  34. return types.NewError(err, types.ErrorCodeChannelModelMappedError, types.ErrOptionWithSkipRetry())
  35. }
  36. adaptor := GetAdaptor(info.ApiType)
  37. if adaptor == nil {
  38. return types.NewError(fmt.Errorf("invalid api type: %d", info.ApiType), types.ErrorCodeInvalidApiType, types.ErrOptionWithSkipRetry())
  39. }
  40. adaptor.Init(info)
  41. if request.MaxTokens == 0 {
  42. request.MaxTokens = uint(model_setting.GetClaudeSettings().GetDefaultMaxTokens(request.Model))
  43. }
  44. if baseModel, effortLevel, ok := reasoning.TrimEffortSuffix(request.Model); ok && effortLevel != "" &&
  45. strings.HasPrefix(request.Model, "claude-opus-4-6") {
  46. request.Model = baseModel
  47. request.Thinking = &dto.Thinking{
  48. Type: "adaptive",
  49. }
  50. request.OutputConfig = json.RawMessage(fmt.Sprintf(`{"effort":"%s"}`, effortLevel))
  51. request.TopP = 0
  52. request.Temperature = common.GetPointer[float64](1.0)
  53. info.UpstreamModelName = request.Model
  54. } else if model_setting.GetClaudeSettings().ThinkingAdapterEnabled &&
  55. strings.HasSuffix(request.Model, "-thinking") {
  56. if request.Thinking == nil {
  57. // 因为BudgetTokens 必须大于1024
  58. if request.MaxTokens < 1280 {
  59. request.MaxTokens = 1280
  60. }
  61. // BudgetTokens 为 max_tokens 的 80%
  62. request.Thinking = &dto.Thinking{
  63. Type: "adaptive",
  64. BudgetTokens: common.GetPointer[int](int(float64(request.MaxTokens) * model_setting.GetClaudeSettings().ThinkingAdapterBudgetTokensPercentage)),
  65. }
  66. // TODO: 临时处理
  67. // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking
  68. request.TopP = 0
  69. request.Temperature = common.GetPointer[float64](1.0)
  70. }
  71. if !model_setting.ShouldPreserveThinkingSuffix(info.OriginModelName) {
  72. request.Model = strings.TrimSuffix(request.Model, "-thinking")
  73. }
  74. info.UpstreamModelName = request.Model
  75. }
  76. if info.ChannelSetting.SystemPrompt != "" {
  77. if request.System == nil {
  78. request.SetStringSystem(info.ChannelSetting.SystemPrompt)
  79. } else if info.ChannelSetting.SystemPromptOverride {
  80. common.SetContextKey(c, constant.ContextKeySystemPromptOverride, true)
  81. if request.IsStringSystem() {
  82. existing := strings.TrimSpace(request.GetStringSystem())
  83. if existing == "" {
  84. request.SetStringSystem(info.ChannelSetting.SystemPrompt)
  85. } else {
  86. request.SetStringSystem(info.ChannelSetting.SystemPrompt + "\n" + existing)
  87. }
  88. } else {
  89. systemContents := request.ParseSystem()
  90. newSystem := dto.ClaudeMediaMessage{Type: dto.ContentTypeText}
  91. newSystem.SetText(info.ChannelSetting.SystemPrompt)
  92. if len(systemContents) == 0 {
  93. request.System = []dto.ClaudeMediaMessage{newSystem}
  94. } else {
  95. request.System = append([]dto.ClaudeMediaMessage{newSystem}, systemContents...)
  96. }
  97. }
  98. }
  99. }
  100. if !model_setting.GetGlobalSettings().PassThroughRequestEnabled &&
  101. !info.ChannelSetting.PassThroughBodyEnabled &&
  102. service.ShouldChatCompletionsUseResponsesGlobal(info.ChannelId, info.ChannelType, info.OriginModelName) {
  103. openAIRequest, convErr := service.ClaudeToOpenAIRequest(*request, info)
  104. if convErr != nil {
  105. return types.NewError(convErr, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
  106. }
  107. usage, newApiErr := chatCompletionsViaResponses(c, info, adaptor, openAIRequest)
  108. if newApiErr != nil {
  109. return newApiErr
  110. }
  111. service.PostClaudeConsumeQuota(c, info, usage)
  112. return nil
  113. }
  114. // Fix thinking.type for models that only support "adaptive" (e.g. claude-sonnet-4-6, claude-opus-4-6).
  115. // Anthropic returns 400 if thinking.type="enabled" is sent to these models.
  116. if request.Thinking != nil && request.Thinking.Type == "enabled" {
  117. request.Thinking.Type = "adaptive"
  118. }
  119. var requestBody io.Reader
  120. if model_setting.GetGlobalSettings().PassThroughRequestEnabled || info.ChannelSetting.PassThroughBodyEnabled {
  121. storage, err := common.GetBodyStorage(c)
  122. if err != nil {
  123. return types.NewErrorWithStatusCode(err, types.ErrorCodeReadRequestBodyFailed, http.StatusBadRequest, types.ErrOptionWithSkipRetry())
  124. }
  125. // In passthrough mode, fix thinking.type in the raw JSON body.
  126. if request.Thinking != nil && request.Thinking.Type == "adaptive" {
  127. bodyBytes, readErr := io.ReadAll(storage)
  128. if readErr != nil {
  129. return types.NewErrorWithStatusCode(readErr, types.ErrorCodeReadRequestBodyFailed, http.StatusBadRequest, types.ErrOptionWithSkipRetry())
  130. }
  131. if gjson.GetBytes(bodyBytes, "thinking.type").String() == "enabled" {
  132. bodyBytes, _ = sjson.SetBytes(bodyBytes, "thinking.type", "adaptive")
  133. }
  134. requestBody = bytes.NewBuffer(bodyBytes)
  135. } else {
  136. requestBody = common.ReaderOnly(storage)
  137. }
  138. } else {
  139. convertedRequest, err := adaptor.ConvertClaudeRequest(c, info, request)
  140. if err != nil {
  141. return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
  142. }
  143. relaycommon.AppendRequestConversionFromRequest(info, convertedRequest)
  144. jsonData, err := common.Marshal(convertedRequest)
  145. if err != nil {
  146. return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
  147. }
  148. // remove disabled fields for Claude API
  149. jsonData, err = relaycommon.RemoveDisabledFields(jsonData, info.ChannelOtherSettings, info.ChannelSetting.PassThroughBodyEnabled)
  150. if err != nil {
  151. return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry())
  152. }
  153. // apply param override
  154. if len(info.ParamOverride) > 0 {
  155. jsonData, err = relaycommon.ApplyParamOverrideWithRelayInfo(jsonData, info)
  156. if err != nil {
  157. return types.NewError(err, types.ErrorCodeChannelParamOverrideInvalid, types.ErrOptionWithSkipRetry())
  158. }
  159. }
  160. if common.DebugEnabled {
  161. common.SysLog("claude requestBody: " + string(jsonData))
  162. }
  163. requestBody = bytes.NewBuffer(jsonData)
  164. }
  165. statusCodeMappingStr := c.GetString("status_code_mapping")
  166. var httpResp *http.Response
  167. resp, err := adaptor.DoRequest(c, info, requestBody)
  168. if err != nil {
  169. return types.NewOpenAIError(err, types.ErrorCodeDoRequestFailed, http.StatusInternalServerError)
  170. }
  171. if resp != nil {
  172. httpResp = resp.(*http.Response)
  173. info.IsStream = info.IsStream || strings.HasPrefix(httpResp.Header.Get("Content-Type"), "text/event-stream")
  174. if httpResp.StatusCode != http.StatusOK {
  175. newAPIError = service.RelayErrorHandler(c.Request.Context(), httpResp, false)
  176. // reset status code 重置状态码
  177. service.ResetStatusCode(newAPIError, statusCodeMappingStr)
  178. return newAPIError
  179. }
  180. }
  181. usage, newAPIError := adaptor.DoResponse(c, httpResp, info)
  182. //log.Printf("usage: %v", usage)
  183. if newAPIError != nil {
  184. // reset status code 重置状态码
  185. service.ResetStatusCode(newAPIError, statusCodeMappingStr)
  186. return newAPIError
  187. }
  188. service.PostClaudeConsumeQuota(c, info, usage.(*dto.Usage))
  189. return nil
  190. }