relay_info.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  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. }
  236. func GenRelayInfoWs(c *gin.Context, ws *websocket.Conn) *RelayInfo {
  237. info := genBaseRelayInfo(c, nil)
  238. info.RelayFormat = types.RelayFormatOpenAIRealtime
  239. info.ClientWs = ws
  240. info.InputAudioFormat = "pcm16"
  241. info.OutputAudioFormat = "pcm16"
  242. info.IsFirstRequest = true
  243. return info
  244. }
  245. func GenRelayInfoClaude(c *gin.Context, request dto.Request) *RelayInfo {
  246. info := genBaseRelayInfo(c, request)
  247. info.RelayFormat = types.RelayFormatClaude
  248. info.ShouldIncludeUsage = false
  249. info.ClaudeConvertInfo = &ClaudeConvertInfo{
  250. LastMessagesType: LastMessageTypeNone,
  251. }
  252. if c.Query("beta") == "true" {
  253. info.IsClaudeBetaQuery = true
  254. }
  255. return info
  256. }
  257. func GenRelayInfoRerank(c *gin.Context, request *dto.RerankRequest) *RelayInfo {
  258. info := genBaseRelayInfo(c, request)
  259. info.RelayMode = relayconstant.RelayModeRerank
  260. info.RelayFormat = types.RelayFormatRerank
  261. info.RerankerInfo = &RerankerInfo{
  262. Documents: request.Documents,
  263. ReturnDocuments: request.GetReturnDocuments(),
  264. }
  265. return info
  266. }
  267. func GenRelayInfoOpenAIAudio(c *gin.Context, request dto.Request) *RelayInfo {
  268. info := genBaseRelayInfo(c, request)
  269. info.RelayFormat = types.RelayFormatOpenAIAudio
  270. return info
  271. }
  272. func GenRelayInfoEmbedding(c *gin.Context, request dto.Request) *RelayInfo {
  273. info := genBaseRelayInfo(c, request)
  274. info.RelayFormat = types.RelayFormatEmbedding
  275. return info
  276. }
  277. func GenRelayInfoResponses(c *gin.Context, request *dto.OpenAIResponsesRequest) *RelayInfo {
  278. info := genBaseRelayInfo(c, request)
  279. info.RelayMode = relayconstant.RelayModeResponses
  280. info.RelayFormat = types.RelayFormatOpenAIResponses
  281. info.ResponsesUsageInfo = &ResponsesUsageInfo{
  282. BuiltInTools: make(map[string]*BuildInToolInfo),
  283. }
  284. if len(request.Tools) > 0 {
  285. for _, tool := range request.GetToolsMap() {
  286. toolType := common.Interface2String(tool["type"])
  287. info.ResponsesUsageInfo.BuiltInTools[toolType] = &BuildInToolInfo{
  288. ToolName: toolType,
  289. CallCount: 0,
  290. }
  291. switch toolType {
  292. case dto.BuildInToolWebSearchPreview:
  293. searchContextSize := common.Interface2String(tool["search_context_size"])
  294. if searchContextSize == "" {
  295. searchContextSize = "medium"
  296. }
  297. info.ResponsesUsageInfo.BuiltInTools[toolType].SearchContextSize = searchContextSize
  298. }
  299. }
  300. }
  301. return info
  302. }
  303. func GenRelayInfoGemini(c *gin.Context, request dto.Request) *RelayInfo {
  304. info := genBaseRelayInfo(c, request)
  305. info.RelayFormat = types.RelayFormatGemini
  306. info.ShouldIncludeUsage = false
  307. return info
  308. }
  309. func GenRelayInfoImage(c *gin.Context, request dto.Request) *RelayInfo {
  310. info := genBaseRelayInfo(c, request)
  311. info.RelayFormat = types.RelayFormatOpenAIImage
  312. return info
  313. }
  314. func GenRelayInfoOpenAI(c *gin.Context, request dto.Request) *RelayInfo {
  315. info := genBaseRelayInfo(c, request)
  316. info.RelayFormat = types.RelayFormatOpenAI
  317. return info
  318. }
  319. func genBaseRelayInfo(c *gin.Context, request dto.Request) *RelayInfo {
  320. //channelType := common.GetContextKeyInt(c, constant.ContextKeyChannelType)
  321. //channelId := common.GetContextKeyInt(c, constant.ContextKeyChannelId)
  322. //paramOverride := common.GetContextKeyStringMap(c, constant.ContextKeyChannelParamOverride)
  323. startTime := common.GetContextKeyTime(c, constant.ContextKeyRequestStartTime)
  324. if startTime.IsZero() {
  325. startTime = time.Now()
  326. }
  327. isStream := false
  328. if request != nil {
  329. isStream = request.IsStream(c)
  330. }
  331. // firstResponseTime = time.Now() - 1 second
  332. info := &RelayInfo{
  333. Request: request,
  334. UserId: common.GetContextKeyInt(c, constant.ContextKeyUserId),
  335. UsingGroup: common.GetContextKeyString(c, constant.ContextKeyUsingGroup),
  336. UserGroup: common.GetContextKeyString(c, constant.ContextKeyUserGroup),
  337. UserQuota: common.GetContextKeyInt(c, constant.ContextKeyUserQuota),
  338. UserEmail: common.GetContextKeyString(c, constant.ContextKeyUserEmail),
  339. OriginModelName: common.GetContextKeyString(c, constant.ContextKeyOriginalModel),
  340. PromptTokens: common.GetContextKeyInt(c, constant.ContextKeyPromptTokens),
  341. TokenId: common.GetContextKeyInt(c, constant.ContextKeyTokenId),
  342. TokenKey: common.GetContextKeyString(c, constant.ContextKeyTokenKey),
  343. TokenUnlimited: common.GetContextKeyBool(c, constant.ContextKeyTokenUnlimited),
  344. isFirstResponse: true,
  345. RelayMode: relayconstant.Path2RelayMode(c.Request.URL.Path),
  346. RequestURLPath: c.Request.URL.String(),
  347. IsStream: isStream,
  348. StartTime: startTime,
  349. FirstResponseTime: startTime.Add(-time.Second),
  350. ThinkingContentInfo: ThinkingContentInfo{
  351. IsFirstThinkingContent: true,
  352. SendLastThinkingContent: false,
  353. },
  354. }
  355. if info.RelayMode == relayconstant.RelayModeUnknown {
  356. info.RelayMode = c.GetInt("relay_mode")
  357. }
  358. if strings.HasPrefix(c.Request.URL.Path, "/pg") {
  359. info.IsPlayground = true
  360. info.RequestURLPath = strings.TrimPrefix(info.RequestURLPath, "/pg")
  361. info.RequestURLPath = "/v1" + info.RequestURLPath
  362. }
  363. userSetting, ok := common.GetContextKeyType[dto.UserSetting](c, constant.ContextKeyUserSetting)
  364. if ok {
  365. info.UserSetting = userSetting
  366. }
  367. return info
  368. }
  369. func GenRelayInfo(c *gin.Context, relayFormat types.RelayFormat, request dto.Request, ws *websocket.Conn) (*RelayInfo, error) {
  370. switch relayFormat {
  371. case types.RelayFormatOpenAI:
  372. return GenRelayInfoOpenAI(c, request), nil
  373. case types.RelayFormatOpenAIAudio:
  374. return GenRelayInfoOpenAIAudio(c, request), nil
  375. case types.RelayFormatOpenAIImage:
  376. return GenRelayInfoImage(c, request), nil
  377. case types.RelayFormatOpenAIRealtime:
  378. return GenRelayInfoWs(c, ws), nil
  379. case types.RelayFormatClaude:
  380. return GenRelayInfoClaude(c, request), nil
  381. case types.RelayFormatRerank:
  382. if request, ok := request.(*dto.RerankRequest); ok {
  383. return GenRelayInfoRerank(c, request), nil
  384. }
  385. return nil, errors.New("request is not a RerankRequest")
  386. case types.RelayFormatGemini:
  387. return GenRelayInfoGemini(c, request), nil
  388. case types.RelayFormatEmbedding:
  389. return GenRelayInfoEmbedding(c, request), nil
  390. case types.RelayFormatOpenAIResponses:
  391. if request, ok := request.(*dto.OpenAIResponsesRequest); ok {
  392. return GenRelayInfoResponses(c, request), nil
  393. }
  394. return nil, errors.New("request is not a OpenAIResponsesRequest")
  395. case types.RelayFormatTask:
  396. return genBaseRelayInfo(c, nil), nil
  397. case types.RelayFormatMjProxy:
  398. return genBaseRelayInfo(c, nil), nil
  399. default:
  400. return nil, errors.New("invalid relay format")
  401. }
  402. }
  403. func (info *RelayInfo) SetPromptTokens(promptTokens int) {
  404. info.PromptTokens = promptTokens
  405. }
  406. func (info *RelayInfo) SetFirstResponseTime() {
  407. if info.isFirstResponse {
  408. info.FirstResponseTime = time.Now()
  409. info.isFirstResponse = false
  410. }
  411. }
  412. func (info *RelayInfo) HasSendResponse() bool {
  413. return info.FirstResponseTime.After(info.StartTime)
  414. }
  415. type TaskRelayInfo struct {
  416. Action string
  417. OriginTaskID string
  418. ConsumeQuota bool
  419. }
  420. type TaskSubmitReq struct {
  421. Prompt string `json:"prompt"`
  422. Model string `json:"model,omitempty"`
  423. Mode string `json:"mode,omitempty"`
  424. Image string `json:"image,omitempty"`
  425. Images []string `json:"images,omitempty"`
  426. Size string `json:"size,omitempty"`
  427. Duration int `json:"duration,omitempty"`
  428. Metadata map[string]interface{} `json:"metadata,omitempty"`
  429. }
  430. func (t TaskSubmitReq) GetPrompt() string {
  431. return t.Prompt
  432. }
  433. func (t TaskSubmitReq) HasImage() bool {
  434. return len(t.Images) > 0
  435. }
  436. type TaskInfo struct {
  437. Code int `json:"code"`
  438. TaskID string `json:"task_id"`
  439. Status string `json:"status"`
  440. Reason string `json:"reason,omitempty"`
  441. Url string `json:"url,omitempty"`
  442. Progress string `json:"progress,omitempty"`
  443. CompletionTokens int `json:"completion_tokens,omitempty"` // 用于按倍率计费
  444. TotalTokens int `json:"total_tokens,omitempty"` // 用于按倍率计费
  445. }
  446. func FailTaskInfo(reason string) *TaskInfo {
  447. return &TaskInfo{
  448. Status: "FAILURE",
  449. Reason: reason,
  450. }
  451. }
  452. // RemoveDisabledFields 从请求 JSON 数据中移除渠道设置中禁用的字段
  453. // service_tier: 服务层级字段,可能导致额外计费(OpenAI、Claude、Responses API 支持)
  454. // store: 数据存储授权字段,涉及用户隐私(仅 OpenAI、Responses API 支持,默认允许透传,禁用后可能导致 Codex 无法使用)
  455. // safety_identifier: 安全标识符,用于向 OpenAI 报告违规用户(仅 OpenAI 支持,涉及用户隐私)
  456. func RemoveDisabledFields(jsonData []byte, channelOtherSettings dto.ChannelOtherSettings) ([]byte, error) {
  457. var data map[string]interface{}
  458. if err := common.Unmarshal(jsonData, &data); err != nil {
  459. common.SysError("RemoveDisabledFields Unmarshal error :" + err.Error())
  460. return jsonData, nil
  461. }
  462. // 默认移除 service_tier,除非明确允许(避免额外计费风险)
  463. if !channelOtherSettings.AllowServiceTier {
  464. if _, exists := data["service_tier"]; exists {
  465. delete(data, "service_tier")
  466. }
  467. }
  468. // 默认允许 store 透传,除非明确禁用(禁用可能影响 Codex 使用)
  469. if channelOtherSettings.DisableStore {
  470. if _, exists := data["store"]; exists {
  471. delete(data, "store")
  472. }
  473. }
  474. // 默认移除 safety_identifier,除非明确允许(保护用户隐私,避免向 OpenAI 报告用户信息)
  475. if !channelOtherSettings.AllowSafetyIdentifier {
  476. if _, exists := data["safety_identifier"]; exists {
  477. delete(data, "safety_identifier")
  478. }
  479. }
  480. jsonDataAfter, err := common.Marshal(data)
  481. if err != nil {
  482. common.SysError("RemoveDisabledFields Marshal error :" + err.Error())
  483. return jsonData, nil
  484. }
  485. return jsonDataAfter, nil
  486. }