Spring AI Ollama 连接本地模型超时问题完全解决指南

一、问题现象

在 Spring Boot 3.2.5 项目中使用 spring-ai-ollama-spring-boot-starter(版本 1.0.0-M6)连接本地 Ollama 部署的 qwen2.5:7b-instruct 模型时,调用聊天接口(例如 RAG 问答)会在约 10 秒后抛出以下异常:

org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:11434/api/chat": timeout
    at org.springframework.web.client.DefaultRestClient...
Caused by: java.net.SocketTimeoutException: timeout
    at okio.SocketAsyncTimeout.newTimeoutException(JvmOkio.kt:146)
    at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(...)

尽管在 application.yml 中已经配置了 spring.ai.ollama.chat.options.timeout: 120s,超时仍然准时在 10 秒左右发生,导致模型生成未完成就被中断。

二、问题场景

  • 本地 Ollama 模型响应慢:使用 7B 或更大参数量的模型(如 qwen2.5:7b-instruct),或者提问复杂度较高时,Ollama 服务端需要较长时间(可能十几秒甚至几十秒)才能返回第一个 token 或完整响应。
  • 只配置了服务端超时,未配置客户端 HTTP 超时:开发者往往认为 spring.ai.ollama.chat.options.timeout 就足够控制整个请求的超时,但实际它只控制发送给 Ollama API 的 timeout 参数(告诉服务端最多生成多久),并不影响 Java 客户端等待响应的时长。
  • 底层 HTTP 客户端为 OkHttp:Spring AI Ollama 在无自定义配置时,默认通过 OkHttp3ClientHttpRequestFactory 使用 OkHttp 发起请求。OkHttp 的默认读超时为 10 秒,这就是超时发生在 10 秒的根本原因。

三、根因分析

1. 两层超时机制相互独立

  • 模型层超时(chat.options.timeout
    该值会被序列化到 POST /api/chat 请求体中的 options.timeout 字段,用于告知 Ollama 服务端允许的最长生成时间。服务端如果超时,会主动中断生成并返回错误。

  • HTTP 客户端层超时(OkHttp 读超时)
    这是 Java 应用等待服务器返回响应的最大时间。如果服务端处理慢(比如模型生成耗时较长),客户端会在达到读超时后直接抛出 SocketTimeoutException无论服务端是否仍在正常工作
    OkHttp 默认 readTimeout = 10_000ms(10 秒)。

只有 HTTP 读超时 > 模型生成所需时间时,请求才能正常完成。 反之,即使服务端允许生成更久,客户端也会先断开连接。

2. 常见配置为何不生效?

  • spring.restclient.read-timeout 无效
    spring.restclient 属性通过 RestClientCustomizer 全局修改 RestClient.Builder,但 Spring AI Ollama 自动配置内部是独立创建 RestClient 的,并未应用全局定制器,因此该配置无法传递到 Ollama 所用客户端。

  • SimpleClientHttpRequestFactory 无效
    实际堆栈中显示底层为 okhttp3.OkHttpClient,而非 JDK 默认的 HttpURLConnection(对应 SimpleClientHttpRequestFactory)。配置后者当然不起作用。

  • spring.okhttp.read-timeout 无效(或直接启动报错)
    Spring Boot 对 OkHttp 的属性前缀是 spring.okhttp,而非 okhttp。即使写成正确前缀,Ollama 自动配置也可能没有使用 Spring 管理的 OkHttpClient Bean,而是直接创建了一个默认 OkHttpClient,因此全局配置同样不生效。

此外,若在 YAML 中不慎写出两个顶级 spring: 键,会触发 DuplicateKeyException 导致启动失败。

4. 自定义 Bean 时的常见坑

直接创建 OllamaApi Bean 时,需注意其构造函数签名在 1.0.0-M6 版本中为:

public OllamaApi(String baseUrl, 
                 RestClient.Builder restClientBuilder, 
                 WebClient.Builder webClientBuilder)

而不是 (String, RestClient)。错误地调用构造函数会导致编译失败。


四、最终解决方案

自定义 OllamaApi Bean,显式控制 OkHttp 超时

直接通过配置类覆盖 OllamaApi Bean,创建一个具有足够长读超时的 OkHttpClient,并将其通过 RestClient.Builder 注入到 OllamaApi 中。此方案完全绕过 Spring 的全局 OkHttp 配置,从根源上解决问题。

步骤:

  1. 在项目中新增配置类 OllamaTimeoutConfig.java
  2. 使用 @Value 注入 spring.ai.ollama.base-url
  3. 构建自定义超时的 OkHttpClient
  4. 创建 RestClient.Builder 并设置 OkHttp3ClientHttpRequestFactory(虽然已过时,但功能正常,可忽略警告)。
  5. 提供空 WebClient.Builder 实例。
  6. 调用正确的 OllamaApi 三参数构造器并返回 Bean。

完整代码:

package com.badao.ai.config;

import okhttp3.OkHttpClient;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
import org.springframework.web.reactive.function.client.WebClient;

import java.time.Duration;

@Configuration
public class OllamaTimeoutConfig {

    @Value("${spring.ai.ollama.base-url}")
    private String baseUrl;

    @Bean
    public OllamaApi ollamaApi() {
        // 1. 自定义 OkHttpClient 超时
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(Duration.ofSeconds(30))       // 连接超时
                .readTimeout(Duration.ofMinutes(3))           // 读超时 3 分钟,大于模型 timeout
                .writeTimeout(Duration.ofSeconds(60))         // 写超时
                .build();

        // 2. 创建 OkHttp3ClientHttpRequestFactory(已过时但可用)
        OkHttp3ClientHttpRequestFactory factory =
                new OkHttp3ClientHttpRequestFactory(okHttpClient);

        // 3. 构建 RestClient.Builder,注入自定义 factory
        RestClient.Builder restClientBuilder = RestClient.builder()
                .baseUrl(baseUrl)
                .requestFactory(factory);

        // 4. 提供 WebClient.Builder(必须,传默认空 builder 即可)
        WebClient.Builder webClientBuilder = WebClient.builder();

        // 5. 调用 OllamaApi 实际构造函数
        return new OllamaApi(baseUrl, restClientBuilder, webClientBuilder);
    }
}

YAML 配置精简:
既然已经通过代码完全掌控了 HTTP 客户端超时,就可以移除 application.yml 中的 spring.restclientspring.okhttp 等无关超时配置,保持清晰:

server:
  port: 885

logging:
  level:
    com.badao: debug
    org.springframework.ai: debug

spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        options:
          model: qwen2.5:7b-instruct
          temperature: 0.5
          timeout: 120s          # 服务端模型生成超时,依然建议保留
      embedding:
        options:
          model: nomic-embed-text
          timeout: 120s
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB

关键要点

  • 读超时必须大于模型超时:这里 readTimeout = 3 分钟,而 chat.options.timeout = 2 分钟,留有充足缓冲。
  • OkHttp3ClientHttpRequestFactory 过时警告:不影响功能,可忽略。如需消除,需整体切换到其他 HTTP 客户端(如 JDK HttpClient),但会增加配置复杂度,不值得。
  • 不要添加额外的 YAML OkHttp 配置,避免干扰。

五、验证效果

  1. 重新编译并启动应用。
  2. 发送之前会导致超时的 RAG 请求。
  3. 观察日志,不再出现 Read timed outSocketTimeoutException
  4. 模型正常返回生成结果,即使耗时超过 10 秒、甚至 1 分钟,也能顺利完成。

六、总结

本次问题的本质是 Spring AI Ollama 使用的底层 OkHttp 读超时默认过短,且 YAML 配置中的服务端超时选项无法控制客户端行为,加上 Spring Boot 全局 OkHttp 属性与 Ollama 自动配置并不互通,导致常规配置尝试全部失效。
最终通过自定义 OllamaApi Bean 直接构建带超时的 OkHttpClient,并依其正确的构造函数注入,彻底解决了超时问题。该方案稳定可靠,推荐遇到同类问题的开发者采用。

Logo

免费领 100 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐