Spring AI + DeepSeek:Java开发者的大模型入门指南

前言:作为一名写了5年Java的后端开发,最近在研究大模型应用开发。发现一个事实——Java开发者做大模型应用,比你想象的简单得多。 这篇文章从0到1讲清楚Spring AI怎么接入DeepSeek,中间踩的坑和解决方案也一并记录了。


一、为什么Java开发者也能做大模型应用?

很多人一提到大模型应用开发,第一反应就是Python。确实,Python生态在AI领域很强。但实际情况是:

大模型落地最缺的不是会写Python的人,而是能把AI集成到企业业务系统里的工程师。

而企业业务系统,Java占了大半壁江山。

Spring AI的出现就是为了解决这个问题——让Java开发者用熟悉的Spring Boot方式,就能接入大模型能力。 不需要学Python,不需要换框架,加个依赖、改个配置就能跑。

对比Python方案Spring AI方案
框架LangChain / LlamaIndexSpring AI
语言PythonJava
与Spring集成需要额外封装原生集成
切换模型改代码改配置文件
适合谁Python开发者Java/Spring开发者

你会Spring Boot,你就能用Spring AI。


二、Spring AI是什么?

Spring AI是Spring官方推出的AI应用开发框架。简单说就是:Spring Boot + 大模型。

它帮你封装了:

  • 大模型调用(对话、Embedding)
  • 向量数据库对接(Chroma、Milvus、Redis等)
  • 工具调用(Function Calling)
  • 对话记忆(ChatMemory)
  • 提示词模板管理(PromptTemplate)

核心好处:切换模型只需要改配置文件,代码不用动。

比如你项目里用DeepSeek,明天领导说换通义千问,只需要改application.yml,Java代码一行不改。


三、DeepSeek是什么?为什么选它?

DeepSeek是国内的一家AI公司,做了一个很猛的大语言模型。选它的理由很实际:

理由说明
兼容OpenAI格式API调用方式和OpenAI一样,Spring AI原生支持
成本低输入1元/百万token,输出2元/百万token,比GPT便宜很多
国内访问快不用翻墙,API稳定
效果好在中文场景下表现很强,性价比极高
有免费额度新用户注册有赠送,够你学习和测试用

便宜、好用、不用翻墙,Java开发者接入成本最低的大模型之一。


四、快速开始:30分钟跑通第一个对话

4.1 环境准备

你需要:

  • JDK 17+
  • Maven 3.6+
  • IDEA(或其他Java IDE)
  • DeepSeek API Key(去 https://platform.deepseek.com/ 注册)

4.2 创建项目

用Spring Initializr(https://start.spring.io/)生成项目:

Project: Maven
Language: Java
Spring Boot: 3.5.x
Group: com.example
Artifact: ai-demo
Dependencies: Spring Web

4.3 添加Spring AI依赖

打开pom.xml,加入Spring AI的BOM和DeepSeek依赖:

<properties>
    <java.version>17</java.version>
    <spring-ai.version>1.1.8</spring-ai.version>
</properties>

<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>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-deepseek</artifactId>
    </dependency>
</dependencies>

4.4 配置application.yml

spring:
  ai:
    deepseek:
      api-key: sk-你的DeepSeek密钥
      base-url: https://api.deepseek.com
    

4.5 写一个Controller

@RestController
@RequestMapping("/api/chat")
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

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

4.6 启动测试

mvn spring-boot:run

浏览器打开:

http://localhost:8080/api/chat/ask?question=你好,请用一句话介绍Java

看到DeepSeek返回文字 = 接入成功! 🎉

整个过程就这几步,不需要装Python,不需要学新框架。Spring Boot开发者看到这段代码应该很亲切——就是标准的Controller + 依赖注入。


五、进阶:Function Calling(让模型调你的代码)

5.1 什么是Function Calling?

普通对话只能问大模型"知道的"东西。但如果你想让它查数据库、调接口、算数据,就需要Function Calling。

通俗理解:你告诉大模型"我有哪些工具",大模型自己决定什么时候用哪个。

流程是这样的:

你定义工具 + 写好描述
       ↓
Spring AI把工具描述发给大模型
       ↓
用户提问:"今天北京天气怎么样?"
       ↓
大模型分析:这个问题需要调 getWeather 工具
       ↓
大模型返回:请调用 getWeather,参数是 {city: "北京"}
       ↓
Spring AI自动执行你的Java方法
       ↓
返回结果给大模型
       ↓
大模型组织最终回答

5.2 代码实现

创建一个工具类,模拟天气查询和数据库查询:

@Component
public class AppTools {

    @Tool(description = "根据城市名称查询当前天气信息")
    public String getWeather(@ToolParam(description = "城市名称,如北京、上海") String city) {
        // 实际开发中这里调天气API,这里模拟返回
        Map<String, String> mockData = Map.of(
            "北京", "晴,温度28°C,湿度45%",
            "上海", "多云,温度26°C,湿度65%",
            "广州", "阵雨,温度30°C,湿度80%"
        );
        return mockData.getOrDefault(city, "暂无该城市天气数据");
    }

    @Tool(description = "查询员工的部门信息,根据工号返回所在部门")
    public String queryDepartment(@ToolParam(description = "员工工号") String empId) {
        // 实际开发中这里查数据库,这里模拟返回
        if ("1001".equals(empId)) return "研发部";
        if ("1002".equals(empId)) return "市场部";
        return "未找到该员工信息";
    }
}

然后在调用时加上 .tools(tools)

@GetMapping("/ask-with-tools")
public String askWithTools(@RequestParam String question) {
    return chatClient.prompt()
            .user(question)
            .tools(appTools)
            .call()
            .content();
}

测试:

# 模型自动调用 getWeather
http://localhost:8080/api/chat/ask-with-tools?question=今天北京天气怎么样?

# 模型自动调用 queryDepartment
http://localhost:8080/api/chat/ask-with-tools?question=工号1001的员工在哪个部门?

大模型自动判断该调哪个工具,参数也自动传对。 这就是Function Calling的魔力。

5.3 关键点

@Tool(description = "xxx") 里的description是给大模型看的"说明书"。description写得好不好,直接决定模型能不能正确调用你的工具。 写得太模糊,模型可能调错;写得太长,模型可能理解不了。

5.4 实际开发建议

  • 工具方法的返回值要尽量简洁,别把整个对象返回,大模型不需要那么多信息
  • description要写清楚输入输出,比如"根据工号查询部门"比"查数据库"好得多
  • 多个工具的description要有区分度,不然模型可能选错

六、进阶:RAG知识库问答

6.1 为什么需要RAG?

大模型有个致命问题——幻觉。你问它你公司内部的信息,它不知道就编一个看起来很真的答案。

RAG(Retrieval Augmented Generation,检索增强生成)的思路是:

你的文档 → 切成小段 → 转成向量 → 存起来
用户提问 → 也转成向量 → 找最相似的几段 → 喂给大模型 → 基于真实文档回答

大模型不再是"凭空回答",而是"看着你的文档回答"。

6.2 一个实际场景:技术博客搜索助手

假设你有很多技术博客文章(Markdown或TXT格式),你想做一个"技术问答机器人"——用户问"Spring Boot怎么配置多数据源",系统能从你的博客里找到相关内容,基于真实文章回答。

这就是RAG能做的事情。

6.3 整体架构

┌──────────────────────────────────────────────┐
│                  文档导入阶段                   │
│                                               │
│  技术博客文章(MD/TXT)                          │
│     ↓                                        │
│  文本切片 (TokenTextSplitter)                  │
│     ↓                                        │
│  向量化 (Ollama nomic-embed-text)             │
│     ↓                                        │
│  存入向量数据库 (Chroma)                       │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│                  问答阶段                      │
│                                               │
│  用户提问: "Redis缓存穿透怎么解决?"            │
│     ↓                                        │
│  问题向量化                                    │
│     ↓                                        │
│  向量检索 Top-5                               │
│     ↓                                        │
│  拼接上下文 + Prompt                          │
│     ↓                                        │
│  大模型回答: 基于你的博客文章给出解答            │
└──────────────────────────────────────────────┘

6.4 核心组件

1)Embedding模型:把文字变成数字

计算机不懂数字,Embedding模型干的事情就是:

"Redis缓存穿透解决方案" → [0.82, 0.15, 0.93, 0.41, ...]

变成数字后就能算"两段话有多像"。

2)向量数据库:存储和检索向量

普通数据库用关键字搜索,向量数据库用"语义相似度"搜索。搜"缓存雪崩",即使文章里写的是"Redis大面积失效",也能找到,因为它们在语义上是相似的。

3)文本切片:把长文章切成小段

太长的文章作为一个向量,检索时匹配不精确。切成小段后,每段只包含一个知识点,检索更精准。

6.5 代码实现

依赖(在pom.xml中添加):

<!-- Ollama Embedding -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>

<!-- Chroma向量数据库 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-chroma</artifactId>
</dependency>

配置(application.yml):

spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      embedding:
        options:
          model: nomic-embed-text
    vectorstore:
      chroma:
        client:
          host: http://localhost:8000
        collection-name: blog-search

RAG服务核心代码:

@Service
public class BlogSearchService {

    private final VectorStore vectorStore;
    private final ChatClient chatClient;

    public BlogSearchService(VectorStore vectorStore, ChatClient.Builder builder) {
        this.vectorStore = vectorStore;
        this.chatClient = builder.build();
    }

    // 导入博客文章
    public void importBlog(String title, String content) {
        Map<String, Object> metadata = Map.of("title", title);
        Document doc = new Document(content, metadata);
        vectorStore.add(List.of(doc));
    }

    // 基于博客内容问答
    public String searchAndAnswer(String question) {
        // 1. 从向量库检索相关文章片段
        SearchRequest request = SearchRequest.query(question)
                .withTopK(5);  // 取最相关的5段
        List<Document> results = vectorStore.similaritySearch(request);

        // 2. 拼接上下文
        String context = results.stream()
                .map(Document::getText)
                .collect(Collectors.joining("\n\n---\n\n"));

        // 3. 用Prompt组装
        String systemPrompt = "你是一个技术博客助手。请基于以下博客文章内容回答用户问题。\n"
                + "要求:\n"
                + "1. 只基于提供的文章内容回答\n"
                + "2. 如果文章中没有相关信息,请说明'现有文章中未找到相关内容'\n"
                + "3. 回答时标注信息来源的文章标题\n";

        String userMessage = "文章内容:\n" + context + "\n\n用户问题:" + question;

        // 4. 调用大模型
        return chatClient.prompt()
                .system(systemPrompt)
                .user(userMessage)
                .call()
                .content();
    }
}

这段代码是RAG的核心逻辑,建议仔细看。 整个流程就四步:检索→拼接→组装Prompt→调用模型。


七、进阶:多轮对话记忆

7.1 为什么需要?

没有对话记忆的话,每次提问都是独立的:

用户:我之前问过Redis缓存的问题
AI:好的,请问你想了解什么?
用户:刚才那个问题的解决方案里,布隆过滤器具体怎么用?
AI:布隆过滤器是一种……(但我不知道你之前问的是什么)← 尴尬了

7.2 Spring AI的ChatMemory

Spring AI提供了ChatMemory接口,实现多轮对话记忆:

// 用Map存储每个用户的对话历史
private final Map<String, ChatMemory> userMemories = new ConcurrentHashMap<>();

@GetMapping("/ask-memory")
public String askWithMemory(@RequestParam String userId,
                            @RequestParam String question) {
    // 每个用户用userId区分
    ChatMemory memory = userMemories
            .computeIfAbsent(userId, k -> new InMemoryChatMemory());

    return chatClient.prompt()
            .user(question)
            .chatMemory(memory)  // 加上记忆
            .call()
            .content();
}

就这么简单,加一行 .chatMemory(memory) 就支持多轮对话了。

7.3 注意事项

  • InMemoryChatMemory:存在内存里,重启就没了。学习阶段够用,生产环境建议用Redis或数据库持久化。
  • 记忆窗口:对话太多会撑爆token,可以用MessageWindowChatMemory限制保留最近N条对话。
  • 用户隔离:不同用户用不同的userId,不然会串对话。

八、踩坑记录

学习过程中遇到的几个坑,记录一下:

坑1:Spring AI版本和Spring Boot版本不兼容

现象:启动报各种奇怪的NoSuchBeanDefinitionException

解决:Spring AI 1.0.x 需要 Spring Boot 3.x。版本对照表:

Spring AISpring Boot
1.0.x3.2.x
1.1.x3.4.x / 3.5.x

建议:用最新的稳定版本组合,别混着用。

坑2:DeepSeek API Key格式

现象:401 Unauthorized

解决:确认api-key格式是 sk-xxx,不是 Bearer sk-xxx。Spring AI会自动加Bearer前缀。

坑3:Chroma连接不上

现象:Connection refused

解决

  1. 确认Chroma服务在运行:docker ps | grep chroma
  2. 确认端口放通:云服务器安全组要开放8000端口
  3. 确认Chroma绑定的是0.0.0.0而不是127.0.0.1

坑4:Ollama Embedding模型没下载

现象:模型找不到

解决

ollama pull nomic-embed-text
ollama list  # 确认列表里有这个模型

坑5:TokenTextSplitter切片后检索不准确

现象:用户问Redis,检索出来的却是MySQL的文章

原因:切片太大,一个切片里混了好几个技术点

解决:调小切片大小。我用的是1000token/段,重叠100token。重叠是为了保证相邻切片之间的上下文连续性。具体参数要根据你的文章长度和内容密度来调。

坑6:多轮对话token超限

现象:聊了几轮后报"maximum context length"错误

原因:历史对话太长,加上当前问题和检索结果,超过了模型的上下文窗口

解决:用MessageWindowChatMemory限制只保留最近10条对话:

ChatMemory memory = MessageWindowChatMemory.builder()
        .maxMessages(10)
        .build();

九、Java开发者的大模型技术栈

总结一下,Java开发者做大模型应用需要掌握的技术栈:

┌─────────────────────────────────────────────┐
│              应用层                           │
│  Spring Boot + Spring AI                     │
├─────────────────────────────────────────────┤
│              模型层                           │
│  DeepSeek / 通义千问 / OpenAI                │
├─────────────────────────────────────────────┤
│              能力层                           │
│  RAG | Function Calling | ChatMemory         │
│  PromptTemplate | Embedding                  │
├─────────────────────────────────────────────┤
│              存储层                           │
│  Chroma/Milvus (向量) | Redis (缓存)         │
│  MySQL (业务数据)                             │
├─────────────────────────────────────────────┤
│              基础设施                         │
│  Docker | Linux | Nginx                      │
└─────────────────────────────────────────────┘

你会发现,这里面大部分东西Java开发者都会。 Spring Boot、MySQL、Redis、Docker——这些是你的优势,不是障碍。


十、下一步学什么?

入门之后,可以继续深入这些方向:

方向说明难度
混合检索向量检索 + 关键词检索(BM25)结合⭐⭐
Reranker对检索结果二次排序,提高准确率⭐⭐⭐
Agent架构ReAct模式,让大模型自主决策执行步骤⭐⭐⭐
MCP协议标准化的工具调用协议⭐⭐
模型微调Fine-tuning,让模型适应特定领域⭐⭐⭐⭐

总结

Java开发者做大模型应用,核心路径就一条:

Spring Boot → Spring AI → 接大模型 → 加RAG → 加工具 → 加记忆

每一步都是在你已有的Spring知识体系上叠加AI能力,不需要推倒重来。

最大的门槛不是技术,是迈出第一步。


本文代码示例基于 Spring AI 1.1.8 + Spring Boot 3.5 + DeepSeek API。
如有问题,欢迎在评论区交流。

更多推荐