| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- package apicompat
- import (
- "encoding/json"
- "testing"
- "github.com/stretchr/testify/require"
- )
- // 上游响应中的 service_tier 必须如实回传,不被重写/丢弃:
- // 非流式(ResponsesResponse → ChatCompletionsResponse)与流式 chunk 均覆盖。
- func TestResponsesToChatCompletions_PreservesUpstreamServiceTier(t *testing.T) {
- resp := &ResponsesResponse{
- ID: "resp_1",
- Object: "response",
- Model: "gpt-5.5",
- Status: "completed",
- ServiceTier: "priority",
- Output: []ResponsesOutput{{
- Type: "message",
- Role: "assistant",
- Content: []ResponsesContentPart{{
- Type: "output_text",
- Text: "hi",
- }},
- }},
- Usage: &ResponsesUsage{InputTokens: 1, OutputTokens: 1},
- }
- chat := ResponsesToChatCompletions(resp, "gpt-5.5")
- require.Equal(t, "priority", chat.ServiceTier)
- // 序列化后字段仍在(omitempty 不丢非空值)。
- raw, err := json.Marshal(chat)
- require.NoError(t, err)
- require.Contains(t, string(raw), `"service_tier":"priority"`)
- }
- func TestResponsesToChatCompletions_OmitsMissingServiceTier(t *testing.T) {
- resp := &ResponsesResponse{ID: "resp_1", Model: "gpt-5.5", Status: "completed"}
- chat := ResponsesToChatCompletions(resp, "gpt-5.5")
- require.Empty(t, chat.ServiceTier)
- raw, err := json.Marshal(chat)
- require.NoError(t, err)
- require.NotContains(t, string(raw), "service_tier")
- }
- func TestResponsesEventToChatChunks_PreservesUpstreamServiceTier(t *testing.T) {
- state := NewResponsesEventToChatState()
- state.IncludeUsage = true
- created := &ResponsesStreamEvent{Type: "response.created"}
- require.NoError(t, json.Unmarshal([]byte(`{"type":"response.created","response":{"id":"resp_s1","model":"gpt-5.5","service_tier":"priority","status":"in_progress"}}`), created))
- chunks := ResponsesEventToChatChunks(created, state)
- require.NotEmpty(t, chunks)
- for _, chunk := range chunks {
- require.Equal(t, "priority", chunk.ServiceTier)
- }
- // 后续 delta chunk 继续携带(OpenAI 流式 chunk 的 service_tier 语义)。
- delta := &ResponsesStreamEvent{Type: "response.output_text.delta", Delta: "hi"}
- chunks = ResponsesEventToChatChunks(delta, state)
- require.NotEmpty(t, chunks)
- require.Equal(t, "priority", chunks[0].ServiceTier)
- // 终止事件同样携带。
- completed := &ResponsesStreamEvent{Type: "response.completed", Response: &ResponsesResponse{
- ID: "resp", Model: "gpt-5.5", Status: "completed",
- Usage: &ResponsesUsage{InputTokens: 1, OutputTokens: 1},
- }}
- chunks = ResponsesEventToChatChunks(completed, state)
- require.NotEmpty(t, chunks)
- for _, chunk := range chunks {
- require.Equal(t, "priority", chunk.ServiceTier)
- }
- }
- func TestResponsesEventToChatChunks_NoServiceTierStaysClean(t *testing.T) {
- state := NewResponsesEventToChatState()
- created := &ResponsesStreamEvent{Type: "response.created", Response: &ResponsesResponse{ID: "resp", Model: "gpt-5.5"}}
- chunks := ResponsesEventToChatChunks(created, state)
- require.NotEmpty(t, chunks)
- require.Empty(t, chunks[0].ServiceTier)
- raw, err := json.Marshal(chunks[0])
- require.NoError(t, err)
- require.NotContains(t, string(raw), "service_tier")
- }
- // 上游 JSON 反序列化时 service_tier 进入 ResponsesResponse(缓冲桥读取链路)。
- func TestResponsesResponse_UnmarshalPreservesServiceTier(t *testing.T) {
- var resp ResponsesResponse
- require.NoError(t, json.Unmarshal([]byte(`{"id":"resp_1","object":"response","model":"gpt-5.5","status":"completed","service_tier":"flex","output":[]}`), &resp))
- require.Equal(t, "flex", resp.ServiceTier)
- }
- // ---------------------------------------------------------------------------
- // 反向转换(Chat-only fallback):CC 响应/流 chunk 的 service_tier 保留到
- // Responses 形态,客户端与计费都能拿到上游回显。
- // ---------------------------------------------------------------------------
- func TestChatCompletionsResponseToResponses_PreservesServiceTier(t *testing.T) {
- cc := &ChatCompletionsResponse{
- ID: "chatcmpl-1",
- Model: "gpt-5.5",
- ServiceTier: "default",
- Choices: []ChatChoice{{
- Index: 0,
- Message: ChatMessage{Role: "assistant", Content: json.RawMessage(`"hi"`)},
- FinishReason: "stop",
- }},
- Usage: &ChatUsage{PromptTokens: 1, CompletionTokens: 1, TotalTokens: 2},
- }
- resp := ChatCompletionsResponseToResponses(cc, "gpt-5.5", nil, nil, false, nil)
- require.Equal(t, "default", resp.ServiceTier)
- raw, err := json.Marshal(resp)
- require.NoError(t, err)
- require.Contains(t, string(raw), `"service_tier":"default"`)
- }
- func TestChatCompletionsResponseToResponses_NilRespOmitsServiceTier(t *testing.T) {
- resp := ChatCompletionsResponseToResponses(nil, "gpt-5.5", nil, nil, false, nil)
- require.Empty(t, resp.ServiceTier)
- }
- func TestChatCompletionsChunkToResponsesEvents_PreservesServiceTier(t *testing.T) {
- state := NewChatCompletionsToResponsesStreamState("gpt-5.5")
- chunk := &ChatCompletionsChunk{
- ID: "chatcmpl-2",
- Model: "gpt-5.5",
- ServiceTier: "flex",
- Choices: []ChatChunkChoice{{
- Index: 0,
- Delta: ChatDelta{Content: strPtr("hi")},
- }},
- }
- events := ChatCompletionsChunkToResponsesEvents(chunk, state)
- require.NotEmpty(t, events)
- // response.created 携带 service_tier。
- created := findEvent(events, "response.created")
- require.NotNil(t, created)
- require.NotNil(t, created.Response)
- require.Equal(t, "flex", created.Response.ServiceTier)
- // 终止事件同样携带。
- final := FinalizeChatCompletionsResponsesStream(state)
- completed := findEvent(final, "response.completed")
- require.NotNil(t, completed)
- require.NotNil(t, completed.Response)
- require.Equal(t, "flex", completed.Response.ServiceTier)
- }
- func TestChatCompletionsChunkToResponsesEvents_NoTierStaysClean(t *testing.T) {
- state := NewChatCompletionsToResponsesStreamState("gpt-5.5")
- chunk := &ChatCompletionsChunk{ID: "chatcmpl-3", Model: "gpt-5.5"}
- events := ChatCompletionsChunkToResponsesEvents(chunk, state)
- created := findEvent(events, "response.created")
- require.NotNil(t, created)
- require.Empty(t, created.Response.ServiceTier)
- raw, err := json.Marshal(created)
- require.NoError(t, err)
- require.NotContains(t, string(raw), "service_tier")
- }
- func findEvent(events []ResponsesStreamEvent, eventType string) *ResponsesStreamEvent {
- for i := range events {
- if events[i].Type == eventType {
- return &events[i]
- }
- }
- return nil
- }
|