feat: Add a Python Callout implementation for Portkey sample - #347
feat: Add a Python Callout implementation for Portkey sample#347pweiber wants to merge 11 commits into
Conversation
Signed-off-by: pweiber <periclesweiber@ciandt.com>
Signed-off-by: pweiber <periclesweiber@ciandt.com>
Signed-off-by: pweiber <periclesweiber@ciandt.com>
Signed-off-by: pweiber <periclesweiber@ciandt.com>
Signed-off-by: pweiber <periclesweiber@ciandt.com>
Signed-off-by: pweiber <periclesweiber@ciandt.com>
Signed-off-by: pweiber <periclesweiber@ciandt.com>
Signed-off-by: pweiber <periclesweiber@ciandt.com>
|
|
||
| Two endpoints, both running in-process on the callout container: | ||
|
|
||
| * **request port (``:9999``)**: Portkey POSTs the translated provider-native |
There was a problem hiding this comment.
This approach of using separate endpoints for request vs. response is nice in that it separates the two operations. However it assumes that Portkey does not (internally) keep state between request and response--for example, when we send a dummy request to the response custom_host (in order to trigger Portkey to translate the correlated response), how sure can we be that Portkey will not try to look at any request attributes (in this case, from the dummy request) in deciding how to translate the response?
An alternative approach would be to run the Capture server with a single port, and have it "park" the in-progress connection until a response is provided, so that Portkey would see both the full request and response in the connection visible to it. More specifically:
Initial setup:
0. Callout configures Portkey sidecar to forward to localhost:9999
Request path:
- Client sends an OpenAI request.
- LB Traffic Extension streams header and body to Callout
- Callout sends the OpenAI request to the Portkey sidecar
- Portkey sidecar translates the request and proxies it to Capture server at localhost:9999
- Capture server captures request and keeps the connection open, but does not write a response yet. A new correlation ID is created.
- Capture server passes captured request along with correlation ID back to Callout, which returns it as body mutations to the LB.
- LB forwards translated request to actual provider backend.
Response path:
8. Actual backend responds with provider's native response.
9. LB traffic Extension streams native response header and body to Callout
10. Callout feeds native response header and body to Capture server, along with correlation ID
11. Capture server uses correlation ID to look up paused connection. It then resumes the paused connection, returning the native response header and body back to Portkey.
12. Portkey sidecar translates the response. From its perspective, the response is on the same connection as the associated request that it translated.
13. Callout receives translated response from Portkey sidecar, and returns it as body mutations to the LB.
This is mostly the same as the current setup, except that the Capture server parks connections instead of returning a dummy response early. The benefit would be that it avoids making any assumptions on how the Portkey sidecar operates.
There was a problem hiding this comment.
Did the changes, let me know if they are looking good now
Signed-off-by: pweiber <periclesweiber@ciandt.com>
Signed-off-by: pweiber <periclesweiber@ciandt.com>
Adds a Portkey Gateway Python sample, a Service Extensions ext_proc callout that turns a Google Cloud external Application Load Balancer into an OpenAI-compatible multi-provider gateway.
The LB routes each request to the right provider backend (Vertex AI, Anthropic, Groq, OpenRouter) via Internet NEGs; the callout fronts a self-hosted Portkey AI gateway running as a sidecar container in the same Cloud Run service to translate OpenAI <-> provider formats and inject the right auth (ADC for Vertex AI; Secret Manager-backed API keys for the others). Rather than letting Portkey call the provider, the callout uses Portkey's custom_host feature to intercept the translated bytes on a loopback port; the LB owns the outbound connection.
The callout doesn't proxy traffic, it only mutates the body and headers, and the LB forwards straight to the provider. Routing is header-driven (x-model-id, the full model id, with the URL map matching on the provider prefix)