Skip to content

fix(bedrock): inference profile prefix resolution#189

Open
meowgorithm wants to merge 1 commit intomainfrom
bedrock-region
Open

fix(bedrock): inference profile prefix resolution#189
meowgorithm wants to merge 1 commit intomainfrom
bedrock-region

Conversation

@meowgorithm
Copy link
Copy Markdown
Member

This PR resolves Bedrock model IDs using AWS region-group prefixes with AWS_DEFAULT_REGION fallback.

Related: charmbracelet/catwalk#224

Resolve Bedrock model IDs using AWS region-group prefixes with
AWS_DEFAULT_REGION fallback, preserve already-prefixed IDs, and default
to global for unmapped regions. Add tests to lock in mapping and
prefixing behavior.

💘 Generated with Crush

Assisted-by: GPT-5.3 Codex via Crush <crush@charm.land>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Resolves AWS Bedrock model ID prefixing by mapping AWS regions to Bedrock “region-group” inference profile prefixes, with an added AWS_DEFAULT_REGION fallback.

Changes:

  • Updated Bedrock auth/config region selection to use AWS_REGION with AWS_DEFAULT_REGION fallback.
  • Reworked Bedrock model ID prefixing to use region-group prefixes (us, eu, jp, au, global) instead of the prior 2-letter region-derived prefix.
  • Added unit tests covering prefixing behavior and region-to-prefix mapping.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
providers/anthropic/bedrock.go Adds region resolution helper, region→prefix mapping, and prefix detection to correctly form Bedrock inference profile model IDs.
providers/anthropic/anthropic_test.go Adds tests verifying prefixing behavior, env var precedence, and region→prefix mapping.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +27 to +29
func awsRegion() string {
return cmp.Or(os.Getenv("AWS_REGION"), os.Getenv("AWS_DEFAULT_REGION"))
}
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.

Comment on lines +1866 to +1867
require.Equal(t, "global", bedrockRegionPrefix("ap-south-1"))
require.Equal(t, "global", bedrockRegionPrefix("sa-east-1"))
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"))

Comment on lines +39 to +40
case region == "ap-southeast-2":
return "au"
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"

}

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."} {

Comment on lines +27 to +29
func awsRegion() string {
return cmp.Or(os.Getenv("AWS_REGION"), os.Getenv("AWS_DEFAULT_REGION"))
}
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants