Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

1044 строки
30 KiB

  1. package dto
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "strings"
  6. "github.com/QuantumNous/new-api/common"
  7. "github.com/QuantumNous/new-api/types"
  8. "github.com/gin-gonic/gin"
  9. )
  10. type ResponseFormat struct {
  11. Type string `json:"type,omitempty"`
  12. JsonSchema json.RawMessage `json:"json_schema,omitempty"`
  13. }
  14. type FormatJsonSchema struct {
  15. Description string `json:"description,omitempty"`
  16. Name string `json:"name"`
  17. Schema any `json:"schema,omitempty"`
  18. Strict json.RawMessage `json:"strict,omitempty"`
  19. }
  20. // GeneralOpenAIRequest represents a general request structure for OpenAI-compatible APIs.
  21. // 参数增加规范:无引用的参数必须使用json.RawMessage类型,并添加omitempty标签
  22. type GeneralOpenAIRequest struct {
  23. Model string `json:"model,omitempty"`
  24. Messages []Message `json:"messages,omitempty"`
  25. Prompt any `json:"prompt,omitempty"`
  26. Prefix any `json:"prefix,omitempty"`
  27. Suffix any `json:"suffix,omitempty"`
  28. Stream bool `json:"stream,omitempty"`
  29. StreamOptions *StreamOptions `json:"stream_options,omitempty"`
  30. MaxTokens uint `json:"max_tokens,omitempty"`
  31. MaxCompletionTokens uint `json:"max_completion_tokens,omitempty"`
  32. ReasoningEffort string `json:"reasoning_effort,omitempty"`
  33. Verbosity json.RawMessage `json:"verbosity,omitempty"` // gpt-5
  34. Temperature *float64 `json:"temperature,omitempty"`
  35. TopP float64 `json:"top_p,omitempty"`
  36. TopK int `json:"top_k,omitempty"`
  37. Stop any `json:"stop,omitempty"`
  38. N int `json:"n,omitempty"`
  39. Input any `json:"input,omitempty"`
  40. Instruction string `json:"instruction,omitempty"`
  41. Size string `json:"size,omitempty"`
  42. Functions json.RawMessage `json:"functions,omitempty"`
  43. FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
  44. PresencePenalty float64 `json:"presence_penalty,omitempty"`
  45. ResponseFormat *ResponseFormat `json:"response_format,omitempty"`
  46. EncodingFormat json.RawMessage `json:"encoding_format,omitempty"`
  47. Seed float64 `json:"seed,omitempty"`
  48. ParallelTooCalls *bool `json:"parallel_tool_calls,omitempty"`
  49. Tools []ToolCallRequest `json:"tools,omitempty"`
  50. ToolChoice any `json:"tool_choice,omitempty"`
  51. FunctionCall json.RawMessage `json:"function_call,omitempty"`
  52. User string `json:"user,omitempty"`
  53. // ServiceTier specifies upstream service level and may affect billing.
  54. // This field is filtered by default and can be enabled via channel setting allow_service_tier.
  55. ServiceTier string `json:"service_tier,omitempty"`
  56. LogProbs bool `json:"logprobs,omitempty"`
  57. TopLogProbs int `json:"top_logprobs,omitempty"`
  58. Dimensions int `json:"dimensions,omitempty"`
  59. Modalities json.RawMessage `json:"modalities,omitempty"`
  60. Audio json.RawMessage `json:"audio,omitempty"`
  61. // 安全标识符,用于帮助 OpenAI 检测可能违反使用政策的应用程序用户
  62. // 注意:此字段会向 OpenAI 发送用户标识信息,默认过滤,可通过 allow_safety_identifier 开启
  63. SafetyIdentifier string `json:"safety_identifier,omitempty"`
  64. // Whether or not to store the output of this chat completion request for use in our model distillation or evals products.
  65. // 是否存储此次请求数据供 OpenAI 用于评估和优化产品
  66. // 注意:默认允许透传,可通过 disable_store 禁用;禁用后可能导致 Codex 无法正常使用
  67. Store json.RawMessage `json:"store,omitempty"`
  68. // Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the user field
  69. PromptCacheKey string `json:"prompt_cache_key,omitempty"`
  70. PromptCacheRetention json.RawMessage `json:"prompt_cache_retention,omitempty"`
  71. LogitBias json.RawMessage `json:"logit_bias,omitempty"`
  72. Metadata json.RawMessage `json:"metadata,omitempty"`
  73. Prediction json.RawMessage `json:"prediction,omitempty"`
  74. // gemini
  75. ExtraBody json.RawMessage `json:"extra_body,omitempty"`
  76. //xai
  77. SearchParameters json.RawMessage `json:"search_parameters,omitempty"`
  78. // claude
  79. WebSearchOptions *WebSearchOptions `json:"web_search_options,omitempty"`
  80. // OpenRouter Params
  81. Usage json.RawMessage `json:"usage,omitempty"`
  82. Reasoning json.RawMessage `json:"reasoning,omitempty"`
  83. // Ali Qwen Params
  84. VlHighResolutionImages json.RawMessage `json:"vl_high_resolution_images,omitempty"`
  85. EnableThinking json.RawMessage `json:"enable_thinking,omitempty"`
  86. ChatTemplateKwargs json.RawMessage `json:"chat_template_kwargs,omitempty"`
  87. EnableSearch json.RawMessage `json:"enable_search,omitempty"`
  88. // ollama Params
  89. Think json.RawMessage `json:"think,omitempty"`
  90. // baidu v2
  91. WebSearch json.RawMessage `json:"web_search,omitempty"`
  92. // doubao,zhipu_v4
  93. THINKING json.RawMessage `json:"thinking,omitempty"`
  94. // pplx Params
  95. SearchDomainFilter json.RawMessage `json:"search_domain_filter,omitempty"`
  96. SearchRecencyFilter string `json:"search_recency_filter,omitempty"`
  97. ReturnImages bool `json:"return_images,omitempty"`
  98. ReturnRelatedQuestions bool `json:"return_related_questions,omitempty"`
  99. SearchMode string `json:"search_mode,omitempty"`
  100. }
  101. // createFileSource 根据数据内容创建正确类型的 FileSource
  102. func createFileSource(data string) *types.FileSource {
  103. if strings.HasPrefix(data, "http://") || strings.HasPrefix(data, "https://") {
  104. return types.NewURLFileSource(data)
  105. }
  106. return types.NewBase64FileSource(data, "")
  107. }
  108. func (r *GeneralOpenAIRequest) GetTokenCountMeta() *types.TokenCountMeta {
  109. var tokenCountMeta types.TokenCountMeta
  110. var texts = make([]string, 0)
  111. var fileMeta = make([]*types.FileMeta, 0)
  112. if r.Prompt != nil {
  113. switch v := r.Prompt.(type) {
  114. case string:
  115. texts = append(texts, v)
  116. case []any:
  117. for _, item := range v {
  118. if str, ok := item.(string); ok {
  119. texts = append(texts, str)
  120. }
  121. }
  122. default:
  123. texts = append(texts, fmt.Sprintf("%v", r.Prompt))
  124. }
  125. }
  126. if r.Input != nil {
  127. inputs := r.ParseInput()
  128. texts = append(texts, inputs...)
  129. }
  130. if r.MaxCompletionTokens > r.MaxTokens {
  131. tokenCountMeta.MaxTokens = int(r.MaxCompletionTokens)
  132. } else {
  133. tokenCountMeta.MaxTokens = int(r.MaxTokens)
  134. }
  135. for _, message := range r.Messages {
  136. tokenCountMeta.MessagesCount++
  137. texts = append(texts, message.Role)
  138. if message.Content != nil {
  139. if message.Name != nil {
  140. tokenCountMeta.NameCount++
  141. texts = append(texts, *message.Name)
  142. }
  143. arrayContent := message.ParseContent()
  144. for _, m := range arrayContent {
  145. if m.Type == ContentTypeImageURL {
  146. imageUrl := m.GetImageMedia()
  147. if imageUrl != nil && imageUrl.Url != "" {
  148. source := createFileSource(imageUrl.Url)
  149. fileMeta = append(fileMeta, &types.FileMeta{
  150. FileType: types.FileTypeImage,
  151. Source: source,
  152. Detail: imageUrl.Detail,
  153. })
  154. }
  155. } else if m.Type == ContentTypeInputAudio {
  156. inputAudio := m.GetInputAudio()
  157. if inputAudio != nil && inputAudio.Data != "" {
  158. source := createFileSource(inputAudio.Data)
  159. fileMeta = append(fileMeta, &types.FileMeta{
  160. FileType: types.FileTypeAudio,
  161. Source: source,
  162. })
  163. }
  164. } else if m.Type == ContentTypeFile {
  165. file := m.GetFile()
  166. if file != nil && file.FileData != "" {
  167. source := createFileSource(file.FileData)
  168. fileMeta = append(fileMeta, &types.FileMeta{
  169. FileType: types.FileTypeFile,
  170. Source: source,
  171. })
  172. }
  173. } else if m.Type == ContentTypeVideoUrl {
  174. videoUrl := m.GetVideoUrl()
  175. if videoUrl != nil && videoUrl.Url != "" {
  176. source := createFileSource(videoUrl.Url)
  177. fileMeta = append(fileMeta, &types.FileMeta{
  178. FileType: types.FileTypeVideo,
  179. Source: source,
  180. })
  181. }
  182. } else {
  183. texts = append(texts, m.Text)
  184. }
  185. }
  186. }
  187. }
  188. if r.Tools != nil {
  189. openaiTools := r.Tools
  190. for _, tool := range openaiTools {
  191. tokenCountMeta.ToolsCount++
  192. texts = append(texts, tool.Function.Name)
  193. if tool.Function.Description != "" {
  194. texts = append(texts, tool.Function.Description)
  195. }
  196. if tool.Function.Parameters != nil {
  197. texts = append(texts, fmt.Sprintf("%v", tool.Function.Parameters))
  198. }
  199. }
  200. //toolTokens := CountTokenInput(countStr, request.Model)
  201. //tkm += 8
  202. //tkm += toolTokens
  203. }
  204. tokenCountMeta.CombineText = strings.Join(texts, "\n")
  205. tokenCountMeta.Files = fileMeta
  206. return &tokenCountMeta
  207. }
  208. func (r *GeneralOpenAIRequest) IsStream(c *gin.Context) bool {
  209. return r.Stream
  210. }
  211. func (r *GeneralOpenAIRequest) SetModelName(modelName string) {
  212. if modelName != "" {
  213. r.Model = modelName
  214. }
  215. }
  216. func (r *GeneralOpenAIRequest) ToMap() map[string]any {
  217. result := make(map[string]any)
  218. data, _ := common.Marshal(r)
  219. _ = common.Unmarshal(data, &result)
  220. return result
  221. }
  222. func (r *GeneralOpenAIRequest) GetSystemRoleName() string {
  223. if strings.HasPrefix(r.Model, "o") {
  224. if !strings.HasPrefix(r.Model, "o1-mini") && !strings.HasPrefix(r.Model, "o1-preview") {
  225. return "developer"
  226. }
  227. } else if strings.HasPrefix(r.Model, "gpt-5") {
  228. return "developer"
  229. }
  230. return "system"
  231. }
  232. const CustomType = "custom"
  233. type ToolCallRequest struct {
  234. ID string `json:"id,omitempty"`
  235. Type string `json:"type"`
  236. Function FunctionRequest `json:"function,omitempty"`
  237. Custom json.RawMessage `json:"custom,omitempty"`
  238. }
  239. type FunctionRequest struct {
  240. Description string `json:"description,omitempty"`
  241. Name string `json:"name"`
  242. Parameters any `json:"parameters,omitempty"`
  243. Arguments string `json:"arguments,omitempty"`
  244. }
  245. type StreamOptions struct {
  246. IncludeUsage bool `json:"include_usage,omitempty"`
  247. // IncludeObfuscation is only for /v1/responses stream payload.
  248. // This field is filtered by default and can be enabled via channel setting allow_include_obfuscation.
  249. IncludeObfuscation bool `json:"include_obfuscation,omitempty"`
  250. }
  251. func (r *GeneralOpenAIRequest) GetMaxTokens() uint {
  252. if r.MaxCompletionTokens != 0 {
  253. return r.MaxCompletionTokens
  254. }
  255. return r.MaxTokens
  256. }
  257. func (r *GeneralOpenAIRequest) ParseInput() []string {
  258. if r.Input == nil {
  259. return nil
  260. }
  261. var input []string
  262. switch r.Input.(type) {
  263. case string:
  264. input = []string{r.Input.(string)}
  265. case []any:
  266. input = make([]string, 0, len(r.Input.([]any)))
  267. for _, item := range r.Input.([]any) {
  268. if str, ok := item.(string); ok {
  269. input = append(input, str)
  270. }
  271. }
  272. }
  273. return input
  274. }
  275. type Message struct {
  276. Role string `json:"role"`
  277. Content any `json:"content"`
  278. Name *string `json:"name,omitempty"`
  279. Prefix *bool `json:"prefix,omitempty"`
  280. ReasoningContent string `json:"reasoning_content,omitempty"`
  281. Reasoning string `json:"reasoning,omitempty"`
  282. ToolCalls json.RawMessage `json:"tool_calls,omitempty"`
  283. ToolCallId string `json:"tool_call_id,omitempty"`
  284. parsedContent []MediaContent
  285. //parsedStringContent *string
  286. }
  287. type MediaContent struct {
  288. Type string `json:"type"`
  289. Text string `json:"text,omitempty"`
  290. ImageUrl any `json:"image_url,omitempty"`
  291. InputAudio any `json:"input_audio,omitempty"`
  292. File any `json:"file,omitempty"`
  293. VideoUrl any `json:"video_url,omitempty"`
  294. // OpenRouter Params
  295. CacheControl json.RawMessage `json:"cache_control,omitempty"`
  296. }
  297. func (m *MediaContent) GetImageMedia() *MessageImageUrl {
  298. if m.ImageUrl != nil {
  299. if _, ok := m.ImageUrl.(*MessageImageUrl); ok {
  300. return m.ImageUrl.(*MessageImageUrl)
  301. }
  302. if itemMap, ok := m.ImageUrl.(map[string]any); ok {
  303. out := &MessageImageUrl{
  304. Url: common.Interface2String(itemMap["url"]),
  305. Detail: common.Interface2String(itemMap["detail"]),
  306. MimeType: common.Interface2String(itemMap["mime_type"]),
  307. }
  308. return out
  309. }
  310. }
  311. return nil
  312. }
  313. func (m *MediaContent) GetInputAudio() *MessageInputAudio {
  314. if m.InputAudio != nil {
  315. if _, ok := m.InputAudio.(*MessageInputAudio); ok {
  316. return m.InputAudio.(*MessageInputAudio)
  317. }
  318. if itemMap, ok := m.InputAudio.(map[string]any); ok {
  319. out := &MessageInputAudio{
  320. Data: common.Interface2String(itemMap["data"]),
  321. Format: common.Interface2String(itemMap["format"]),
  322. }
  323. return out
  324. }
  325. }
  326. return nil
  327. }
  328. func (m *MediaContent) GetFile() *MessageFile {
  329. if m.File != nil {
  330. if _, ok := m.File.(*MessageFile); ok {
  331. return m.File.(*MessageFile)
  332. }
  333. if itemMap, ok := m.File.(map[string]any); ok {
  334. out := &MessageFile{
  335. FileName: common.Interface2String(itemMap["file_name"]),
  336. FileData: common.Interface2String(itemMap["file_data"]),
  337. FileId: common.Interface2String(itemMap["file_id"]),
  338. }
  339. return out
  340. }
  341. }
  342. return nil
  343. }
  344. func (m *MediaContent) GetVideoUrl() *MessageVideoUrl {
  345. if m.VideoUrl != nil {
  346. if _, ok := m.VideoUrl.(*MessageVideoUrl); ok {
  347. return m.VideoUrl.(*MessageVideoUrl)
  348. }
  349. if itemMap, ok := m.VideoUrl.(map[string]any); ok {
  350. out := &MessageVideoUrl{
  351. Url: common.Interface2String(itemMap["url"]),
  352. }
  353. return out
  354. }
  355. }
  356. return nil
  357. }
  358. type MessageImageUrl struct {
  359. Url string `json:"url"`
  360. Detail string `json:"detail,omitempty"`
  361. MimeType string
  362. }
  363. func (m *MessageImageUrl) IsRemoteImage() bool {
  364. return strings.HasPrefix(m.Url, "http")
  365. }
  366. type MessageInputAudio struct {
  367. Data string `json:"data"` //base64
  368. Format string `json:"format"`
  369. }
  370. type MessageFile struct {
  371. FileName string `json:"filename,omitempty"`
  372. FileData string `json:"file_data,omitempty"`
  373. FileId string `json:"file_id,omitempty"`
  374. }
  375. type MessageVideoUrl struct {
  376. Url string `json:"url"`
  377. }
  378. const (
  379. ContentTypeText = "text"
  380. ContentTypeImageURL = "image_url"
  381. ContentTypeInputAudio = "input_audio"
  382. ContentTypeFile = "file"
  383. ContentTypeVideoUrl = "video_url" // 阿里百炼视频识别
  384. //ContentTypeAudioUrl = "audio_url"
  385. )
  386. func (m *Message) GetPrefix() bool {
  387. if m.Prefix == nil {
  388. return false
  389. }
  390. return *m.Prefix
  391. }
  392. func (m *Message) SetPrefix(prefix bool) {
  393. m.Prefix = &prefix
  394. }
  395. func (m *Message) ParseToolCalls() []ToolCallRequest {
  396. if m.ToolCalls == nil {
  397. return nil
  398. }
  399. var toolCalls []ToolCallRequest
  400. if err := json.Unmarshal(m.ToolCalls, &toolCalls); err == nil {
  401. return toolCalls
  402. }
  403. return toolCalls
  404. }
  405. func (m *Message) SetToolCalls(toolCalls any) {
  406. toolCallsJson, _ := json.Marshal(toolCalls)
  407. m.ToolCalls = toolCallsJson
  408. }
  409. func (m *Message) StringContent() string {
  410. switch m.Content.(type) {
  411. case string:
  412. return m.Content.(string)
  413. case []any:
  414. var contentStr string
  415. for _, contentItem := range m.Content.([]any) {
  416. contentMap, ok := contentItem.(map[string]any)
  417. if !ok {
  418. continue
  419. }
  420. if contentMap["type"] == ContentTypeText {
  421. if subStr, ok := contentMap["text"].(string); ok {
  422. contentStr += subStr
  423. }
  424. }
  425. }
  426. return contentStr
  427. }
  428. return ""
  429. }
  430. func (m *Message) SetNullContent() {
  431. m.Content = nil
  432. m.parsedContent = nil
  433. }
  434. func (m *Message) SetStringContent(content string) {
  435. m.Content = content
  436. m.parsedContent = nil
  437. }
  438. func (m *Message) SetMediaContent(content []MediaContent) {
  439. m.Content = content
  440. m.parsedContent = content
  441. }
  442. func (m *Message) IsStringContent() bool {
  443. _, ok := m.Content.(string)
  444. if ok {
  445. return true
  446. }
  447. return false
  448. }
  449. func (m *Message) ParseContent() []MediaContent {
  450. if m.Content == nil {
  451. return nil
  452. }
  453. if len(m.parsedContent) > 0 {
  454. return m.parsedContent
  455. }
  456. var contentList []MediaContent
  457. // 先尝试解析为字符串
  458. content, ok := m.Content.(string)
  459. if ok {
  460. contentList = []MediaContent{{
  461. Type: ContentTypeText,
  462. Text: content,
  463. }}
  464. m.parsedContent = contentList
  465. return contentList
  466. }
  467. // 尝试解析为数组
  468. //var arrayContent []map[string]interface{}
  469. // 直接是 []MediaContent 的情况(如通过 SetMediaContent 设置)
  470. if mediaContent, ok := m.Content.([]MediaContent); ok {
  471. m.parsedContent = mediaContent
  472. return mediaContent
  473. }
  474. arrayContent, ok := m.Content.([]any)
  475. if !ok {
  476. return contentList
  477. }
  478. for _, contentItemAny := range arrayContent {
  479. mediaItem, ok := contentItemAny.(MediaContent)
  480. if ok {
  481. contentList = append(contentList, mediaItem)
  482. continue
  483. }
  484. contentItem, ok := contentItemAny.(map[string]any)
  485. if !ok {
  486. continue
  487. }
  488. contentType, ok := contentItem["type"].(string)
  489. if !ok {
  490. continue
  491. }
  492. switch contentType {
  493. case ContentTypeText:
  494. if text, ok := contentItem["text"].(string); ok {
  495. contentList = append(contentList, MediaContent{
  496. Type: ContentTypeText,
  497. Text: text,
  498. })
  499. }
  500. case ContentTypeImageURL:
  501. imageUrl := contentItem["image_url"]
  502. temp := &MessageImageUrl{
  503. Detail: "high",
  504. }
  505. switch v := imageUrl.(type) {
  506. case string:
  507. temp.Url = v
  508. case map[string]interface{}:
  509. url, ok1 := v["url"].(string)
  510. detail, ok2 := v["detail"].(string)
  511. if ok2 {
  512. temp.Detail = detail
  513. }
  514. if ok1 {
  515. temp.Url = url
  516. }
  517. }
  518. contentList = append(contentList, MediaContent{
  519. Type: ContentTypeImageURL,
  520. ImageUrl: temp,
  521. })
  522. case ContentTypeInputAudio:
  523. if audioData, ok := contentItem["input_audio"].(map[string]interface{}); ok {
  524. data, ok1 := audioData["data"].(string)
  525. format, ok2 := audioData["format"].(string)
  526. if ok1 && ok2 {
  527. temp := &MessageInputAudio{
  528. Data: data,
  529. Format: format,
  530. }
  531. contentList = append(contentList, MediaContent{
  532. Type: ContentTypeInputAudio,
  533. InputAudio: temp,
  534. })
  535. }
  536. }
  537. case ContentTypeFile:
  538. if fileData, ok := contentItem["file"].(map[string]interface{}); ok {
  539. fileId, ok3 := fileData["file_id"].(string)
  540. if ok3 {
  541. contentList = append(contentList, MediaContent{
  542. Type: ContentTypeFile,
  543. File: &MessageFile{
  544. FileId: fileId,
  545. },
  546. })
  547. } else {
  548. fileName, ok1 := fileData["filename"].(string)
  549. fileDataStr, ok2 := fileData["file_data"].(string)
  550. if ok1 && ok2 {
  551. contentList = append(contentList, MediaContent{
  552. Type: ContentTypeFile,
  553. File: &MessageFile{
  554. FileName: fileName,
  555. FileData: fileDataStr,
  556. },
  557. })
  558. }
  559. }
  560. }
  561. case ContentTypeVideoUrl:
  562. if videoUrl, ok := contentItem["video_url"].(string); ok {
  563. contentList = append(contentList, MediaContent{
  564. Type: ContentTypeVideoUrl,
  565. VideoUrl: &MessageVideoUrl{
  566. Url: videoUrl,
  567. },
  568. })
  569. }
  570. }
  571. }
  572. if len(contentList) > 0 {
  573. m.parsedContent = contentList
  574. }
  575. return contentList
  576. }
  577. // old code
  578. /*func (m *Message) StringContent() string {
  579. if m.parsedStringContent != nil {
  580. return *m.parsedStringContent
  581. }
  582. var stringContent string
  583. if err := json.Unmarshal(m.Content, &stringContent); err == nil {
  584. m.parsedStringContent = &stringContent
  585. return stringContent
  586. }
  587. contentStr := new(strings.Builder)
  588. arrayContent := m.ParseContent()
  589. for _, content := range arrayContent {
  590. if content.Type == ContentTypeText {
  591. contentStr.WriteString(content.Text)
  592. }
  593. }
  594. stringContent = contentStr.String()
  595. m.parsedStringContent = &stringContent
  596. return stringContent
  597. }
  598. func (m *Message) SetNullContent() {
  599. m.Content = nil
  600. m.parsedStringContent = nil
  601. m.parsedContent = nil
  602. }
  603. func (m *Message) SetStringContent(content string) {
  604. jsonContent, _ := json.Marshal(content)
  605. m.Content = jsonContent
  606. m.parsedStringContent = &content
  607. m.parsedContent = nil
  608. }
  609. func (m *Message) SetMediaContent(content []MediaContent) {
  610. jsonContent, _ := json.Marshal(content)
  611. m.Content = jsonContent
  612. m.parsedContent = nil
  613. m.parsedStringContent = nil
  614. }
  615. func (m *Message) IsStringContent() bool {
  616. if m.parsedStringContent != nil {
  617. return true
  618. }
  619. var stringContent string
  620. if err := json.Unmarshal(m.Content, &stringContent); err == nil {
  621. m.parsedStringContent = &stringContent
  622. return true
  623. }
  624. return false
  625. }
  626. func (m *Message) ParseContent() []MediaContent {
  627. if m.parsedContent != nil {
  628. return m.parsedContent
  629. }
  630. var contentList []MediaContent
  631. // 先尝试解析为字符串
  632. var stringContent string
  633. if err := json.Unmarshal(m.Content, &stringContent); err == nil {
  634. contentList = []MediaContent{{
  635. Type: ContentTypeText,
  636. Text: stringContent,
  637. }}
  638. m.parsedContent = contentList
  639. return contentList
  640. }
  641. // 尝试解析为数组
  642. var arrayContent []map[string]interface{}
  643. if err := json.Unmarshal(m.Content, &arrayContent); err == nil {
  644. for _, contentItem := range arrayContent {
  645. contentType, ok := contentItem["type"].(string)
  646. if !ok {
  647. continue
  648. }
  649. switch contentType {
  650. case ContentTypeText:
  651. if text, ok := contentItem["text"].(string); ok {
  652. contentList = append(contentList, MediaContent{
  653. Type: ContentTypeText,
  654. Text: text,
  655. })
  656. }
  657. case ContentTypeImageURL:
  658. imageUrl := contentItem["image_url"]
  659. temp := &MessageImageUrl{
  660. Detail: "high",
  661. }
  662. switch v := imageUrl.(type) {
  663. case string:
  664. temp.Url = v
  665. case map[string]interface{}:
  666. url, ok1 := v["url"].(string)
  667. detail, ok2 := v["detail"].(string)
  668. if ok2 {
  669. temp.Detail = detail
  670. }
  671. if ok1 {
  672. temp.Url = url
  673. }
  674. }
  675. contentList = append(contentList, MediaContent{
  676. Type: ContentTypeImageURL,
  677. ImageUrl: temp,
  678. })
  679. case ContentTypeInputAudio:
  680. if audioData, ok := contentItem["input_audio"].(map[string]interface{}); ok {
  681. data, ok1 := audioData["data"].(string)
  682. format, ok2 := audioData["format"].(string)
  683. if ok1 && ok2 {
  684. temp := &MessageInputAudio{
  685. Data: data,
  686. Format: format,
  687. }
  688. contentList = append(contentList, MediaContent{
  689. Type: ContentTypeInputAudio,
  690. InputAudio: temp,
  691. })
  692. }
  693. }
  694. case ContentTypeFile:
  695. if fileData, ok := contentItem["file"].(map[string]interface{}); ok {
  696. fileId, ok3 := fileData["file_id"].(string)
  697. if ok3 {
  698. contentList = append(contentList, MediaContent{
  699. Type: ContentTypeFile,
  700. File: &MessageFile{
  701. FileId: fileId,
  702. },
  703. })
  704. } else {
  705. fileName, ok1 := fileData["filename"].(string)
  706. fileDataStr, ok2 := fileData["file_data"].(string)
  707. if ok1 && ok2 {
  708. contentList = append(contentList, MediaContent{
  709. Type: ContentTypeFile,
  710. File: &MessageFile{
  711. FileName: fileName,
  712. FileData: fileDataStr,
  713. },
  714. })
  715. }
  716. }
  717. }
  718. case ContentTypeVideoUrl:
  719. if videoUrl, ok := contentItem["video_url"].(string); ok {
  720. contentList = append(contentList, MediaContent{
  721. Type: ContentTypeVideoUrl,
  722. VideoUrl: &MessageVideoUrl{
  723. Url: videoUrl,
  724. },
  725. })
  726. }
  727. }
  728. }
  729. }
  730. if len(contentList) > 0 {
  731. m.parsedContent = contentList
  732. }
  733. return contentList
  734. }*/
  735. type WebSearchOptions struct {
  736. SearchContextSize string `json:"search_context_size,omitempty"`
  737. UserLocation json.RawMessage `json:"user_location,omitempty"`
  738. }
  739. // https://platform.openai.com/docs/api-reference/responses/create
  740. type OpenAIResponsesRequest struct {
  741. Model string `json:"model"`
  742. Input json.RawMessage `json:"input,omitempty"`
  743. Include json.RawMessage `json:"include,omitempty"`
  744. // 在后台运行推理,暂时还不支持依赖的接口
  745. // Background json.RawMessage `json:"background,omitempty"`
  746. Conversation json.RawMessage `json:"conversation,omitempty"`
  747. ContextManagement json.RawMessage `json:"context_management,omitempty"`
  748. Instructions json.RawMessage `json:"instructions,omitempty"`
  749. MaxOutputTokens uint `json:"max_output_tokens,omitempty"`
  750. TopLogProbs *int `json:"top_logprobs,omitempty"`
  751. Metadata json.RawMessage `json:"metadata,omitempty"`
  752. ParallelToolCalls json.RawMessage `json:"parallel_tool_calls,omitempty"`
  753. PreviousResponseID string `json:"previous_response_id,omitempty"`
  754. Reasoning *Reasoning `json:"reasoning,omitempty"`
  755. // ServiceTier specifies upstream service level and may affect billing.
  756. // This field is filtered by default and can be enabled via channel setting allow_service_tier.
  757. ServiceTier string `json:"service_tier,omitempty"`
  758. // Store controls whether upstream may store request/response data.
  759. // This field is allowed by default and can be disabled via channel setting disable_store.
  760. Store json.RawMessage `json:"store,omitempty"`
  761. PromptCacheKey json.RawMessage `json:"prompt_cache_key,omitempty"`
  762. PromptCacheRetention json.RawMessage `json:"prompt_cache_retention,omitempty"`
  763. // SafetyIdentifier carries client identity for policy abuse detection.
  764. // This field is filtered by default and can be enabled via channel setting allow_safety_identifier.
  765. SafetyIdentifier string `json:"safety_identifier,omitempty"`
  766. Stream bool `json:"stream,omitempty"`
  767. StreamOptions *StreamOptions `json:"stream_options,omitempty"`
  768. Temperature *float64 `json:"temperature,omitempty"`
  769. Text json.RawMessage `json:"text,omitempty"`
  770. ToolChoice json.RawMessage `json:"tool_choice,omitempty"`
  771. Tools json.RawMessage `json:"tools,omitempty"` // 需要处理的参数很少,MCP 参数太多不确定,所以用 map
  772. TopP *float64 `json:"top_p,omitempty"`
  773. Truncation string `json:"truncation,omitempty"`
  774. User string `json:"user,omitempty"`
  775. MaxToolCalls uint `json:"max_tool_calls,omitempty"`
  776. Prompt json.RawMessage `json:"prompt,omitempty"`
  777. // qwen
  778. EnableThinking json.RawMessage `json:"enable_thinking,omitempty"`
  779. // perplexity
  780. Preset json.RawMessage `json:"preset,omitempty"`
  781. }
  782. func (r *OpenAIResponsesRequest) GetTokenCountMeta() *types.TokenCountMeta {
  783. var fileMeta = make([]*types.FileMeta, 0)
  784. var texts = make([]string, 0)
  785. if r.Input != nil {
  786. inputs := r.ParseInput()
  787. for _, input := range inputs {
  788. if input.Type == "input_image" {
  789. if input.ImageUrl != "" {
  790. fileMeta = append(fileMeta, &types.FileMeta{
  791. FileType: types.FileTypeImage,
  792. Source: createFileSource(input.ImageUrl),
  793. Detail: input.Detail,
  794. })
  795. }
  796. } else if input.Type == "input_file" {
  797. if input.FileUrl != "" {
  798. fileMeta = append(fileMeta, &types.FileMeta{
  799. FileType: types.FileTypeFile,
  800. Source: createFileSource(input.FileUrl),
  801. })
  802. }
  803. } else {
  804. texts = append(texts, input.Text)
  805. }
  806. }
  807. }
  808. if len(r.Instructions) > 0 {
  809. texts = append(texts, string(r.Instructions))
  810. }
  811. if len(r.Metadata) > 0 {
  812. texts = append(texts, string(r.Metadata))
  813. }
  814. if len(r.Text) > 0 {
  815. texts = append(texts, string(r.Text))
  816. }
  817. if len(r.ToolChoice) > 0 {
  818. texts = append(texts, string(r.ToolChoice))
  819. }
  820. if len(r.Prompt) > 0 {
  821. texts = append(texts, string(r.Prompt))
  822. }
  823. if len(r.Tools) > 0 {
  824. texts = append(texts, string(r.Tools))
  825. }
  826. return &types.TokenCountMeta{
  827. CombineText: strings.Join(texts, "\n"),
  828. Files: fileMeta,
  829. MaxTokens: int(r.MaxOutputTokens),
  830. }
  831. }
  832. func (r *OpenAIResponsesRequest) IsStream(c *gin.Context) bool {
  833. return r.Stream
  834. }
  835. func (r *OpenAIResponsesRequest) SetModelName(modelName string) {
  836. if modelName != "" {
  837. r.Model = modelName
  838. }
  839. }
  840. func (r *OpenAIResponsesRequest) GetToolsMap() []map[string]any {
  841. var toolsMap []map[string]any
  842. if len(r.Tools) > 0 {
  843. _ = common.Unmarshal(r.Tools, &toolsMap)
  844. }
  845. return toolsMap
  846. }
  847. type Reasoning struct {
  848. Effort string `json:"effort,omitempty"`
  849. Summary string `json:"summary,omitempty"`
  850. }
  851. type Input struct {
  852. Type string `json:"type,omitempty"`
  853. Role string `json:"role,omitempty"`
  854. Content json.RawMessage `json:"content,omitempty"`
  855. }
  856. type MediaInput struct {
  857. Type string `json:"type"`
  858. Text string `json:"text,omitempty"`
  859. FileUrl string `json:"file_url,omitempty"`
  860. ImageUrl string `json:"image_url,omitempty"`
  861. Detail string `json:"detail,omitempty"` // 仅 input_image 有效
  862. }
  863. // ParseInput parses the Responses API `input` field into a normalized slice of MediaInput.
  864. // Reference implementation mirrors Message.ParseContent:
  865. // - input can be a string, treated as an input_text item
  866. // - input can be an array of objects with a `type` field
  867. // supported types: input_text, input_image, input_file
  868. func (r *OpenAIResponsesRequest) ParseInput() []MediaInput {
  869. if r.Input == nil {
  870. return nil
  871. }
  872. var mediaInputs []MediaInput
  873. // Try string first
  874. // if str, ok := common.GetJsonType(r.Input); ok {
  875. // inputs = append(inputs, MediaInput{Type: "input_text", Text: str})
  876. // return inputs
  877. // }
  878. if common.GetJsonType(r.Input) == "string" {
  879. var str string
  880. _ = common.Unmarshal(r.Input, &str)
  881. mediaInputs = append(mediaInputs, MediaInput{Type: "input_text", Text: str})
  882. return mediaInputs
  883. }
  884. // Try array of parts
  885. if common.GetJsonType(r.Input) == "array" {
  886. var inputs []Input
  887. _ = common.Unmarshal(r.Input, &inputs)
  888. for _, input := range inputs {
  889. if common.GetJsonType(input.Content) == "string" {
  890. var str string
  891. _ = common.Unmarshal(input.Content, &str)
  892. mediaInputs = append(mediaInputs, MediaInput{Type: "input_text", Text: str})
  893. }
  894. if common.GetJsonType(input.Content) == "array" {
  895. var array []any
  896. _ = common.Unmarshal(input.Content, &array)
  897. for _, itemAny := range array {
  898. // Already parsed MediaContent
  899. if media, ok := itemAny.(MediaInput); ok {
  900. mediaInputs = append(mediaInputs, media)
  901. continue
  902. }
  903. // Generic map
  904. item, ok := itemAny.(map[string]any)
  905. if !ok {
  906. continue
  907. }
  908. typeVal, ok := item["type"].(string)
  909. if !ok {
  910. continue
  911. }
  912. switch typeVal {
  913. case "input_text":
  914. text, _ := item["text"].(string)
  915. mediaInputs = append(mediaInputs, MediaInput{Type: "input_text", Text: text})
  916. case "input_image":
  917. // image_url may be string or object with url field
  918. var imageUrl string
  919. switch v := item["image_url"].(type) {
  920. case string:
  921. imageUrl = v
  922. case map[string]any:
  923. if url, ok := v["url"].(string); ok {
  924. imageUrl = url
  925. }
  926. }
  927. mediaInputs = append(mediaInputs, MediaInput{Type: "input_image", ImageUrl: imageUrl})
  928. case "input_file":
  929. // file_url may be string or object with url field
  930. var fileUrl string
  931. switch v := item["file_url"].(type) {
  932. case string:
  933. fileUrl = v
  934. case map[string]any:
  935. if url, ok := v["url"].(string); ok {
  936. fileUrl = url
  937. }
  938. }
  939. mediaInputs = append(mediaInputs, MediaInput{Type: "input_file", FileUrl: fileUrl})
  940. }
  941. }
  942. }
  943. }
  944. }
  945. return mediaInputs
  946. }