fix(gateway): bedrock chat uses the aws settings in the deployment config (#47365)

The deployment's aws_* litellm_params (keys, region, session, role,
aws_bedrock_runtime_endpoint) sit in their own group, not in extra, and
only the Vertex group was copied into the call's params, so Bedrock chat
signed with the environment's credentials and called the default regional
endpoint. Copy the AWS group the same way, resolving os.environ/ references.
This commit is contained in:
Nate Armstrong 2026-10-19 12:55:27 -07:00 • committed by GitHub
parent 0ac7a383db
commit 28feeab36c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 7 deletions

View file

@ -187,23 +187,30 @@ fn split(mut body: Map<String, Value>) -> (Value, Map<String, Value>) {
/// A deployment's connection for a provider chat translates. Python's router reads each
/// `os.environ/` setting from the environment when it loads the deployment, and `completion`
/// hands the Vertex AI settings to the call, where a request body cannot name them.
/// hands the Vertex AI and AWS settings to the call, where a request body cannot name them.
async fn connection(
gateway: &Gateway,
deployment: &Deployment,
optional_params: &mut Map<String, Value>,
) -> Result<(Option<String>, Option<String>), Error> {
let vertex = match serde_json::to_value(&deployment.litellm_params.vertex) {
Ok(Value::Object(fields)) => fields,
_ => Map::new(),
};
// The Vertex AI and AWS settings both live in their own groups of `litellm_params`, not in
// `extra`, so each is copied into the call's params, where the provider config reads it.
let mut settings = Map::new();
for group in [
serde_json::to_value(&deployment.litellm_params.vertex),
serde_json::to_value(&deployment.litellm_params.aws),
] {
if let Ok(Value::Object(fields)) = group {
settings.extend(fields);
}
}
let references: Vec<&str> = [
deployment.api_key.as_deref(),
deployment.api_base.as_deref(),
]
.into_iter()
.flatten()
.chain(vertex.values().filter_map(Value::as_str))
.chain(settings.values().filter_map(Value::as_str))
.filter_map(|value| value.strip_prefix(ENV_REFERENCE))
.collect();
let secrets = match references.is_empty() {
@ -221,7 +228,7 @@ async fn connection(
Some(name) => secrets.as_ref().and_then(|secrets| secrets.get(name)),
None => Some(value.to_owned()),
};
for (name, value) in vertex {
for (name, value) in settings {
let value = match value {
Value::String(text) => resolve(&text).map(Value::String),
Value::Null => None,

View file

@ -276,6 +276,13 @@ async fn harness() -> Harness {
model: bedrock/{BEDROCK_MODEL}
api_base: {upstream}
api_key: bedrock-token
- model_name: bedrock-keys
litellm_params:
model: bedrock/{BEDROCK_MODEL}
aws_access_key_id: AKIACONFIGKEY
aws_secret_access_key: config-secret
aws_region_name: us-west-2
aws_bedrock_runtime_endpoint: {upstream}
- model_name: claude-dropping
litellm_params:
model: anthropic/{ANTHROPIC_MODEL}
@ -449,6 +456,24 @@ async fn anthropic_tool_round_trip() {
);
}
#[tokio::test]
async fn bedrock_signs_with_the_keys_region_and_endpoint_in_the_config() {
let harness = harness().await;
harness
.json(json!({"model": "bedrock-keys", "messages": answered("tooluse_1"), "max_tokens": 16}))
.await;
let sent = harness.sent();
assert_eq!(sent.len(), 1, "the configured endpoint gets the call");
assert!(sent[0].path.ends_with("/converse"), "{}", sent[0].path);
let authorization = sent[0].header("authorization").unwrap();
assert!(
authorization.starts_with("AWS4-HMAC-SHA256 Credential=AKIACONFIGKEY/")
&& authorization.contains("/us-west-2/bedrock/aws4_request"),
"{authorization}"
);
}
#[tokio::test]
async fn bedrock_tool_round_trip() {
let harness = harness().await;