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