3434import reactor .core .publisher .Mono ;
3535
3636import org .springframework .ai .anthropic .api .StreamHelper .ChatCompletionResponseBuilder ;
37+ import org .springframework .ai .model .ApiKey ;
3738import org .springframework .ai .model .ChatModelDescription ;
3839import org .springframework .ai .model .ModelOptionsUtils ;
40+ import org .springframework .ai .model .SimpleApiKey ;
3941import org .springframework .ai .observation .conventions .AiProvider ;
4042import org .springframework .ai .retry .RetryUtils ;
4143import org .springframework .http .HttpHeaders ;
6062 * @author Alexandros Pappas
6163 * @author Jonghoon Park
6264 * @author Claudio Silva Junior
65+ * @author Filip Hrisafov
6366 * @since 1.0.0
6467 */
6568public 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