openai_ws_forwarder_hotpath_optimization_test.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package service
  2. import (
  3. "net/http"
  4. "testing"
  5. "github.com/stretchr/testify/require"
  6. )
  7. func TestParseOpenAIWSEventEnvelope(t *testing.T) {
  8. eventType, responseID, response := parseOpenAIWSEventEnvelope([]byte(`{"type":"response.completed","response":{"id":"resp_1","model":"gpt-5.1"}}`))
  9. require.Equal(t, "response.completed", eventType)
  10. require.Equal(t, "resp_1", responseID)
  11. require.True(t, response.Exists())
  12. require.Equal(t, `{"id":"resp_1","model":"gpt-5.1"}`, response.Raw)
  13. eventType, responseID, response = parseOpenAIWSEventEnvelope([]byte(`{"type":"response.delta","id":"evt_1"}`))
  14. require.Equal(t, "response.delta", eventType)
  15. require.Equal(t, "evt_1", responseID)
  16. require.False(t, response.Exists())
  17. }
  18. func TestParseOpenAIWSResponseUsageFromCompletedEvent(t *testing.T) {
  19. usage := &OpenAIUsage{}
  20. parseOpenAIWSResponseUsageFromCompletedEvent(
  21. []byte(`{"type":"response.completed","response":{"usage":{"input_tokens":11,"output_tokens":7,"input_tokens_details":{"cached_tokens":3}}}}`),
  22. usage,
  23. )
  24. require.Equal(t, 11, usage.InputTokens)
  25. require.Equal(t, 7, usage.OutputTokens)
  26. require.Equal(t, 3, usage.CacheReadInputTokens)
  27. parseOpenAIWSResponseUsageFromCompletedEvent(
  28. []byte(`{"type":"response.completed","response":{"usage":{"prompt_tokens":19,"completion_tokens":5,"prompt_tokens_details":{"cached_tokens":4}}}}`),
  29. usage,
  30. )
  31. require.Equal(t, 19, usage.InputTokens)
  32. require.Equal(t, 5, usage.OutputTokens)
  33. require.Equal(t, 4, usage.CacheReadInputTokens)
  34. parseOpenAIWSResponseUsageFromCompletedEvent(
  35. []byte(`{"type":"response.completed","response":{"usage":{"input_tokens":0,"output_tokens":0,"input_tokens_details":{"cached_tokens":0}}}}`),
  36. usage,
  37. )
  38. require.Equal(t, OpenAIUsage{InputTokens: 19, OutputTokens: 5, CacheReadInputTokens: 4}, *usage)
  39. parseOpenAIWSResponseUsageFromCompletedEvent(
  40. []byte(`{"type":"response.failed","response":{"usage":{"input_tokens":3,"output_tokens":0,"input_tokens_details":{"cached_tokens":0}}}}`),
  41. usage,
  42. )
  43. require.Equal(t, OpenAIUsage{InputTokens: 3}, *usage)
  44. }
  45. func TestOpenAIWSEventShouldParseUsageTerminalEvents(t *testing.T) {
  46. t.Parallel()
  47. for _, eventType := range []string{
  48. "response.completed",
  49. "response.done",
  50. "response.failed",
  51. "response.incomplete",
  52. "response.cancelled",
  53. "response.canceled",
  54. } {
  55. require.True(t, openAIWSEventShouldParseUsage(eventType), eventType)
  56. require.True(t, openAIWSEventShouldParseUsage(" "+eventType+" "), eventType)
  57. }
  58. require.False(t, openAIWSEventShouldParseUsage("response.output_text.delta"))
  59. require.True(t, openAIWSEventShouldParseUsage("response.output_text.done"))
  60. require.False(t, openAIWSEventShouldParseUsage(""))
  61. require.False(t, openAIWSMessageShouldParseUsage("response.in_progress", []byte(`{"type":"response.in_progress"}`)))
  62. require.True(t, openAIWSMessageShouldParseUsage("response.in_progress", []byte(`{"type":"response.in_progress","usage":{}}`)))
  63. require.False(t, openAIWSMessageShouldParseUsage("response.output_text.delta", []byte(`{"type":"response.output_text.delta","usage":{}}`)))
  64. }
  65. func TestOpenAIWSErrorEventHelpers_ConsistentWithWrapper(t *testing.T) {
  66. message := []byte(`{"type":"error","error":{"type":"invalid_request_error","code":"invalid_request","message":"invalid input"}}`)
  67. codeRaw, errTypeRaw, errMsgRaw := parseOpenAIWSErrorEventFields(message)
  68. wrappedReason, wrappedRecoverable := classifyOpenAIWSErrorEvent(message)
  69. rawReason, rawRecoverable := classifyOpenAIWSErrorEventFromRaw(codeRaw, errTypeRaw, errMsgRaw)
  70. require.Equal(t, wrappedReason, rawReason)
  71. require.Equal(t, wrappedRecoverable, rawRecoverable)
  72. wrappedStatus := openAIWSErrorHTTPStatus(message)
  73. rawStatus := openAIWSErrorHTTPStatusFromRaw(codeRaw, errTypeRaw)
  74. require.Equal(t, wrappedStatus, rawStatus)
  75. require.Equal(t, http.StatusBadRequest, rawStatus)
  76. wrappedCode, wrappedType, wrappedMsg := summarizeOpenAIWSErrorEventFields(message)
  77. rawCode, rawType, rawMsg := summarizeOpenAIWSErrorEventFieldsFromRaw(codeRaw, errTypeRaw, errMsgRaw)
  78. require.Equal(t, wrappedCode, rawCode)
  79. require.Equal(t, wrappedType, rawType)
  80. require.Equal(t, wrappedMsg, rawMsg)
  81. }
  82. func TestOpenAIWSMessageLikelyContainsToolCalls(t *testing.T) {
  83. require.False(t, openAIWSMessageLikelyContainsToolCalls([]byte(`{"type":"response.output_text.delta","delta":"hello"}`)))
  84. require.True(t, openAIWSMessageLikelyContainsToolCalls([]byte(`{"type":"response.output_item.added","item":{"tool_calls":[{"id":"tc1"}]}}`)))
  85. require.True(t, openAIWSMessageLikelyContainsToolCalls([]byte(`{"type":"response.output_item.added","item":{"type":"function_call"}}`)))
  86. }
  87. func TestReplaceOpenAIWSMessageModel_OptimizedStillCorrect(t *testing.T) {
  88. noModel := []byte(`{"type":"response.output_text.delta","delta":"hello"}`)
  89. require.Equal(t, string(noModel), string(replaceOpenAIWSMessageModel(noModel, "gpt-5.1", "custom-model")))
  90. rootOnly := []byte(`{"type":"response.created","model":"gpt-5.1"}`)
  91. require.Equal(t, `{"type":"response.created","model":"custom-model"}`, string(replaceOpenAIWSMessageModel(rootOnly, "gpt-5.1", "custom-model")))
  92. responseOnly := []byte(`{"type":"response.completed","response":{"model":"gpt-5.1"}}`)
  93. require.Equal(t, `{"type":"response.completed","response":{"model":"custom-model"}}`, string(replaceOpenAIWSMessageModel(responseOnly, "gpt-5.1", "custom-model")))
  94. both := []byte(`{"model":"gpt-5.1","response":{"model":"gpt-5.1"}}`)
  95. require.Equal(t, `{"model":"custom-model","response":{"model":"custom-model"}}`, string(replaceOpenAIWSMessageModel(both, "gpt-5.1", "custom-model")))
  96. }