Skip to content
Open
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
30 changes: 30 additions & 0 deletions providers/anthropic/anthropic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1838,6 +1838,36 @@ func TestGenerate_BetaAPI(t *testing.T) {
})
}

func TestBedrockRegionPrefixing(t *testing.T) {
t.Run("uses us prefix when region is unset", func(t *testing.T) {
t.Setenv("AWS_REGION", "")
t.Setenv("AWS_DEFAULT_REGION", "")
require.Equal(t, "us.anthropic.claude-sonnet-4-5-20250929-v1:0", bedrockPrefixModelWithRegion("anthropic.claude-sonnet-4-5-20250929-v1:0"))
})

t.Run("uses AWS_DEFAULT_REGION when AWS_REGION is unset", func(t *testing.T) {
t.Setenv("AWS_REGION", "")
t.Setenv("AWS_DEFAULT_REGION", "eu-west-1")
require.Equal(t, "eu.anthropic.claude-sonnet-4-5-20250929-v1:0", bedrockPrefixModelWithRegion("anthropic.claude-sonnet-4-5-20250929-v1:0"))
})

t.Run("keeps already-prefixed model IDs", func(t *testing.T) {
t.Setenv("AWS_REGION", "us-east-1")
t.Setenv("AWS_DEFAULT_REGION", "")
require.Equal(t, "global.anthropic.claude-sonnet-4-5-20250929-v1:0", bedrockPrefixModelWithRegion("global.anthropic.claude-sonnet-4-5-20250929-v1:0"))
})

t.Run("maps known regions to profile prefixes", func(t *testing.T) {
require.Equal(t, "us", bedrockRegionPrefix("us-west-2"))
require.Equal(t, "us", bedrockRegionPrefix("ca-central-1"))
require.Equal(t, "eu", bedrockRegionPrefix("eu-central-1"))
require.Equal(t, "jp", bedrockRegionPrefix("ap-northeast-1"))
require.Equal(t, "au", bedrockRegionPrefix("ap-southeast-2"))
require.Equal(t, "global", bedrockRegionPrefix("ap-south-1"))
require.Equal(t, "global", bedrockRegionPrefix("sa-east-1"))
Comment on lines +1866 to +1867
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
require.Equal(t, "global", bedrockRegionPrefix("ap-south-1"))
require.Equal(t, "global", bedrockRegionPrefix("sa-east-1"))
require.Equal(t, "apac", bedrockRegionPrefix("ap-south-1"))
require.Equal(t, "apac", bedrockRegionPrefix("ap-northeast-2"))
require.Equal(t, "global", bedrockRegionPrefix("ap-south-1"))
require.Equal(t, "global", bedrockRegionPrefix("sa-east-1"))

})
}

func TestStream_BetaAPI(t *testing.T) {
t.Parallel()

Expand Down
40 changes: 32 additions & 8 deletions providers/anthropic/bedrock.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,43 @@ import (

func bedrockBasicAuthConfig(apiKey string) aws.Config {
return aws.Config{
Region: cmp.Or(os.Getenv("AWS_REGION"), "us-east-1"),
Region: cmp.Or(awsRegion(), "us-east-1"),
BearerAuthTokenProvider: bearer.StaticTokenProvider{Token: bearer.Token{Value: apiKey}},
}
}

func bedrockPrefixModelWithRegion(modelID string) string {
region := os.Getenv("AWS_REGION")
if len(region) < 2 {
region = "us-east-1"
}
prefix := region[:2] + "."
if strings.HasPrefix(modelID, prefix) {
if hasBedrockInferenceProfilePrefix(modelID) {
return modelID
}
return prefix + modelID
region := cmp.Or(awsRegion(), "us-east-1")
return bedrockRegionPrefix(region) + "." + modelID
}

func awsRegion() string {
return cmp.Or(os.Getenv("AWS_REGION"), os.Getenv("AWS_DEFAULT_REGION"))
}
Comment on lines +27 to +29
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

awsRegion() only checks AWS_REGION/AWS_DEFAULT_REGION. When Bedrock is used with default AWS auth, config.LoadDefaultConfig can resolve the region from shared config/IMDS even when both env vars are unset; in that case model prefixing will still default to us-east-1 ("us") and can generate the wrong inference-profile prefix. Consider passing the resolved AWS config region into the prefixing logic (or changing this helper to accept a region parameter) so the model ID prefix matches the actual region the SDK will use.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm not 100% sure about this one. Adding the awsRegion() function seems to do 2 things:

  • duplicate the logic of which region is supposed to be selected, which is done by aws' config.LoadDefaultConfig
  • could eventually lead to a situation where if no AWS_REGION / AWS_DEFAULT_REGION is set we run into models with the wrong prefix, because we're prefixing the model before resolving the region by aws' default helper (check resolveConfigLoaders in config.go in the config@v1.32.12)

Maybe we can take a safer path and just do the following:

  • get the default aws config from its config library
  • if an API key is present/ no-auth was passed, we update the config with an API key
  • if not, we move on with the parsed config
  • if an error happened during parsing - we fallback to the default region
  • BONUS: no if-else :)
// keep the model prefix enrichment method, but also add the resolved region instead of using the awsRegion helper
  func bedrockPrefixModelWithRegion(modelID, region string) string {
     if hasBedrockInferenceProfilePrefix(modelID) {
          return modelID
      }
      return bedrockRegionPrefix(cmp.Or(region, "us-east-1")) + "." + modelID
  }

// in anthropic.go

if a.options.useBedrock {
          cfg, err := config.LoadDefaultConfig(ctx)
          if err != nil {
              cfg = aws.Config{}
          }

          // we inline the logic of the bedrockBasicAuthConfig function (or just call it)
          // if the user has no region configured, we fallback to us-east 
          cfg.Region = cmp.Or(cfg.Region, "us-east-1") 
          if a.options.skipAuth || a.options.apiKey != "" { 
              cfg.BearerAuthTokenProvider = bearer.StaticTokenProvider{
                  Token: bearer.Token{Value: a.options.apiKey},
              }
          }

          // now we update the model prefix, as we're sure about the region
          // and update the client options
          modelID = bedrockPrefixModelWithRegion(modelID, cfg.Region)
          clientOptions = append(clientOptions, bedrock.WithConfig(cfg))

          if a.options.baseURL != "" {
              clientOptions = append(clientOptions, option.WithBaseURL(a.options.baseURL)
          }
      }

      return languageModel{
          modelID:  modelID,
          provider: a.options.name,
          options:  a.options,
          client:   anthropic.NewClient(clientOptions...),
      }, nil
  }

I hope that makes sense. We get the win of relying just on AWS "stdlib" to get the configuration and we still get to overlay the bedrock API key, if needed.


func bedrockRegionPrefix(region string) string {
switch {
case strings.HasPrefix(region, "us-") || region == "ca-central-1":
return "us"
case strings.HasPrefix(region, "eu-"):
return "eu"
case region == "ap-northeast-1":
return "jp"
case region == "ap-southeast-2":
return "au"
Comment on lines +39 to +40
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Let's add the missing case again, to make sure we support the apac region as well:

Suggested change
case region == "ap-southeast-2":
return "au"
case region == "ap-southeast-2":
return "au"
case strings.HasPrefix(region, "ap-"):
return "apac"

default:
return "global"
}
}

func hasBedrockInferenceProfilePrefix(modelID string) bool {
for _, prefix := range []string{"us.", "eu.", "jp.", "au.", "global."} {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

And finally, the missing apac prefix should be added here as well:

Suggested change
for _, prefix := range []string{"us.", "eu.", "jp.", "au.", "global."} {
for _, prefix := range []string{"us.", "eu.", "jp.", "au.", "apac.", "global."} {

if strings.HasPrefix(modelID, prefix) {
return true
}
}
return false
}
Loading