11/* 
2-  * Copyright 2023-2024  the original author or authors. 
2+  * Copyright 2023-2025  the original author or authors. 
33 * 
44 * Licensed under the Apache License, Version 2.0 (the "License"); 
55 * you may not use this file except in compliance with the License. 
3030import  com .fasterxml .jackson .annotation .JsonProperty ;
3131import  org .apache .commons .logging .Log ;
3232import  org .apache .commons .logging .LogFactory ;
33+ import  org .springframework .ai .ollama .api .common .OllamaApiConstants ;
34+ import  org .springframework .ai .retry .RetryUtils ;
3335import  reactor .core .publisher .Flux ;
3436import  reactor .core .publisher .Mono ;
3537
3638import  org .springframework .ai .model .ModelOptionsUtils ;
37- import  org .springframework .ai .observation .conventions .AiProvider ;
3839import  org .springframework .http .HttpHeaders ;
3940import  org .springframework .http .HttpMethod ;
4041import  org .springframework .http .MediaType ;
4142import  org .springframework .http .ResponseEntity ;
42- import  org .springframework .http .client .ClientHttpResponse ;
4343import  org .springframework .util .Assert ;
44- import  org .springframework .util .StreamUtils ;
4544import  org .springframework .web .client .ResponseErrorHandler ;
4645import  org .springframework .web .client .RestClient ;
4746import  org .springframework .web .reactive .function .client .WebClient ;
5150 * 
5251 * @author Christian Tzolov 
5352 * @author Thomas Vitale 
53+  * @author Jonghoon Park 
5454 * @since 0.8.0 
5555 */ 
5656// @formatter:off 
5757public  class  OllamaApi  {
5858
59- 	public  static  final   String   PROVIDER_NAME  =  AiProvider . OLLAMA . value ();
59+ 	public  static  Builder   builder () {  return   new   Builder (); } 
6060
6161	public  static  final  String  REQUEST_BODY_NULL_ERROR  = "The request body can not be null." ;
6262
6363	private  static  final  Log  logger  = LogFactory .getLog (OllamaApi .class );
6464
65- 	private  static  final  String  DEFAULT_BASE_URL  = "http://localhost:11434" ;
66- 
67- 	private  final  ResponseErrorHandler  responseErrorHandler ;
68- 
6965	private  final  RestClient  restClient ;
7066
7167	private  final  WebClient  webClient ;
7268
7369	/** 
7470	 * Default constructor that uses the default localhost url. 
7571	 */ 
72+ 	@ Deprecated (since  = "1.0.0.M8" )
7673	public  OllamaApi () {
77- 		this (DEFAULT_BASE_URL );
74+ 		this (OllamaApiConstants . DEFAULT_BASE_URL );
7875	}
7976
8077	/** 
8178	 * Crate a new OllamaApi instance with the given base url. 
8279	 * @param baseUrl The base url of the Ollama server. 
8380	 */ 
81+ 	@ Deprecated (since  = "1.0.0.M8" )
8482	public  OllamaApi (String  baseUrl ) {
85- 		this (baseUrl , RestClient .builder (), WebClient .builder ());
83+ 		this (baseUrl , RestClient .builder (), WebClient .builder (),  RetryUtils . DEFAULT_RESPONSE_ERROR_HANDLER );
8684	}
8785
8886	/** 
8987	 * Crate a new OllamaApi instance with the given base url and 
9088	 * {@link RestClient.Builder}. 
9189	 * @param baseUrl The base url of the Ollama server. 
9290	 * @param restClientBuilder The {@link RestClient.Builder} to use. 
91+ 	 * @param webClientBuilder The {@link WebClient.Builder} to use. 
9392	 */ 
93+ 	@ Deprecated (since  = "1.0.0.M8" )
9494	public  OllamaApi (String  baseUrl , RestClient .Builder  restClientBuilder , WebClient .Builder  webClientBuilder ) {
95+ 		this (baseUrl , restClientBuilder , webClientBuilder , RetryUtils .DEFAULT_RESPONSE_ERROR_HANDLER );
96+ 	}
9597
96- 		this .responseErrorHandler  = new  OllamaResponseErrorHandler ();
98+ 	/** 
99+ 	 * Create a new OllamaApi instance 
100+ 	 * @param baseUrl The base url of the Ollama server. 
101+ 	 * @param restClientBuilder The {@link RestClient.Builder} to use. 
102+      * @param webClientBuilder The {@link WebClient.Builder} to use. 
103+ 	 * @param responseErrorHandler Response error handler. 
104+ 	 */ 
105+ 	private  OllamaApi (String  baseUrl , RestClient .Builder  restClientBuilder , WebClient .Builder  webClientBuilder , ResponseErrorHandler  responseErrorHandler ) {
97106
98107		Consumer <HttpHeaders > defaultHeaders  = headers  -> {
99108			headers .setContentType (MediaType .APPLICATION_JSON );
100109			headers .setAccept (List .of (MediaType .APPLICATION_JSON ));
101110		};
102111
103- 		this .restClient  = restClientBuilder .baseUrl (baseUrl ).defaultHeaders (defaultHeaders ).build ();
112+ 		this .restClient  = restClientBuilder .baseUrl (baseUrl )
113+ 				.defaultHeaders (defaultHeaders )
114+ 				.defaultStatusHandler (responseErrorHandler )
115+ 				.build ();
104116
105- 		this .webClient  = webClientBuilder .baseUrl (baseUrl ).defaultHeaders (defaultHeaders ).build ();
117+ 		this .webClient  = webClientBuilder 
118+ 				.baseUrl (baseUrl )
119+ 				.defaultHeaders (defaultHeaders )
120+ 				.build ();
106121	}
107122
108123	/** 
@@ -121,7 +136,6 @@ public ChatResponse chat(ChatRequest chatRequest) {
121136			.uri ("/api/chat" )
122137			.body (chatRequest )
123138			.retrieve ()
124- 			.onStatus (this .responseErrorHandler )
125139			.body (ChatResponse .class );
126140	}
127141
@@ -188,7 +202,6 @@ public EmbeddingsResponse embed(EmbeddingsRequest embeddingsRequest) {
188202			.uri ("/api/embed" )
189203			.body (embeddingsRequest )
190204			.retrieve ()
191- 			.onStatus (this .responseErrorHandler )
192205			.body (EmbeddingsResponse .class );
193206	}
194207
@@ -199,7 +212,6 @@ public ListModelResponse listModels() {
199212		return  this .restClient .get ()
200213				.uri ("/api/tags" )
201214				.retrieve ()
202- 				.onStatus (this .responseErrorHandler )
203215				.body (ListModelResponse .class );
204216	}
205217
@@ -212,7 +224,6 @@ public ShowModelResponse showModel(ShowModelRequest showModelRequest) {
212224				.uri ("/api/show" )
213225				.body (showModelRequest )
214226				.retrieve ()
215- 				.onStatus (this .responseErrorHandler )
216227				.body (ShowModelResponse .class );
217228	}
218229
@@ -225,7 +236,6 @@ public ResponseEntity<Void> copyModel(CopyModelRequest copyModelRequest) {
225236				.uri ("/api/copy" )
226237				.body (copyModelRequest )
227238				.retrieve ()
228- 				.onStatus (this .responseErrorHandler )
229239				.toBodilessEntity ();
230240	}
231241
@@ -238,7 +248,6 @@ public ResponseEntity<Void> deleteModel(DeleteModelRequest deleteModelRequest) {
238248				.uri ("/api/delete" )
239249				.body (deleteModelRequest )
240250				.retrieve ()
241- 				.onStatus (this .responseErrorHandler )
242251				.toBodilessEntity ();
243252	}
244253
@@ -261,26 +270,6 @@ public Flux<ProgressResponse> pullModel(PullModelRequest pullModelRequest) {
261270				.bodyToFlux (ProgressResponse .class );
262271	}
263272
264- 	private  static  class  OllamaResponseErrorHandler  implements  ResponseErrorHandler  {
265- 
266- 		@ Override 
267- 		public  boolean  hasError (ClientHttpResponse  response ) throws  IOException  {
268- 			return  response .getStatusCode ().isError ();
269- 		}
270- 
271- 		@ Override 
272- 		public  void  handleError (ClientHttpResponse  response ) throws  IOException  {
273- 			if  (response .getStatusCode ().isError ()) {
274- 				int  statusCode  = response .getStatusCode ().value ();
275- 				String  statusText  = response .getStatusText ();
276- 				String  message  = StreamUtils .copyToString (response .getBody (), java .nio .charset .StandardCharsets .UTF_8 );
277- 				logger .warn (String .format ("[%s] %s - %s" , statusCode , statusText , message ));
278- 				throw  new  RuntimeException (String .format ("[%s] %s - %s" , statusCode , statusText , message ));
279- 			}
280- 		}
281- 
282- 	}
283- 
284273	/** 
285274	 * Chat message object. 
286275	 * 
@@ -736,5 +725,44 @@ public record ProgressResponse(
736725			@ JsonProperty ("completed" ) Long  completed 
737726	) { }
738727
728+ 	public  static  class  Builder  {
729+ 
730+ 		private  String  baseUrl  = OllamaApiConstants .DEFAULT_BASE_URL ;
731+ 
732+ 		private  RestClient .Builder  restClientBuilder  = RestClient .builder ();
733+ 
734+ 		private  WebClient .Builder  webClientBuilder  = WebClient .builder ();
735+ 
736+ 		private  ResponseErrorHandler  responseErrorHandler  = RetryUtils .DEFAULT_RESPONSE_ERROR_HANDLER ;
737+ 
738+ 		public  Builder  baseUrl (String  baseUrl ) {
739+ 			Assert .hasText (baseUrl , "baseUrl cannot be null or empty" );
740+ 			this .baseUrl  = baseUrl ;
741+ 			return  this ;
742+ 		}
743+ 
744+ 		public  Builder  restClientBuilder (RestClient .Builder  restClientBuilder ) {
745+ 			Assert .notNull (restClientBuilder , "restClientBuilder cannot be null" );
746+ 			this .restClientBuilder  = restClientBuilder ;
747+ 			return  this ;
748+ 		}
749+ 
750+ 		public  Builder  webClientBuilder (WebClient .Builder  webClientBuilder ) {
751+ 			Assert .notNull (webClientBuilder , "webClientBuilder cannot be null" );
752+ 			this .webClientBuilder  = webClientBuilder ;
753+ 			return  this ;
754+ 		}
755+ 
756+ 		public  Builder  responseErrorHandler (ResponseErrorHandler  responseErrorHandler ) {
757+ 			Assert .notNull (responseErrorHandler , "responseErrorHandler cannot be null" );
758+ 			this .responseErrorHandler  = responseErrorHandler ;
759+ 			return  this ;
760+ 		}
761+ 
762+ 		public  OllamaApi  build () {
763+ 			return  new  OllamaApi (this .baseUrl , this .restClientBuilder , this .webClientBuilder , this .responseErrorHandler );
764+ 		}
765+ 
766+ 	}
739767}
740768// @formatter:on 
0 commit comments