relay-aws.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. package aws
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "strings"
  8. "github.com/QuantumNous/new-api/common"
  9. "github.com/QuantumNous/new-api/dto"
  10. "github.com/QuantumNous/new-api/relay/channel/claude"
  11. relaycommon "github.com/QuantumNous/new-api/relay/common"
  12. "github.com/QuantumNous/new-api/relay/helper"
  13. "github.com/QuantumNous/new-api/types"
  14. "github.com/gin-gonic/gin"
  15. "github.com/pkg/errors"
  16. "github.com/aws/aws-sdk-go-v2/aws"
  17. "github.com/aws/aws-sdk-go-v2/credentials"
  18. "github.com/aws/aws-sdk-go-v2/service/bedrockruntime"
  19. bedrockruntimeTypes "github.com/aws/aws-sdk-go-v2/service/bedrockruntime/types"
  20. "github.com/aws/smithy-go/auth/bearer"
  21. )
  22. func newAwsClient(c *gin.Context, info *relaycommon.RelayInfo) (*bedrockruntime.Client, error) {
  23. awsSecret := strings.Split(info.ApiKey, "|")
  24. var client *bedrockruntime.Client
  25. switch len(awsSecret) {
  26. case 2:
  27. apiKey := awsSecret[0]
  28. region := awsSecret[1]
  29. client = bedrockruntime.New(bedrockruntime.Options{
  30. Region: region,
  31. BearerAuthTokenProvider: bearer.StaticTokenProvider{Token: bearer.Token{Value: apiKey}},
  32. })
  33. case 3:
  34. ak := awsSecret[0]
  35. sk := awsSecret[1]
  36. region := awsSecret[2]
  37. client = bedrockruntime.New(bedrockruntime.Options{
  38. Region: region,
  39. Credentials: aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(ak, sk, "")),
  40. })
  41. default:
  42. return nil, errors.New("invalid aws secret key")
  43. }
  44. return client, nil
  45. }
  46. func doAwsClientRequest(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor, requestBody io.Reader) (any, error) {
  47. awsCli, err := newAwsClient(c, info)
  48. if err != nil {
  49. return nil, types.NewError(err, types.ErrorCodeChannelAwsClientError)
  50. }
  51. a.AwsClient = awsCli
  52. println(info.UpstreamModelName)
  53. // 获取对应的AWS模型ID
  54. awsModelId := getAwsModelID(info.UpstreamModelName)
  55. awsRegionPrefix := getAwsRegionPrefix(awsCli.Options().Region)
  56. canCrossRegion := awsModelCanCrossRegion(awsModelId, awsRegionPrefix)
  57. if canCrossRegion {
  58. awsModelId = awsModelCrossRegion(awsModelId, awsRegionPrefix)
  59. }
  60. if isNovaModel(awsModelId) {
  61. var novaReq *NovaRequest
  62. err = common.DecodeJson(requestBody, &novaReq)
  63. if err != nil {
  64. return nil, types.NewError(errors.Wrap(err, "decode nova request fail"), types.ErrorCodeBadRequestBody)
  65. }
  66. // 使用InvokeModel API,但使用Nova格式的请求体
  67. awsReq := &bedrockruntime.InvokeModelInput{
  68. ModelId: aws.String(awsModelId),
  69. Accept: aws.String("application/json"),
  70. ContentType: aws.String("application/json"),
  71. }
  72. reqBody, err := common.Marshal(novaReq)
  73. if err != nil {
  74. return nil, types.NewError(errors.Wrap(err, "marshal nova request"), types.ErrorCodeBadResponseBody)
  75. }
  76. awsReq.Body = reqBody
  77. return nil, nil
  78. } else {
  79. awsClaudeReq, err := formatRequest(requestBody)
  80. if err != nil {
  81. return nil, types.NewError(errors.Wrap(err, "format aws request fail"), types.ErrorCodeBadRequestBody)
  82. }
  83. if info.IsStream {
  84. awsReq := &bedrockruntime.InvokeModelWithResponseStreamInput{
  85. ModelId: aws.String(awsModelId),
  86. Accept: aws.String("application/json"),
  87. ContentType: aws.String("application/json"),
  88. }
  89. awsReq.Body, err = common.Marshal(awsClaudeReq)
  90. if err != nil {
  91. return nil, types.NewError(errors.Wrap(err, "marshal aws request fail"), types.ErrorCodeBadRequestBody)
  92. }
  93. a.AwsReq = awsReq
  94. return nil, nil
  95. } else {
  96. awsReq := &bedrockruntime.InvokeModelInput{
  97. ModelId: aws.String(awsModelId),
  98. Accept: aws.String("application/json"),
  99. ContentType: aws.String("application/json"),
  100. }
  101. awsReq.Body, err = common.Marshal(awsClaudeReq)
  102. if err != nil {
  103. return nil, types.NewError(errors.Wrap(err, "marshal aws request fail"), types.ErrorCodeBadRequestBody)
  104. }
  105. a.AwsReq = awsReq
  106. return nil, nil
  107. }
  108. }
  109. }
  110. func getAwsRegionPrefix(awsRegionId string) string {
  111. parts := strings.Split(awsRegionId, "-")
  112. regionPrefix := ""
  113. if len(parts) > 0 {
  114. regionPrefix = parts[0]
  115. }
  116. return regionPrefix
  117. }
  118. func awsModelCanCrossRegion(awsModelId, awsRegionPrefix string) bool {
  119. regionSet, exists := awsModelCanCrossRegionMap[awsModelId]
  120. return exists && regionSet[awsRegionPrefix]
  121. }
  122. func awsModelCrossRegion(awsModelId, awsRegionPrefix string) string {
  123. modelPrefix, find := awsRegionCrossModelPrefixMap[awsRegionPrefix]
  124. if !find {
  125. return awsModelId
  126. }
  127. return modelPrefix + "." + awsModelId
  128. }
  129. func getAwsModelID(requestModel string) string {
  130. if awsModelIDName, ok := awsModelIDMap[requestModel]; ok {
  131. return awsModelIDName
  132. }
  133. return requestModel
  134. }
  135. func awsHandler(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor) (*types.NewAPIError, *dto.Usage) {
  136. awsResp, err := a.AwsClient.InvokeModel(c.Request.Context(), a.AwsReq.(*bedrockruntime.InvokeModelInput))
  137. if err != nil {
  138. return types.NewOpenAIError(errors.Wrap(err, "InvokeModel"), types.ErrorCodeAwsInvokeError, http.StatusInternalServerError), nil
  139. }
  140. claudeInfo := &claude.ClaudeResponseInfo{
  141. ResponseId: helper.GetResponseID(c),
  142. Created: common.GetTimestamp(),
  143. Model: info.UpstreamModelName,
  144. ResponseText: strings.Builder{},
  145. Usage: &dto.Usage{},
  146. }
  147. // 复制上游 Content-Type 到客户端响应头
  148. if awsResp.ContentType != nil && *awsResp.ContentType != "" {
  149. c.Writer.Header().Set("Content-Type", *awsResp.ContentType)
  150. }
  151. handlerErr := claude.HandleClaudeResponseData(c, info, claudeInfo, nil, awsResp.Body, claude.RequestModeMessage)
  152. if handlerErr != nil {
  153. return handlerErr, nil
  154. }
  155. return nil, claudeInfo.Usage
  156. }
  157. func awsStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor) (*types.NewAPIError, *dto.Usage) {
  158. awsResp, err := a.AwsClient.InvokeModelWithResponseStream(c.Request.Context(), a.AwsReq.(*bedrockruntime.InvokeModelWithResponseStreamInput))
  159. if err != nil {
  160. return types.NewOpenAIError(errors.Wrap(err, "InvokeModelWithResponseStream"), types.ErrorCodeAwsInvokeError, http.StatusInternalServerError), nil
  161. }
  162. stream := awsResp.GetStream()
  163. defer stream.Close()
  164. claudeInfo := &claude.ClaudeResponseInfo{
  165. ResponseId: helper.GetResponseID(c),
  166. Created: common.GetTimestamp(),
  167. Model: info.UpstreamModelName,
  168. ResponseText: strings.Builder{},
  169. Usage: &dto.Usage{},
  170. }
  171. for event := range stream.Events() {
  172. switch v := event.(type) {
  173. case *bedrockruntimeTypes.ResponseStreamMemberChunk:
  174. info.SetFirstResponseTime()
  175. respErr := claude.HandleStreamResponseData(c, info, claudeInfo, string(v.Value.Bytes), claude.RequestModeMessage)
  176. if respErr != nil {
  177. return respErr, nil
  178. }
  179. case *bedrockruntimeTypes.UnknownUnionMember:
  180. fmt.Println("unknown tag:", v.Tag)
  181. return types.NewError(errors.New("unknown response type"), types.ErrorCodeInvalidRequest), nil
  182. default:
  183. fmt.Println("union is nil or unknown type")
  184. return types.NewError(errors.New("nil or unknown response type"), types.ErrorCodeInvalidRequest), nil
  185. }
  186. }
  187. claude.HandleStreamFinalResponse(c, info, claudeInfo, claude.RequestModeMessage)
  188. return nil, claudeInfo.Usage
  189. }
  190. // Nova模型处理函数
  191. func handleNovaRequest(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor) (*types.NewAPIError, *dto.Usage) {
  192. awsResp, err := a.AwsClient.InvokeModel(c.Request.Context(), a.AwsReq.(*bedrockruntime.InvokeModelInput))
  193. if err != nil {
  194. return types.NewError(errors.Wrap(err, "InvokeModel"), types.ErrorCodeChannelAwsClientError), nil
  195. }
  196. // 解析Nova响应
  197. var novaResp struct {
  198. Output struct {
  199. Message struct {
  200. Content []struct {
  201. Text string `json:"text"`
  202. } `json:"content"`
  203. } `json:"message"`
  204. } `json:"output"`
  205. Usage struct {
  206. InputTokens int `json:"inputTokens"`
  207. OutputTokens int `json:"outputTokens"`
  208. TotalTokens int `json:"totalTokens"`
  209. } `json:"usage"`
  210. }
  211. if err := json.Unmarshal(awsResp.Body, &novaResp); err != nil {
  212. return types.NewError(errors.Wrap(err, "unmarshal nova response"), types.ErrorCodeBadResponseBody), nil
  213. }
  214. // 构造OpenAI格式响应
  215. response := dto.OpenAITextResponse{
  216. Id: helper.GetResponseID(c),
  217. Object: "chat.completion",
  218. Created: common.GetTimestamp(),
  219. Model: info.UpstreamModelName,
  220. Choices: []dto.OpenAITextResponseChoice{{
  221. Index: 0,
  222. Message: dto.Message{
  223. Role: "assistant",
  224. Content: novaResp.Output.Message.Content[0].Text,
  225. },
  226. FinishReason: "stop",
  227. }},
  228. Usage: dto.Usage{
  229. PromptTokens: novaResp.Usage.InputTokens,
  230. CompletionTokens: novaResp.Usage.OutputTokens,
  231. TotalTokens: novaResp.Usage.TotalTokens,
  232. },
  233. }
  234. c.JSON(http.StatusOK, response)
  235. return nil, &response.Usage
  236. }