Skip to content

Commit ae9284c

Browse files
filiphrspring-builds
authored andcommitted
Add support for ApiKey for Anthropic and use it dynamically for every request
Fixes #3365 - Set ApiKey as late as possible Signed-off-by: Filip Hrisafov <[email protected]> (cherry picked from commit 0a1cf81)
1 parent 5ecfcce commit ae9284c

File tree

2 files changed

+377
-6
lines changed

2 files changed

+377
-6
lines changed

models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434
import reactor.core.publisher.Mono;
3535

3636
import org.springframework.ai.anthropic.api.StreamHelper.ChatCompletionResponseBuilder;
37+
import org.springframework.ai.model.ApiKey;
3738
import org.springframework.ai.model.ChatModelDescription;
3839
import org.springframework.ai.model.ModelOptionsUtils;
40+
import org.springframework.ai.model.SimpleApiKey;
3941
import org.springframework.ai.observation.conventions.AiProvider;
4042
import org.springframework.ai.retry.RetryUtils;
4143
import org.springframework.http.HttpHeaders;
@@ -60,6 +62,7 @@
6062
* @author Alexandros Pappas
6163
* @author Jonghoon Park
6264
* @author Claudio Silva Junior
65+
* @author Filip Hrisafov
6366
* @since 1.0.0
6467
*/
6568
public final class AnthropicApi {
@@ -96,6 +99,8 @@ public static Builder builder() {
9699

97100
private final WebClient webClient;
98101

102+
private final ApiKey apiKey;
103+
99104
/**
100105
* Create a new client api.
101106
* @param baseUrl api base URL.
@@ -107,18 +112,18 @@ public static Builder builder() {
107112
* @param responseErrorHandler Response error handler.
108113
* @param anthropicBetaFeatures Anthropic beta features.
109114
*/
110-
private AnthropicApi(String baseUrl, String completionsPath, String anthropicApiKey, String anthropicVersion,
115+
private AnthropicApi(String baseUrl, String completionsPath, ApiKey anthropicApiKey, String anthropicVersion,
111116
RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder,
112117
ResponseErrorHandler responseErrorHandler, String anthropicBetaFeatures) {
113118

114119
Consumer<HttpHeaders> jsonContentHeaders = headers -> {
115-
headers.add(HEADER_X_API_KEY, anthropicApiKey);
116120
headers.add(HEADER_ANTHROPIC_VERSION, anthropicVersion);
117121
headers.add(HEADER_ANTHROPIC_BETA, anthropicBetaFeatures);
118122
headers.setContentType(MediaType.APPLICATION_JSON);
119123
};
120124

121125
this.completionsPath = completionsPath;
126+
this.apiKey = anthropicApiKey;
122127

123128
this.restClient = restClientBuilder.clone()
124129
.baseUrl(baseUrl)
@@ -160,12 +165,17 @@ public ResponseEntity<ChatCompletionResponse> chatCompletionEntity(ChatCompletio
160165
Assert.isTrue(!chatRequest.stream(), "Request must set the stream property to false.");
161166
Assert.notNull(additionalHttpHeader, "The additional HTTP headers can not be null.");
162167

168+
// @formatter:off
163169
return this.restClient.post()
164170
.uri(this.completionsPath)
165-
.headers(headers -> headers.addAll(additionalHttpHeader))
171+
.headers(headers -> {
172+
headers.addAll(additionalHttpHeader);
173+
addDefaultHeadersIfMissing(headers);
174+
})
166175
.body(chatRequest)
167176
.retrieve()
168177
.toEntity(ChatCompletionResponse.class);
178+
// @formatter:on
169179
}
170180

171181
/**
@@ -196,9 +206,13 @@ public Flux<ChatCompletionResponse> chatCompletionStream(ChatCompletionRequest c
196206

197207
AtomicReference<ChatCompletionResponseBuilder> chatCompletionReference = new AtomicReference<>();
198208

209+
// @formatter:off
199210
return this.webClient.post()
200211
.uri(this.completionsPath)
201-
.headers(headers -> headers.addAll(additionalHttpHeader))
212+
.headers(headers -> {
213+
headers.addAll(additionalHttpHeader);
214+
addDefaultHeadersIfMissing(headers);
215+
}) // @formatter:off
202216
.body(Mono.just(chatRequest), ChatCompletionRequest.class)
203217
.retrieve()
204218
.bodyToFlux(String.class)
@@ -232,6 +246,15 @@ public Flux<ChatCompletionResponse> chatCompletionStream(ChatCompletionRequest c
232246
.filter(chatCompletionResponse -> chatCompletionResponse.type() != null);
233247
}
234248

249+
private void addDefaultHeadersIfMissing(HttpHeaders headers) {
250+
if (!headers.containsKey(HEADER_X_API_KEY)) {
251+
String apiKeyValue = this.apiKey.getValue();
252+
if (StringUtils.hasText(apiKeyValue)) {
253+
headers.add(HEADER_X_API_KEY, apiKeyValue);
254+
}
255+
}
256+
}
257+
235258
/**
236259
* Check the <a href="https://docs.anthropic.com/claude/docs/models-overview">Models
237260
* overview</a> and <a href=
@@ -1349,7 +1372,7 @@ public static class Builder {
13491372

13501373
private String completionsPath = DEFAULT_MESSAGE_COMPLETIONS_PATH;
13511374

1352-
private String apiKey;
1375+
private ApiKey apiKey;
13531376

13541377
private String anthropicVersion = DEFAULT_ANTHROPIC_VERSION;
13551378

@@ -1373,12 +1396,18 @@ public Builder completionsPath(String completionsPath) {
13731396
return this;
13741397
}
13751398

1376-
public Builder apiKey(String apiKey) {
1399+
public Builder apiKey(ApiKey apiKey) {
13771400
Assert.notNull(apiKey, "apiKey cannot be null");
13781401
this.apiKey = apiKey;
13791402
return this;
13801403
}
13811404

1405+
public Builder apiKey(String simpleApiKey) {
1406+
Assert.notNull(simpleApiKey, "simpleApiKey cannot be null");
1407+
this.apiKey = new SimpleApiKey(simpleApiKey);
1408+
return this;
1409+
}
1410+
13821411
public Builder anthropicVersion(String anthropicVersion) {
13831412
Assert.notNull(anthropicVersion, "anthropicVersion cannot be null");
13841413
this.anthropicVersion = anthropicVersion;

0 commit comments

Comments
 (0)