Spring AI Ollama 连接超时问题排查与解决:OkHttp 读超时配置全指南

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 版本中为:

java 复制代码
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。

完整代码:

java 复制代码
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 等无关超时配置,保持清晰:

yaml 复制代码
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,并依其正确的构造函数注入,彻底解决了超时问题。该方案稳定可靠,推荐遇到同类问题的开发者采用。

相关推荐
火山引擎开发者社区1 小时前
火山AgentPlan/CodingPlan同步上线GLM-5.2
人工智能
冬奇Lab2 小时前
Skill 系列(05):Skill 工作流串联——4 种模式实测,并发加速 1.5x
人工智能·开源
冬奇Lab2 小时前
每日一个开源项目(第141篇):hiring-agent - HackerRank 开源了他们的简历评分系统,你的简历能得几分?
人工智能·面试·开源
甲维斯2 小时前
又升级咯!坦克大战2026,科技与复古并存!
前端·人工智能·游戏开发
姗姗来迟了4 小时前
用React Hook封装AI对话状态
人工智能
Goodbye5 小时前
从 Token 到 Embedding:LLM 核心基础深度解析
javascript·人工智能
阿瑞IT5 小时前
AI Agent 在甘特计划变更场景中的动态响应工程实践
人工智能
用户938515635075 小时前
工具调用背后:LLM 如何突破“缸中大脑”,操控真实世界?
javascript·人工智能