Created a new java.time.Duration read timeout configuration property with sensible default of 1 minute. Introduced at org.springframework.ai.autoconfigure.openai.OpenAiParentProperties level so that it can be overriden by more specific configurations.

Did a bit of refactoring of org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration to stay DRY.

Eg: spring.ai.openai.read-timeout = 5m or spring.ai.openai.chat.read-timeout = 10m

Closes #354

Comment From: making

As commented in https://github.com/spring-projects/spring-ai/issues/354#issuecomment-1965697951 , using RestClientCustomer is a simple and effective method for many clients.

Comment From: thingersoft

@making See my comment on related issue.

Comment From: thingersoft

@tzolov or @markpollack Please see my latest comment on the issue

Comment From: markpollack

Hi. I added comments here, I think for 0.8.1 we can make an interim approach before tackling the general problem.

Comment From: markpollack

Sorry, i was confusing retry timeout with http timeout. We will address this issue, sorry for the confusion.

Comment From: thingersoft

Sorry, i was confusing retry timeout with http timeout. We will address this issue, sorry for the confusion.

@markpollack Ok, so you don't want me to contribute on it?

Comment From: markpollack

Hi. I think we can achieve what is needed but in a different way. The code you added to merge the property spring.ai.openai.base-url with spring.ai.openai.chat.base-url is already in the code base. See here.

The read timeout can be added by declaring a @Bean that returns an appropriately configured ClientHttpRequestFactory if the bean is not provided. Then the ClientHttpRequestFactory is added as an argument to the OpenAiChatClient and other clients. The ClientHttpRequestFactory can then be added as additional constructor argument to an OpenAiApi constructor.

Let me know what you think.

Comment From: thingersoft

Hi Mark, having a constructor with both a RestClient.Builder and a ClientHttpRequestFactory parameter wouldn't be measleading for the end user? I mean when both are provided wich instance of ClientHttpRequestFactory would be used, the one coming with the RestClient.Builder or the ClientHttpRequestFactory parameter? Maybe we could just declare a conditional RestClient.Builder bean giving it some custom name like openAiRestClientBuilder, and inject that bean into the clients. This way end users could override that bean without potentials side effects.

What you think?

Also I'm not sure if you intended to keep the new timeout property or just rely on overridable beans declarations.

The code you added to merge the property spring.ai.openai.base-url with spring.ai.openai.chat.base-url is already in the code base.

I see but the properties merge and validation logic is still duplicated for OpenAiImageClient and OpenAiAudioTranscriptionClient beans. So i merged my previous refactoring with the latest changes to stay DRY.

Let me know.

Comment From: markpollack

I understand the confusion with a constructor that has both a RestClient.Builder and a ClientHttpRequestFactory I need to think about it more.

I would like to have the top level property, so it is easy to use, vs writing code that uses the ClientHttpRequestFactory as that type of code isn't very obvious write.

The merge logic is duplicated for OpenAiImageClient and OpenAiAudioTranscriptionClient beans, but I don't see a way around it as each of those clients rely on their own options class to retrieve the value, e.g. spring.ai.openai.image.base-url

Comment From: thingersoft

The merge logic is duplicated for OpenAiImageClient and OpenAiAudioTranscriptionClient beans, but I don't see a way around it as each of those clients rely on their own options class to retrieve the value, e.g. spring.ai.openai.image.base-url

The specific option classes inherit from OpenAiParentProperties, so logic can be centralized for common properties. If you look at the code I already did it in OpenAiAutoConfiguration:

private static <T extends OpenAiParentProperties> OpenAiConnectionProperties checkAndOverrideProperties(
    OpenAiConnectionProperties commonProperties, T specificProperties) {

    String apiKey = StringUtils.hasText(specificProperties.getApiKey()) ? specificProperties.getApiKey()
            : commonProperties.getApiKey();

    String baseUrl = StringUtils.hasText(specificProperties.getBaseUrl()) ? specificProperties.getBaseUrl()
            : commonProperties.getBaseUrl();

    Duration readTimeout = specificProperties.getReadTimeout() != null ? specificProperties.getReadTimeout()
            : commonProperties.getReadTimeout();

    Assert.hasText(apiKey, "OpenAI API key must be set");
    Assert.hasText(baseUrl, "OpenAI base URL must be set");
    Assert.notNull(readTimeout, "OpenAI base read timeout must be set");

    OpenAiConnectionProperties overridenCommonProperties = new OpenAiConnectionProperties();
    overridenCommonProperties.setApiKey(apiKey);
    overridenCommonProperties.setBaseUrl(baseUrl);
    overridenCommonProperties.setReadTimeout(readTimeout);

    return overridenCommonProperties;

}

Comment From: thingersoft

I understand the confusion with a constructor that has both a RestClient.Builder and a ClientHttpRequestFactory I need to think about it more.

Hello, any news?

Comment From: markpollack

Just to update, there are many issues floating around on this topic, i'm trying to reconcile and figure out the best approach.

Comment From: markpollack

I found out that the only way to do this on a per-call request is to use the netty infra.

https://projectreactor.io/docs/netty/release/reference/index.html#response-timeout

https://docs.spring.io/spring-framework/reference/web/webflux-webclient/client-builder.html#webflux-client-builder-reactor-timeout

WebClient.create().get()
        .uri("https://example.org/path")
        .httpRequest(httpRequest -> {
            HttpClientRequest reactorRequest = httpRequest.getNativeRequest();
            reactorRequest.responseTimeout(Duration.ofSeconds(2));
        })
        .retrieve()
        .bodyToMono(String.class);

Thought I don't know if folks are comfortable with defaulting to use ReactorClientHttpConnector in order to provide this feature.