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.M6" )
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.M6" )
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.M6" )
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+ public 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