message_reasoning_test.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. package dto
  2. import (
  3. "testing"
  4. "github.com/QuantumNous/new-api/common"
  5. "github.com/stretchr/testify/require"
  6. "github.com/tidwall/gjson"
  7. )
  8. // TestMessageReasoningContentPreservesEmptyString verifies that an explicitly
  9. // set empty reasoning_content string survives the JSON round-trip.
  10. //
  11. // This is critical for the request-forwarding path (non-passThrough mode):
  12. // the gateway unmarshals the client request into GeneralOpenAIRequest, then
  13. // re-marshals it before sending upstream. If Message.ReasoningContent were
  14. // `string` + `omitempty` (the old type), the empty string would be silently
  15. // dropped, causing the upstream to never receive the field.
  16. //
  17. // With the fix (`*string` + `omitempty`), nil = absent, &"" = explicit empty.
  18. func TestMessageReasoningContentPreservesEmptyString(t *testing.T) {
  19. raw := []byte(`{
  20. "role": "assistant",
  21. "content": "Hello",
  22. "reasoning_content": "",
  23. "reasoning": ""
  24. }`)
  25. var msg Message
  26. err := common.Unmarshal(raw, &msg)
  27. require.NoError(t, err)
  28. // Pointers must be non-nil: the field was explicitly set to ""
  29. require.NotNil(t, msg.ReasoningContent, "reasoning_content should be non-nil when explicitly set to empty string")
  30. require.NotNil(t, msg.Reasoning, "reasoning should be non-nil when explicitly set to empty string")
  31. require.Equal(t, "", *msg.ReasoningContent)
  32. require.Equal(t, "", *msg.Reasoning)
  33. // Re-marshal — the fields must still be present in the output JSON
  34. encoded, err := common.Marshal(msg)
  35. require.NoError(t, err)
  36. require.True(t, gjson.GetBytes(encoded, "reasoning_content").Exists(),
  37. "reasoning_content should exist in re-marshaled JSON when explicitly set to empty string")
  38. require.True(t, gjson.GetBytes(encoded, "reasoning").Exists(),
  39. "reasoning should exist in re-marshaled JSON when explicitly set to empty string")
  40. require.Equal(t, "", gjson.GetBytes(encoded, "reasoning_content").String())
  41. require.Equal(t, "", gjson.GetBytes(encoded, "reasoning").String())
  42. }
  43. // TestMessageReasoningContentOmitsAbsentField verifies that when
  44. // reasoning_content / reasoning are absent from the input JSON, they remain
  45. // absent after a round-trip (nil pointer → omitted by omitempty).
  46. func TestMessageReasoningContentOmitsAbsentField(t *testing.T) {
  47. raw := []byte(`{
  48. "role": "assistant",
  49. "content": "Hello"
  50. }`)
  51. var msg Message
  52. err := common.Unmarshal(raw, &msg)
  53. require.NoError(t, err)
  54. // Pointers must be nil: the fields were not present in the input
  55. require.Nil(t, msg.ReasoningContent)
  56. require.Nil(t, msg.Reasoning)
  57. // Re-marshal — the fields must NOT appear in the output JSON
  58. encoded, err := common.Marshal(msg)
  59. require.NoError(t, err)
  60. require.False(t, gjson.GetBytes(encoded, "reasoning_content").Exists(),
  61. "reasoning_content should not exist in re-marshaled JSON when absent from input")
  62. require.False(t, gjson.GetBytes(encoded, "reasoning").Exists(),
  63. "reasoning should not exist in re-marshaled JSON when absent from input")
  64. }
  65. // TestMessageGetReasoningContent verifies the GetReasoningContent helper
  66. // method that is used in token-counting code paths.
  67. func TestMessageGetReasoningContent(t *testing.T) {
  68. t.Run("both nil returns empty", func(t *testing.T) {
  69. msg := Message{Role: "assistant"}
  70. require.Equal(t, "", msg.GetReasoningContent())
  71. })
  72. t.Run("ReasoningContent takes priority", func(t *testing.T) {
  73. rc := "thinking..."
  74. r := "should be ignored"
  75. msg := Message{ReasoningContent: &rc, Reasoning: &r}
  76. require.Equal(t, "thinking...", msg.GetReasoningContent())
  77. })
  78. t.Run("falls back to Reasoning when ReasoningContent is nil", func(t *testing.T) {
  79. r := "fallback reasoning"
  80. msg := Message{Reasoning: &r}
  81. require.Equal(t, "fallback reasoning", msg.GetReasoningContent())
  82. })
  83. t.Run("empty string values returned correctly", func(t *testing.T) {
  84. empty := ""
  85. msg := Message{ReasoningContent: &empty}
  86. require.Equal(t, "", msg.GetReasoningContent())
  87. })
  88. }