Skip to content

Commit 5cd5f1b

Browse files
fix: updates image generation request tracing to match latest OpenInference (#1520)
**Description** updates image generation request tracing to match latest OpenInference - Span name changed to ImagesResponse - Add output.mime_type and output.value attributes - Remove llm.model_name from request attributes (now in invocation_parameters) - Fix missing RecordResponse call in translator **Related Issues/PRs (if applicable)** #1519 Signed-off-by: Adrian Cole <[email protected]> Co-authored-by: Ignasi Barrera <[email protected]>
1 parent e584dff commit 5cd5f1b

13 files changed

+292
-144
lines changed

internal/extproc/imagegeneration_processor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ func (i *imageGenerationProcessorUpstreamFilter) SetBackend(ctx context.Context,
416416
i.metrics.SetBackend(b)
417417
i.modelNameOverride = b.ModelNameOverride
418418
i.backendName = b.Name
419+
i.span = rp.span
419420
if err = i.selectTranslator(b.Schema); err != nil {
420421
return fmt.Errorf("failed to select translator: %w", err)
421422
}
@@ -442,7 +443,6 @@ func (i *imageGenerationProcessorUpstreamFilter) SetBackend(ctx context.Context,
442443
}
443444
}
444445
rp.upstreamFilter = i
445-
i.span = rp.span
446446
return
447447
}
448448

internal/extproc/imagegeneration_processor_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
openaisdk "github.com/openai/openai-go/v2"
2121
"github.com/stretchr/testify/require"
2222

23+
"github.com/envoyproxy/ai-gateway/internal/apischema/openai"
2324
"github.com/envoyproxy/ai-gateway/internal/extproc/translator"
2425
"github.com/envoyproxy/ai-gateway/internal/filterapi"
2526
"github.com/envoyproxy/ai-gateway/internal/internalapi"
@@ -449,9 +450,9 @@ func TestImageGeneration_ParseBody(t *testing.T) {
449450
jsonBody := `{"model":"gpt-image-1-mini","prompt":"a cat","size":"1024x1024","quality":"low"}`
450451
modelName, rb, err := parseOpenAIImageGenerationBody(&extprocv3.HttpBody{Body: []byte(jsonBody)})
451452
require.NoError(t, err)
452-
require.Equal(t, "gpt-image-1-mini", modelName)
453+
require.Equal(t, openai.ModelGPTImage1Mini, modelName)
453454
require.NotNil(t, rb)
454-
require.Equal(t, "gpt-image-1-mini", rb.Model)
455+
require.Equal(t, openai.ModelGPTImage1Mini, rb.Model)
455456
require.Equal(t, "a cat", rb.Prompt)
456457
})
457458
t.Run("error", func(t *testing.T) {

internal/extproc/translator/imagegeneration_openai_openai.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,13 @@ func (o *openAIToOpenAIImageGenerationTranslator) ResponseBody(_ map[string]stri
145145
tokenUsage.TotalTokens = uint32(resp.Usage.TotalTokens) //nolint:gosec
146146
}
147147

148-
// Provide response model for metrics
148+
// There is no response model field, so use the request one.
149149
responseModel = o.requestModel
150150

151+
// Record the response in the span if tracing is enabled.
152+
if o.span != nil {
153+
o.span.RecordResponse(resp)
154+
}
155+
151156
return
152157
}

internal/extproc/translator/imagegeneration_openai_openai_test.go

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
)
1919

2020
func TestOpenAIToOpenAIImageTranslator_RequestBody_ModelOverrideAndPath(t *testing.T) {
21-
tr := NewImageGenerationOpenAIToOpenAITranslator("v1", "gpt-image-1", nil)
21+
tr := NewImageGenerationOpenAIToOpenAITranslator("v1", openai.ModelGPTImage1Mini, nil)
2222
req := &openaisdk.ImageGenerateParams{Model: openaisdk.ImageModelDallE3, Prompt: "a cat"}
2323
original, _ := json.Marshal(req)
2424

@@ -32,9 +32,9 @@ func TestOpenAIToOpenAIImageTranslator_RequestBody_ModelOverrideAndPath(t *testi
3232

3333
require.NotNil(t, bm)
3434
mutated := bm.GetBody()
35-
var got openaisdk.ImageGenerateParams
36-
require.NoError(t, json.Unmarshal(mutated, &got))
37-
require.Equal(t, "gpt-image-1", got.Model)
35+
var actual openaisdk.ImageGenerateParams
36+
require.NoError(t, json.Unmarshal(mutated, &actual))
37+
require.Equal(t, openai.ModelGPTImage1Mini, actual.Model)
3838
}
3939

4040
func TestOpenAIToOpenAIImageTranslator_RequestBody_ForceMutation(t *testing.T) {
@@ -67,11 +67,11 @@ func TestOpenAIToOpenAIImageTranslator_ResponseError_NonJSON(t *testing.T) {
6767
require.NotNil(t, bm)
6868

6969
// Body should be OpenAI error JSON
70-
var got struct {
70+
var actual struct {
7171
Error openai.ErrorType `json:"error"`
7272
}
73-
require.NoError(t, json.Unmarshal(bm.GetBody(), &got))
74-
require.Equal(t, openAIBackendError, got.Error.Type)
73+
require.NoError(t, json.Unmarshal(bm.GetBody(), &actual))
74+
require.Equal(t, openAIBackendError, actual.Error.Type)
7575
}
7676

7777
func TestOpenAIToOpenAIImageTranslator_ResponseBody_OK(t *testing.T) {
@@ -127,7 +127,7 @@ func TestOpenAIToOpenAIImageTranslator_ResponseError_ReadError(t *testing.T) {
127127

128128
func TestOpenAIToOpenAIImageTranslator_ResponseBody_ModelPropagatesFromRequest(t *testing.T) {
129129
// Use override so effective model differs from original
130-
tr := NewImageGenerationOpenAIToOpenAITranslator("v1", "gpt-image-1", nil)
130+
tr := NewImageGenerationOpenAIToOpenAITranslator("v1", openai.ModelGPTImage1Mini, nil)
131131
req := &openaisdk.ImageGenerateParams{Model: openaisdk.ImageModelDallE3, Prompt: "a cat"}
132132
original, _ := json.Marshal(req)
133133
// Call RequestBody first to set requestModel inside translator
@@ -142,7 +142,7 @@ func TestOpenAIToOpenAIImageTranslator_ResponseBody_ModelPropagatesFromRequest(t
142142
buf, _ := json.Marshal(resp)
143143
_, _, _, respModel, err := tr.ResponseBody(map[string]string{}, bytes.NewReader(buf), true)
144144
require.NoError(t, err)
145-
require.Equal(t, "gpt-image-1", respModel)
145+
require.Equal(t, openai.ModelGPTImage1Mini, respModel)
146146
}
147147

148148
func TestOpenAIToOpenAIImageTranslator_ResponseHeaders_NoOp(t *testing.T) {
@@ -158,3 +158,28 @@ func TestOpenAIToOpenAIImageTranslator_ResponseBody_DecodeError(t *testing.T) {
158158
require.Error(t, err)
159159
require.Contains(t, err.Error(), "failed to decode response body")
160160
}
161+
162+
func TestOpenAIToOpenAIImageTranslator_ResponseBody_RecordsSpan(t *testing.T) {
163+
mockSpan := &mockImageGenerationSpan{}
164+
tr := NewImageGenerationOpenAIToOpenAITranslator("v1", "", mockSpan)
165+
166+
resp := &openaisdk.ImagesResponse{
167+
Data: []openaisdk.Image{{URL: "https://example.com/img.png"}},
168+
Size: openaisdk.ImagesResponseSize1024x1024,
169+
}
170+
buf, _ := json.Marshal(resp)
171+
_, _, _, _, err := tr.ResponseBody(map[string]string{}, bytes.NewReader(buf), true)
172+
require.NoError(t, err)
173+
require.NotNil(t, mockSpan.recordedResponse)
174+
}
175+
176+
type mockImageGenerationSpan struct {
177+
recordedResponse *openaisdk.ImagesResponse
178+
}
179+
180+
func (m *mockImageGenerationSpan) RecordResponse(resp *openaisdk.ImagesResponse) {
181+
m.recordedResponse = resp
182+
}
183+
184+
func (m *mockImageGenerationSpan) EndSpanOnError(int, []byte) {}
185+
func (m *mockImageGenerationSpan) EndSpan() {}

internal/tracing/image_generation_span_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
type testImageGenerationRecorder struct{}
2424

2525
func (r testImageGenerationRecorder) StartParams(_ *openaisdk.ImageGenerateParams, _ []byte) (string, []oteltrace.SpanStartOption) {
26-
return "ImageGeneration", nil
26+
return "ImagesResponse", nil
2727
}
2828

2929
func (r testImageGenerationRecorder) RecordRequest(span oteltrace.Span, req *openaisdk.ImageGenerateParams, _ []byte) {

internal/tracing/image_generation_tracer_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func TestImageGenerationTracer_StartSpanAndInjectHeaders(t *testing.T) {
7171
name: "basic image generation request",
7272
req: imageGenReq,
7373
existingHeaders: map[string]string{},
74-
expectedSpanName: "ImageGeneration",
74+
expectedSpanName: "ImagesResponse",
7575
expectedAttrs: []attribute.KeyValue{
7676
attribute.String("model", imageGenReq.Model),
7777
attribute.String("prompt", imageGenReq.Prompt),
@@ -90,7 +90,7 @@ func TestImageGenerationTracer_StartSpanAndInjectHeaders(t *testing.T) {
9090
existingHeaders: map[string]string{
9191
"traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
9292
},
93-
expectedSpanName: "ImageGeneration",
93+
expectedSpanName: "ImagesResponse",
9494
expectedAttrs: []attribute.KeyValue{
9595
attribute.String("model", imageGenReq.Model),
9696
attribute.String("prompt", imageGenReq.Prompt),
@@ -115,7 +115,7 @@ func TestImageGenerationTracer_StartSpanAndInjectHeaders(t *testing.T) {
115115
N: openaiparam.NewOpt[int64](2),
116116
},
117117
existingHeaders: map[string]string{},
118-
expectedSpanName: "ImageGeneration",
118+
expectedSpanName: "ImagesResponse",
119119
expectedAttrs: []attribute.KeyValue{
120120
attribute.String("model", openaisdk.ImageModelGPTImage1),
121121
attribute.String("prompt", "a cat and a dog"),
@@ -366,7 +366,7 @@ var _ tracing.ImageGenerationRecorder = testImageGenTracerRecorder{}
366366
type testImageGenTracerRecorder struct{}
367367

368368
func (r testImageGenTracerRecorder) StartParams(_ *openaisdk.ImageGenerateParams, _ []byte) (spanName string, opts []oteltrace.SpanStartOption) {
369-
return "ImageGeneration", imageGenStartOpts
369+
return "ImagesResponse", imageGenStartOpts
370370
}
371371

372372
func (r testImageGenTracerRecorder) RecordRequest(span oteltrace.Span, req *openaisdk.ImageGenerateParams, body []byte) {

internal/tracing/openinference/openai/image_generation.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ var imageGenStartOpts = []trace.SpanStartOption{trace.WithSpanKind(trace.SpanKin
5252

5353
// StartParams implements the same method as defined in tracing.ImageGenerationRecorder.
5454
func (r *ImageGenerationRecorder) StartParams(*openaisdk.ImageGenerateParams, []byte) (spanName string, opts []trace.SpanStartOption) {
55-
return "ImageGeneration", imageGenStartOpts
55+
return "ImagesResponse", imageGenStartOpts
5656
}
5757

5858
// RecordRequest implements the same method as defined in tracing.ImageGenerationRecorder.
@@ -86,11 +86,10 @@ func (r *ImageGenerationRecorder) RecordResponseOnError(span trace.Span, statusC
8686
}
8787

8888
// buildImageGenerationRequestAttributes builds OpenInference attributes from the image generation request.
89-
func buildImageGenerationRequestAttributes(req *openaisdk.ImageGenerateParams, body string, config *openinference.TraceConfig) []attribute.KeyValue {
89+
func buildImageGenerationRequestAttributes(_ *openaisdk.ImageGenerateParams, body string, config *openinference.TraceConfig) []attribute.KeyValue {
9090
attrs := []attribute.KeyValue{
9191
attribute.String(openinference.SpanKind, openinference.SpanKindLLM),
9292
attribute.String(openinference.LLMSystem, openinference.LLMSystemOpenAI),
93-
attribute.String(openinference.LLMModelName, req.Model),
9493
}
9594

9695
if config.HideInputs {
@@ -100,6 +99,10 @@ func buildImageGenerationRequestAttributes(req *openaisdk.ImageGenerateParams, b
10099
attrs = append(attrs, attribute.String(openinference.InputMimeType, openinference.MimeTypeJSON))
101100
}
102101

102+
if !config.HideLLMInvocationParameters {
103+
attrs = append(attrs, attribute.String(openinference.LLMInvocationParameters, body))
104+
}
105+
103106
return attrs
104107
}
105108

0 commit comments

Comments
 (0)