openai_gateway_grok_tool_protocol.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. package service
  2. import (
  3. "bufio"
  4. "bytes"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "strings"
  9. "github.com/Wei-Shaw/sub2api/internal/pkg/apicompat"
  10. "github.com/gin-gonic/gin"
  11. "github.com/tidwall/gjson"
  12. )
  13. const grokResponsesClientToolMappingContextKey = "grok_responses_client_tool_mapping"
  14. func adaptResponsesClientToolsForFunctionUpstream(body []byte, upstream string) ([]byte, apicompat.ResponsesClientToolMapping, error) {
  15. return adaptResponsesClientToolsForFunctionUpstreamWithMapping(
  16. body,
  17. upstream,
  18. apicompat.ResponsesClientToolMapping{},
  19. )
  20. }
  21. func adaptResponsesClientToolsForFunctionUpstreamWithMapping(
  22. body []byte,
  23. upstream string,
  24. inherited apicompat.ResponsesClientToolMapping,
  25. inheritedLoweredTools ...[]any,
  26. ) ([]byte, apicompat.ResponsesClientToolMapping, error) {
  27. decoder := json.NewDecoder(bytes.NewReader(body))
  28. decoder.UseNumber()
  29. var requestBody map[string]any
  30. if err := decoder.Decode(&requestBody); err != nil {
  31. return body, apicompat.ResponsesClientToolMapping{}, fmt.Errorf("decode %s Responses client tools: %w", upstream, err)
  32. }
  33. mapping, changed, err := apicompat.AdaptResponsesClientToolsWithInheritedMapping(requestBody, inherited, inheritedLoweredTools...)
  34. if err != nil {
  35. return body, apicompat.ResponsesClientToolMapping{}, err
  36. }
  37. if !changed {
  38. return body, mapping, nil
  39. }
  40. rebuilt, err := marshalOpenAIUpstreamJSON(requestBody)
  41. if err != nil {
  42. return body, apicompat.ResponsesClientToolMapping{}, fmt.Errorf("encode %s Responses client tools: %w", upstream, err)
  43. }
  44. return rebuilt, mapping, nil
  45. }
  46. func adaptGrokResponsesClientTools(body []byte) ([]byte, apicompat.ResponsesClientToolMapping, error) {
  47. return adaptResponsesClientToolsForFunctionUpstream(body, "Grok")
  48. }
  49. func hasResponsesClientToolMapping(mapping apicompat.ResponsesClientToolMapping) bool {
  50. return len(mapping.CustomTools) > 0 || mapping.ToolSearch || len(mapping.NamespaceTools) > 0
  51. }
  52. func hasGrokResponsesClientToolMapping(mapping apicompat.ResponsesClientToolMapping) bool {
  53. return hasResponsesClientToolMapping(mapping)
  54. }
  55. func setGrokResponsesClientToolMapping(c *gin.Context, mapping apicompat.ResponsesClientToolMapping) {
  56. if c == nil {
  57. return
  58. }
  59. if !hasGrokResponsesClientToolMapping(mapping) {
  60. clearGrokResponsesClientToolMapping(c)
  61. return
  62. }
  63. c.Set(grokResponsesClientToolMappingContextKey, mapping)
  64. }
  65. func clearGrokResponsesClientToolMapping(c *gin.Context) {
  66. if c == nil {
  67. return
  68. }
  69. if _, exists := c.Get(grokResponsesClientToolMappingContextKey); !exists {
  70. return
  71. }
  72. c.Set(grokResponsesClientToolMappingContextKey, apicompat.ResponsesClientToolMapping{})
  73. }
  74. func grokResponsesClientToolMapping(c *gin.Context) (apicompat.ResponsesClientToolMapping, bool) {
  75. if c == nil {
  76. return apicompat.ResponsesClientToolMapping{}, false
  77. }
  78. value, ok := c.Get(grokResponsesClientToolMappingContextKey)
  79. if !ok {
  80. return apicompat.ResponsesClientToolMapping{}, false
  81. }
  82. mapping, ok := value.(apicompat.ResponsesClientToolMapping)
  83. return mapping, ok && hasGrokResponsesClientToolMapping(mapping)
  84. }
  85. func restoreGrokResponsesClientToolPayload(c *gin.Context, payload []byte) ([]byte, error) {
  86. mapping, ok := grokResponsesClientToolMapping(c)
  87. if !ok || !bytes.Contains(payload, []byte(`"function_call"`)) || !json.Valid(payload) {
  88. return payload, nil
  89. }
  90. restored, _, err := apicompat.RestoreResponsesClientToolPayload(payload, mapping)
  91. return restored, err
  92. }
  93. type responsesClientToolStreamBody struct {
  94. *io.PipeReader
  95. source io.Closer
  96. }
  97. func (b *responsesClientToolStreamBody) Close() error {
  98. readerErr := b.PipeReader.Close()
  99. sourceErr := b.source.Close()
  100. if readerErr != nil {
  101. return readerErr
  102. }
  103. return sourceErr
  104. }
  105. func newResponsesClientToolStreamBody(
  106. source io.ReadCloser,
  107. mapping apicompat.ResponsesClientToolMapping,
  108. maxLineSize int,
  109. ) io.ReadCloser {
  110. reader, writer := io.Pipe()
  111. body := &responsesClientToolStreamBody{PipeReader: reader, source: source}
  112. go transformResponsesClientToolStream(source, writer, mapping, maxLineSize)
  113. return body
  114. }
  115. func newGrokResponsesClientToolStreamBody(
  116. source io.ReadCloser,
  117. mapping apicompat.ResponsesClientToolMapping,
  118. maxLineSize int,
  119. ) io.ReadCloser {
  120. return newResponsesClientToolStreamBody(source, mapping, maxLineSize)
  121. }
  122. func transformResponsesClientToolStream(
  123. source io.ReadCloser,
  124. destination *io.PipeWriter,
  125. mapping apicompat.ResponsesClientToolMapping,
  126. maxLineSize int,
  127. ) {
  128. defer func() { _ = source.Close() }()
  129. if maxLineSize <= 0 {
  130. maxLineSize = defaultMaxLineSize
  131. }
  132. scanner := bufio.NewScanner(source)
  133. scanBuf := getSSEScannerBuf64K()
  134. defer putSSEScannerBuf64K(scanBuf)
  135. scanner.Buffer(scanBuf[:0], maxLineSize)
  136. documents := newOpenAISSEJSONDocumentScanner(scanner)
  137. restorer := apicompat.NewResponsesClientToolStreamRestorer(mapping)
  138. buffered := bufio.NewWriterSize(destination, 4*1024)
  139. pendingFields := make([]string, 0, 2)
  140. frameHadEventField := false
  141. frameEmitted := false
  142. writeLine := func(line string) error {
  143. if _, err := buffered.WriteString(line); err != nil {
  144. return err
  145. }
  146. return buffered.WriteByte('\n')
  147. }
  148. writePendingFields := func(payload []byte, includeNonEvent bool) error {
  149. eventType := strings.TrimSpace(gjson.GetBytes(payload, "type").String())
  150. for _, field := range pendingFields {
  151. if _, isEvent := extractOpenAISSEEventLine(field); isEvent {
  152. if eventType != "" {
  153. if err := writeLine("event: " + eventType); err != nil {
  154. return err
  155. }
  156. } else if err := writeLine(field); err != nil {
  157. return err
  158. }
  159. continue
  160. }
  161. if includeNonEvent {
  162. if err := writeLine(field); err != nil {
  163. return err
  164. }
  165. }
  166. }
  167. return nil
  168. }
  169. writePayloads := func(payloads [][]byte) error {
  170. for index, payload := range payloads {
  171. if index == 0 {
  172. if err := writePendingFields(payload, true); err != nil {
  173. return err
  174. }
  175. } else if frameHadEventField {
  176. eventType := strings.TrimSpace(gjson.GetBytes(payload, "type").String())
  177. if eventType != "" {
  178. if err := writeLine("event: " + eventType); err != nil {
  179. return err
  180. }
  181. }
  182. }
  183. if err := writeLine("data: " + string(payload)); err != nil {
  184. return err
  185. }
  186. if err := writeLine(""); err != nil {
  187. return err
  188. }
  189. }
  190. return buffered.Flush()
  191. }
  192. for documents.Scan() {
  193. line := documents.Text()
  194. data, isData := extractOpenAISSEDataLine(line)
  195. if isData {
  196. payload := []byte(data)
  197. payloads := [][]byte{payload}
  198. if json.Valid(payload) {
  199. var err error
  200. payloads, _, err = restorer.RestoreEvent(payload)
  201. if err != nil {
  202. _ = buffered.Flush()
  203. _ = destination.CloseWithError(fmt.Errorf("restore Responses client tool event: %w", err))
  204. return
  205. }
  206. }
  207. if err := writePayloads(payloads); err != nil {
  208. _ = destination.CloseWithError(err)
  209. return
  210. }
  211. pendingFields = pendingFields[:0]
  212. frameHadEventField = false
  213. frameEmitted = true
  214. continue
  215. }
  216. if line == "" {
  217. if !frameEmitted {
  218. for _, field := range pendingFields {
  219. if err := writeLine(field); err != nil {
  220. _ = destination.CloseWithError(err)
  221. return
  222. }
  223. }
  224. if len(pendingFields) > 0 {
  225. if err := writeLine(""); err != nil {
  226. _ = destination.CloseWithError(err)
  227. return
  228. }
  229. if err := buffered.Flush(); err != nil {
  230. _ = destination.CloseWithError(err)
  231. return
  232. }
  233. }
  234. }
  235. pendingFields = pendingFields[:0]
  236. frameHadEventField = false
  237. frameEmitted = false
  238. continue
  239. }
  240. if _, isEvent := extractOpenAISSEEventLine(line); isEvent {
  241. frameHadEventField = true
  242. }
  243. pendingFields = append(pendingFields, line)
  244. }
  245. for _, field := range pendingFields {
  246. if err := writeLine(field); err != nil {
  247. _ = destination.CloseWithError(err)
  248. return
  249. }
  250. }
  251. if err := buffered.Flush(); err != nil {
  252. _ = destination.CloseWithError(err)
  253. return
  254. }
  255. if err := documents.Err(); err != nil {
  256. _ = destination.CloseWithError(err)
  257. return
  258. }
  259. _ = destination.Close()
  260. }