1.1 开篇场景

星期一早上,小王打开了 Jira,看到产品经理提了一张新需求单:

[SMART-101] 为客服系统接入 AI 对话功能
用户可以在对话框输入问题,AI 助手给出智能回复。本周五演示。

小王是一名有五年经验的 Java 工程师,负责公司核心业务系统的后端开发。他打开 GitHub,搜索"java openai",看到了三个都在活跃维护的库:

  • Spring AI — 星标 3.8k,Spring 官方出品,Readme 里有熟悉的 @Autowiredapplication.yml
  • LangChain4j — 星标 5.1k,自称"Java 版 LangChain",号称支持 Agent、RAG、工具调用
  • openai-java — OpenAI 官方 Java SDK,文档齐全,API 和 Python 版一一对应

三个库,都能跑,都有文档,都有人用。该选哪个?

这种选择困难是真实存在的。和小王一样,很多 Java 工程师在第一次接触 AI 开发时都会遭遇这个问题——不是因为资料太少,而是因为资料太多,却缺少一个有说服力的对比基准。

本章的解决方案很直接:用同一个功能,写三次。实现完全相同的接口——接收用户消息、返回 AI 回复——分别用三个框架完成,然后把代码并排放在一起,让差异自己说话。

读完本章,你将能够:

  • 理解三个框架各自的设计哲学,而不只是看到表面的 API 差异
  • 根据项目场景做出有依据的选型决策
  • 跑通 SmartDesk 项目的第一个 AI 对话接口
  • 避开新手最容易踩的 5 个工程陷阱

1.2 原理解析:三套框架的设计哲学

在看代码之前,先理解这三个框架的设计者在解决什么问题。这很重要——当你遇到奇怪的行为或文档里找不到答案时,设计哲学会告诉你"应该往哪里找"。

Spring AI:把 AI 接入 Spring 生态

Spring AI 的核心命题是:为 Spring 生态提供 AI 能力,而不是创建一个新的 AI 框架

它的设计思路直接从 Spring Data、Spring Security 那里借鉴过来。你用过 Spring Data JPA 吗?你只需要声明一个 JpaRepository<User, Long> 接口,框架就帮你把 SQL、连接池、事务全搞定了。你不需要了解 Hibernate 的底层细节,只需要按照 Spring 的约定配置就够了。

Spring AI 想对 AI 调用做同样的事:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}

配置两行,注入一个 ChatClient.Builder,就能调用 GPT-4。不需要了解 OpenAI REST API 的细节,不需要构建 HTTP 请求,不需要解析 JSON 响应。这就是"约定优于配置"在 AI 领域的体现。

Spring AI 的另一个核心设计是模型无关性:同样的代码,只改一行配置,就能从 OpenAI 切换到 Anthropic Claude、Google Gemini 或本地的 Ollama。这对于需要多模型对比测试的场景非常有价值。

适合谁:已有 Spring Boot 项目,需要快速接入基础 AI 对话能力,团队对 Spring 生态熟悉,不需要复杂的 Agent 编排。

LangChain4j:把 LangChain 的可组合性带到 Java 世界

LangChain4j 的核心命题是:构建复杂 AI 应用所需的完整工具链

它来自 Python 生态的 LangChain——那个让 Agent、RAG、工具调用等概念在工程实践中落地的框架。LangChain4j 不只是把 Python API 翻译成 Java,而是用 Java 的方式重新设计了接口。

最具代表性的设计是 AiServices

interface CustomerSupport {
    @SystemMessage("你是一个专业的客服助手")
    String chat(String userMessage);
    
    @SystemMessage("总结以下对话内容")
    String summarize(@MemoryId String sessionId, String conversation);
}

CustomerSupport support = AiServices.create(CustomerSupport.class, model);

这个接口定义不只是一个调用封装——它是一份业务语义的声明。@SystemMessage 直接和方法绑定,@MemoryId 自动处理对话记忆,方法名本身就是业务意图的表达。这是 Java 语言特性(接口、注解)与 AI 能力的深度融合,而不是简单的 API 包装。

LangChain4j 还提供了一套完整的"AI 工程基础设施":内置 RAG pipeline、工具调用框架、对话记忆管理、文档分割与嵌入等。这些能力在 Spring AI 里都是后来补充的,而在 LangChain4j 里是从一开始就作为核心设计存在的。

适合谁:需要构建具备真实业务价值的 AI 应用——RAG 知识库、多工具 Agent、有状态对话流——且愿意在工程设计上投入时间。这是本书 SmartDesk 项目的主线选择。

原生 SDK:最大控制权,也是最大责任

原生 SDK(openai-java)的设计理念只有一句话:忠实映射 OpenAI REST API,不做任何额外抽象

它是 OpenAI 官方维护的 Java 客户端,API 设计与 Python SDK 保持一致。每一个请求参数都有对应的 Java 方法,每一个响应字段都有对应的 Java 类型。没有 auto-configuration,没有 AiServices,没有框架魔法——你需要自己构建请求、自己解析响应、自己处理错误、自己实现重试。

这种"透明度"在某些场景下是优势:

  • 极致性能调优:当你需要精确控制连接池大小、请求超时、并发策略时,原生 SDK 不会在中间层引入任何不可控的延迟
  • 使用最新特性:OpenAI 每次发布新 API 特性,原生 SDK 通常是最先支持的;Spring AI 和 LangChain4j 需要等待版本发布
  • 构建自己的框架:如果你在为公司构建 AI 中间件平台,原生 SDK 就是你的地基

适合谁:有极致延迟/成本要求、需要自定义框架层、或者在做本书第 8 章的成本优化工作。


1.3 动手实验:同一个功能,三种写法

现在来到最直观的部分。我们要实现的功能非常简单:

接收一条字符串消息,调用 GPT-4o-mini,返回 AI 的文本回复。

三个实现的 HTTP 接口格式完全相同:

GET /spring-ai/chat?message=你好
GET /langchain4j/chat?message=你好
GET /native/chat?message=你好

请求进去,AI 回复出来。差异只在实现方式上。

方式一:Spring AI

@RestController
@RequestMapping("/spring-ai")
public class SpringAiDemo {

    private final ChatClient chatClient;

    // Spring AI 的 auto-configuration 自动创建 ChatClient.Builder Bean
    // 你只需要声明依赖,框架负责注入
    public SpringAiDemo(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @GetMapping("/chat")
    public String chat(@RequestParam String message) {
        return chatClient.prompt()
                .user(message)
                .call()
                .content();
    }
}

代码分析:这 4 行核心代码背后有多少框架在工作?

  1. ChatClient.Builderspring-ai-openai-spring-boot-starter 自动注册,配置来自 application.yml
  2. .prompt() 开启一次对话构建上下文
  3. .user(message) 设置角色为 user,构建请求消息
  4. .call() 触发同步 HTTP 请求,等待响应
  5. .content() 从响应中提取文本,自动跳过 token 计数、finish_reason 等元数据

对于只需要基础对话的场景,这就是最简洁的写法。整个控制器文件不到 20 行,没有任何冗余。

方式二:LangChain4j

@RestController
@RequestMapping("/langchain4j")
public class LangChain4jDemo {

    // 这是 LangChain4j 最核心的设计:用接口声明 AI 服务
    interface Assistant {
        @SystemMessage("你是 SmartDesk 智能客服助手,请用简洁专业的语言回答用户问题。")
        String chat(String userMessage);
    }

    private final Assistant assistant;

    public LangChain4jDemo(@Value("${openai.api-key}") String apiKey) {
        OpenAiChatModel model = OpenAiChatModel.builder()
                .apiKey(apiKey)
                .modelName("gpt-4o-mini")
                .temperature(0.7)
                .build();

        // AiServices.create() 用动态代理实现这个接口
        this.assistant = AiServices.create(Assistant.class, model);
    }

    @GetMapping("/chat")
    public String chat(@RequestParam String message) {
        return assistant.chat(message);  // 就是一次普通方法调用
    }
}

代码分析:注意几个设计上的不同之处。

首先,Assistant 接口不只是个调用封装——它携带了业务语义@SystemMessage 注解把系统提示词和具体方法绑定在一起,这意味着不同的业务场景可以定义不同的接口方法,各自有独立的 system prompt,而不需要在业务逻辑里手动拼接。

其次,AiServices.create() 这一行才是真正的魔法。LangChain4j 在运行时为 Assistant 接口生成了一个动态代理实现,代理负责把方法调用翻译成 OpenAI API 请求。对于业务代码来说,调用 assistant.chat(message) 就是调用一个普通的 Java 方法,完全不需要感知底层的 AI 调用细节。

这种设计在大型项目中的价值非常明显:你可以轻松对 Assistant 接口做 Mock,在单元测试中完全绕过 AI 服务。

方式三:原生 SDK

@RestController
@RequestMapping("/native")
public class NativeSdkDemo {

    private final OpenAIClient client;

    public NativeSdkDemo(@Value("${openai.api-key}") String apiKey) {
        this.client = OpenAIOkHttpClient.builder()
                .apiKey(apiKey)
                .build();
    }

    @GetMapping("/chat")
    public String chat(@RequestParam String message) {
        // 手动构建请求参数对象
        ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
                .model(ChatModel.GPT_4O_MINI)
                .addUserMessage(message)
                .build();

        // 发起请求,获取完整响应对象
        ChatCompletion completion = client.chat().completions().create(params);

        // 手动沿层次结构提取文本内容
        return completion.choices().get(0).message().content().orElse("(无回复)");
    }
}

代码分析:原生 SDK 的代码最长,但每一行都是显式的。

ChatCompletionCreateParams.builder() 明确告诉你这是一个不可变的参数对象,所有设置都在 build 之前完成。ChatModel.GPT_4O_MINI 是枚举类型——这是原生 SDK 少数提供类型安全的地方,防止因模型名称拼写错误产生的运行时异常。

completion.choices().get(0).message().content() 这一串方法调用精确映射了 OpenAI API 的 JSON 结构:一个请求对应多个 choices(默认为 1),每个 choice 有一个 message,message 的 content 字段是 Optional 类型(因为某些特殊情况下模型可能不返回文本内容)。

这种"忠实映射"在需要访问响应元数据(如 token 用量、finish_reason、logprobs)时非常方便——那些数据就在响应对象里,取就行了;而在 Spring AI 的 .content() 之后,这些元数据就不见了。

三种写法的直接对比

功能代码行数(不含类定义和导入):
  Spring AI       : 4 行
  LangChain4j     : 3 行(接口定义 + 方法调用)
  原生 SDK         : 7 行

初始化代码量:
  Spring AI       : 1 行(builder.build())
  LangChain4j     : 4 行(构建 model + AiServices.create)
  原生 SDK         : 3 行(构建 client)

系统提示词的位置:
  Spring AI       : 在 .system() 方法调用中
  LangChain4j     : 在接口方法的 @SystemMessage 注解中
  原生 SDK         : 在 ChatCompletionCreateParams 的消息列表中

1.4 深度对比:功能矩阵与 API 设计差异

功能矩阵

能力 Spring AI LangChain4j 原生 SDK
RAG 支持 有(向量存储抽象 + ETL pipeline) 有(成熟,多种 embedding 策略) 无(需自行实现)
Agent 框架 有(工具调用封装) 有(完整 Agent 框架,ReAct 等) 无(需自行实现)
MCP 支持 有(1.0 起内置) 有(0.30+ 支持)
Streaming
Function Calling 有(@Tool 注解) 有(@Tool 注解,更成熟) 有(原生 API)
多模型切换 优秀(同一 API 切换不同 provider) 良好(需换 model 实现类) 仅 OpenAI
Spring Boot 集成 原生(starter 自动配置) 有(官方 starter) 无(需手动配置)
社区活跃度 高(Spring 生态背书) 高(独立活跃社区) 高(OpenAI 官方维护)
学习曲线 低(Spring 开发者直接上手) 中(需理解 AiServices 设计模式) 低(API 即文档)

关键差异说明

  • RAG 成熟度:LangChain4j 的 RAG pipeline 经历了更多生产项目的打磨,支持更丰富的分块策略(句子分割、递归分割、语义分割)和重排序(reranking)。Spring AI 的 RAG 功能在 1.0 版本后有了大幅改善,但整体仍稍逊一筹。

  • Agent 能力:LangChain4j 提供了 AiAgent 接口和多种内置 Agent 策略(ReAct、Plan-and-Execute),Spring AI 的 Agent 能力更多依赖于工具调用的组合使用。

  • 多模型切换:Spring AI 的模型抽象层设计得更彻底。同一个 ChatClient,只改配置文件,就能从 OpenAI 切换到 Anthropic。LangChain4j 需要替换 model 实现类,虽然通过依赖注入也能做到无缝切换,但代码层面稍有入侵。

API 设计对比:处理系统消息

三个框架处理系统提示词(system prompt)的方式揭示了它们截然不同的设计哲学:

Spring AI — 流式 API,运行时注入

// 系统消息在调用时动态注入
chatClient.prompt()
    .system("你是一个专业的客服助手,只回答与产品相关的问题")
    .user(userMessage)
    .call()
    .content();

LangChain4j — 注解声明,编译期绑定

interface Assistant {
    // 系统消息与方法静态绑定,是接口契约的一部分
    @SystemMessage("你是一个专业的客服助手,只回答与产品相关的问题")
    String chat(String userMessage);
}

原生 SDK — 显式消息列表,完全手动

ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
    .model(ChatModel.GPT_4O_MINI)
    // 系统消息就是消息列表里的一个条目,和用户消息平级
    .addSystemMessage("你是一个专业的客服助手,只回答与产品相关的问题")
    .addUserMessage(userMessage)
    .build();

Spring AI 的方式最灵活,系统提示词可以在运行时根据上下文动态生成。LangChain4j 的方式最规范,系统提示词是接口定义的一部分,修改需要改代码而非运行时逻辑(这在某些场景下反而是优点——意外的 system prompt 修改会在 code review 时被发现)。原生 SDK 的方式最直接,消息就是消息,没有任何语义包装。

API 设计对比:处理模型参数

Spring AI — 配置文件驱动

spring:
  ai:
    openai:
      chat:
        options:
          model: gpt-4o-mini
          temperature: 0.7
          max-tokens: 1024

代码里完全看不到 temperature 这些参数——它们在配置文件里。要临时覆盖:

chatClient.prompt()
    .user(message)
    .options(OpenAiChatOptions.builder()
        .withTemperature(0.9f)
        .build())
    .call()
    .content();

LangChain4j — 构建器模式,代码中配置

OpenAiChatModel model = OpenAiChatModel.builder()
    .apiKey(apiKey)
    .modelName("gpt-4o-mini")
    .temperature(0.7)
    .maxTokens(1024)
    .build();

参数在代码中,清晰直观,但修改需要重新部署(除非结合 @Value 从配置文件读取)。

原生 SDK — 请求级参数

ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
    .model(ChatModel.GPT_4O_MINI)
    .temperature(0.7)
    .maxTokens(1024)
    .addUserMessage(message)
    .build();

每次请求都可以有不同的参数,灵活性最高,但也容易导致参数散落在各处、难以统一管理。

API 设计对比:错误处理

三个框架在错误处理上的差异直接影响系统稳定性:

Spring AI

try {
    return chatClient.prompt().user(message).call().content();
} catch (org.springframework.ai.retry.NonTransientAiException e) {
    // 认证失败、模型不存在等不可重试错误
    log.error("AI 调用配置错误: {}", e.getMessage());
    throw new ServiceException("AI 服务配置异常,请联系管理员");
} catch (org.springframework.ai.retry.TransientAiException e) {
    // 网络超时、限流等可重试错误
    // Spring AI 默认会自动重试(RetryTemplate),这里是最终失败
    log.warn("AI 调用暂时失败,已重试多次: {}", e.getMessage());
    return "AI 服务暂时不可用,请稍后再试";
}

Spring AI 内置了 RetryTemplate,默认对 transient 错误(5xx、超时、限流)进行指数退避重试,开发者不需要手动实现重试逻辑。

LangChain4j

try {
    return assistant.chat(message);
} catch (dev.langchain4j.exception.HttpException e) {
    // HTTP 层面的错误(401、429、500 等)
    if (e.statusCode() == 429) {
        return "请求过于频繁,请稍后再试";
    }
    throw e;
} catch (Exception e) {
    // 其他错误
    log.error("AI 服务调用失败", e);
    return "服务暂时不可用";
}

原生 SDK

try {
    return completion.choices().get(0).message().content().orElse("(无回复)");
} catch (com.openai.errors.OpenAIError e) {
    // 所有 OpenAI API 错误的基类
    log.error("OpenAI API 错误, status={}, message={}", e.statusCode(), e.message());
    throw new RuntimeException("AI 调用失败: " + e.message());
}

原生 SDK 没有内置重试机制,需要自行实现或引入第三方库(如 Resilience4j)。


1.5 选型决策树

把前面的对比结果浓缩成一棵决策树:

你的场景是什么?
│
├─ Spring Boot 存量项目,只需基础对话或简单 RAG?
│   团队对 Spring 熟悉,上线时间紧?
│   └─ → 选 Spring AI
│       优点:零学习曲线,开箱即用,Spring 生态无缝集成
│       注意:Agent 能力较弱,RAG 功能相对基础
│
├─ 需要完整 Agent 编排、生产级 RAG、多工具协作?
│   愿意在工程设计上投入时间?
│   └─ → 选 LangChain4j(本书主线选择)
│       优点:功能最完整,AiServices 设计优雅,测试友好
│       注意:初期学习成本略高,版本更新较快
│
└─ 对延迟/成本有极致要求,愿意自己封装基础设施?
    或者在构建公司 AI 中间件平台?
    └─ → 选原生 SDK
        优点:最大控制权,最快跟进 OpenAI 新特性
        注意:需要自行实现重试、超时、日志等基础设施

本书的技术栈策略

SmartDesk 项目选择 LangChain4j 作为主线框架,理由如下:

  1. 本书的核心目标是帮助读者构建有实际业务价值的 AI 应用,而不只是跑通一个 Hello World。LangChain4j 的 Agent、RAG、工具调用等能力对于真实项目更有实用价值。

  2. LangChain4j 的 AiServices 设计与 Java 工程师熟悉的"接口定义先行"思维高度契合,学习迁移成本低。

  3. 对比章节Spring Boot 集成场景中,会同时展示 Spring AI 的写法,让读者了解两个主流框架的差异。

  4. 第 8 章(成本优化)会引入原生 SDK,展示如何在对延迟和成本有极致要求时绕过框架层直接控制每一个请求。


1.6 SmartDesk 装配:确定技术栈,跑通第一个对话

现在我们来建立 SmartDesk 项目的第一个模块,验证三套框架都能正常工作。

项目结构

smartdesk/chapter-01/
├── pom.xml
├── .env.example
└── src/
    └── main/
        ├── java/
        │   └── com/smartdesk/ch01/
        │       ├── Chapter01Application.java   # Spring Boot 主类
        │       ├── SpringAiDemo.java            # Spring AI 演示
        │       ├── LangChain4jDemo.java         # LangChain4j 演示
        │       └── NativeSdkDemo.java           # 原生 SDK 演示
        └── resources/
            └── application.yml

关键依赖说明

pom.xml 中有几个值得注意的配置:

<properties>
    <java.version>21</java.version>
    <spring-ai.version>1.0.0</spring-ai.version>
    <langchain4j.version>0.35.0</langchain4j.version>
</properties>

为什么用 Java 21?Spring Boot 3.x 要求 Java 17 作为最低版本,但 Java 21 引入了虚拟线程(Virtual Threads)。当 AI 请求的响应时间以秒计时,使用虚拟线程可以让服务在等待 AI 响应的同时以极低的线程开销处理其他请求。本书后续章节会利用这个特性。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>${spring-ai.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>${langchain4j.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

同时引入 Spring AI 和 LangChain4j 的 BOM(Bill of Materials),可以避免这两个框架各自依赖的传递依赖产生版本冲突。这是工程陷阱 #1 的解决方案——后面会详细讲。

启动步骤

第一步:配置 API Key

# 复制示例配置
cp smartdesk/chapter-01/.env.example smartdesk/chapter-01/.env

# 编辑 .env,填入你的 API Key
# OPENAI_API_KEY=sk-proj-...

第二步:启动应用

cd smartdesk/chapter-01

# 方式一:Maven Wrapper(推荐,无需本地安装 Maven)
OPENAI_API_KEY=你的密钥 ./mvnw spring-boot:run

# 方式二:设置环境变量后运行
export OPENAI_API_KEY=你的密钥
./mvnw spring-boot:run

第三步:测试三个接口

打开浏览器或使用 curl:

# Spring AI 接口
curl "http://localhost:8080/spring-ai/chat?message=你好,介绍一下自己"

# 预期响应(示例):
# 你好!我是由 OpenAI 开发的 GPT-4o-mini 语言模型,
# 可以帮助你回答问题、分析信息、辅助写作...

# LangChain4j 接口(带有系统提示词的客服角色)
curl "http://localhost:8080/langchain4j/chat?message=你好,介绍一下自己"

# 预期响应(示例):
# 您好!我是 SmartDesk 智能客服助手,专为您提供产品咨询和问题解答服务...
# (注意系统提示词的影响:LangChain4j 的回复带有明确的客服身份)

# 原生 SDK 接口
curl "http://localhost:8080/native/chat?message=你好,介绍一下自己"

# 预期响应(示例):
# 你好!我是 GPT-4o-mini,一个由 OpenAI 训练的语言模型...

观察三个响应的差异:注意 Spring AI 和原生 SDK 的回复与 LangChain4j 的回复在语气和身份定位上的区别——这正是 @SystemMessage 的作用。即使问的是同一个问题,LangChain4j 的助手会把自己定位为 SmartDesk 的客服,因为系统提示词在接口层面就已经绑定好了。

SmartDesk 第一个里程碑达成:三套框架均已跑通,第一个 AI 对话接口正常工作。后续章节将在 LangChain4j 的基础上,逐步构建出一个真正有业务价值的智能客服系统。


1.7 工程陷阱

从实验到生产,还有一段距离。这是在使用这三套框架时最容易踩到的五个坑:

陷阱 1:依赖版本地狱

现象:在 pom.xml 中同时引入 Spring AI 和 LangChain4j 后,启动时报 NoSuchMethodErrorClassNotFoundException

根本原因:Spring AI 和 LangChain4j 都会引入 jackson-databind、reactor-core 等基础库,但可能依赖不同的版本。没有使用 BOM 时,Maven 依赖仲裁的结果可能不是任何一个框架期望的版本。

解决方案

<!-- 始终通过 BOM 引入,让框架自己管理传递依赖版本 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>${spring-ai.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>${langchain4j.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

另外,每次升级 spring-ai 或 langchain4j 的次版本(minor version),都要完整跑一遍集成测试。这两个框架目前仍处于快速迭代阶段,0.35.0 到 0.36.0 可能有不向后兼容的 API 变更。

陷阱 2:API Key 泄露进日志

现象:开启 DEBUG 日志后,控制台输出中出现了完整的 Authorization: Bearer sk-proj-... 请求头。

根本原因:在排查问题时,开发者通常会把相关包的日志级别调到 DEBUG。但 spring-ailangchain4j 在 DEBUG 级别会输出完整的 HTTP 请求,包括请求头中的 Authorization 字段。

解决方案

logging:
  level:
    # 永远不要把这两行设为 DEBUG,即使是在排查问题时
    org.springframework.ai: INFO
    dev.langchain4j: INFO
    # 如果必须查看请求内容,用 TRACE 替代,且只在本地开发环境

对于 LangChain4j,可以在 model 构建时显式关闭请求日志:

OpenAiChatModel.builder()
    .logRequests(false)   // 即使日志级别是 DEBUG,也不输出请求体
    .logResponses(false)
    .build();

对应的 application.yml 配置:

langchain4j:
  open-ai:
    chat-model:
      log-requests: false
      log-responses: false

陷阱 3:默认重试策略不适合生产环境

现象:单次 AI 请求超时后,应用直接返回 500 错误,没有任何重试。或者反过来:某个偶发的 429 限流错误,导致系统反复重试,加剧了限流问题(雪崩)。

根本原因

  • 原生 SDK 默认不重试,超时即报错
  • Spring AI 有内置重试,但默认配置(3 次,指数退避起始 1 秒)在某些场景下会产生雪崩效应

解决方案(以 Spring AI 为例):

spring:
  ai:
    retry:
      max-attempts: 2        # 生产环境建议不超过 2 次重试
      backoff:
        initial-interval: 2s # 起始等待时间
        multiplier: 2.0      # 指数退避系数
        max-interval: 10s    # 最大等待时间上限
      on-http-codes: 429, 503  # 只对限流和服务不可用重试

对于原生 SDK,推荐用 Resilience4j 封装:

Retry retry = Retry.of("openai", RetryConfig.custom()
    .maxAttempts(2)
    .waitDuration(Duration.ofSeconds(2))
    .retryOnException(e -> e instanceof OpenAIError openAIError 
        && openAIError.statusCode() == 429)
    .build());

return Retry.decorateSupplier(retry, () -> callOpenAI(message)).get();

陷阱 4:模型名称拼写错误产生的迷惑报错

现象:将 gpt-4o-mini 写成 gpt4o-minigpt-4-mini,应用启动一切正常,但在调用时抛出 OpenAIException: model_not_found,错误信息不够直观。

根本原因:Spring AI 和 LangChain4j 对模型名称都采用字符串类型,没有编译期校验。

解决方案

方案一,使用枚举常量(原生 SDK 提供了这个机制,其他框架需要自定义):

// 创建一个常量类,集中管理所有模型名称
public final class AiModels {
    public static final String GPT_4O_MINI = "gpt-4o-mini";
    public static final String GPT_4O = "gpt-4o";
    public static final String TEXT_EMBEDDING_3_SMALL = "text-embedding-3-small";
    
    private AiModels() {}
}

方案二,在应用启动时做快速失败验证:

@Component
public class AiConfigValidator implements ApplicationRunner {
    @Autowired private ChatClient chatClient;
    
    @Override
    public void run(ApplicationArguments args) {
        // 启动时发送一条测试消息,验证配置正确
        // 如果模型名称错误,在启动阶段就会失败
        chatClient.prompt().user("ping").call().content();
        log.info("AI 配置验证通过");
    }
}

陷阱 5:默认超时时间对复杂提示词太短

现象:简单问题都能正常返回,但当提示词包含大量上下文(如 RAG 检索到的多段文档)时,请求经常超时。

根本原因:对于包含 2000-3000 tokens 的提示词,GPT-4o-mini 的响应时间可能达到 15-30 秒,而很多框架和中间件(Nginx、Spring MVC)的默认超时都在 10 秒以下。

解决方案

# Spring Boot 服务器超时(接收端)
server:
  tomcat:
    connection-timeout: 60s

# Spring AI 的 HTTP 客户端超时(发送端)
spring:
  ai:
    openai:
      http-client:
        read-timeout: 60s
        connect-timeout: 10s

LangChain4j:

OpenAiChatModel.builder()
    .timeout(Duration.ofSeconds(60))
    .build();

更好的解法是从根本上避免超长提示词——这是第 2 章(Prompt 工程)和第 4 章(RAG 优化)要讨论的话题。


1.8 本章小结

本章完成了三件事:

理解了三个框架的本质差异。Spring AI 是"为 Spring 项目加 AI 能力的最短路径",LangChain4j 是"构建严肃 AI 应用的完整工具链",原生 SDK 是"当你需要掌控一切时的基础设施"。这三个框架不是竞争关系,而是适用于不同场景的工具。

通过代码对比建立了直观认知。同一个功能三种写法并排对比,让抽象的"设计哲学"变成了可以直接观察的代码差异。Spring AI 最简洁,LangChain4j 最具表达力,原生 SDK 最透明——没有最好的,只有最适合当前场景的。

跑通了 SmartDesk 的第一个接口。三套框架全部可用,技术栈确定:LangChain4j 作为主线,Spring AI 用于对比学习,原生 SDK 在成本优化章节登场。


下一章预告:接口跑通了,但提示词里只有一句"你好"。在真实的客服场景中,提示词工程决定了 AI 回复质量的 80%。第 2 章将深入提示词的结构设计:系统提示词的最佳实践、Few-shot 示例的排列技巧、以及如何用 LangChain4j 的 PromptTemplate 构建可维护的提示词库。


SmartDesk 里程碑 1:第一个 AI 对话接口跑通 ✓

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐