relay_info.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  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/types"
  13. "github.com/gin-gonic/gin"
  14. "github.com/gorilla/websocket"
  15. )
  16. type ThinkingContentInfo struct {
  17. IsFirstThinkingContent bool
  18. SendLastThinkingContent bool
  19. HasSentThinkingContent bool
  20. }
  21. const (
  22. LastMessageTypeNone = "none"
  23. LastMessageTypeText = "text"
  24. LastMessageTypeTools = "tools"
  25. LastMessageTypeThinking = "thinking"
  26. )
  27. type ClaudeConvertInfo struct {
  28. LastMessagesType string
  29. Index int
  30. Usage *dto.Usage
  31. FinishReason string
  32. Done bool
  33. }
  34. type RerankerInfo struct {
  35. Documents []any
  36. ReturnDocuments bool
  37. }
  38. type BuildInToolInfo struct {
  39. ToolName string
  40. CallCount int
  41. SearchContextSize string
  42. }
  43. type ResponsesUsageInfo struct {
  44. BuiltInTools map[string]*BuildInToolInfo
  45. }
  46. type ChannelMeta struct {
  47. ChannelType int
  48. ChannelId int
  49. ChannelIsMultiKey bool
  50. ChannelMultiKeyIndex int
  51. ChannelBaseUrl string
  52. ApiType int
  53. ApiVersion string
  54. ApiKey string
  55. Organization string
  56. ChannelCreateTime int64
  57. ParamOverride map[string]interface{}
  58. HeadersOverride map[string]interface{}
  59. ChannelSetting dto.ChannelSettings
  60. ChannelOtherSettings dto.ChannelOtherSettings
  61. UpstreamModelName string
  62. IsModelMapped bool
  63. SupportStreamOptions bool // 是否支持流式选项
  64. }
  65. type TokenCountMeta struct {
  66. //promptTokens int
  67. estimatePromptTokens int
  68. }
  69. type RelayInfo struct {
  70. TokenId int
  71. TokenKey string
  72. TokenGroup string
  73. UserId int
  74. UsingGroup string // 使用的分组,当auto跨分组重试时,会变动
  75. UserGroup string // 用户所在分组
  76. TokenUnlimited bool
  77. StartTime time.Time
  78. FirstResponseTime time.Time
  79. isFirstResponse bool
  80. //SendLastReasoningResponse bool
  81. IsStream bool
  82. IsGeminiBatchEmbedding bool
  83. IsPlayground bool
  84. UsePrice bool
  85. RelayMode int
  86. OriginModelName string
  87. RequestURLPath string
  88. ShouldIncludeUsage bool
  89. DisablePing bool // 是否禁止向下游发送自定义 Ping
  90. ClientWs *websocket.Conn
  91. TargetWs *websocket.Conn
  92. InputAudioFormat string
  93. OutputAudioFormat string
  94. RealtimeTools []dto.RealTimeTool
  95. IsFirstRequest bool
  96. AudioUsage bool
  97. ReasoningEffort string
  98. UserSetting dto.UserSetting
  99. UserEmail string
  100. UserQuota int
  101. RelayFormat types.RelayFormat
  102. SendResponseCount int
  103. FinalPreConsumedQuota int // 最终预消耗的配额
  104. IsClaudeBetaQuery bool // /v1/messages?beta=true
  105. PriceData types.PriceData
  106. Request dto.Request
  107. ThinkingContentInfo
  108. TokenCountMeta
  109. *ClaudeConvertInfo
  110. *RerankerInfo
  111. *ResponsesUsageInfo
  112. *ChannelMeta
  113. *TaskRelayInfo
  114. }
  115. func (info *RelayInfo) InitChannelMeta(c *gin.Context) {
  116. channelType := common.GetContextKeyInt(c, constant.ContextKeyChannelType)
  117. paramOverride := common.GetContextKeyStringMap(c, constant.ContextKeyChannelParamOverride)
  118. headerOverride := common.GetContextKeyStringMap(c, constant.ContextKeyChannelHeaderOverride)
  119. apiType, _ := common.ChannelType2APIType(channelType)
  120. channelMeta := &ChannelMeta{
  121. ChannelType: channelType,
  122. ChannelId: common.GetContextKeyInt(c, constant.ContextKeyChannelId),
  123. ChannelIsMultiKey: common.GetContextKeyBool(c, constant.ContextKeyChannelIsMultiKey),
  124. ChannelMultiKeyIndex: common.GetContextKeyInt(c, constant.ContextKeyChannelMultiKeyIndex),
  125. ChannelBaseUrl: common.GetContextKeyString(c, constant.ContextKeyChannelBaseUrl),
  126. ApiType: apiType,
  127. ApiVersion: c.GetString("api_version"),
  128. ApiKey: common.GetContextKeyString(c, constant.ContextKeyChannelKey),
  129. Organization: c.GetString("channel_organization"),
  130. ChannelCreateTime: c.GetInt64("channel_create_time"),
  131. ParamOverride: paramOverride,
  132. HeadersOverride: headerOverride,
  133. UpstreamModelName: common.GetContextKeyString(c, constant.ContextKeyOriginalModel),
  134. IsModelMapped: false,
  135. SupportStreamOptions: false,
  136. }
  137. if channelType == constant.ChannelTypeAzure {
  138. channelMeta.ApiVersion = GetAPIVersion(c)
  139. }
  140. if channelType == constant.ChannelTypeVertexAi {
  141. channelMeta.ApiVersion = c.GetString("region")
  142. }
  143. channelSetting, ok := common.GetContextKeyType[dto.ChannelSettings](c, constant.ContextKeyChannelSetting)
  144. if ok {
  145. channelMeta.ChannelSetting = channelSetting
  146. }
  147. channelOtherSettings, ok := common.GetContextKeyType[dto.ChannelOtherSettings](c, constant.ContextKeyChannelOtherSetting)
  148. if ok {
  149. channelMeta.ChannelOtherSettings = channelOtherSettings
  150. }
  151. if streamSupportedChannels[channelMeta.ChannelType] {
  152. channelMeta.SupportStreamOptions = true
  153. }
  154. info.ChannelMeta = channelMeta
  155. // reset some fields based on channel meta
  156. // 重置某些字段,例如模型名称等
  157. if info.Request != nil {
  158. info.Request.SetModelName(info.OriginModelName)
  159. }
  160. }
  161. func (info *RelayInfo) ToString() string {
  162. if info == nil {
  163. return "RelayInfo<nil>"
  164. }
  165. // Basic info
  166. b := &strings.Builder{}
  167. fmt.Fprintf(b, "RelayInfo{ ")
  168. fmt.Fprintf(b, "RelayFormat: %s, ", info.RelayFormat)
  169. fmt.Fprintf(b, "RelayMode: %d, ", info.RelayMode)
  170. fmt.Fprintf(b, "IsStream: %t, ", info.IsStream)
  171. fmt.Fprintf(b, "IsPlayground: %t, ", info.IsPlayground)
  172. fmt.Fprintf(b, "RequestURLPath: %q, ", info.RequestURLPath)
  173. fmt.Fprintf(b, "OriginModelName: %q, ", info.OriginModelName)
  174. fmt.Fprintf(b, "EstimatePromptTokens: %d, ", info.estimatePromptTokens)
  175. fmt.Fprintf(b, "ShouldIncludeUsage: %t, ", info.ShouldIncludeUsage)
  176. fmt.Fprintf(b, "DisablePing: %t, ", info.DisablePing)
  177. fmt.Fprintf(b, "SendResponseCount: %d, ", info.SendResponseCount)
  178. fmt.Fprintf(b, "FinalPreConsumedQuota: %d, ", info.FinalPreConsumedQuota)
  179. // User & token info (mask secrets)
  180. fmt.Fprintf(b, "User{ Id: %d, Email: %q, Group: %q, UsingGroup: %q, Quota: %d }, ",
  181. info.UserId, common.MaskEmail(info.UserEmail), info.UserGroup, info.UsingGroup, info.UserQuota)
  182. fmt.Fprintf(b, "Token{ Id: %d, Unlimited: %t, Key: ***masked*** }, ", info.TokenId, info.TokenUnlimited)
  183. // Time info
  184. latencyMs := info.FirstResponseTime.Sub(info.StartTime).Milliseconds()
  185. fmt.Fprintf(b, "Timing{ Start: %s, FirstResponse: %s, LatencyMs: %d }, ",
  186. info.StartTime.Format(time.RFC3339Nano), info.FirstResponseTime.Format(time.RFC3339Nano), latencyMs)
  187. // Audio / realtime
  188. if info.InputAudioFormat != "" || info.OutputAudioFormat != "" || len(info.RealtimeTools) > 0 || info.AudioUsage {
  189. fmt.Fprintf(b, "Realtime{ AudioUsage: %t, InFmt: %q, OutFmt: %q, Tools: %d }, ",
  190. info.AudioUsage, info.InputAudioFormat, info.OutputAudioFormat, len(info.RealtimeTools))
  191. }
  192. // Reasoning
  193. if info.ReasoningEffort != "" {
  194. fmt.Fprintf(b, "ReasoningEffort: %q, ", info.ReasoningEffort)
  195. }
  196. // Price data (non-sensitive)
  197. if info.PriceData.UsePrice {
  198. fmt.Fprintf(b, "PriceData{ %s }, ", info.PriceData.ToSetting())
  199. }
  200. // Channel metadata (mask ApiKey)
  201. if info.ChannelMeta != nil {
  202. cm := info.ChannelMeta
  203. 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*** }, ",
  204. cm.ChannelType, cm.ChannelId, cm.ChannelIsMultiKey, cm.ChannelMultiKeyIndex, cm.ChannelBaseUrl, cm.ApiType, cm.ApiVersion, cm.Organization, cm.ChannelCreateTime, cm.UpstreamModelName, cm.IsModelMapped, cm.SupportStreamOptions)
  205. }
  206. // Responses usage info (non-sensitive)
  207. if info.ResponsesUsageInfo != nil && len(info.ResponsesUsageInfo.BuiltInTools) > 0 {
  208. fmt.Fprintf(b, "ResponsesTools{ ")
  209. first := true
  210. for name, tool := range info.ResponsesUsageInfo.BuiltInTools {
  211. if !first {
  212. fmt.Fprintf(b, ", ")
  213. }
  214. first = false
  215. if tool != nil {
  216. fmt.Fprintf(b, "%s: calls=%d", name, tool.CallCount)
  217. } else {
  218. fmt.Fprintf(b, "%s: calls=0", name)
  219. }
  220. }
  221. fmt.Fprintf(b, " }, ")
  222. }
  223. fmt.Fprintf(b, "}")
  224. return b.String()
  225. }
  226. // 定义支持流式选项的通道类型
  227. var streamSupportedChannels = map[int]bool{
  228. constant.ChannelTypeOpenAI: true,
  229. constant.ChannelTypeAnthropic: true,
  230. constant.ChannelTypeAws: true,
  231. constant.ChannelTypeGemini: true,
  232. constant.ChannelCloudflare: true,
  233. constant.ChannelTypeAzure: true,
  234. constant.ChannelTypeVolcEngine: true,
  235. constant.ChannelTypeOllama: true,
  236. constant.ChannelTypeXai: true,
  237. constant.ChannelTypeDeepSeek: true,
  238. constant.ChannelTypeBaiduV2: true,
  239. constant.ChannelTypeZhipu_v4: true,
  240. constant.ChannelTypeAli: true,
  241. constant.ChannelTypeSubmodel: true,
  242. }
  243. func GenRelayInfoWs(c *gin.Context, ws *websocket.Conn) *RelayInfo {
  244. info := genBaseRelayInfo(c, nil)
  245. info.RelayFormat = types.RelayFormatOpenAIRealtime
  246. info.ClientWs = ws
  247. info.InputAudioFormat = "pcm16"
  248. info.OutputAudioFormat = "pcm16"
  249. info.IsFirstRequest = true
  250. return info
  251. }
  252. func GenRelayInfoClaude(c *gin.Context, request dto.Request) *RelayInfo {
  253. info := genBaseRelayInfo(c, request)
  254. info.RelayFormat = types.RelayFormatClaude
  255. info.ShouldIncludeUsage = false
  256. info.ClaudeConvertInfo = &ClaudeConvertInfo{
  257. LastMessagesType: LastMessageTypeNone,
  258. }
  259. if c.Query("beta") == "true" {
  260. info.IsClaudeBetaQuery = true
  261. }
  262. return info
  263. }
  264. func GenRelayInfoRerank(c *gin.Context, request *dto.RerankRequest) *RelayInfo {
  265. info := genBaseRelayInfo(c, request)
  266. info.RelayMode = relayconstant.RelayModeRerank
  267. info.RelayFormat = types.RelayFormatRerank
  268. info.RerankerInfo = &RerankerInfo{
  269. Documents: request.Documents,
  270. ReturnDocuments: request.GetReturnDocuments(),
  271. }
  272. return info
  273. }
  274. func GenRelayInfoOpenAIAudio(c *gin.Context, request dto.Request) *RelayInfo {
  275. info := genBaseRelayInfo(c, request)
  276. info.RelayFormat = types.RelayFormatOpenAIAudio
  277. return info
  278. }
  279. func GenRelayInfoEmbedding(c *gin.Context, request dto.Request) *RelayInfo {
  280. info := genBaseRelayInfo(c, request)
  281. info.RelayFormat = types.RelayFormatEmbedding
  282. return info
  283. }
  284. func GenRelayInfoResponses(c *gin.Context, request *dto.OpenAIResponsesRequest) *RelayInfo {
  285. info := genBaseRelayInfo(c, request)
  286. info.RelayMode = relayconstant.RelayModeResponses
  287. info.RelayFormat = types.RelayFormatOpenAIResponses
  288. info.ResponsesUsageInfo = &ResponsesUsageInfo{
  289. BuiltInTools: make(map[string]*BuildInToolInfo),
  290. }
  291. if len(request.Tools) > 0 {
  292. for _, tool := range request.GetToolsMap() {
  293. toolType := common.Interface2String(tool["type"])
  294. info.ResponsesUsageInfo.BuiltInTools[toolType] = &BuildInToolInfo{
  295. ToolName: toolType,
  296. CallCount: 0,
  297. }
  298. switch toolType {
  299. case dto.BuildInToolWebSearchPreview:
  300. searchContextSize := common.Interface2String(tool["search_context_size"])
  301. if searchContextSize == "" {
  302. searchContextSize = "medium"
  303. }
  304. info.ResponsesUsageInfo.BuiltInTools[toolType].SearchContextSize = searchContextSize
  305. }
  306. }
  307. }
  308. return info
  309. }
  310. func GenRelayInfoGemini(c *gin.Context, request dto.Request) *RelayInfo {
  311. info := genBaseRelayInfo(c, request)
  312. info.RelayFormat = types.RelayFormatGemini
  313. info.ShouldIncludeUsage = false
  314. return info
  315. }
  316. func GenRelayInfoImage(c *gin.Context, request dto.Request) *RelayInfo {
  317. info := genBaseRelayInfo(c, request)
  318. info.RelayFormat = types.RelayFormatOpenAIImage
  319. return info
  320. }
  321. func GenRelayInfoOpenAI(c *gin.Context, request dto.Request) *RelayInfo {
  322. info := genBaseRelayInfo(c, request)
  323. info.RelayFormat = types.RelayFormatOpenAI
  324. return info
  325. }
  326. func genBaseRelayInfo(c *gin.Context, request dto.Request) *RelayInfo {
  327. //channelType := common.GetContextKeyInt(c, constant.ContextKeyChannelType)
  328. //channelId := common.GetContextKeyInt(c, constant.ContextKeyChannelId)
  329. //paramOverride := common.GetContextKeyStringMap(c, constant.ContextKeyChannelParamOverride)
  330. tokenGroup := common.GetContextKeyString(c, constant.ContextKeyTokenGroup)
  331. // 当令牌分组为空时,表示使用用户分组
  332. if tokenGroup == "" {
  333. tokenGroup = common.GetContextKeyString(c, constant.ContextKeyUserGroup)
  334. }
  335. startTime := common.GetContextKeyTime(c, constant.ContextKeyRequestStartTime)
  336. if startTime.IsZero() {
  337. startTime = time.Now()
  338. }
  339. isStream := false
  340. if request != nil {
  341. isStream = request.IsStream(c)
  342. }
  343. // firstResponseTime = time.Now() - 1 second
  344. info := &RelayInfo{
  345. Request: request,
  346. UserId: common.GetContextKeyInt(c, constant.ContextKeyUserId),
  347. UsingGroup: common.GetContextKeyString(c, constant.ContextKeyUsingGroup),
  348. UserGroup: common.GetContextKeyString(c, constant.ContextKeyUserGroup),
  349. UserQuota: common.GetContextKeyInt(c, constant.ContextKeyUserQuota),
  350. UserEmail: common.GetContextKeyString(c, constant.ContextKeyUserEmail),
  351. OriginModelName: common.GetContextKeyString(c, constant.ContextKeyOriginalModel),
  352. TokenId: common.GetContextKeyInt(c, constant.ContextKeyTokenId),
  353. TokenKey: common.GetContextKeyString(c, constant.ContextKeyTokenKey),
  354. TokenUnlimited: common.GetContextKeyBool(c, constant.ContextKeyTokenUnlimited),
  355. TokenGroup: tokenGroup,
  356. isFirstResponse: true,
  357. RelayMode: relayconstant.Path2RelayMode(c.Request.URL.Path),
  358. RequestURLPath: c.Request.URL.String(),
  359. IsStream: isStream,
  360. StartTime: startTime,
  361. FirstResponseTime: startTime.Add(-time.Second),
  362. ThinkingContentInfo: ThinkingContentInfo{
  363. IsFirstThinkingContent: true,
  364. SendLastThinkingContent: false,
  365. },
  366. TokenCountMeta: TokenCountMeta{
  367. //promptTokens: common.GetContextKeyInt(c, constant.ContextKeyPromptTokens),
  368. estimatePromptTokens: common.GetContextKeyInt(c, constant.ContextKeyEstimatedTokens),
  369. },
  370. }
  371. if info.RelayMode == relayconstant.RelayModeUnknown {
  372. info.RelayMode = c.GetInt("relay_mode")
  373. }
  374. if strings.HasPrefix(c.Request.URL.Path, "/pg") {
  375. info.IsPlayground = true
  376. info.RequestURLPath = strings.TrimPrefix(info.RequestURLPath, "/pg")
  377. info.RequestURLPath = "/v1" + info.RequestURLPath
  378. }
  379. userSetting, ok := common.GetContextKeyType[dto.UserSetting](c, constant.ContextKeyUserSetting)
  380. if ok {
  381. info.UserSetting = userSetting
  382. }
  383. return info
  384. }
  385. func GenRelayInfo(c *gin.Context, relayFormat types.RelayFormat, request dto.Request, ws *websocket.Conn) (*RelayInfo, error) {
  386. switch relayFormat {
  387. case types.RelayFormatOpenAI:
  388. return GenRelayInfoOpenAI(c, request), nil
  389. case types.RelayFormatOpenAIAudio:
  390. return GenRelayInfoOpenAIAudio(c, request), nil
  391. case types.RelayFormatOpenAIImage:
  392. return GenRelayInfoImage(c, request), nil
  393. case types.RelayFormatOpenAIRealtime:
  394. return GenRelayInfoWs(c, ws), nil
  395. case types.RelayFormatClaude:
  396. return GenRelayInfoClaude(c, request), nil
  397. case types.RelayFormatRerank:
  398. if request, ok := request.(*dto.RerankRequest); ok {
  399. return GenRelayInfoRerank(c, request), nil
  400. }
  401. return nil, errors.New("request is not a RerankRequest")
  402. case types.RelayFormatGemini:
  403. return GenRelayInfoGemini(c, request), nil
  404. case types.RelayFormatEmbedding:
  405. return GenRelayInfoEmbedding(c, request), nil
  406. case types.RelayFormatOpenAIResponses:
  407. if request, ok := request.(*dto.OpenAIResponsesRequest); ok {
  408. return GenRelayInfoResponses(c, request), nil
  409. }
  410. return nil, errors.New("request is not a OpenAIResponsesRequest")
  411. case types.RelayFormatTask:
  412. return genBaseRelayInfo(c, nil), nil
  413. case types.RelayFormatMjProxy:
  414. return genBaseRelayInfo(c, nil), nil
  415. default:
  416. return nil, errors.New("invalid relay format")
  417. }
  418. }
  419. //func (info *RelayInfo) SetPromptTokens(promptTokens int) {
  420. // info.promptTokens = promptTokens
  421. //}
  422. func (info *RelayInfo) SetEstimatePromptTokens(promptTokens int) {
  423. info.estimatePromptTokens = promptTokens
  424. }
  425. func (info *RelayInfo) GetEstimatePromptTokens() int {
  426. return info.estimatePromptTokens
  427. }
  428. func (info *RelayInfo) SetFirstResponseTime() {
  429. if info.isFirstResponse {
  430. info.FirstResponseTime = time.Now()
  431. info.isFirstResponse = false
  432. }
  433. }
  434. func (info *RelayInfo) HasSendResponse() bool {
  435. return info.FirstResponseTime.After(info.StartTime)
  436. }
  437. type TaskRelayInfo struct {
  438. Action string
  439. OriginTaskID string
  440. ConsumeQuota bool
  441. }
  442. type TaskSubmitReq struct {
  443. Prompt string `json:"prompt"`
  444. Model string `json:"model,omitempty"`
  445. Mode string `json:"mode,omitempty"`
  446. Image string `json:"image,omitempty"`
  447. Images []string `json:"images,omitempty"`
  448. Size string `json:"size,omitempty"`
  449. Duration int `json:"duration,omitempty"`
  450. Seconds string `json:"seconds,omitempty"`
  451. InputReference string `json:"input_reference,omitempty"`
  452. Metadata map[string]interface{} `json:"metadata,omitempty"`
  453. }
  454. func (t *TaskSubmitReq) GetPrompt() string {
  455. return t.Prompt
  456. }
  457. func (t *TaskSubmitReq) HasImage() bool {
  458. return len(t.Images) > 0
  459. }
  460. func (t *TaskSubmitReq) UnmarshalJSON(data []byte) error {
  461. type Alias TaskSubmitReq
  462. aux := &struct {
  463. Metadata json.RawMessage `json:"metadata,omitempty"`
  464. *Alias
  465. }{
  466. Alias: (*Alias)(t),
  467. }
  468. if err := common.Unmarshal(data, &aux); err != nil {
  469. return err
  470. }
  471. if len(aux.Metadata) > 0 {
  472. var metadataStr string
  473. if err := common.Unmarshal(aux.Metadata, &metadataStr); err == nil && metadataStr != "" {
  474. var metadataObj map[string]interface{}
  475. if err := common.Unmarshal([]byte(metadataStr), &metadataObj); err == nil {
  476. t.Metadata = metadataObj
  477. return nil
  478. }
  479. }
  480. var metadataObj map[string]interface{}
  481. if err := common.Unmarshal(aux.Metadata, &metadataObj); err == nil {
  482. t.Metadata = metadataObj
  483. }
  484. }
  485. return nil
  486. }
  487. func (t *TaskSubmitReq) UnmarshalMetadata(v any) error {
  488. metadata := t.Metadata
  489. if metadata != nil {
  490. metadataBytes, err := json.Marshal(metadata)
  491. if err != nil {
  492. return fmt.Errorf("marshal metadata failed: %w", err)
  493. }
  494. err = json.Unmarshal(metadataBytes, v)
  495. if err != nil {
  496. return fmt.Errorf("unmarshal metadata to target failed: %w", err)
  497. }
  498. }
  499. return nil
  500. }
  501. type TaskInfo struct {
  502. Code int `json:"code"`
  503. TaskID string `json:"task_id"`
  504. Status string `json:"status"`
  505. Reason string `json:"reason,omitempty"`
  506. Url string `json:"url,omitempty"`
  507. RemoteUrl string `json:"remote_url,omitempty"`
  508. Progress string `json:"progress,omitempty"`
  509. CompletionTokens int `json:"completion_tokens,omitempty"` // 用于按倍率计费
  510. TotalTokens int `json:"total_tokens,omitempty"` // 用于按倍率计费
  511. }
  512. func FailTaskInfo(reason string) *TaskInfo {
  513. return &TaskInfo{
  514. Status: "FAILURE",
  515. Reason: reason,
  516. }
  517. }
  518. // RemoveDisabledFields 从请求 JSON 数据中移除渠道设置中禁用的字段
  519. // service_tier: 服务层级字段,可能导致额外计费(OpenAI、Claude、Responses API 支持)
  520. // store: 数据存储授权字段,涉及用户隐私(仅 OpenAI、Responses API 支持,默认允许透传,禁用后可能导致 Codex 无法使用)
  521. // safety_identifier: 安全标识符,用于向 OpenAI 报告违规用户(仅 OpenAI 支持,涉及用户隐私)
  522. func RemoveDisabledFields(jsonData []byte, channelOtherSettings dto.ChannelOtherSettings) ([]byte, error) {
  523. var data map[string]interface{}
  524. if err := common.Unmarshal(jsonData, &data); err != nil {
  525. common.SysError("RemoveDisabledFields Unmarshal error :" + err.Error())
  526. return jsonData, nil
  527. }
  528. // 默认移除 service_tier,除非明确允许(避免额外计费风险)
  529. if !channelOtherSettings.AllowServiceTier {
  530. if _, exists := data["service_tier"]; exists {
  531. delete(data, "service_tier")
  532. }
  533. }
  534. // 默认允许 store 透传,除非明确禁用(禁用可能影响 Codex 使用)
  535. if channelOtherSettings.DisableStore {
  536. if _, exists := data["store"]; exists {
  537. delete(data, "store")
  538. }
  539. }
  540. // 默认移除 safety_identifier,除非明确允许(保护用户隐私,避免向 OpenAI 报告用户信息)
  541. if !channelOtherSettings.AllowSafetyIdentifier {
  542. if _, exists := data["safety_identifier"]; exists {
  543. delete(data, "safety_identifier")
  544. }
  545. }
  546. jsonDataAfter, err := common.Marshal(data)
  547. if err != nil {
  548. common.SysError("RemoveDisabledFields Marshal error :" + err.Error())
  549. return jsonData, nil
  550. }
  551. return jsonDataAfter, nil
  552. }