relay_info.go 18 KB

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