relay_info.go 20 KB

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