relay_info.go 25 KB

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