Skip to content

Commit 39aefb1

Browse files
PatilHrushikeshnutanix-Hrushikesh
authored andcommitted
chore: integrate image generation endpoint into main application
This commit wires up the image generation functionality into the main application and updates test configurations. Changes include: - Register image generation processor and metrics in main.go - Add image generation metadata support to translator - Update test configurations to include image generation endpoint - Add test cases for image generation functionality - Update Envoy configuration for image generation routing These changes complete the integration of the image generation feature into the AI Gateway application.
1 parent cbc40ea commit 39aefb1

File tree

4 files changed

+82
-0
lines changed

4 files changed

+82
-0
lines changed

cmd/extproc/mainlib/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ func Main(ctx context.Context, args []string, stderr io.Writer) (err error) {
230230
}
231231
chatCompletionMetrics := metrics.NewChatCompletion(meter, metricsRequestHeaderAttributes)
232232
embeddingsMetrics := metrics.NewEmbeddings(meter, metricsRequestHeaderAttributes)
233+
imageGenerationMetrics := metrics.NewImageGeneration(meter, metricsRequestHeaderAttributes)
233234
mcpMetrics := metrics.NewMCP(meter)
234235

235236
tracing, err := tracing.NewTracingFromEnv(ctx, os.Stdout, spanRequestHeaderAttributes)
@@ -244,6 +245,7 @@ func Main(ctx context.Context, args []string, stderr io.Writer) (err error) {
244245
server.Register(path.Join(flags.rootPrefix, "/v1/chat/completions"), extproc.ChatCompletionProcessorFactory(chatCompletionMetrics))
245246
server.Register(path.Join(flags.rootPrefix, "/v1/completions"), extproc.CompletionsProcessorFactory(nil))
246247
server.Register(path.Join(flags.rootPrefix, "/v1/embeddings"), extproc.EmbeddingsProcessorFactory(embeddingsMetrics))
248+
server.Register(path.Join(flags.rootPrefix, "/v1/images/generations"), extproc.ImageGenerationProcessorFactory(imageGenerationMetrics))
247249
server.Register(path.Join(flags.rootPrefix, "/v1/models"), extproc.NewModelsProcessor)
248250
server.Register(path.Join(flags.rootPrefix, "/anthropic/v1/messages"), extproc.MessagesProcessorFactory(chatCompletionMetrics))
249251

internal/extproc/translator/translator.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,61 @@ type LLMTokenUsage struct {
215215
TotalTokens uint32
216216
}
217217

218+
// ImageGenerationMetadata contains metadata extracted from image generation responses
219+
// for metrics and observability.
220+
type ImageGenerationMetadata struct {
221+
// ImageCount is the number of images generated in the response.
222+
ImageCount int
223+
// Model is the AI model used for image generation.
224+
Model string
225+
// Size is the size/dimensions of the generated images.
226+
Size string
227+
}
228+
218229
// SJSONOptions are the options used for sjson operations in the translator.
219230
// This is also used outside the package to share the same options for consistency.
220231
var SJSONOptions = &sjson.Options{
221232
Optimistic: true,
222233
ReplaceInPlace: true,
223234
}
235+
236+
// ImageGenerationTranslator translates the request and response messages between the client and the backend API schemas
237+
// for /v1/images/generations endpoint of OpenAI.
238+
//
239+
// This is created per request and is not thread-safe.
240+
type ImageGenerationTranslator interface {
241+
// RequestBody translates the request body.
242+
// - raw is the raw request body.
243+
// - body is the request body parsed into the [openai.ImageGenerationRequest].
244+
// - forceBodyMutation is true if the translator should always mutate the body, even if no changes are made.
245+
// - This returns headerMutation and bodyMutation that can be nil to indicate no mutation.
246+
RequestBody(raw []byte, body *openai.ImageGenerationRequest, forceBodyMutation bool) (
247+
headerMutation *extprocv3.HeaderMutation,
248+
bodyMutation *extprocv3.BodyMutation,
249+
err error,
250+
)
251+
252+
// ResponseHeaders translates the response headers.
253+
// - headers is the response headers.
254+
// - This returns headerMutation that can be nil to indicate no mutation.
255+
ResponseHeaders(headers map[string]string) (
256+
headerMutation *extprocv3.HeaderMutation,
257+
err error,
258+
)
259+
260+
// ResponseBody translates the response body.
261+
// - body is the response body.
262+
// - This returns headerMutation and bodyMutation that can be nil to indicate no mutation.
263+
ResponseBody(respHeaders map[string]string, body io.Reader, endOfStream bool) (
264+
headerMutation *extprocv3.HeaderMutation,
265+
bodyMutation *extprocv3.BodyMutation,
266+
tokenUsage LLMTokenUsage,
267+
imageMetadata ImageGenerationMetadata,
268+
err error,
269+
)
270+
271+
// ResponseError translates the response error. This is called when the upstream response status code is not successful (2xx).
272+
// - respHeaders is the response headers.
273+
// - body is the response body that contains the error message.
274+
ResponseError(respHeaders map[string]string, body io.Reader) (headerMutation *extprocv3.HeaderMutation, bodyMutation *extprocv3.BodyMutation, err error)
275+
}

tests/extproc/testupstream_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,30 @@ func TestWithTestUpstream(t *testing.T) {
127127
// expResponseBodyFunc is a function to check the response body. This can be used instead of the expResponseBody field.
128128
expResponseBodyFunc func(require.TestingT, []byte)
129129
}{
130+
{
131+
name: "openai - /v1/images/generations",
132+
backend: "openai",
133+
path: "/v1/images/generations",
134+
method: http.MethodPost,
135+
requestBody: `{"model":"dall-e-2","prompt":"a cat wearing sunglasses"}`,
136+
expPath: "/v1/images/generations",
137+
responseBody: `{"created":1736890000,"data":[{"url":"https://example.com/image1.png"}],"model":"dall-e-2","usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}`,
138+
expStatus: http.StatusOK,
139+
expResponseBody: `{"created":1736890000,"data":[{"url":"https://example.com/image1.png"}],"model":"dall-e-2","usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}`,
140+
},
141+
{
142+
name: "openai - /v1/images/generations - non json upstream error mapped to OpenAI",
143+
backend: "openai",
144+
path: "/v1/images/generations",
145+
method: http.MethodPost,
146+
requestBody: `{"model":"dall-e-3","prompt":"a scenic beach"}`,
147+
expPath: "/v1/images/generations",
148+
responseHeaders: "content-type:text/plain",
149+
responseStatus: strconv.Itoa(http.StatusServiceUnavailable),
150+
responseBody: `backend timeout`,
151+
expStatus: http.StatusServiceUnavailable,
152+
expResponseBody: `{"error":{"type":"OpenAIBackendError","message":"backend timeout","code":"503"}}`,
153+
},
130154
{
131155
name: "unknown path",
132156
path: "/unknown",

tests/extproc/vcr/envoy.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ static_resources:
8787
path: "/v1/embeddings"
8888
route:
8989
cluster: openai
90+
- match:
91+
path: "/v1/images/generations"
92+
route:
93+
cluster: openai
9094
- match:
9195
path: "/v1/models"
9296
route:

0 commit comments

Comments
 (0)