relay_info.go 19 KB

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