openai_sse_json_documents.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package service
  2. import (
  3. "bufio"
  4. "bytes"
  5. "encoding/json"
  6. "io"
  7. "strings"
  8. )
  9. const (
  10. maxOpenAIConcatenatedJSONDocuments = 16
  11. maxOpenAIConcatenatedJSONBytes = 16 * 1024 * 1024
  12. )
  13. // splitOpenAIConcatenatedJSONDocuments recognizes the narrow corruption shape
  14. // produced when multiple complete Responses events arrive in one transport
  15. // message. Other malformed payloads are left untouched for normal error paths.
  16. func splitOpenAIConcatenatedJSONDocuments(payload []byte) ([][]byte, bool) {
  17. payload = bytes.TrimSpace(payload)
  18. if len(payload) == 0 || len(payload) > maxOpenAIConcatenatedJSONBytes || json.Valid(payload) {
  19. return nil, false
  20. }
  21. decoder := json.NewDecoder(bytes.NewReader(payload))
  22. documents := make([][]byte, 0, 2)
  23. for {
  24. var raw json.RawMessage
  25. err := decoder.Decode(&raw)
  26. if err != nil {
  27. if err == io.EOF && len(documents) > 1 {
  28. return documents, true
  29. }
  30. return nil, false
  31. }
  32. raw = bytes.TrimSpace(raw)
  33. var envelope struct {
  34. Type string `json:"type"`
  35. }
  36. if err := json.Unmarshal(raw, &envelope); err != nil {
  37. return nil, false
  38. }
  39. eventType := strings.TrimSpace(envelope.Type)
  40. if eventType == "" || strings.ContainsAny(eventType, "\r\n") {
  41. return nil, false
  42. }
  43. if len(documents) == maxOpenAIConcatenatedJSONDocuments {
  44. return nil, false
  45. }
  46. documents = append(documents, raw)
  47. }
  48. }
  49. type openAISSEJSONDocumentScanner struct {
  50. scanner *bufio.Scanner
  51. pending []string
  52. current string
  53. }
  54. func newOpenAISSEJSONDocumentScanner(scanner *bufio.Scanner) *openAISSEJSONDocumentScanner {
  55. return &openAISSEJSONDocumentScanner{scanner: scanner}
  56. }
  57. func (s *openAISSEJSONDocumentScanner) Scan() bool {
  58. if len(s.pending) > 0 {
  59. s.current = s.pending[0]
  60. s.pending = s.pending[1:]
  61. return true
  62. }
  63. if s.scanner == nil || !s.scanner.Scan() {
  64. return false
  65. }
  66. line := s.scanner.Text()
  67. data, ok := extractOpenAISSEDataLine(line)
  68. if !ok {
  69. s.current = line
  70. return true
  71. }
  72. if len(data) > maxOpenAIConcatenatedJSONBytes {
  73. s.current = line
  74. return true
  75. }
  76. documents, repaired := splitOpenAIConcatenatedJSONDocuments([]byte(data))
  77. if !repaired {
  78. s.current = line
  79. return true
  80. }
  81. expanded := make([]string, 0, len(documents)*3)
  82. for i, document := range documents {
  83. if i > 0 {
  84. var envelope struct {
  85. Type string `json:"type"`
  86. }
  87. _ = json.Unmarshal(document, &envelope)
  88. expanded = append(expanded, "event: "+strings.TrimSpace(envelope.Type))
  89. }
  90. expanded = append(expanded, "data: "+string(document), "")
  91. }
  92. s.current = expanded[0]
  93. s.pending = expanded[1:]
  94. return true
  95. }
  96. func (s *openAISSEJSONDocumentScanner) Text() string {
  97. return s.current
  98. }
  99. func (s *openAISSEJSONDocumentScanner) Err() error {
  100. if s.scanner == nil {
  101. return nil
  102. }
  103. return s.scanner.Err()
  104. }