
Java 开发者接入大模型真正的拦路虎不是 prompt 写不好而是 API 调用太散、上下文管理太累、工具调用又要自己拼协议。LangChain 在 Python 世界解决了这些问题但它是 Python 生态Spring AI 的出现让 Java 开发者在 Spring Boot 体系内获得了类似的抽象能力Spring AI Alibaba 又进一步补齐了国内模型接入和 Graph Agent 编排能力。这篇文章面向已经会 Spring Boot 基础、想了解 Spring AI 和 Agent 实战的 Java 工程师从核心概念开始逐步走到可运行的 Chat 接口、结构化输出、Function Calling再到面试考点和排错清单。1. 先理清 Java 大模型开发的核心概念1.1 从一次 HTTP 调用到框架抽象大模型 API 本质上就是一个 HTTP 接口。很多人一开始从 Postman 或 curl 调通之后以为项目接入就完成了等到写业务代码才发现问题集中在几个地方每次都要手动拼装 JSON 请求体容易漏字段。模型返回可能是流式的需要处理 SSE 格式。多轮对话要自己保存和拼接历史消息否则上下文会丢。不同厂商的 API 路径、参数名、返回结构各不相同换模型要改一堆代码。异常处理、重试、超时、令牌桶都需要从零实现。Spring AI 做的事情是把“模型请求”抽象成几个稳定的接口ChatModel、EmbeddingModel、ImageModel、TranscriptionModel、VectorStore等。应用程序只面向这些接口编程底层用不同的适配器接入不同厂商。它和 Spring JDBC / JPA 抽象数据库的思路类似你写业务代码框架处理变化。在 Spring AI 里最常用的是ChatModel和ChatClient。ChatModel是模型调用的底层入口ChatClient是面向业务代码的封装提供 prompt、message、memory、tool 的流畅 API。如果从来没有用过可以直接从ChatClient开始。下面这段可以看作核心抽象的最小示例Service public class AiChatService { private final ChatClient chatClient; public AiChatService(ChatClient.Builder builder) { this.chatClient builder.build(); } public String chat(String userMessage) { return chatClient.prompt(userMessage).call().content(); } }这段代码不关心你用的是 OpenAI、DashScope 还是 Ollama 本地模型。只要在配置里切换模型提供方业务代码基本不用动。这解决了“今天接通义明天换其他模型”时的迁移成本问题。1.2 LangChain 与 Spring AI 的关系并不冲突LangChain 是 Python 生态里最有影响力的 LLM 应用框架它提出了一整套组件化思路模型封装、链式编排、记忆、工具、检索、Agent。Java 开发者不需要直接写 Python但需要理解 LangChain 里那套抽象因为 Spring AI 的设计逻辑和它高度相似。从能力映射看LangChain 抽象解决的问题Spring AI 对应能力LLM / ChatModel统一调用不同模型ChatModelPrompt Template把用户输入变成结构化模板PromptTemplateOutput Parser把模型输出解析成结构化数据BeanOutputConverter / StructuredOutputConverterMemory保留多轮对话状态ChatMemory / Message 历史Tool / Function Calling让模型调用外部系统Tool 注解 ToolCallbackChain / LangGraph编排多步流程手动编排 / Spring AI Alibaba GraphVectorStore Retriever根据向量检索补充上下文VectorStore / DocumentRetriever这里有一个面试中非常容易出现的问题LangChain 和 LangGraph 有什么区别。LangChain 更强调“链式调用”典型写法是把 prompt、model、parser 串成一条线适合流程固定、变化少的场景。LangGraph 则把执行流程建模成一张有向图节点可以是 LLM、工具或者自定义逻辑边可以带条件判断适合有状态、有分支、需要人工介入或多次工具调用的 Agent 场景。Spring AI Alibaba 的 Graph 模块思路也和 LangGraph 相似。1.3 Agent 不是魔法而是一个控制循环Agent 是近两年大模型应用里出现频率最高的词现在很多介绍把它包装成了“AI 自己规划任务”。更准确的看法是Agent 是一段程序循环循环里让大模型反复做三件事观察当前状态、决定下一步动作、执行动作并收集结果。一个典型的 Agent 循环可以简化为把用户需求和可用工具描述放进 prompt。模型返回一个普通文本回复或者返回一个工具调用请求tool_call。如果返回的是工具调用请求应用代码执行对应方法把执行结果作为新消息追加到上下文。再次调用模型让模型基于工具结果继续生成。直到模型认为不再需要工具输出最终答案。这个循环在 Spring AI 中主要由 ChatClient 内置的 Tool Calling 机制完成不需要自己实现 HTTP 回调。开发者的核心工作变成两件事第一用 Java 方法把外部能力暴露给模型第二控制循环的边界避免模型反复调用工具或陷入死循环。理解这一点后再去看大模型面试题里的“什么是 Agent”就不会只背概念。它可以被描述为基于大模型推理能力通过工具调用与外部环境交互并在一段循环中动态决定下一步动作的程序。2. 在动手之前先确认环境2.1 版本和运行环境Spring AI 项目迭代速度快版本对应关系并不稳定。在跑代码之前先把环境确认好否则后面报错会让你误以为代码写错了。一个比较稳妥的最小环境JDK17 或 21。Spring Boot 3.x 要求 JDK 17 起。构建工具Maven 3.8 或 Gradle 7.5。Spring Boot3.2建议使用与 Spring AI 版本匹配的 Spring Boot 版本。IDEIntelliJ IDEA 或 Spring Tools Suite 都可以关键是启用 Lombok 注解处理如果使用。项目初始化可以通过 Spring Initializr 创建也可以在已有 Spring Boot 项目里添加依赖。这里有个很重要的判断不要直接照抄网上任意版本号。Spring AI 的版本号、模型 starter 的名称、配置前缀都会随版本演进变化。比如早期版本用spring-ai-openai-spring-boot-starter后续可能统一为spring-ai-starter-model-openai。在写 pom 时建议去官方文档确认当前稳定版本并且在 pom 中声明 BOM集中管理版本。因为不同版本之间的 API 差异很大下面示例以“能理解思路”为主落地前请用当前官方 release 替换版本号。2.2 申请模型服务与 API Key实验阶段可以选 OpenAI 兼容协议的平台也可以选阿里云 DashScope。DashScope 是国内开发者经常用的模型服务通过 Spring AI Alibaba 接入更顺。申请完成后不要在 Java 代码里硬编码 API Key。推荐放到环境变量export DASHSCOPE_API_KEYyour-dashscope-api-key如果本地开发时使用 application.yml建议把密钥放到系统环境变量或 IDE 的环境变量配置里配置文件用${DASHSCOPE_API_KEY}占位spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus注意spring.ai.dashscope.*是常见前缀如果你使用的 Spring AI Alibaba 版本已经改成了spring.ai.alibaba.dashscope.*要以官方文档为准。配置项没有生效时最常见的表现是应用启动不报错但请求时返回 401 或 invalid api key。2.3 最小项目结构推荐在一个 Spring Boot 工程里按模块组织controller 负责入口service 负责调用 AIdomain 存放记录类型。下面是一个适合学习的结构ai-demo/ ├── pom.xml ├── src/main/java/com/example/aidemo/ │ ├── AiDemoApplication.java │ ├── controller/AiController.java │ ├── service/AiChatService.java │ └── tool/OrderTool.java └── src/main/resources/ └── application.ymlpom.xml 中关键依赖可以按这个思路声明parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.x/version relativePath/ /parent properties java.version17/java.version spring-ai.version当前官方 release 版本/spring-ai.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency /dependencies使用spring-ai-bom统一管理版本可以避免直接把 starter 的版本写散。如果你要接入阿里云 DashScope依赖替换为 Spring AI Alibaba 提供的 starter具体 artifact 以官方文档为准。3. 用 Spring AI 跑通第一条大模型请求3.1 编写 Controller 和 Service环境确认好后先跑通最简单的聊天接口。这一步的目的不是做业务而是验证“配置 - 依赖 - API Key - 模型调用”这条链路是否通。假设使用 Spring AI 的ChatClient代码可以这样写RestController RequestMapping(/api/ai) public class AiController { private final AiChatService aiChatService; public AiController(AiChatService aiChatService) { this.aiChatService aiChatService; } GetMapping(/chat) public String chat(RequestParam(defaultValue 用一句话介绍 Spring AI) String message) { return aiChatService.chat(message); } }Service 内维护一个 ChatClientService public class AiChatService { private final ChatClient chatClient; public AiChatService(ChatClient.Builder builder) { this.chatClient builder.build(); } public String chat(String message) { return chatClient.prompt(message).call().content(); } }这里的关键点是ChatClient.Builder由 Spring AI 自动注入不需要手动 new。prompt(message)接受用户输入call()发起同步调用content()取出模型生成的文本。如果项目需要异步或流式响应后续可以换成stream()方法。3.2 模型参数说明在 Spring AI 中模型参数可以通过 application.yml 全局配置也可以在每次调用时覆盖。常见参数如下参数作用默认值示例调大影响调小影响使用建议temperature采样随机性0.7 左右输出更多样但可能不稳定输出更确定适合结构化任务代码生成、JSON 输出建议 0 到 0.3maxTokens / maxOutputTokens限制最大输出长度视模型而定可输出更长结果成本更高输出可能被截断根据业务字段长度设置topP核采样概率1.0候选词更多候选词更集中与 temperature 二选一调model使用的模型名厂商默认不同模型能力不同便宜模型推理较弱按任务选模型不要把轻量模型和最强模型混用一个重要经验结构化输出任务里temperature 不要设太高。如果模型经常返回多余解释、格式飘忽先尝试把 temperature 降到 0.2 以下再考虑增加 prompt 约束或输出解析器。3.3 验证输出与流式响应启动 Spring Boot 应用后用 curl 发起请求curl http://localhost:8080/api/ai/chat?message请用一句话介绍SpringAI正常响应会是一个普通文本字符串例如Spring AI 是一个面向 Java 开发者的 AI 应用开发框架用来统一接入大模型并提供工具编排能力。如果看到这个结果说明链路已通。接下来再深入做结构化输出和工具调用。如果希望实现打字机效果需要换成流式响应。Spring AI 中可以直接使用FluxStringGetMapping(/chat/stream) public FluxString chatStream(RequestParam String message) { return aiChatService.chatStream(message); }Service 内调用public FluxString chatStream(String message) { return chatClient.prompt(message).stream().content(); }前端通过 SSE 接收即可。流式响应的好处是首字延迟更低用户体验更好但也要处理连接断开和超时问题。4. 从聊天走向结构化让模型返回 JSON4.1 为什么要结构化输出聊天接口返回字符串只能用于人机对话。真实业务里模型输出通常要落入数据库、对接前端表单或作为下游系统入参。比如“从这段客户反馈中提取故障类型、影响用户数和优先级”如果模型返回一段自然语言开发就要做字符串切分既脆弱又难维护。正确做法是让模型直接返回 JSON再用 Jackson 反序列化成 Java 对象。但直接要求模型“返回 JSON”并不够。即使模型大多数时候返回合法 JSON偶尔也会在 JSON 外面加 markdown 代码块或者把字段名大小写写错。Spring AI 的BeanOutputConverter会把“返回格式说明”拼入 prompt并在拿到结果后做一次可靠的 JSON 解析。4.2 使用 BeanOutputConverter 实现先定义一个 Java record 描述期望输出结构public record BookInfo(String title, String author, int pageCount) { }然后在 Service 里这样写private final ChatClient chatClient; public BookInfo extractBook(String text) { var converter new BeanOutputConverter(BookInfo.class); String prompt 请从以下内容中提取图书信息。 要求只输出 JSON不要输出其他解释。 输出格式 {format} 内容 {text} .replace({format}, converter.getFormat()) .replace({text}, text); String content chatClient.prompt(prompt).call().content(); return converter.convert(content); }converter.getFormat()会生成类似“JSON 结构包含 title、author、pageCount 字段”的说明converter.convert负责把模型返回的字符串转换成BookInfo对象。这个方案比手写 JSON 解析更稳原因在于格式说明和解析逻辑集中在转换器里不会散落在业务代码中。4.3 常见 JSON 解析问题问题现象常见原因处理方式报 JsonParseException模型返回了 markdown 代码块或额外文本使用 BeanOutputConverter 清理并解析或要求模型只输出纯 JSON字段缺失输入内容没有对应信息给 record 字段设置默认值或在 prompt 中指定空值输出 null类型转换失败模型把 pageCount 输出成 “unknown”在 prompt 中明确字段类型和枚举选项输出语义错误模型返回了格式正确但内容错误的结果降低 temperature必要时让模型先列出依据再输出一个实用建议如果业务对 JSON 稳定性要求很高不要只依赖 prompt 约束。可以在转换器外层增加失败重试或者让模型输出结果并附带“置信度”再由业务规则过滤。5. Agent 实战给大模型装上 Function Calling 工具5.1 设计一个业务工具Agent 能不能落地取决于工具是否可用、描述是否清晰。模型并不知道你的 Java 方法内部逻辑它只能看到方法名、参数描述和返回值。所以工具方法必须做到方法名语义明确、Tool描述完整、参数类型尽量简单。以订单查询为例import org.springframework.ai.tool.annotation.Tool; Component public class OrderTool { Tool(description 根据订单号查询订单状态订单号类似 ORD202501001) public String queryOrderStatus(String orderId) { if (orderId null || orderId.isBlank()) { return 订单号不能为空; } return 订单 orderId 当前状态已发货预计明天送达。; } }Spring AI 会扫描被Tool注解的方法并把方法签名转换为模型的工具描述。这个机制和 OpenAI Function Calling 本质上是一样的只是把 JSON Schema 的构造过程隐藏了。5.2 把工具注册进 ChatClient要让模型能调用这个工具需要把工具 Bean 放入 ChatClient 的构建过程Service public class AgentService { private final ChatClient chatClient; public AgentService(ChatClient.Builder builder, OrderTool orderTool) { this.chatClient builder .defaultTools(orderTool) .build(); } public String queryOrder(String orderId) { return chatClient.prompt(帮我查一下订单 orderId 的状态) .call() .content(); } }这里的关键是defaultTools(orderTool)。如果不在构建时注册模型即使知道有工具也无法调用。注册后模型看到用户问题里有“查一下订单”这样的意图会先返回一个工具调用请求Spring AI 再调用OrderTool.queryOrderStatus把结果放回上下文最后生成自然语言回复。5.3 从固定返回走向真实数据库查询为了更接近生产场景可以把工具方法改成查询数据库。假设有一张订单表CREATE TABLE orders ( order_id VARCHAR(32) PRIMARY KEY, status VARCHAR(20), receiver VARCHAR(50), address VARCHAR(200), created_at DATETIME );对应的 Repository 和工具方法可以这样组织Repository public interface OrderRepository extends JpaRepositoryOrderEntity, String { OptionalOrderEntity findByOrderId(String orderId); }工具类注入 RepositoryComponent public class OrderTool { private final OrderRepository orderRepository; public OrderTool(OrderRepository orderRepository) { this.orderRepository orderRepository; } Tool(description 根据订单号查询订单状态、收货人和收货地址) public String queryOrderInfo(String orderId) { return orderRepository.findByOrderId(orderId) .map(order - 订单号 order.getOrderId() 状态 order.getStatus() 收货人 order.getReceiver() 地址 order.getAddress()) .orElse(未找到订单 orderId); } }工具方法返回字符串而不是实体对象是为了减少模型需要处理的 Token。数据库字段很多时只返回当前问题需要的信息不要把所有字段都拼进上下文中。5.4 多轮记忆与上下文控制生产环境里 Agent 通常不止一个工具。比如查询订单后再计算运费、查询库存后推荐替代商品。多工具场景中模型的每次工具调用结果都会追加到上下文ChatClient 会维护这个循环。但有一个边界要特别注意工具结果和用户消息会不断累积上下文长度会增长。如果不想让模型记住“上一轮对话”之外的信息可以在每次构建 prompt 时只携带本轮消息。如果需要多轮记忆可以使用 Spring AI 的ChatMemory或者自己维护一个ListMessage并在每个请求头透传会话 ID。public String chatWithMemory(String sessionId, String userMessage) { return chatClient.prompt() .user(userMessage) .advisors(a - a.param(chat_memory_conversation_id, sessionId)) .call() .content(); }这里的 advisor 是 Spring AI 提供的横切能力可以在不污染业务代码的情况下操作上下文。生产环境要注意会话 ID 一定要由外部传入不能直接使用不可信的长文本作为 key。6. Spring AI Alibaba 与 Graph Agent 编排6.1 Spring AI Alibaba 解决什么问题Spring AI Alibaba 是阿里开源的一套基于 Spring AI 的扩展目标是让 Java 开发者更容易接入阿里云 DashScope 模型并提供更贴近国内场景的 Agent 编排能力。它并不是要把 Spring AI 替换掉而是在 Spring AI 基础之上增加适配器和组件。对于国内团队使用 Spring AI Alibaba 有几个实际收益DashScope 模型接入可以由一个 starter 完成不需要手工拼 HTTP。适配了通义系列模型能够使用 qwen-plus、qwen-max 等模型。提供 Graph