relay_info.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. package common
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "strings"
  7. "time"
  8. "github.com/QuantumNous/new-api/common"
  9. "github.com/QuantumNous/new-api/constant"
  10. "github.com/QuantumNous/new-api/dto"
  11. relayconstant "github.com/QuantumNous/new-api/relay/constant"
  12. "github.com/QuantumNous/new-api/setting/model_setting"
  13. "github.com/QuantumNous/new-api/types"
  14. "github.com/gin-gonic/gin"
  15. "github.com/gorilla/websocket"
  16. )
  17. type ThinkingContentInfo struct {
  18. IsFirstThinkingContent bool
  19. SendLastThinkingContent bool
  20. HasSentThinkingContent bool
  21. }
  22. const (
  23. LastMessageTypeNone = "none"
  24. LastMessageTypeText = "text"
  25. LastMessageTypeTools = "tools"
  26. LastMessageTypeThinking = "thinking"
  27. )
  28. type ClaudeConvertInfo struct {
  29. LastMessagesType string
  30. Index int
  31. Usage *dto.Usage
  32. FinishReason string
  33. Done bool
  34. ToolCallBaseIndex int
  35. ToolCallMaxIndexOffset int
  36. }
  37. type RerankerInfo struct {
  38. Documents []any
  39. ReturnDocuments bool
  40. }
  41. type BuildInToolInfo struct {
  42. ToolName string
  43. CallCount int
  44. SearchContextSize string
  45. }
  46. type ResponsesUsageInfo struct {
  47. BuiltInTools map[string]*BuildInToolInfo
  48. }
  49. type ChannelMeta struct {
  50. ChannelType int
  51. ChannelId int
  52. ChannelIsMultiKey bool
  53. ChannelMultiKeyIndex int
  54. ChannelBaseUrl string
  55. ApiType int
  56. ApiVersion string
  57. ApiKey string
  58. Organization string
  59. ChannelCreateTime int64
  60. ParamOverride map[string]interface{}
  61. HeadersOverride map[string]interface{}
  62. ChannelSetting dto.ChannelSettings
  63. ChannelOtherSettings dto.ChannelOtherSettings
  64. UpstreamModelName string
  65. IsModelMapped bool
  66. SupportStreamOptions bool // 是否支持流式选项
  67. }
  68. type TokenCountMeta struct {
  69. //promptTokens int
  70. estimatePromptTokens int
  71. }
  72. type RelayInfo struct {
  73. TokenId int
  74. TokenKey string
  75. TokenGroup string
  76. UserId int
  77. UsingGroup string // 使用的分组,当auto跨分组重试时,会变动
  78. UserGroup string // 用户所在分组
  79. TokenUnlimited bool
  80. StartTime time.Time
  81. FirstResponseTime time.Time
  82. isFirstResponse bool
  83. //SendLastReasoningResponse bool
  84. IsStream bool
  85. IsGeminiBatchEmbedding bool
  86. IsPlayground bool
  87. UsePrice bool
  88. RelayMode int
  89. OriginModelName string
  90. RequestURLPath string
  91. ShouldIncludeUsage bool
  92. DisablePing bool // 是否禁止向下游发送自定义 Ping
  93. ClientWs *websocket.Conn
  94. TargetWs *websocket.Conn
  95. InputAudioFormat string
  96. OutputAudioFormat string
  97. RealtimeTools []dto.RealTimeTool
  98. IsFirstRequest bool
  99. AudioUsage bool
  100. ReasoningEffort string
  101. UserSetting dto.UserSetting
  102. UserEmail string
  103. UserQuota int
  104. RelayFormat types.RelayFormat
  105. SendResponseCount int
  106. ReceivedResponseCount int
  107. FinalPreConsumedQuota int // 最终预消耗的配额
  108. // Billing 是计费会话,封装了预扣费/结算/退款的统一生命周期。
  109. // 免费模型和按次计费(MJ/Task)时为 nil。
  110. Billing BillingSettler
  111. // BillingSource indicates whether this request is billed from wallet quota or subscription.
  112. // "" or "wallet" => wallet; "subscription" => subscription
  113. BillingSource string
  114. // SubscriptionId is the user_subscriptions.id used when BillingSource == "subscription"
  115. SubscriptionId int
  116. // SubscriptionPreConsumed is the amount pre-consumed on subscription item (quota units or 1)
  117. SubscriptionPreConsumed int64
  118. // SubscriptionPostDelta is the post-consume delta applied to amount_used (quota units; can be negative).
  119. SubscriptionPostDelta int64
  120. // SubscriptionPlanId / SubscriptionPlanTitle are used for logging/UI display.
  121. SubscriptionPlanId int
  122. SubscriptionPlanTitle string
  123. // RequestId is used for idempotent pre-consume/refund
  124. RequestId string
  125. // SubscriptionAmountTotal / SubscriptionAmountUsedAfterPreConsume are used to compute remaining in logs.
  126. SubscriptionAmountTotal int64
  127. SubscriptionAmountUsedAfterPreConsume int64
  128. IsClaudeBetaQuery bool // /v1/messages?beta=true
  129. IsChannelTest bool // channel test request
  130. RetryIndex int
  131. LastError *types.NewAPIError
  132. PriceData types.PriceData
  133. Request dto.Request
  134. // RequestConversionChain records request format conversions in order, e.g.
  135. // ["openai", "openai_responses"] or ["openai", "claude"].
  136. RequestConversionChain []types.RelayFormat
  137. // 最终请求到上游的格式 TODO: 当前仅设置了Claude
  138. FinalRequestRelayFormat types.RelayFormat
  139. ThinkingContentInfo
  140. TokenCountMeta
  141. *ClaudeConvertInfo
  142. *RerankerInfo
  143. *ResponsesUsageInfo
  144. *ChannelMeta
  145. *TaskRelayInfo
  146. }
  147. func (info *RelayInfo) InitChannelMeta(c *gin.Context) {
  148. channelType := common.GetContextKeyInt(c, constant.ContextKeyChannelType)
  149. paramOverride := common.GetContextKeyStringMap(c, constant.ContextKeyChannelParamOverride)
  150. headerOverride := common.GetContextKeyStringMap(c, constant.ContextKeyChannelHeaderOverride)
  151. apiType, _ := common.ChannelType2APIType(channelType)
  152. channelMeta := &ChannelMeta{
  153. ChannelType: channelType,
  154. ChannelId: common.GetContextKeyInt(c, constant.ContextKeyChannelId),
  155. ChannelIsMultiKey: common.GetContextKeyBool(c, constant.ContextKeyChannelIsMultiKey),
  156. ChannelMultiKeyIndex: common.GetContextKeyInt(c, constant.ContextKeyChannelMultiKeyIndex),
  157. ChannelBaseUrl: common.GetContextKeyString(c, constant.ContextKeyChannelBaseUrl),
  158. ApiType: apiType,
  159. ApiVersion: c.GetString("api_version"),
  160. ApiKey: common.GetContextKeyString(c, constant.ContextKeyChannelKey),
  161. Organization: c.GetString("channel_organization"),
  162. ChannelCreateTime: c.GetInt64("channel_create_time"),
  163. ParamOverride: paramOverride,
  164. HeadersOverride: headerOverride,
  165. UpstreamModelName: common.GetContextKeyString(c, constant.ContextKeyOriginalModel),
  166. IsModelMapped: false,
  167. SupportStreamOptions: false,
  168. }
  169. if channelType == constant.ChannelTypeAzure {
  170. channelMeta.ApiVersion = GetAPIVersion(c)
  171. }
  172. if channelType == constant.ChannelTypeVertexAi {
  173. channelMeta.ApiVersion = c.GetString("region")
  174. }
  175. channelSetting, ok := common.GetContextKeyType[dto.ChannelSettings](c, constant.ContextKeyChannelSetting)
  176. if ok {
  177. channelMeta.ChannelSetting = channelSetting
  178. }
  179. channelOtherSettings, ok := common.GetContextKeyType[dto.ChannelOtherSettings](c, constant.ContextKeyChannelOtherSetting)
  180. if ok {
  181. channelMeta.ChannelOtherSettings = channelOtherSettings
  182. }
  183. if streamSupportedChannels[channelMeta.ChannelType] {
  184. channelMeta.SupportStreamOptions = true
  185. }
  186. info.ChannelMeta = channelMeta
  187. // reset some fields based on channel meta
  188. // 重置某些字段,例如模型名称等
  189. if info.Request != nil {
  190. info.Request.SetModelName(info.OriginModelName)
  191. }
  192. }
  193. func (info *RelayInfo) ToString() string {
  194. if info == nil {
  195. return "RelayInfo<nil>"
  196. }
  197. // Basic info
  198. b := &strings.Builder{}
  199. fmt.Fprintf(b, "RelayInfo{ ")
  200. fmt.Fprintf(b, "RelayFormat: %s, ", info.RelayFormat)
  201. fmt.Fprintf(b, "RelayMode: %d, ", info.RelayMode)
  202. fmt.Fprintf(b, "IsStream: %t, ", info.IsStream)
  203. fmt.Fprintf(b, "IsPlayground: %t, ", info.IsPlayground)
  204. fmt.Fprintf(b, "RequestURLPath: %q, ", info.RequestURLPath)
  205. fmt.Fprintf(b, "OriginModelName: %q, ", info.OriginModelName)
  206. fmt.Fprintf(b, "EstimatePromptTokens: %d, ", info.estimatePromptTokens)
  207. fmt.Fprintf(b, "ShouldIncludeUsage: %t, ", info.ShouldIncludeUsage)
  208. fmt.Fprintf(b, "DisablePing: %t, ", info.DisablePing)
  209. fmt.Fprintf(b, "SendResponseCount: %d, ", info.SendResponseCount)
  210. fmt.Fprintf(b, "FinalPreConsumedQuota: %d, ", info.FinalPreConsumedQuota)
  211. // User & token info (mask secrets)
  212. fmt.Fprintf(b, "User{ Id: %d, Email: %q, Group: %q, UsingGroup: %q, Quota: %d }, ",
  213. info.UserId, common.MaskEmail(info.UserEmail), info.UserGroup, info.UsingGroup, info.UserQuota)
  214. fmt.Fprintf(b, "Token{ Id: %d, Unlimited: %t, Key: ***masked*** }, ", info.TokenId, info.TokenUnlimited)
  215. // Time info
  216. latencyMs := info.FirstResponseTime.Sub(info.StartTime).Milliseconds()
  217. fmt.Fprintf(b, "Timing{ Start: %s, FirstResponse: %s, LatencyMs: %d }, ",
  218. info.StartTime.Format(time.RFC3339Nano), info.FirstResponseTime.Format(time.RFC3339Nano), latencyMs)
  219. // Audio / realtime
  220. if info.InputAudioFormat != "" || info.OutputAudioFormat != "" || len(info.RealtimeTools) > 0 || info.AudioUsage {
  221. fmt.Fprintf(b, "Realtime{ AudioUsage: %t, InFmt: %q, OutFmt: %q, Tools: %d }, ",
  222. info.AudioUsage, info.InputAudioFormat, info.OutputAudioFormat, len(info.RealtimeTools))
  223. }
  224. // Reasoning
  225. if info.ReasoningEffort != "" {
  226. fmt.Fprintf(b, "ReasoningEffort: %q, ", info.ReasoningEffort)
  227. }
  228. // Price data (non-sensitive)
  229. if info.PriceData.UsePrice {
  230. fmt.Fprintf(b, "PriceData{ %s }, ", info.PriceData.ToSetting())
  231. }
  232. // Channel metadata (mask ApiKey)
  233. if info.ChannelMeta != nil {
  234. cm := info.ChannelMeta
  235. fmt.Fprintf(b, "ChannelMeta{ Type: %d, Id: %d, IsMultiKey: %t, MultiKeyIndex: %d, BaseURL: %q, ApiType: %d, ApiVersion: %q, Organization: %q, CreateTime: %d, UpstreamModelName: %q, IsModelMapped: %t, SupportStreamOptions: %t, ApiKey: ***masked*** }, ",
  236. cm.ChannelType, cm.ChannelId, cm.ChannelIsMultiKey, cm.ChannelMultiKeyIndex, cm.ChannelBaseUrl, cm.ApiType, cm.ApiVersion, cm.Organization, cm.ChannelCreateTime, cm.UpstreamModelName, cm.IsModelMapped, cm.SupportStreamOptions)
  237. }
  238. // Responses usage info (non-sensitive)
  239. if info.ResponsesUsageInfo != nil && len(info.ResponsesUsageInfo.BuiltInTools) > 0 {
  240. fmt.Fprintf(b, "ResponsesTools{ ")
  241. first := true
  242. for name, tool := range info.ResponsesUsageInfo.BuiltInTools {
  243. if !first {
  244. fmt.Fprintf(b, ", ")
  245. }
  246. first = false
  247. if tool != nil {
  248. fmt.Fprintf(b, "%s: calls=%d", name, tool.CallCount)
  249. } else {
  250. fmt.Fprintf(b, "%s: calls=0", name)
  251. }
  252. }
  253. fmt.Fprintf(b, " }, ")
  254. }
  255. fmt.Fprintf(b, "}")
  256. return b.String()
  257. }
  258. // 定义支持流式选项的通道类型
  259. var streamSupportedChannels = map[int]bool{
  260. constant.ChannelTypeOpenAI: true,
  261. constant.ChannelTypeAnthropic: true,
  262. constant.ChannelTypeAws: true,
  263. constant.ChannelTypeGemini: true,
  264. constant.ChannelCloudflare: true,
  265. constant.ChannelTypeAzure: true,
  266. constant.ChannelTypeVolcEngine: true,
  267. constant.ChannelTypeOllama: true,
  268. constant.ChannelTypeXai: true,
  269. constant.ChannelTypeDeepSeek: true,
  270. constant.ChannelTypeBaiduV2: true,
  271. constant.ChannelTypeZhipu_v4: true,
  272. constant.ChannelTypeAli: true,
  273. constant.ChannelTypeSubmodel: true,
  274. constant.ChannelTypeCodex: true,
  275. constant.ChannelTypeMoonshot: true,
  276. constant.ChannelTypeMiniMax: true,
  277. constant.ChannelTypeSiliconFlow: true,
  278. }
  279. func GenRelayInfoWs(c *gin.Context, ws *websocket.Conn) *RelayInfo {
  280. info := genBaseRelayInfo(c, nil)
  281. info.RelayFormat = types.RelayFormatOpenAIRealtime
  282. info.ClientWs = ws
  283. info.InputAudioFormat = "pcm16"
  284. info.OutputAudioFormat = "pcm16"
  285. info.IsFirstRequest = true
  286. return info
  287. }
  288. func GenRelayInfoClaude(c *gin.Context, request dto.Request) *RelayInfo {
  289. info := genBaseRelayInfo(c, request)
  290. info.RelayFormat = types.RelayFormatClaude
  291. info.ShouldIncludeUsage = false
  292. info.ClaudeConvertInfo = &ClaudeConvertInfo{
  293. LastMessagesType: LastMessageTypeNone,
  294. }
  295. info.IsClaudeBetaQuery = c.Query("beta") == "true" || isClaudeBetaForced(c)
  296. return info
  297. }
  298. func isClaudeBetaForced(c *gin.Context) bool {
  299. channelOtherSettings, ok := common.GetContextKeyType[dto.ChannelOtherSettings](c, constant.ContextKeyChannelOtherSetting)
  300. return ok && channelOtherSettings.ClaudeBetaQuery
  301. }
  302. func GenRelayInfoRerank(c *gin.Context, request *dto.RerankRequest) *RelayInfo {
  303. info := genBaseRelayInfo(c, request)
  304. info.RelayMode = relayconstant.RelayModeRerank
  305. info.RelayFormat = types.RelayFormatRerank
  306. info.RerankerInfo = &RerankerInfo{
  307. Documents: request.Documents,
  308. ReturnDocuments: request.GetReturnDocuments(),
  309. }
  310. return info
  311. }
  312. func GenRelayInfoOpenAIAudio(c *gin.Context, request dto.Request) *RelayInfo {
  313. info := genBaseRelayInfo(c, request)
  314. info.RelayFormat = types.RelayFormatOpenAIAudio
  315. return info
  316. }
  317. func GenRelayInfoEmbedding(c *gin.Context, request dto.Request) *RelayInfo {
  318. info := genBaseRelayInfo(c, request)
  319. info.RelayFormat = types.RelayFormatEmbedding
  320. return info
  321. }
  322. func GenRelayInfoResponses(c *gin.Context, request *dto.OpenAIResponsesRequest) *RelayInfo {
  323. info := genBaseRelayInfo(c, request)
  324. info.RelayMode = relayconstant.RelayModeResponses
  325. info.RelayFormat = types.RelayFormatOpenAIResponses
  326. info.ResponsesUsageInfo = &ResponsesUsageInfo{
  327. BuiltInTools: make(map[string]*BuildInToolInfo),
  328. }
  329. if len(request.Tools) > 0 {
  330. for _, tool := range request.GetToolsMap() {
  331. toolType := common.Interface2String(tool["type"])
  332. info.ResponsesUsageInfo.BuiltInTools[toolType] = &BuildInToolInfo{
  333. ToolName: toolType,
  334. CallCount: 0,
  335. }
  336. switch toolType {
  337. case dto.BuildInToolWebSearchPreview:
  338. searchContextSize := common.Interface2String(tool["search_context_size"])
  339. if searchContextSize == "" {
  340. searchContextSize = "medium"
  341. }
  342. info.ResponsesUsageInfo.BuiltInTools[toolType].SearchContextSize = searchContextSize
  343. }
  344. }
  345. }
  346. return info
  347. }
  348. func GenRelayInfoGemini(c *gin.Context, request dto.Request) *RelayInfo {
  349. info := genBaseRelayInfo(c, request)
  350. info.RelayFormat = types.RelayFormatGemini
  351. info.ShouldIncludeUsage = false
  352. return info
  353. }
  354. func GenRelayInfoImage(c *gin.Context, request dto.Request) *RelayInfo {
  355. info := genBaseRelayInfo(c, request)
  356. info.RelayFormat = types.RelayFormatOpenAIImage
  357. return info
  358. }
  359. func GenRelayInfoOpenAI(c *gin.Context, request dto.Request) *RelayInfo {
  360. info := genBaseRelayInfo(c, request)
  361. info.RelayFormat = types.RelayFormatOpenAI
  362. return info
  363. }
  364. func genBaseRelayInfo(c *gin.Context, request dto.Request) *RelayInfo {
  365. //channelType := common.GetContextKeyInt(c, constant.ContextKeyChannelType)
  366. //channelId := common.GetContextKeyInt(c, constant.ContextKeyChannelId)
  367. //paramOverride := common.GetContextKeyStringMap(c, constant.ContextKeyChannelParamOverride)
  368. tokenGroup := common.GetContextKeyString(c, constant.ContextKeyTokenGroup)
  369. // 当令牌分组为空时,表示使用用户分组
  370. if tokenGroup == "" {
  371. tokenGroup = common.GetContextKeyString(c, constant.ContextKeyUserGroup)
  372. }
  373. startTime := common.GetContextKeyTime(c, constant.ContextKeyRequestStartTime)
  374. if startTime.IsZero() {
  375. startTime = time.Now()
  376. }
  377. isStream := false
  378. if request != nil {
  379. isStream = request.IsStream(c)
  380. }
  381. // firstResponseTime = time.Now() - 1 second
  382. reqId := common.GetContextKeyString(c, common.RequestIdKey)
  383. if reqId == "" {
  384. reqId = common.GetTimeString() + common.GetRandomString(8)
  385. }
  386. info := &RelayInfo{
  387. Request: request,
  388. RequestId: reqId,
  389. UserId: common.GetContextKeyInt(c, constant.ContextKeyUserId),
  390. UsingGroup: common.GetContextKeyString(c, constant.ContextKeyUsingGroup),
  391. UserGroup: common.GetContextKeyString(c, constant.ContextKeyUserGroup),
  392. UserQuota: common.GetContextKeyInt(c, constant.ContextKeyUserQuota),
  393. UserEmail: common.GetContextKeyString(c, constant.ContextKeyUserEmail),
  394. OriginModelName: common.GetContextKeyString(c, constant.ContextKeyOriginalModel),
  395. TokenId: common.GetContextKeyInt(c, constant.ContextKeyTokenId),
  396. TokenKey: common.GetContextKeyString(c, constant.ContextKeyTokenKey),
  397. TokenUnlimited: common.GetContextKeyBool(c, constant.ContextKeyTokenUnlimited),
  398. TokenGroup: tokenGroup,
  399. isFirstResponse: true,
  400. RelayMode: relayconstant.Path2RelayMode(c.Request.URL.Path),
  401. RequestURLPath: c.Request.URL.String(),
  402. IsStream: isStream,
  403. StartTime: startTime,
  404. FirstResponseTime: startTime.Add(-time.Second),
  405. ThinkingContentInfo: ThinkingContentInfo{
  406. IsFirstThinkingContent: true,
  407. SendLastThinkingContent: false,
  408. },
  409. TokenCountMeta: TokenCountMeta{
  410. //promptTokens: common.GetContextKeyInt(c, constant.ContextKeyPromptTokens),
  411. estimatePromptTokens: common.GetContextKeyInt(c, constant.ContextKeyEstimatedTokens),
  412. },
  413. }
  414. if info.RelayMode == relayconstant.RelayModeUnknown {
  415. info.RelayMode = c.GetInt("relay_mode")
  416. }
  417. if strings.HasPrefix(c.Request.URL.Path, "/pg") {
  418. info.IsPlayground = true
  419. info.RequestURLPath = strings.TrimPrefix(info.RequestURLPath, "/pg")
  420. info.RequestURLPath = "/v1" + info.RequestURLPath
  421. }
  422. userSetting, ok := common.GetContextKeyType[dto.UserSetting](c, constant.ContextKeyUserSetting)
  423. if ok {
  424. info.UserSetting = userSetting
  425. }
  426. return info
  427. }
  428. func GenRelayInfo(c *gin.Context, relayFormat types.RelayFormat, request dto.Request, ws *websocket.Conn) (*RelayInfo, error) {
  429. var info *RelayInfo
  430. var err error
  431. switch relayFormat {
  432. case types.RelayFormatOpenAI:
  433. info = GenRelayInfoOpenAI(c, request)
  434. case types.RelayFormatOpenAIAudio:
  435. info = GenRelayInfoOpenAIAudio(c, request)
  436. case types.RelayFormatOpenAIImage:
  437. info = GenRelayInfoImage(c, request)
  438. case types.RelayFormatOpenAIRealtime:
  439. info = GenRelayInfoWs(c, ws)
  440. case types.RelayFormatClaude:
  441. info = GenRelayInfoClaude(c, request)
  442. case types.RelayFormatRerank:
  443. if request, ok := request.(*dto.RerankRequest); ok {
  444. info = GenRelayInfoRerank(c, request)
  445. break
  446. }
  447. err = errors.New("request is not a RerankRequest")
  448. case types.RelayFormatGemini:
  449. info = GenRelayInfoGemini(c, request)
  450. case types.RelayFormatEmbedding:
  451. info = GenRelayInfoEmbedding(c, request)
  452. case types.RelayFormatOpenAIResponses:
  453. if request, ok := request.(*dto.OpenAIResponsesRequest); ok {
  454. info = GenRelayInfoResponses(c, request)
  455. break
  456. }
  457. err = errors.New("request is not a OpenAIResponsesRequest")
  458. case types.RelayFormatOpenAIResponsesCompaction:
  459. if request, ok := request.(*dto.OpenAIResponsesCompactionRequest); ok {
  460. return GenRelayInfoResponsesCompaction(c, request), nil
  461. }
  462. return nil, errors.New("request is not a OpenAIResponsesCompactionRequest")
  463. case types.RelayFormatTask:
  464. info = genBaseRelayInfo(c, nil)
  465. case types.RelayFormatMjProxy:
  466. info = genBaseRelayInfo(c, nil)
  467. default:
  468. err = errors.New("invalid relay format")
  469. }
  470. if err != nil {
  471. return nil, err
  472. }
  473. if info == nil {
  474. return nil, errors.New("failed to build relay info")
  475. }
  476. info.InitRequestConversionChain()
  477. return info, nil
  478. }
  479. func (info *RelayInfo) InitRequestConversionChain() {
  480. if info == nil {
  481. return
  482. }
  483. if len(info.RequestConversionChain) > 0 {
  484. return
  485. }
  486. if info.RelayFormat == "" {
  487. return
  488. }
  489. info.RequestConversionChain = []types.RelayFormat{info.RelayFormat}
  490. }
  491. func (info *RelayInfo) AppendRequestConversion(format types.RelayFormat) {
  492. if info == nil {
  493. return
  494. }
  495. if format == "" {
  496. return
  497. }
  498. if len(info.RequestConversionChain) == 0 {
  499. info.RequestConversionChain = []types.RelayFormat{format}
  500. return
  501. }
  502. last := info.RequestConversionChain[len(info.RequestConversionChain)-1]
  503. if last == format {
  504. return
  505. }
  506. info.RequestConversionChain = append(info.RequestConversionChain, format)
  507. }
  508. func GenRelayInfoResponsesCompaction(c *gin.Context, request *dto.OpenAIResponsesCompactionRequest) *RelayInfo {
  509. info := genBaseRelayInfo(c, request)
  510. if info.RelayMode == relayconstant.RelayModeUnknown {
  511. info.RelayMode = relayconstant.RelayModeResponsesCompact
  512. }
  513. info.RelayFormat = types.RelayFormatOpenAIResponsesCompaction
  514. return info
  515. }
  516. //func (info *RelayInfo) SetPromptTokens(promptTokens int) {
  517. // info.promptTokens = promptTokens
  518. //}
  519. func (info *RelayInfo) SetEstimatePromptTokens(promptTokens int) {
  520. info.estimatePromptTokens = promptTokens
  521. }
  522. func (info *RelayInfo) GetEstimatePromptTokens() int {
  523. return info.estimatePromptTokens
  524. }
  525. func (info *RelayInfo) SetFirstResponseTime() {
  526. if info.isFirstResponse {
  527. info.FirstResponseTime = time.Now()
  528. info.isFirstResponse = false
  529. }
  530. }
  531. func (info *RelayInfo) HasSendResponse() bool {
  532. return info.FirstResponseTime.After(info.StartTime)
  533. }
  534. type TaskRelayInfo struct {
  535. Action string
  536. OriginTaskID string
  537. ConsumeQuota bool
  538. }
  539. type TaskSubmitReq struct {
  540. Prompt string `json:"prompt"`
  541. Model string `json:"model,omitempty"`
  542. Mode string `json:"mode,omitempty"`
  543. Image string `json:"image,omitempty"`
  544. Images []string `json:"images,omitempty"`
  545. Size string `json:"size,omitempty"`
  546. Duration int `json:"duration,omitempty"`
  547. Seconds string `json:"seconds,omitempty"`
  548. InputReference string `json:"input_reference,omitempty"`
  549. Metadata map[string]interface{} `json:"metadata,omitempty"`
  550. }
  551. func (t *TaskSubmitReq) GetPrompt() string {
  552. return t.Prompt
  553. }
  554. func (t *TaskSubmitReq) HasImage() bool {
  555. return len(t.Images) > 0
  556. }
  557. func (t *TaskSubmitReq) UnmarshalJSON(data []byte) error {
  558. type Alias TaskSubmitReq
  559. aux := &struct {
  560. Metadata json.RawMessage `json:"metadata,omitempty"`
  561. *Alias
  562. }{
  563. Alias: (*Alias)(t),
  564. }
  565. if err := common.Unmarshal(data, &aux); err != nil {
  566. return err
  567. }
  568. if len(aux.Metadata) > 0 {
  569. var metadataStr string
  570. if err := common.Unmarshal(aux.Metadata, &metadataStr); err == nil && metadataStr != "" {
  571. var metadataObj map[string]interface{}
  572. if err := common.Unmarshal([]byte(metadataStr), &metadataObj); err == nil {
  573. t.Metadata = metadataObj
  574. return nil
  575. }
  576. }
  577. var metadataObj map[string]interface{}
  578. if err := common.Unmarshal(aux.Metadata, &metadataObj); err == nil {
  579. t.Metadata = metadataObj
  580. }
  581. }
  582. return nil
  583. }
  584. func (t *TaskSubmitReq) UnmarshalMetadata(v any) error {
  585. metadata := t.Metadata
  586. if metadata != nil {
  587. metadataBytes, err := json.Marshal(metadata)
  588. if err != nil {
  589. return fmt.Errorf("marshal metadata failed: %w", err)
  590. }
  591. err = json.Unmarshal(metadataBytes, v)
  592. if err != nil {
  593. return fmt.Errorf("unmarshal metadata to target failed: %w", err)
  594. }
  595. }
  596. return nil
  597. }
  598. type TaskInfo struct {
  599. Code int `json:"code"`
  600. TaskID string `json:"task_id"`
  601. Status string `json:"status"`
  602. Reason string `json:"reason,omitempty"`
  603. Url string `json:"url,omitempty"`
  604. RemoteUrl string `json:"remote_url,omitempty"`
  605. Progress string `json:"progress,omitempty"`
  606. CompletionTokens int `json:"completion_tokens,omitempty"` // 用于按倍率计费
  607. TotalTokens int `json:"total_tokens,omitempty"` // 用于按倍率计费
  608. }
  609. func FailTaskInfo(reason string) *TaskInfo {
  610. return &TaskInfo{
  611. Status: "FAILURE",
  612. Reason: reason,
  613. }
  614. }
  615. // RemoveDisabledFields 从请求 JSON 数据中移除渠道设置中禁用的字段
  616. // service_tier: 服务层级字段,可能导致额外计费(OpenAI、Claude、Responses API 支持)
  617. // store: 数据存储授权字段,涉及用户隐私(仅 OpenAI、Responses API 支持,默认允许透传,禁用后可能导致 Codex 无法使用)
  618. // safety_identifier: 安全标识符,用于向 OpenAI 报告违规用户(仅 OpenAI 支持,涉及用户隐私)
  619. func RemoveDisabledFields(jsonData []byte, channelOtherSettings dto.ChannelOtherSettings) ([]byte, error) {
  620. var data map[string]interface{}
  621. if err := common.Unmarshal(jsonData, &data); err != nil {
  622. common.SysError("RemoveDisabledFields Unmarshal error :" + err.Error())
  623. return jsonData, nil
  624. }
  625. // 默认移除 service_tier,除非明确允许(避免额外计费风险)
  626. if !channelOtherSettings.AllowServiceTier {
  627. if _, exists := data["service_tier"]; exists {
  628. delete(data, "service_tier")
  629. }
  630. }
  631. // 默认允许 store 透传,除非明确禁用(禁用可能影响 Codex 使用)
  632. if channelOtherSettings.DisableStore {
  633. if _, exists := data["store"]; exists {
  634. delete(data, "store")
  635. }
  636. }
  637. // 默认移除 safety_identifier,除非明确允许(保护用户隐私,避免向 OpenAI 报告用户信息)
  638. if !channelOtherSettings.AllowSafetyIdentifier {
  639. if _, exists := data["safety_identifier"]; exists {
  640. delete(data, "safety_identifier")
  641. }
  642. }
  643. jsonDataAfter, err := common.Marshal(data)
  644. if err != nil {
  645. common.SysError("RemoveDisabledFields Marshal error :" + err.Error())
  646. return jsonData, nil
  647. }
  648. return jsonDataAfter, nil
  649. }
  650. // RemoveGeminiDisabledFields removes disabled fields from Gemini request JSON data
  651. // Currently supports removing functionResponse.id field which Vertex AI does not support
  652. func RemoveGeminiDisabledFields(jsonData []byte) ([]byte, error) {
  653. if !model_setting.GetGeminiSettings().RemoveFunctionResponseIdEnabled {
  654. return jsonData, nil
  655. }
  656. var data map[string]interface{}
  657. if err := common.Unmarshal(jsonData, &data); err != nil {
  658. common.SysError("RemoveGeminiDisabledFields Unmarshal error: " + err.Error())
  659. return jsonData, nil
  660. }
  661. // Process contents array
  662. // Handle both camelCase (functionResponse) and snake_case (function_response)
  663. if contents, ok := data["contents"].([]interface{}); ok {
  664. for _, content := range contents {
  665. if contentMap, ok := content.(map[string]interface{}); ok {
  666. if parts, ok := contentMap["parts"].([]interface{}); ok {
  667. for _, part := range parts {
  668. if partMap, ok := part.(map[string]interface{}); ok {
  669. // Check functionResponse (camelCase)
  670. if funcResp, ok := partMap["functionResponse"].(map[string]interface{}); ok {
  671. delete(funcResp, "id")
  672. }
  673. // Check function_response (snake_case)
  674. if funcResp, ok := partMap["function_response"].(map[string]interface{}); ok {
  675. delete(funcResp, "id")
  676. }
  677. }
  678. }
  679. }
  680. }
  681. }
  682. }
  683. jsonDataAfter, err := common.Marshal(data)
  684. if err != nil {
  685. common.SysError("RemoveGeminiDisabledFields Marshal error: " + err.Error())
  686. return jsonData, nil
  687. }
  688. return jsonDataAfter, nil
  689. }