Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 7 additions & 1 deletion api/v1alpha1/shared_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ package v1alpha1
type VersionedAPISchema struct {
// Name is the name of the API schema of the AIGatewayRoute or AIServiceBackend.
//
// +kubebuilder:validation:Enum=OpenAI;AWSBedrock;AzureOpenAI;GCPVertexAI;GCPAnthropic;Anthropic
// +kubebuilder:validation:Enum=OpenAI;AWSBedrock;AzureOpenAI;GCPVertexAI;GCPAnthropic;Anthropic;AWSAnthropic
Name APISchema `json:"name"`

// Version is the version of the API schema.
Expand Down Expand Up @@ -65,6 +65,12 @@ const (
// APISchemaAnthropic is the native Anthropic API schema.
// https://docs.claude.com/en/home
APISchemaAnthropic APISchema = "Anthropic"
// APISchemaAWSAnthropic is the schema for Anthropic models hosted on AWS Bedrock.
// Uses the native Anthropic Messages API format for requests and responses.
//
// https://aws.amazon.com/bedrock/anthropic/
// https://docs.claude.com/en/api/claude-on-amazon-bedrock
APISchemaAWSAnthropic APISchema = "AWSAnthropic"
)

const (
Expand Down
37 changes: 37 additions & 0 deletions examples/basic/aws.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,25 @@ spec:
- name: envoy-ai-gateway-basic-aws
---
apiVersion: aigateway.envoyproxy.io/v1alpha1
kind: AIGatewayRoute
metadata:
name: envoy-ai-gateway-basic-aws-bedrock-anthropic
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: anthropic.claude-3-5-sonnet-20241022-v2:0
backendRefs:
- name: envoy-ai-gateway-basic-aws-bedrock-anthropic
---
apiVersion: aigateway.envoyproxy.io/v1alpha1
kind: AIServiceBackend
metadata:
name: envoy-ai-gateway-basic-aws
Expand All @@ -36,6 +55,20 @@ spec:
group: gateway.envoyproxy.io
---
apiVersion: aigateway.envoyproxy.io/v1alpha1
kind: AIServiceBackend
metadata:
name: envoy-ai-gateway-basic-aws-bedrock-anthropic
namespace: default
spec:
schema:
name: AWSAnthropic
version: bedrock-2023-05-31
backendRef:
name: envoy-ai-gateway-basic-aws
kind: Backend
group: gateway.envoyproxy.io
---
apiVersion: aigateway.envoyproxy.io/v1alpha1
kind: BackendSecurityPolicy
metadata:
name: envoy-ai-gateway-basic-aws-credentials
Expand All @@ -45,6 +78,9 @@ spec:
- group: aigateway.envoyproxy.io
kind: AIServiceBackend
name: envoy-ai-gateway-basic-aws
- group: aigateway.envoyproxy.io
kind: AIServiceBackend
name: envoy-ai-gateway-basic-aws-bedrock-anthropic
type: AWSCredentials
awsCredentials:
region: us-east-1
Expand Down Expand Up @@ -85,6 +121,7 @@ metadata:
type: Opaque
stringData:
# Replace this with your AWS credentials.
# You can also use AWS IAM roles for service accounts (IRSA) in EKS.
credentials: |
[default]
aws_access_key_id = AWS_ACCESS_KEY_ID
Expand Down
5 changes: 4 additions & 1 deletion internal/extproc/messages_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,13 @@ func (c *messagesProcessorUpstreamFilter) selectTranslator(out filterapi.Version
// Anthropic → GCP Anthropic (request direction translator).
// Uses backend config version (GCP Vertex AI requires specific versions like "vertex-2023-10-16").
c.translator = translator.NewAnthropicToGCPAnthropicTranslator(out.Version, c.modelNameOverride)
case filterapi.APISchemaAWSAnthropic:
// Anthropic → AWS Bedrock Anthropic (request direction translator).
c.translator = translator.NewAnthropicToAWSAnthropicTranslator(out.Version, c.modelNameOverride)
case filterapi.APISchemaAnthropic:
c.translator = translator.NewAnthropicToAnthropicTranslator(out.Version, c.modelNameOverride)
default:
return fmt.Errorf("/v1/messages endpoint only supports backends that return native Anthropic format (GCPAnthropic). Backend %s uses different model format", out.Name)
return fmt.Errorf("/v1/messages endpoint only supports backends that return native Anthropic format (Anthropic, GCPAnthropic, AWSAnthropic). Backend %s uses different model format", out.Name)
}
return nil
}
Expand Down
78 changes: 78 additions & 0 deletions internal/extproc/translator/anthropic_awsanthropic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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.

package translator

import (
"cmp"
"fmt"
"net/url"

corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
"github.com/tidwall/sjson"

anthropicschema "github.com/envoyproxy/ai-gateway/internal/apischema/anthropic"
"github.com/envoyproxy/ai-gateway/internal/internalapi"
)

// NewAnthropicToAWSAnthropicTranslator creates a translator for Anthropic to AWS Bedrock Anthropic format.
// AWS Bedrock supports the native Anthropic Messages API, so this is essentially a passthrough
// translator with AWS-specific path modifications.
func NewAnthropicToAWSAnthropicTranslator(apiVersion string, modelNameOverride internalapi.ModelNameOverride) AnthropicMessagesTranslator {
anthropicTranslator := NewAnthropicToAnthropicTranslator(apiVersion, modelNameOverride).(*anthropicToAnthropicTranslator)
return &anthropicToAWSAnthropicTranslator{
apiVersion: apiVersion,
anthropicToAnthropicTranslator: *anthropicTranslator,
}
}

type anthropicToAWSAnthropicTranslator struct {
anthropicToAnthropicTranslator
apiVersion string
}

// RequestBody implements [AnthropicMessagesTranslator.RequestBody] for Anthropic to AWS Bedrock Anthropic translation.
// This handles the transformation from native Anthropic format to AWS Bedrock format.
// https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages-request-response.html
func (a *anthropicToAWSAnthropicTranslator) RequestBody(rawBody []byte, body *anthropicschema.MessagesRequest, _ bool) (
headerMutation *extprocv3.HeaderMutation, bodyMutation *extprocv3.BodyMutation, err error,
) {
a.stream = body.GetStream()
a.requestModel = cmp.Or(a.modelNameOverride, body.GetModel())

var mutatedBody []byte
mutatedBody, err = sjson.SetBytes(rawBody, anthropicVersionKey, a.apiVersion)
if err != nil {
return nil, nil, fmt.Errorf("failed to set anthropic_version field: %w", err)
}
// Remove the model field from the body as AWS Bedrock expects the model to be specified in the path.
// Otherwise, AWS complains "extra inputs are not permitted".
mutatedBody, _ = sjson.DeleteBytes(mutatedBody, "model")

// Determine the AWS Bedrock path based on whether streaming is requested.
var pathTemplate string
if body.GetStream() {
pathTemplate = "/model/%s/invoke-stream"
} else {
pathTemplate = "/model/%s/invoke"
}

// URL encode the model ID for the path to handle ARNs with special characters.
// AWS Bedrock model IDs can be simple names (e.g., "anthropic.claude-3-5-sonnet-20241022-v2:0")
// or full ARNs which may contain special characters.
encodedModelID := url.PathEscape(a.requestModel)
path := fmt.Sprintf(pathTemplate, encodedModelID)

headerMutation = &extprocv3.HeaderMutation{
SetHeaders: []*corev3.HeaderValueOption{
// Overwriting path of the Anthropic to Anthropic translator
{Header: &corev3.HeaderValue{Key: ":path", RawValue: []byte(path)}},
},
}
bodyMutation = &extprocv3.BodyMutation{Mutation: &extprocv3.BodyMutation_Body{Body: mutatedBody}}
setContentLength(headerMutation, mutatedBody)
return
}
Loading