diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockAwsConnectionProperties.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockAwsConnectionProperties.java index cc47b675b63..54de46eee3d 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockAwsConnectionProperties.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockAwsConnectionProperties.java @@ -53,10 +53,30 @@ public class BedrockAwsConnectionProperties { private String sessionToken; /** - * Set model timeout, Defaults 5 min. + * Maximum duration of the entire API call operation. */ private Duration timeout = Duration.ofMinutes(5L); + /** + * Maximum time to wait while establishing connection with AWS service. + */ + private Duration connectionTimeout = Duration.ofSeconds(5L); + + /** + * Maximum duration spent reading response data. + */ + private Duration asyncReadTimeout = Duration.ofSeconds(30L); + + /** + * Maximum time to wait for a new connection from the pool. + */ + private Duration connectionAcquisitionTimeout = Duration.ofSeconds(30L); + + /** + * Maximum time to wait for response data. + */ + private Duration socketTimeout = Duration.ofSeconds(90L); + public String getRegion() { return this.region; } @@ -89,6 +109,38 @@ public void setTimeout(Duration timeout) { this.timeout = timeout; } + public Duration getConnectionTimeout() { + return connectionTimeout; + } + + public void setConnectionTimeout(Duration connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + public Duration getAsyncReadTimeout() { + return asyncReadTimeout; + } + + public void setAsyncReadTimeout(Duration asyncReadTimeout) { + this.asyncReadTimeout = asyncReadTimeout; + } + + public Duration getConnectionAcquisitionTimeout() { + return connectionAcquisitionTimeout; + } + + public void setConnectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) { + this.connectionAcquisitionTimeout = connectionAcquisitionTimeout; + } + + public Duration getSocketTimeout() { + return socketTimeout; + } + + public void setSocketTimeout(Duration socketTimeout) { + this.socketTimeout = socketTimeout; + } + public String getSessionToken() { return this.sessionToken; } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatAutoConfiguration.java index 311c1bfb807..96be2491325 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatAutoConfiguration.java @@ -76,6 +76,10 @@ public BedrockProxyChatModel bedrockProxyChatModel(AwsCredentialsProvider creden .credentialsProvider(credentialsProvider) .region(regionProvider.getRegion()) .timeout(connectionProperties.getTimeout()) + .connectionTimeout(connectionProperties.getConnectionTimeout()) + .asyncReadTimeout(connectionProperties.getAsyncReadTimeout()) + .connectionAcquisitionTimeout(connectionProperties.getConnectionAcquisitionTimeout()) + .socketTimeout(connectionProperties.getSocketTimeout()) .defaultOptions(chatProperties.getOptions()) .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) .toolCallingManager(toolCallingManager) diff --git a/models/spring-ai-bedrock-converse/pom.xml b/models/spring-ai-bedrock-converse/pom.xml index cc536e4d556..d084db287a7 100644 --- a/models/spring-ai-bedrock-converse/pom.xml +++ b/models/spring-ai-bedrock-converse/pom.xml @@ -76,6 +76,12 @@ ${bedrockruntime.version} + + software.amazon.awssdk + apache-client + ${bedrockruntime.version} + + org.springframework.ai diff --git a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java index 071e77a78cb..c2329825808 100644 --- a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java +++ b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java @@ -46,6 +46,7 @@ import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.core.document.Document; import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.http.apache.ApacheHttpClient; import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; @@ -782,7 +783,15 @@ public static final class Builder { private Region region = Region.US_EAST_1; - private Duration timeout = Duration.ofMinutes(10); + private Duration timeout = Duration.ofMinutes(5L); + + private Duration connectionTimeout = Duration.ofSeconds(5L); + + private Duration asyncReadTimeout = Duration.ofSeconds(30L); + + private Duration connectionAcquisitionTimeout = Duration.ofSeconds(30L); + + private Duration socketTimeout = Duration.ofSeconds(30L); private ToolCallingManager toolCallingManager; @@ -836,6 +845,30 @@ public Builder timeout(Duration timeout) { return this; } + public Builder connectionTimeout(Duration connectionTimeout) { + Assert.notNull(connectionTimeout, "'connectionTimeout' must not be null."); + this.connectionTimeout = connectionTimeout; + return this; + } + + public Builder asyncReadTimeout(Duration asyncReadTimeout) { + Assert.notNull(asyncReadTimeout, "'asyncReadTimeout' must not be null."); + this.asyncReadTimeout = asyncReadTimeout; + return this; + } + + public Builder connectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) { + Assert.notNull(connectionAcquisitionTimeout, "'connectionAcquisitionTimeout' must not be null."); + this.connectionAcquisitionTimeout = connectionAcquisitionTimeout; + return this; + } + + public Builder socketTimeout(Duration socketTimeout) { + Assert.notNull(socketTimeout, "'socketTimeout' must not be null."); + this.socketTimeout = socketTimeout; + return this; + } + public Builder defaultOptions(BedrockChatOptions defaultOptions) { Assert.notNull(defaultOptions, "'defaultOptions' must not be null."); this.defaultOptions = defaultOptions; @@ -867,9 +900,15 @@ public Builder bedrockRuntimeAsyncClient(BedrockRuntimeAsyncClient bedrockRuntim public BedrockProxyChatModel build() { if (this.bedrockRuntimeClient == null) { + + var httpClientBuilder = ApacheHttpClient.builder() + .connectionAcquisitionTimeout(connectionAcquisitionTimeout) + .connectionTimeout(this.connectionTimeout) + .socketTimeout(this.socketTimeout); + this.bedrockRuntimeClient = BedrockRuntimeClient.builder() .region(this.region) - .httpClientBuilder(null) + .httpClientBuilder(httpClientBuilder) .credentialsProvider(this.credentialsProvider) .overrideConfiguration(c -> c.apiCallTimeout(this.timeout)) .build(); @@ -877,10 +916,11 @@ public BedrockProxyChatModel build() { if (this.bedrockRuntimeAsyncClient == null) { - // TODO: Is it ok to configure the NettyNioAsyncHttpClient explicitly??? var httpClientBuilder = NettyNioAsyncHttpClient.builder() .tcpKeepAlive(true) - .connectionAcquisitionTimeout(Duration.ofSeconds(30)) + .readTimeout(this.asyncReadTimeout) + .connectionTimeout(this.connectionTimeout) + .connectionAcquisitionTimeout(this.connectionAcquisitionTimeout) .maxConcurrency(200); var builder = BedrockRuntimeAsyncClient.builder() diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc index 44966528e06..25258aa19a5 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc @@ -73,11 +73,14 @@ The prefix `spring.ai.bedrock.aws` is the property prefix to configure the conne |==== | Property | Description | Default -| spring.ai.bedrock.aws.region | AWS region to use. | us-east-1 -| spring.ai.bedrock.aws.timeout | AWS timeout to use. | 5m -| spring.ai.bedrock.aws.access-key | AWS access key. | - -| spring.ai.bedrock.aws.secret-key | AWS secret key. | - -| spring.ai.bedrock.aws.session-token | AWS session token for temporary credentials. | - +| spring.ai.bedrock.aws.region | AWS region to use | us-east-1 +| spring.ai.bedrock.aws.timeout | AWS max duration for entire API call | 5m +| spring.ai.bedrock.aws.connectionTimeout | Max duration to wait while establishing connection | 5s +| spring.ai.bedrock.aws.connectionAcquisitionTimeout | Max duration to wait for new connection from the pool | 30s +| spring.ai.bedrock.aws.asyncReadTimeout | Max duration spent reading asynchronous responses | 30s +| spring.ai.bedrock.aws.access-key | AWS access key | - +| spring.ai.bedrock.aws.secret-key | AWS secret key | - +| spring.ai.bedrock.aws.session-token | AWS session token for temporary credentials | - |==== [NOTE]