Skip to content

Commit a55f468

Browse files
authored
feat: add support for anthropic to AWS bedrock for Anthropic translation (#1418)
**Description** This PR implement adds a translator from the Anthropic API to AWS Bedrock for Anthropic. **Related Issues/PRs (if applicable)** Closes #1371 --------- Signed-off-by: secustor <sebastian@poxhofer.at>
1 parent f547bad commit a55f468

File tree

15 files changed

+859
-10
lines changed

15 files changed

+859
-10
lines changed

api/v1alpha1/shared_types.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ package v1alpha1
1515
type VersionedAPISchema struct {
1616
// Name is the name of the API schema of the AIGatewayRoute or AIServiceBackend.
1717
//
18-
// +kubebuilder:validation:Enum=OpenAI;AWSBedrock;AzureOpenAI;GCPVertexAI;GCPAnthropic;Anthropic
18+
// +kubebuilder:validation:Enum=OpenAI;AWSBedrock;AzureOpenAI;GCPVertexAI;GCPAnthropic;Anthropic;AWSAnthropic
1919
Name APISchema `json:"name"`
2020

2121
// Version is the version of the API schema.
@@ -65,6 +65,12 @@ const (
6565
// APISchemaAnthropic is the native Anthropic API schema.
6666
// https://docs.claude.com/en/home
6767
APISchemaAnthropic APISchema = "Anthropic"
68+
// APISchemaAWSAnthropic is the schema for Anthropic models hosted on AWS Bedrock.
69+
// Uses the native Anthropic Messages API format for requests and responses.
70+
//
71+
// https://aws.amazon.com/bedrock/anthropic/
72+
// https://docs.claude.com/en/api/claude-on-amazon-bedrock
73+
APISchemaAWSAnthropic APISchema = "AWSAnthropic"
6874
)
6975

7076
const (

examples/basic/aws.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,25 @@ spec:
2323
- name: envoy-ai-gateway-basic-aws
2424
---
2525
apiVersion: aigateway.envoyproxy.io/v1alpha1
26+
kind: AIGatewayRoute
27+
metadata:
28+
name: envoy-ai-gateway-basic-aws-bedrock-anthropic
29+
namespace: default
30+
spec:
31+
parentRefs:
32+
- name: envoy-ai-gateway-basic
33+
kind: Gateway
34+
group: gateway.networking.k8s.io
35+
rules:
36+
- matches:
37+
- headers:
38+
- type: Exact
39+
name: x-ai-eg-model
40+
value: anthropic.claude-3-5-sonnet-20241022-v2:0
41+
backendRefs:
42+
- name: envoy-ai-gateway-basic-aws-bedrock-anthropic
43+
---
44+
apiVersion: aigateway.envoyproxy.io/v1alpha1
2645
kind: AIServiceBackend
2746
metadata:
2847
name: envoy-ai-gateway-basic-aws
@@ -36,6 +55,20 @@ spec:
3655
group: gateway.envoyproxy.io
3756
---
3857
apiVersion: aigateway.envoyproxy.io/v1alpha1
58+
kind: AIServiceBackend
59+
metadata:
60+
name: envoy-ai-gateway-basic-aws-bedrock-anthropic
61+
namespace: default
62+
spec:
63+
schema:
64+
name: AWSAnthropic
65+
version: bedrock-2023-05-31
66+
backendRef:
67+
name: envoy-ai-gateway-basic-aws
68+
kind: Backend
69+
group: gateway.envoyproxy.io
70+
---
71+
apiVersion: aigateway.envoyproxy.io/v1alpha1
3972
kind: BackendSecurityPolicy
4073
metadata:
4174
name: envoy-ai-gateway-basic-aws-credentials
@@ -45,6 +78,9 @@ spec:
4578
- group: aigateway.envoyproxy.io
4679
kind: AIServiceBackend
4780
name: envoy-ai-gateway-basic-aws
81+
- group: aigateway.envoyproxy.io
82+
kind: AIServiceBackend
83+
name: envoy-ai-gateway-basic-aws-bedrock-anthropic
4884
type: AWSCredentials
4985
awsCredentials:
5086
region: us-east-1
@@ -85,6 +121,7 @@ metadata:
85121
type: Opaque
86122
stringData:
87123
# Replace this with your AWS credentials.
124+
# You can also use AWS IAM roles for service accounts (IRSA) in EKS.
88125
credentials: |
89126
[default]
90127
aws_access_key_id = AWS_ACCESS_KEY_ID

internal/extproc/messages_processor.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,13 @@ func (c *messagesProcessorUpstreamFilter) selectTranslator(out filterapi.Version
157157
// Anthropic → GCP Anthropic (request direction translator).
158158
// Uses backend config version (GCP Vertex AI requires specific versions like "vertex-2023-10-16").
159159
c.translator = translator.NewAnthropicToGCPAnthropicTranslator(out.Version, c.modelNameOverride)
160+
case filterapi.APISchemaAWSAnthropic:
161+
// Anthropic → AWS Bedrock Anthropic (request direction translator).
162+
c.translator = translator.NewAnthropicToAWSAnthropicTranslator(out.Version, c.modelNameOverride)
160163
case filterapi.APISchemaAnthropic:
161164
c.translator = translator.NewAnthropicToAnthropicTranslator(out.Version, c.modelNameOverride)
162165
default:
163-
return fmt.Errorf("/v1/messages endpoint only supports backends that return native Anthropic format (GCPAnthropic). Backend %s uses different model format", out.Name)
166+
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)
164167
}
165168
return nil
166169
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright Envoy AI Gateway Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
// The full text of the Apache license is available in the LICENSE file at
4+
// the root of the repo.
5+
6+
package translator
7+
8+
import (
9+
"cmp"
10+
"fmt"
11+
"net/url"
12+
13+
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
14+
extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
15+
"github.com/tidwall/sjson"
16+
17+
anthropicschema "github.com/envoyproxy/ai-gateway/internal/apischema/anthropic"
18+
"github.com/envoyproxy/ai-gateway/internal/internalapi"
19+
)
20+
21+
// NewAnthropicToAWSAnthropicTranslator creates a translator for Anthropic to AWS Bedrock Anthropic format.
22+
// AWS Bedrock supports the native Anthropic Messages API, so this is essentially a passthrough
23+
// translator with AWS-specific path modifications.
24+
func NewAnthropicToAWSAnthropicTranslator(apiVersion string, modelNameOverride internalapi.ModelNameOverride) AnthropicMessagesTranslator {
25+
anthropicTranslator := NewAnthropicToAnthropicTranslator(apiVersion, modelNameOverride).(*anthropicToAnthropicTranslator)
26+
return &anthropicToAWSAnthropicTranslator{
27+
apiVersion: apiVersion,
28+
anthropicToAnthropicTranslator: *anthropicTranslator,
29+
}
30+
}
31+
32+
type anthropicToAWSAnthropicTranslator struct {
33+
anthropicToAnthropicTranslator
34+
apiVersion string
35+
}
36+
37+
// RequestBody implements [AnthropicMessagesTranslator.RequestBody] for Anthropic to AWS Bedrock Anthropic translation.
38+
// This handles the transformation from native Anthropic format to AWS Bedrock format.
39+
// https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages-request-response.html
40+
func (a *anthropicToAWSAnthropicTranslator) RequestBody(rawBody []byte, body *anthropicschema.MessagesRequest, _ bool) (
41+
headerMutation *extprocv3.HeaderMutation, bodyMutation *extprocv3.BodyMutation, err error,
42+
) {
43+
a.stream = body.GetStream()
44+
a.requestModel = cmp.Or(a.modelNameOverride, body.GetModel())
45+
46+
var mutatedBody []byte
47+
mutatedBody, err = sjson.SetBytes(rawBody, anthropicVersionKey, a.apiVersion)
48+
if err != nil {
49+
return nil, nil, fmt.Errorf("failed to set anthropic_version field: %w", err)
50+
}
51+
// Remove the model field from the body as AWS Bedrock expects the model to be specified in the path.
52+
// Otherwise, AWS complains "extra inputs are not permitted".
53+
mutatedBody, _ = sjson.DeleteBytes(mutatedBody, "model")
54+
55+
// Determine the AWS Bedrock path based on whether streaming is requested.
56+
var pathTemplate string
57+
if body.GetStream() {
58+
pathTemplate = "/model/%s/invoke-stream"
59+
} else {
60+
pathTemplate = "/model/%s/invoke"
61+
}
62+
63+
// URL encode the model ID for the path to handle ARNs with special characters.
64+
// AWS Bedrock model IDs can be simple names (e.g., "anthropic.claude-3-5-sonnet-20241022-v2:0")
65+
// or full ARNs which may contain special characters.
66+
encodedModelID := url.PathEscape(a.requestModel)
67+
path := fmt.Sprintf(pathTemplate, encodedModelID)
68+
69+
headerMutation = &extprocv3.HeaderMutation{
70+
SetHeaders: []*corev3.HeaderValueOption{
71+
// Overwriting path of the Anthropic to Anthropic translator
72+
{Header: &corev3.HeaderValue{Key: ":path", RawValue: []byte(path)}},
73+
},
74+
}
75+
bodyMutation = &extprocv3.BodyMutation{Mutation: &extprocv3.BodyMutation_Body{Body: mutatedBody}}
76+
setContentLength(headerMutation, mutatedBody)
77+
return
78+
}

0 commit comments

Comments
 (0)