Skip to content

Commit b7bf103

Browse files
test: improve OpenInference image generation tests
Signed-off-by: Hrushikesh Patil <[email protected]>
1 parent 582ad9c commit b7bf103

File tree

1 file changed

+137
-50
lines changed

1 file changed

+137
-50
lines changed

internal/tracing/openinference/openai/image_generation_test.go

Lines changed: 137 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,80 +6,167 @@
66
package openai
77

88
import (
9+
"encoding/json"
910
"strconv"
1011
"testing"
1112

1213
"github.com/stretchr/testify/require"
1314
"go.opentelemetry.io/otel/attribute"
15+
"go.opentelemetry.io/otel/codes"
1416
"go.opentelemetry.io/otel/sdk/trace"
15-
"go.opentelemetry.io/otel/sdk/trace/tracetest"
17+
oteltrace "go.opentelemetry.io/otel/trace"
1618

19+
"github.com/envoyproxy/ai-gateway/internal/testing/testotel"
1720
tracing "github.com/envoyproxy/ai-gateway/internal/tracing/api"
1821
openaisdk "github.com/openai/openai-go/v2"
1922
openaiparam "github.com/openai/openai-go/v2/packages/param"
2023
)
2124

22-
func TestImageGenerationRecorder_RequestAttributes_SDK(t *testing.T) {
23-
exporter := tracetest.NewInMemoryExporter()
24-
tp := trace.NewTracerProvider(trace.WithSyncer(exporter))
25-
tr := tp.Tracer("test")
26-
27-
recorder := NewImageGenerationRecorder(nil) // default config
28-
29-
params := &openaisdk.ImageGenerateParams{
25+
var (
26+
// Test data constants following chat completion pattern
27+
basicImageReq = &openaisdk.ImageGenerateParams{
3028
Model: openaisdk.ImageModelGPTImage1,
3129
Prompt: "a hummingbird",
3230
Size: openaisdk.ImageGenerateParamsSize1024x1024,
3331
Quality: openaisdk.ImageGenerateParamsQualityHigh,
3432
ResponseFormat: openaisdk.ImageGenerateParamsResponseFormatB64JSON,
35-
N: openaiparam.NewOpt[int64](2),
33+
N: openaiparam.NewOpt[int64](1),
3634
}
35+
basicImageReqBody = mustJSON(basicImageReq)
36+
37+
basicImageResp = &openaisdk.ImagesResponse{
38+
Data: []openaisdk.Image{{URL: "https://example.com/img.png"}},
39+
Size: openaisdk.ImagesResponseSize1024x1024,
40+
Usage: openaisdk.ImagesResponseUsage{
41+
InputTokens: 8,
42+
OutputTokens: 1056,
43+
TotalTokens: 1064,
44+
},
45+
}
46+
basicImageRespBody = mustJSON(basicImageResp)
47+
)
3748

38-
spanName, opts := recorder.StartParams(params, []byte("{}"))
39-
require.Equal(t, "ImageGeneration", spanName)
40-
41-
_, span := tr.Start(t.Context(), spanName, opts...)
42-
recorder.RecordRequest(span, params, []byte(`{"prompt":"a hummingbird"}`))
43-
span.End()
44-
45-
spans := exporter.GetSpans()
46-
require.Len(t, spans, 1)
47-
got := spans[0]
48-
49-
// Verify a subset of attributes were recorded
50-
attrs := attributesToMap(got.Attributes)
51-
require.Equal(t, "openai", attrs["llm.system"]) // LLMSystemOpenAI
52-
require.Equal(t, string(openaisdk.ImageModelGPTImage1), attrs["llm.model_name"]) // model
53-
require.Equal(t, "{\"prompt\":\"a hummingbird\"}", attrs["input.value"]) // input body json
54-
require.Equal(t, "image_generation", attrs["gen_ai.operation.name"]) // operation name
55-
require.Equal(t, "1024x1024", attrs["gen_ai.image.size"]) // size
56-
require.Equal(t, "high", attrs["gen_ai.image.quality"]) // quality
57-
require.Equal(t, "b64_json", attrs["gen_ai.image.response_format"]) // response_format
58-
require.Equal(t, "2", attrs["gen_ai.image.n"]) // n
49+
func TestImageGenerationRecorder_StartParams(t *testing.T) {
50+
tests := []struct {
51+
name string
52+
req *openaisdk.ImageGenerateParams
53+
reqBody []byte
54+
expectedSpanName string
55+
}{
56+
{
57+
name: "basic request",
58+
req: basicImageReq,
59+
reqBody: basicImageReqBody,
60+
expectedSpanName: "ImageGeneration",
61+
},
62+
}
63+
64+
for _, tt := range tests {
65+
t.Run(tt.name, func(t *testing.T) {
66+
recorder := NewImageGenerationRecorder(nil)
67+
68+
spanName, opts := recorder.StartParams(tt.req, tt.reqBody)
69+
actualSpan := testotel.RecordNewSpan(t, spanName, opts...)
70+
71+
require.Equal(t, tt.expectedSpanName, actualSpan.Name)
72+
require.Equal(t, oteltrace.SpanKindInternal, actualSpan.SpanKind)
73+
})
74+
}
5975
}
6076

61-
func TestImageGenerationRecorder_ResponseAttributes_SDK(t *testing.T) {
62-
exporter := tracetest.NewInMemoryExporter()
63-
tp := trace.NewTracerProvider(trace.WithSyncer(exporter))
64-
tr := tp.Tracer("test")
77+
func TestImageGenerationRecorder_RecordRequest(t *testing.T) {
78+
tests := []struct {
79+
name string
80+
req *openaisdk.ImageGenerateParams
81+
reqBody []byte
82+
expectedAttrs []attribute.KeyValue
83+
}{
84+
{
85+
name: "basic request",
86+
req: basicImageReq,
87+
reqBody: basicImageReqBody,
88+
expectedAttrs: []attribute.KeyValue{
89+
attribute.String("gen_ai.operation.name", "image_generation"),
90+
attribute.String("gen_ai.image.size", "1024x1024"),
91+
attribute.String("gen_ai.image.quality", "high"),
92+
attribute.String("gen_ai.image.response_format", "b64_json"),
93+
attribute.String("gen_ai.image.n", "1"),
94+
},
95+
},
96+
}
6597

66-
recorder := NewImageGenerationRecorder(nil)
98+
for _, tt := range tests {
99+
t.Run(tt.name, func(t *testing.T) {
100+
recorder := NewImageGenerationRecorder(nil)
101+
102+
actualSpan := testotel.RecordWithSpan(t, func(span oteltrace.Span) bool {
103+
recorder.RecordRequest(span, tt.req, tt.reqBody)
104+
return false
105+
})
106+
107+
// Check that key attributes are present
108+
attrs := attributesToMap(actualSpan.Attributes)
109+
require.Equal(t, "image_generation", attrs["gen_ai.operation.name"])
110+
require.Equal(t, "1024x1024", attrs["gen_ai.image.size"])
111+
require.Equal(t, "high", attrs["gen_ai.image.quality"])
112+
require.Equal(t, "b64_json", attrs["gen_ai.image.response_format"])
113+
require.Equal(t, "1", attrs["gen_ai.image.n"])
114+
})
115+
}
116+
}
67117

68-
_, span := tr.Start(t.Context(), "ImageGeneration")
69-
resp := &openaisdk.ImagesResponse{
70-
Data: []openaisdk.Image{{URL: "https://example.com/img.png"}},
71-
Size: openaisdk.ImagesResponseSize1024x1024,
118+
func TestImageGenerationRecorder_RecordResponse(t *testing.T) {
119+
tests := []struct {
120+
name string
121+
respBody []byte
122+
expectedAttrs []attribute.KeyValue
123+
expectedStatus trace.Status
124+
}{
125+
{
126+
name: "successful response",
127+
respBody: basicImageRespBody,
128+
expectedAttrs: []attribute.KeyValue{
129+
attribute.String("gen_ai.image.count", "1"),
130+
attribute.String("gen_ai.image.urls", "https://example.com/img.png"),
131+
},
132+
expectedStatus: trace.Status{Code: codes.Ok, Description: ""},
133+
},
134+
}
135+
136+
for _, tt := range tests {
137+
t.Run(tt.name, func(t *testing.T) {
138+
recorder := NewImageGenerationRecorder(nil)
139+
140+
resp := &openaisdk.ImagesResponse{}
141+
err := json.Unmarshal(tt.respBody, resp)
142+
require.NoError(t, err)
143+
144+
actualSpan := testotel.RecordWithSpan(t, func(span oteltrace.Span) bool {
145+
recorder.RecordResponse(span, resp)
146+
return false
147+
})
148+
149+
// Check that key attributes are present
150+
attrs := attributesToMap(actualSpan.Attributes)
151+
require.Equal(t, "1", attrs["gen_ai.image.count"])
152+
require.Equal(t, "https://example.com/img.png", attrs["gen_ai.image.urls"])
153+
require.Equal(t, trace.Status{Code: codes.Ok, Description: ""}, actualSpan.Status)
154+
})
72155
}
73-
recorder.RecordResponse(span, resp)
74-
span.End()
75-
76-
spans := exporter.GetSpans()
77-
require.Len(t, spans, 1)
78-
got := spans[0]
79-
attrs := attributesToMap(got.Attributes)
80-
// Count and urls should be present
81-
require.Equal(t, "1", attrs["gen_ai.image.count"])
82-
require.Equal(t, "https://example.com/img.png", attrs["gen_ai.image.urls"])
156+
}
157+
158+
func TestImageGenerationRecorder_RecordResponseOnError(t *testing.T) {
159+
recorder := NewImageGenerationRecorder(nil)
160+
161+
actualSpan := testotel.RecordWithSpan(t, func(span oteltrace.Span) bool {
162+
recorder.RecordResponseOnError(span, 400, []byte(`{"error":{"message":"Invalid request","type":"invalid_request_error"}}`))
163+
return false
164+
})
165+
166+
require.Equal(t, trace.Status{
167+
Code: codes.Error,
168+
Description: `Error code: 400 - {"error":{"message":"Invalid request","type":"invalid_request_error"}}`,
169+
}, actualSpan.Status)
83170
}
84171

85172
// attributesToMap converts attribute KeyValue to a simple map for assertions.

0 commit comments

Comments
 (0)