Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/extproc/mainlib/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ func Main(ctx context.Context, args []string, stderr io.Writer) (err error) {
server.Register(path.Join(flags.rootPrefix, endpointPrefixes.OpenAI, "/v1/models"), extproc.NewModelsProcessor)
server.Register(path.Join(flags.rootPrefix, endpointPrefixes.Anthropic, "/v1/messages"), extproc.NewFactory(
messagesMetricsFactory, tracing.MessageTracer(), endpointspec.MessagesEndpointSpec{}))
// These are for OpenAI schema backends that support /v1/messages endpoint (no endpoint prefix as OpenAI prefix is '/')
server.Register(path.Join(flags.rootPrefix, endpointPrefixes.OpenAI, "/v1/messages"), extproc.NewFactory(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The root path / is intended for unified API endpoints which works for heterogeneous backends by translating between API schemas. For /v1/messages currently we only support pass through hence prefixed with anthropic, you could configure this root path on the client side to be compatible with the gateway path.

@yuzisun yuzisun Feb 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I see you want Claude Code to work with models using OpenAI API, but I think vllm actually now supports v1/messages natively.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#1867

Please follow through the conversations here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yuzisun Yes, I discussed the implementation details in the tagged issue. Please take a look and let me know what you think.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @changminbark and @yuzisun
I work with @ehfd, just tested the PR in our cluster with curl and claude code, works perfectly! Looks great!

@gavrissh gavrissh Feb 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the /v1/messages -> /v1/chat/completions translation will work with the existing registered endpoint, and the current change in this file is not required -

	server.Register(path.Join(flags.rootPrefix, endpointPrefixes.Anthropic, "/v1/messages"), extproc.NewFactory(
		messagesMetricsFactory, tracing.MessageTracer(), endpointspec.MessagesEndpointSpec{}))

https://github.com/envoyproxy/ai-gateway/pull/1878/changes#diff-33280abdb776271e54b6d3fea30758bc5ea3f458cdf3cce1b4d57e3be8caa853R310

The anthropic prefix can be set to an empty string anthropic: "" in values.yaml, if you don't need it in the access URL

@ehfd ehfd Feb 25, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ai-gateway-extproc time=2026-02-14T07:39:50.432Z level=ERROR msg="error processing request message" error="rpc error: code = Internal desc = cannot set backend: failed to create translator for backend nrp-llm/envoy-ai-gateway-nrp-glm-4/route/envoy-ai-gateway-nrp-glm/rule/1/ref/0: /v1/messages endpoint only supports backends that return native Anthropic format (Anthropic, GCPAnthropic, AWSAnthropic). Backend OpenAI uses different model format"

In core, we want to get this rid and not have to duplicate APISchemaAnthropic, BackendSecurityPolicyAnthropicAPIKey, AnthropicAPIKey with the OpenAI schema. So the correct implementation is a translator like in https://github.com/envoyproxy/ai-gateway/tree/main/internal/translator?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, adding OpenAI Translator for existing anthropic /v1/messages endpoint is correct

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I should get rid of registering the route with the OpenAI prefix?

@ehfd ehfd Feb 25, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dd9b9a9

Available here. I think this is resolved.

messagesMetricsFactory, tracing.MessageTracer(), endpointspec.MessagesEndpointSpec{}))

// Create and register gRPC server with ExternalProcessorServer (the service Envoy calls).
if err = filterapi.StartConfigWatcher(ctx, flags.configPath, server, l, time.Second*5); err != nil {
Expand Down
104 changes: 104 additions & 0 deletions examples/basic/anthropic-openai-local.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Copyright Envoy AI Gateway Authors
# SPDX-License-Identifier: Apache-2.0
# The full text of the Apache license is available in the LICENSE file at
# the root of the repo.

# This example routes /v1/messages (Anthropic Messages API) requests to a
# local vLLM backend.
# The AIServiceBackend schema is set to OpenAI so the gateway passes through
# the Anthropic request format with translation.

apiVersion: gateway.networking.k8s.io/v1
Comment thread
changminbark marked this conversation as resolved.
Outdated
kind: GatewayClass
metadata:
name: envoy-ai-gateway-basic
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
name: envoy-ai-gateway-basic
namespace: default
spec:
provider:
type: Kubernetes
kubernetes:
envoyDeployment:
container:
resources: {}
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: envoy-ai-gateway-basic
namespace: default
spec:
gatewayClassName: envoy-ai-gateway-basic
listeners:
- name: http
protocol: HTTP
port: 80
infrastructure:
parametersRef:
group: gateway.envoyproxy.io
kind: EnvoyProxy
name: envoy-ai-gateway-basic
---
# By default, Envoy Gateway sets the buffer limit to 32kiB which is not sufficient for AI workloads.
# This ClientTrafficPolicy sets the buffer limit to 50MiB as an example.
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: ClientTrafficPolicy
metadata:
name: client-buffer-limit
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: envoy-ai-gateway-basic
connection:
bufferLimit: 50Mi
---
apiVersion: aigateway.envoyproxy.io/v1alpha1
kind: AIGatewayRoute
metadata:
name: envoy-ai-gateway-basic-openai
Comment thread
changminbark marked this conversation as resolved.
Outdated
namespace: default
spec:
parentRefs:
- name: envoy-ai-gateway-basic
kind: Gateway
group: gateway.networking.k8s.io
rules:
- matches:
- headers:
- type: Exact
name: x-ai-eg-model
value: Qwen/Qwen2.5-0.5B-Instruct # Replace with the model name served by your vLLM instance.
backendRefs:
- name: envoy-ai-gateway-basic-openai
---
apiVersion: aigateway.envoyproxy.io/v1alpha1
kind: AIServiceBackend
metadata:
name: envoy-ai-gateway-basic-openai
namespace: default
spec:
schema:
name: OpenAI # vLLM exposes an OpenAI-compatible API, so Anthropic Messages Requests are translated
backendRef:
name: envoy-ai-gateway-basic-openai
kind: Backend
group: gateway.envoyproxy.io
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: Backend
metadata:
name: envoy-ai-gateway-basic-openai
namespace: default
spec:
endpoints:
- ip:
address: 0.0.0.0 # Replace with your vLLM service hostname or IP (e.g. localhost's internal IP from kind cluster).
port: 8000 # Replace with the port your vLLM instance listens on (default: 8000).
2 changes: 2 additions & 0 deletions internal/endpointspec/endpointspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ func (MessagesEndpointSpec) GetTranslator(schema filterapi.VersionedAPISchema, m
return translator.NewAnthropicToAWSAnthropicTranslator(schema.Version, modelNameOverride), nil
case filterapi.APISchemaAnthropic:
return translator.NewAnthropicToAnthropicTranslator(schema.Version, modelNameOverride), nil
case filterapi.APISchemaOpenAI:
return translator.NewAnthropicToChatCompletionOpenAITranslator(schema.Version, modelNameOverride), nil
default:
return nil, fmt.Errorf("/v1/messages endpoint only supports backends that return native Anthropic format (Anthropic, GCPAnthropic, AWSAnthropic). Backend %s uses different model format", schema.Name)
}
Expand Down
3 changes: 2 additions & 1 deletion internal/endpointspec/endpointspec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,13 +236,14 @@ func TestMessagesEndpointSpec_GetTranslator(t *testing.T) {
{Name: filterapi.APISchemaGCPAnthropic},
{Name: filterapi.APISchemaAWSAnthropic},
{Name: filterapi.APISchemaAnthropic},
{Name: filterapi.APISchemaOpenAI}, // This is for OpenAI-schema backends like vLLM that support the /v1/messages endpoint
} {
translator, err := spec.GetTranslator(schema, "override")
require.NoError(t, err)
require.NotNil(t, translator)
}

_, err := spec.GetTranslator(filterapi.VersionedAPISchema{Name: filterapi.APISchemaOpenAI}, "override")
_, err := spec.GetTranslator(filterapi.VersionedAPISchema{Name: filterapi.APISchemaCohere}, "override")
require.ErrorContains(t, err, "only supports")
}

Expand Down
Loading