Skip to content

Commit a78ece0

Browse files
authored
refactor(tracing): rearrange type parameters & avoid nested generic type parameter (#1599)
**Description** This stops the nested generics type embedding and switches to have explicit type parameters for generic tracing interfaces. **Related Issues/PRs (if applicable)** Follow up on #1582 --------- Signed-off-by: Takeshi Yoneda <[email protected]>
1 parent 84edf7e commit a78ece0

File tree

11 files changed

+89
-97
lines changed

11 files changed

+89
-97
lines changed

internal/extproc/chatcompletion_processor_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func Test_chatCompletionProcessorUpstreamFilter_SelectTranslator(t *testing.T) {
7171
}
7272

7373
type mockTracer struct {
74-
tracing.NoopTracer[openai.ChatCompletionRequest, tracing.ChatCompletionSpan]
74+
tracing.NoopTracer[openai.ChatCompletionRequest, openai.ChatCompletionResponse, openai.ChatCompletionResponseChunk]
7575
startSpanCalled bool
7676
returnedSpan tracing.ChatCompletionSpan
7777
}
@@ -88,7 +88,7 @@ func (m *mockTracer) StartSpanAndInjectHeaders(_ context.Context, _ map[string]s
8888
func Test_chatCompletionProcessorRouterFilter_ProcessRequestBody(t *testing.T) {
8989
t.Run("body parser error", func(t *testing.T) {
9090
p := &chatCompletionProcessorRouterFilter{
91-
tracer: tracing.NoopTracer[openai.ChatCompletionRequest, tracing.ChatCompletionSpan]{},
91+
tracer: tracing.NoopTracer[openai.ChatCompletionRequest, openai.ChatCompletionResponse, openai.ChatCompletionResponseChunk]{},
9292
}
9393
_, err := p.ProcessRequestBody(t.Context(), &extprocv3.HttpBody{Body: []byte("nonjson")})
9494
require.ErrorContains(t, err, "invalid character 'o' in literal null")
@@ -100,7 +100,7 @@ func Test_chatCompletionProcessorRouterFilter_ProcessRequestBody(t *testing.T) {
100100
config: &filterapi.RuntimeConfig{},
101101
requestHeaders: headers,
102102
logger: slog.Default(),
103-
tracer: tracing.NoopTracer[openai.ChatCompletionRequest, tracing.ChatCompletionSpan]{},
103+
tracer: tracing.NoopTracer[openai.ChatCompletionRequest, openai.ChatCompletionResponse, openai.ChatCompletionResponseChunk]{},
104104
}
105105
resp, err := p.ProcessRequestBody(t.Context(), &extprocv3.HttpBody{Body: bodyFromModel(t, "some-model", false, nil)})
106106
require.NoError(t, err)
@@ -159,7 +159,7 @@ func Test_chatCompletionProcessorRouterFilter_ProcessRequestBody(t *testing.T) {
159159
},
160160
requestHeaders: headers,
161161
logger: slog.Default(),
162-
tracer: tracing.NoopTracer[openai.ChatCompletionRequest, tracing.ChatCompletionSpan]{},
162+
tracer: tracing.NoopTracer[openai.ChatCompletionRequest, openai.ChatCompletionResponse, openai.ChatCompletionResponseChunk]{},
163163
}
164164
resp, err := p.ProcessRequestBody(t.Context(), &extprocv3.HttpBody{Body: bodyFromModel(t, "some-model", true, opt)})
165165
require.NoError(t, err)

internal/extproc/completions_processor_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ func (m *mockCompletionTranslator) ResponseError(map[string]string, io.Reader) (
423423

424424
// mockCompletionTracer implements tracing.CompletionTracer for testing span creation.
425425
type mockCompletionTracer struct {
426-
tracing.NoopTracer[openai.CompletionRequest, tracing.CompletionSpan]
426+
tracing.NoopTracer[openai.CompletionRequest, openai.ChatCompletionResponse, openai.ChatCompletionResponse]
427427
startSpanCalled bool
428428
returnedSpan tracing.CompletionSpan
429429
}

internal/extproc/embeddings_processor_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func Test_embeddingsProcessorUpstreamFilter_SelectTranslator(t *testing.T) {
5959
func Test_embeddingsProcessorRouterFilter_ProcessRequestBody(t *testing.T) {
6060
t.Run("body parser error", func(t *testing.T) {
6161
p := &embeddingsProcessorRouterFilter{
62-
tracer: tracing.NoopTracer[openai.EmbeddingRequest, tracing.EmbeddingsSpan]{},
62+
tracer: tracing.NoopTracer[openai.EmbeddingRequest, openai.EmbeddingResponse, struct{}]{},
6363
}
6464
_, err := p.ProcessRequestBody(t.Context(), &extprocv3.HttpBody{Body: []byte("nonjson")})
6565
require.ErrorContains(t, err, "invalid character 'o' in literal null")
@@ -71,7 +71,7 @@ func Test_embeddingsProcessorRouterFilter_ProcessRequestBody(t *testing.T) {
7171
config: &filterapi.RuntimeConfig{},
7272
requestHeaders: headers,
7373
logger: slog.Default(),
74-
tracer: tracing.NoopTracer[openai.EmbeddingRequest, tracing.EmbeddingsSpan]{},
74+
tracer: tracing.NoopTracer[openai.EmbeddingRequest, openai.EmbeddingResponse, struct{}]{},
7575
}
7676
resp, err := p.ProcessRequestBody(t.Context(), &extprocv3.HttpBody{Body: embeddingBodyFromModel(t, "some-model")})
7777
require.NoError(t, err)

internal/extproc/imagegeneration_processor_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func Test_imageGenerationProcessorUpstreamFilter_SelectTranslator(t *testing.T)
6060
}
6161

6262
type mockImageGenerationTracer struct {
63-
tracing.NoopTracer[openaisdk.ImageGenerateParams, tracing.ImageGenerationSpan]
63+
tracing.NoopTracer[openaisdk.ImageGenerateParams, openaisdk.ImagesResponse, struct{}]
6464
startSpanCalled bool
6565
returnedSpan tracing.ImageGenerationSpan
6666
}

internal/extproc/rerank_processor_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func Test_rerankProcessorUpstreamFilter_SelectTranslator(t *testing.T) {
5454
func Test_rerankProcessorRouterFilter_ProcessRequestBody(t *testing.T) {
5555
t.Run("body parser error", func(t *testing.T) {
5656
p := &rerankProcessorRouterFilter{
57-
tracer: tracing.NoopTracer[cohere.RerankV2Request, tracing.RerankSpan]{},
57+
tracer: tracing.NoopTracer[cohere.RerankV2Request, cohere.RerankV2Response, struct{}]{},
5858
}
5959
_, err := p.ProcessRequestBody(t.Context(), &extprocv3.HttpBody{Body: []byte("nonjson")})
6060
require.ErrorContains(t, err, "invalid character 'o' in literal null")
@@ -66,7 +66,7 @@ func Test_rerankProcessorRouterFilter_ProcessRequestBody(t *testing.T) {
6666
config: &filterapi.RuntimeConfig{},
6767
requestHeaders: headers,
6868
logger: slog.Default(),
69-
tracer: tracing.NoopTracer[cohere.RerankV2Request, tracing.RerankSpan]{},
69+
tracer: tracing.NoopTracer[cohere.RerankV2Request, cohere.RerankV2Response, struct{}]{},
7070
}
7171
resp, err := p.ProcessRequestBody(t.Context(), &extprocv3.HttpBody{Body: rerankBodyFromModel("rerank-english-v3")})
7272
require.NoError(t, err)

internal/tracing/api/api.go

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type (
3838
Shutdown(context.Context) error
3939
}
4040
// RequestTracer standardizes tracer implementations for non-MCP requests.
41-
RequestTracer[ReqT any, SpanT any] interface {
41+
RequestTracer[ReqT any, RespT any, RespChunkT any] interface {
4242
// StartSpanAndInjectHeaders starts a span and injects trace context into the header mutation.
4343
//
4444
// Parameters:
@@ -49,25 +49,25 @@ type (
4949
// - body: contains the original raw request body as a byte slice.
5050
//
5151
// Returns nil unless the span is sampled.
52-
StartSpanAndInjectHeaders(ctx context.Context, headers map[string]string, carrier propagation.TextMapCarrier, req *ReqT, body []byte) SpanT
52+
StartSpanAndInjectHeaders(ctx context.Context, headers map[string]string, carrier propagation.TextMapCarrier, req *ReqT, body []byte) Span[RespT, RespChunkT]
5353
}
5454
// ChatCompletionTracer creates spans for OpenAI chat completion requests.
55-
ChatCompletionTracer = RequestTracer[openai.ChatCompletionRequest, ChatCompletionSpan]
55+
ChatCompletionTracer = RequestTracer[openai.ChatCompletionRequest, openai.ChatCompletionResponse, openai.ChatCompletionResponseChunk]
5656
// CompletionTracer creates spans for OpenAI completion requests.
57-
CompletionTracer = RequestTracer[openai.CompletionRequest, CompletionSpan]
57+
CompletionTracer = RequestTracer[openai.CompletionRequest, openai.CompletionResponse, openai.CompletionResponse]
5858
// EmbeddingsTracer creates spans for OpenAI embeddings requests.
59-
EmbeddingsTracer = RequestTracer[openai.EmbeddingRequest, EmbeddingsSpan]
59+
EmbeddingsTracer = RequestTracer[openai.EmbeddingRequest, openai.EmbeddingResponse, struct{}]
6060
// ImageGenerationTracer creates spans for OpenAI image generation requests.
61-
ImageGenerationTracer = RequestTracer[openaisdk.ImageGenerateParams, ImageGenerationSpan]
61+
ImageGenerationTracer = RequestTracer[openaisdk.ImageGenerateParams, openaisdk.ImagesResponse, struct{}]
6262
// RerankTracer creates spans for rerank requests.
63-
RerankTracer = RequestTracer[cohere.RerankV2Request, RerankSpan]
63+
RerankTracer = RequestTracer[cohere.RerankV2Request, cohere.RerankV2Response, struct{}]
6464
)
6565

6666
type (
6767
// Span standardizes span interfaces, supporting both streaming and non-streaming endpoints.
68-
Span[ChunkT any, RespT any] interface {
68+
Span[RespT any, RespChunkT any] interface {
6969
// RecordResponseChunk records streaming response chunks. Implementations that do not support streaming should provide a no-op implementation.
70-
RecordResponseChunk(resp *ChunkT)
70+
RecordResponseChunk(resp *RespChunkT)
7171
// RecordResponse records the response attributes to the span.
7272
RecordResponse(resp *RespT)
7373
// EndSpanOnError finalizes and ends the span with an error status.
@@ -76,21 +76,21 @@ type (
7676
EndSpan()
7777
}
7878
// ChatCompletionSpan represents an OpenAI chat completion.
79-
ChatCompletionSpan = Span[openai.ChatCompletionResponseChunk, openai.ChatCompletionResponse]
79+
ChatCompletionSpan = Span[openai.ChatCompletionResponse, openai.ChatCompletionResponseChunk]
8080
// CompletionSpan represents an OpenAI completion request.
8181
// Note: Completion streaming chunks are full CompletionResponse objects, not deltas like chat completions.
8282
CompletionSpan = Span[openai.CompletionResponse, openai.CompletionResponse]
8383
// EmbeddingsSpan represents an OpenAI embeddings request. The chunk type is unused and therefore set to struct{}.
84-
EmbeddingsSpan = Span[struct{}, openai.EmbeddingResponse]
84+
EmbeddingsSpan = Span[openai.EmbeddingResponse, struct{}]
8585
// ImageGenerationSpan represents an OpenAI image generation.
86-
ImageGenerationSpan = Span[struct{}, openaisdk.ImagesResponse]
86+
ImageGenerationSpan = Span[openaisdk.ImagesResponse, struct{}]
8787
// RerankSpan represents a rerank request span.
88-
RerankSpan = Span[struct{}, cohere.RerankV2Response]
88+
RerankSpan = Span[cohere.RerankV2Response, struct{}]
8989
)
9090

9191
type (
9292
// SpanRecorder standardizes recorder implementations for non-MCP tracers.
93-
SpanRecorder[ReqT any, ChunkT any, RespT any] interface {
93+
SpanRecorder[ReqT any, RespT any, RespChunkT any] interface {
9494
// StartParams returns the name and options to start the span with.
9595
//
9696
// Parameters:
@@ -106,19 +106,19 @@ type (
106106
// RecordResponseOnError ends recording the span with an error status.
107107
RecordResponseOnError(span trace.Span, statusCode int, body []byte)
108108
// RecordResponseChunks records response chunk attributes to the span for streaming response.
109-
RecordResponseChunks(span trace.Span, chunks []*ChunkT)
109+
RecordResponseChunks(span trace.Span, chunks []*RespChunkT)
110110
}
111111
// ChatCompletionRecorder records attributes to a span according to a semantic convention.
112-
ChatCompletionRecorder = SpanRecorder[openai.ChatCompletionRequest, openai.ChatCompletionResponseChunk, openai.ChatCompletionResponse]
112+
ChatCompletionRecorder = SpanRecorder[openai.ChatCompletionRequest, openai.ChatCompletionResponse, openai.ChatCompletionResponseChunk]
113113
// CompletionRecorder records attributes to a span according to a semantic convention.
114114
// Note: Completion streaming chunks are full CompletionResponse objects, not deltas like chat completions.
115115
CompletionRecorder = SpanRecorder[openai.CompletionRequest, openai.CompletionResponse, openai.CompletionResponse]
116116
// ImageGenerationRecorder records attributes to a span according to a semantic convention.
117-
ImageGenerationRecorder = SpanRecorder[openaisdk.ImageGenerateParams, struct{}, openaisdk.ImagesResponse]
117+
ImageGenerationRecorder = SpanRecorder[openaisdk.ImageGenerateParams, openaisdk.ImagesResponse, struct{}]
118118
// EmbeddingsRecorder records attributes to a span according to a semantic convention.
119-
EmbeddingsRecorder = SpanRecorder[openai.EmbeddingRequest, struct{}, openai.EmbeddingResponse]
119+
EmbeddingsRecorder = SpanRecorder[openai.EmbeddingRequest, openai.EmbeddingResponse, struct{}]
120120
// RerankRecorder records attributes to a span according to a semantic convention.
121-
RerankRecorder = SpanRecorder[cohere.RerankV2Request, struct{}, cohere.RerankV2Response]
121+
RerankRecorder = SpanRecorder[cohere.RerankV2Request, cohere.RerankV2Response, struct{}]
122122
)
123123

124124
// NoopChunkRecorder provides a no-op RecordResponseChunks implementation for recorders that don't emit streaming chunks.
@@ -136,27 +136,27 @@ func (t NoopTracing) MCPTracer() MCPTracer {
136136

137137
// ChatCompletionTracer implements Tracing.ChatCompletionTracer.
138138
func (NoopTracing) ChatCompletionTracer() ChatCompletionTracer {
139-
return NoopTracer[openai.ChatCompletionRequest, ChatCompletionSpan]{}
139+
return NoopTracer[openai.ChatCompletionRequest, openai.ChatCompletionResponse, openai.ChatCompletionResponseChunk]{}
140140
}
141141

142142
// CompletionTracer implements Tracing.CompletionTracer.
143143
func (NoopTracing) CompletionTracer() CompletionTracer {
144-
return NoopTracer[openai.CompletionRequest, CompletionSpan]{}
144+
return NoopTracer[openai.CompletionRequest, openai.CompletionResponse, openai.CompletionResponse]{}
145145
}
146146

147147
// EmbeddingsTracer implements Tracing.EmbeddingsTracer.
148148
func (NoopTracing) EmbeddingsTracer() EmbeddingsTracer {
149-
return NoopTracer[openai.EmbeddingRequest, EmbeddingsSpan]{}
149+
return NoopTracer[openai.EmbeddingRequest, openai.EmbeddingResponse, struct{}]{}
150150
}
151151

152152
// ImageGenerationTracer implements Tracing.ImageGenerationTracer.
153153
func (NoopTracing) ImageGenerationTracer() ImageGenerationTracer {
154-
return NoopTracer[openaisdk.ImageGenerateParams, ImageGenerationSpan]{}
154+
return NoopTracer[openaisdk.ImageGenerateParams, openaisdk.ImagesResponse, struct{}]{}
155155
}
156156

157157
// RerankTracer implements Tracing.RerankTracer.
158158
func (NoopTracing) RerankTracer() RerankTracer {
159-
return NoopTracer[cohere.RerankV2Request, RerankSpan]{}
159+
return NoopTracer[cohere.RerankV2Request, cohere.RerankV2Response, struct{}]{}
160160
}
161161

162162
// Shutdown implements Tracing.Shutdown.
@@ -165,10 +165,10 @@ func (NoopTracing) Shutdown(context.Context) error {
165165
}
166166

167167
// NoopTracer implements RequestTracer without producing spans.
168-
type NoopTracer[ReqT any, SpanT any] struct{}
168+
type NoopTracer[ReqT any, RespT any, RespChunkT any] struct{}
169169

170170
// StartSpanAndInjectHeaders implements RequestTracer.StartSpanAndInjectHeaders.
171-
func (NoopTracer[ReqT, SpanT]) StartSpanAndInjectHeaders(context.Context, map[string]string, propagation.TextMapCarrier, *ReqT, []byte) SpanT {
172-
var zero SpanT
171+
func (NoopTracer[ReqT, RespT, RespChunkT]) StartSpanAndInjectHeaders(context.Context, map[string]string, propagation.TextMapCarrier, *ReqT, []byte) Span[RespT, RespChunkT] {
172+
var zero Span[RespT, RespChunkT]
173173
return zero
174174
}

internal/tracing/api/api_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616

1717
func TestNoopTracing(t *testing.T) {
1818
tracing := NoopTracing{}
19-
require.IsType(t, NoopTracer[openai.ChatCompletionRequest, ChatCompletionSpan]{}, tracing.ChatCompletionTracer())
19+
require.IsType(t, NoopTracer[openai.ChatCompletionRequest, openai.ChatCompletionResponse, openai.ChatCompletionResponseChunk]{}, tracing.ChatCompletionTracer())
2020
require.Equal(t, NoopMCPTracer{}, tracing.MCPTracer())
2121

2222
// Calling shutdown twice should not cause an error.
@@ -25,7 +25,7 @@ func TestNoopTracing(t *testing.T) {
2525
}
2626

2727
func TestNoopTracerChatCompletion(t *testing.T) {
28-
tracer := NoopTracer[openai.ChatCompletionRequest, ChatCompletionSpan]{}
28+
tracer := NoopTracer[openai.ChatCompletionRequest, openai.ChatCompletionResponse, openai.ChatCompletionResponseChunk]{}
2929

3030
readHeaders := map[string]string{}
3131
writeHeaders := propagation.MapCarrier{}

internal/tracing/span.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type responseRecorder[RespT any] interface {
1919
RecordResponseOnError(trace.Span, int, []byte)
2020
}
2121

22-
type streamResponseRecorder[ChunkT any, RespT any] interface {
22+
type streamResponseRecorder[RespT, ChunkT any] interface {
2323
responseRecorder[RespT]
2424
RecordResponseChunks(trace.Span, []*ChunkT)
2525
}
@@ -28,58 +28,58 @@ type noopChunkRecorder[ChunkT any] struct{}
2828

2929
func (noopChunkRecorder[ChunkT]) RecordResponseChunk(*ChunkT) {}
3030

31-
type streamingSpan[ChunkT any, RespT any, Recorder streamResponseRecorder[ChunkT, RespT]] struct {
31+
type streamingSpan[RespT, ChunkT any] struct {
3232
span trace.Span
33-
recorder Recorder
33+
recorder streamResponseRecorder[RespT, ChunkT]
3434
chunks []*ChunkT
3535
}
3636

37-
func (s *streamingSpan[ChunkT, RespT, Recorder]) RecordResponseChunk(resp *ChunkT) {
37+
func (s *streamingSpan[RespT, ChunkT]) RecordResponseChunk(resp *ChunkT) {
3838
s.chunks = append(s.chunks, resp)
3939
}
4040

41-
func (s *streamingSpan[ChunkT, RespT, Recorder]) RecordResponse(resp *RespT) {
41+
func (s *streamingSpan[RespT, ChunkT]) RecordResponse(resp *RespT) {
4242
s.recorder.RecordResponse(s.span, resp)
4343
}
4444

45-
func (s *streamingSpan[ChunkT, RespT, Recorder]) EndSpan() {
45+
func (s *streamingSpan[RespT, ChunkT]) EndSpan() {
4646
if len(s.chunks) > 0 {
4747
s.recorder.RecordResponseChunks(s.span, s.chunks)
4848
}
4949
s.span.End()
5050
}
5151

52-
func (s *streamingSpan[ChunkT, RespT, Recorder]) EndSpanOnError(statusCode int, body []byte) {
52+
func (s *streamingSpan[RespT, ChunkT]) EndSpanOnError(statusCode int, body []byte) {
5353
s.recorder.RecordResponseOnError(s.span, statusCode, body)
5454
s.span.End()
5555
}
5656

57-
type responseSpan[ChunkT any, RespT any, Recorder responseRecorder[RespT]] struct {
57+
type responseSpan[RespT, ChunkT any] struct {
5858
noopChunkRecorder[ChunkT]
5959
span trace.Span
60-
recorder Recorder
60+
recorder streamResponseRecorder[RespT, ChunkT]
6161
}
6262

63-
func (s *responseSpan[ChunkT, RespT, Recorder]) RecordResponse(resp *RespT) {
63+
func (s *responseSpan[RespT, ChunkT]) RecordResponse(resp *RespT) {
6464
s.recorder.RecordResponse(s.span, resp)
6565
}
6666

67-
func (s *responseSpan[ChunkT, RespT, Recorder]) EndSpan() {
67+
func (s *responseSpan[RespT, ChunkT]) EndSpan() {
6868
s.span.End()
6969
}
7070

71-
func (s *responseSpan[ChunkT, RespT, Recorder]) EndSpanOnError(statusCode int, body []byte) {
71+
func (s *responseSpan[RespT, ChunkT]) EndSpanOnError(statusCode int, body []byte) {
7272
s.recorder.RecordResponseOnError(s.span, statusCode, body)
7373
s.span.End()
7474
}
7575

7676
// Type aliases tying generic implementations to concrete recorder contracts.
7777
type (
78-
chatCompletionSpan = streamingSpan[openai.ChatCompletionResponseChunk, openai.ChatCompletionResponse, tracing.ChatCompletionRecorder]
79-
completionSpan = streamingSpan[openai.CompletionResponse, openai.CompletionResponse, tracing.CompletionRecorder]
80-
embeddingsSpan = responseSpan[struct{}, openai.EmbeddingResponse, tracing.EmbeddingsRecorder]
81-
imageGenerationSpan = responseSpan[struct{}, openaisdk.ImagesResponse, tracing.ImageGenerationRecorder]
82-
rerankSpan = responseSpan[struct{}, cohereschema.RerankV2Response, tracing.RerankRecorder]
78+
chatCompletionSpan = streamingSpan[openai.ChatCompletionResponse, openai.ChatCompletionResponseChunk]
79+
completionSpan = streamingSpan[openai.CompletionResponse, openai.CompletionResponse]
80+
embeddingsSpan = responseSpan[openai.EmbeddingResponse, struct{}]
81+
imageGenerationSpan = responseSpan[openaisdk.ImagesResponse, struct{}]
82+
rerankSpan = responseSpan[cohereschema.RerankV2Response, struct{}]
8383
)
8484

8585
var (

0 commit comments

Comments
 (0)