乐于分享
好东西不私藏

手搓一个 Agent01:从文档问答到生产级智能体

手搓一个 Agent01:从文档问答到生产级智能体

✦ 干货分享 ✦

第1章:一个文档丢进去能聊?我们先来做这件事

你好,欢迎来到这门"Java + Spring AI 开发 Agent 应用"的保姆级连载。

做这个连载之前,我默认你已经: - 会写 Java,懂 Spring Boot 那一套(Controller、Service、注入) - 会用 Maven 管理依赖 - 但从来没碰过 AI 应用,不懂什么是 Agent、什么是 RAG

有这些底子就够了,剩下的我们一章一章补。

这一章,我不打算上来就画大饼讲什么"Agent 智能体改变世界"。我们先做一件最小的、能跑的事:把文档丢给程序,然后它陪我们聊天。就这么简单。

为什么先做这个?因为秦国统一六国,也是一步一步来的。AI 应用也一样——先让它"动起来",你才能看见后面的路。


01什么叫 Agent?一句话就够了

先把这个概念敲碎。

Agent 的完整定义可以写三篇论文,但你想快速理解,记住这一句就够:

"

Agent 是一个能自主决定调工具、进行多步推理、并且带记忆的 LLM 应用。

"

拆开看,就是三个零件: - 调工具:它能自己决定"我要不要调用一下这个函数" - 多步推理:它不会一步出结果,而是想一步、走一步、再想下一步 - 带记忆:它记得你们之前聊过什么

那它跟你已经听过无数次的 Chatbot 和 RAG 问答有什么区别?这张表一看就懂:

维度
Chatbot
RAG 问答
Agent
主动决策
无,你说一句它答一句
无,只在你问时检索+回答
有,自己决定下一步做什么
工具
一般无
有,可自主调用
记忆
会话级
会话级
可持久化、跨会话
多步
通常单步
有,可串联多个动作
典型场景
客服闲聊
回答"文档里写了啥"
自己完成一个任务流

Chatbot 是"你问我答",RAG 是"带上下文地问答",Agent 是"你交代一件事,我自己想办法做完"。区别就在这个"自己想办法"——它开始有主观能动性了。

这一章,我们先不做完整的 Agent,我们先把地基打出来。地基是什么?就是那个能聊的壳。


02我们这套连载到底做的是啥:DocMind

连载贯穿始终的项目,我给起了个名字,叫 DocMind,一个个人文档资料助手。

设想一下场景:你的硬盘里躺着一堆 PDF、Markdown、技术文档、面试笔记。你想问它"我上个月那篇关于微服务的笔记里,重点说了啥",难道还一篇篇翻?

DocMind 就是想干这件事:把文档喂进去,之后你有任何问题,直接问,它从文档里找答案回答你。

但这里我必须诚实一点,管理一下你的预期——因为这两年"AI 能写代码""AI 取代程序员"这种调调太多了,我不想让你抱着不切实际的幻想入门。

DocMind 能做到的: - 基于你文档内容,回答相关问题 - 总结、找要点、对比不同文档的观点 - 引用原文出处(后面章节我们会做)

DocMind 暂时不能做到的: - 不能凭空编造文档里没有的事实(我们用了技术手段去避免它胡说) - 不能实时上网查最新资料(那是联网搜索的能力,我们没规划) - 不能像科幻片那样"全知全能"地帮你决策人生

一句话总结预期:它是一个很听话、记得住你资料的"读后问答专家",不是一个神。

诚实管理预期,比夸大其词更能让你在这条路上走下去。


03为什么是 Java + Spring AI?

你可能一上来就有个疑问:外面铺天盖地都是 Python + LangChain 做 AI,你怎么带我做 Java?

理由很实在,我说给你听:

第一,企业生态成熟。 Java 干了二十多年企业级开发,稳定性、事务、并发、监控、部署,一套成熟的体系摆在那。真要落到生产环境里,Java 的底气是别的语言不好比的。你要是写企业内部的 AI 应用,Java 几乎是绕不开的选择。

第二,Spring AI 抽象层干净,换 Provider 零成本。 这是我最看重的一点。所谓 Provider,就是你调的是哪个大模型——DeepSeek、OpenAI、通义千问、豆包都算是不同 Provider。

Spring AI 干了一件事特别好的事:它把这些乱七八糟的大模型 API 全部统一成一套接口。今天你调 DeepSeek,明天你想换 OpenAI,改一个配置项,代码几乎不用动。这种"换底不换皮"的抽象,就是工程上的体面。

第三,对 Java 开发者极其友好。 它的编程模型就是你熟悉的 Spring 那一套:注入、Bean、配置。你已经会 Spring Boot 了,那学 Spring AI 的曲线基本是平的。

第四,Maven 依赖管理规范。 版本冲突、传递依赖、BOM 管理,这些都是 Java 生态帮你处理好了的。你加依赖,就像你平常加速成包一样。

技术选型这件事,没有绝对的对错,只有合不合适。对于企业 JAVA 团队而言,Java + Spring AI 就是那个"合适"。


04先跑起来!DocMind v0 最小可跑版本

空谈没用,咱们直接开干。

DocMind v0 的目标极简到不能再简:一个 Spring Boot 服务 + 一个极简 HTML 页面。你在网页里输入一句话,发出去,服务端调用 DeepSeek,把回答流式地一个字一个字吐回页面给你看。

没错,这一步我们还没接文档,就纯聊天。为什么先做这个?因为要走通那根最关键的"管子":前端 ↔ 后端 ↔ 大模型 这条数据流。管子通了,后面往里面灌什么都行。

技术栈就三样: - Spring Boot + Spring AI - DeepSeek 大模型 - EventSource(SSE)做流式输出

第一步:pom.xml 关键依赖

打开你的 pom.xml,加这三个东西:

1<parent>

2<groupId>org.springframework.boot</groupId>

3<artifactId>spring-boot-starter-parent</artifactId>

4<version>3.4.0</version>

5<relativePath/>

6</parent>

7

8<dependencies>

9<!-- 最基础的 Web 能力 -->

10<dependency>

11<groupId>org.springframework.boot</groupId>

12<artifactId>spring-boot-starter-web</artifactId>

13</dependency>

14

15<!-- Spring AI:用 BOM 统一管理版本,免得版本打架 -->

16<dependency>

17<groupId>org.springframework.ai</groupId>

18<artifactId>spring-ai-bom</artifactId>

19<version>1.0.0</version>

20<type>pom</type>

21<scope>import</scope>

22</dependency>

23

24<!-- 接 DeepSeek 的适配器,配置好就能用 -->

25<dependency>

26<groupId>org.springframework.ai</groupId>

27<artifactId>spring-ai-starter-model-deepseek</artifactId>

28</dependency>

29</dependencies>

注意两个细节: 1. spring-ai-bom 的 version 写在 dependencyManagement 的 import 下,统一管理下面所有 Spring AI 模块的版本。 2. 因为要引 BOM,建议用 spring-boot-starter-parent 作为 parent。

第二步:application.yml 配置

1spring:

2ai:

3deepseek:

4# 从环境变量读,别把 key 硬编码写进文件!

5api-key${DEEPSEEK_API_KEY}

6base-urlhttps://api.deepseek.com

7chat:

8options:

9modeldeepseek-chat

base-url 用默认的 https://api.deepseek.com 就行,也可以不写。

第三步:写 Controller——核心就一行流式调用

这才是精髓所在。Spring AI 的 ChatClient 已经被自动装配好了,你直接注入就能用:

1importorg.springframework.ai.chat.client.ChatClient;

2importorg.springframework.web.bind.annotation.*;

3importreactor.core.publisher.Flux;

4importorg.springframework.http.MediaType;

5

6@RestController

7publicclassChatController {

8

9privatefinal ChatClient chatClient;

10

11// 构造注入,ChatClient 由 starter 自动装配

12publicChatController(ChatClient.Builder builder) {

13this.chatClient = builder.build();

14    }

15

16@GetMapping(value = "/api/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

17public Flux<String> chat(@RequestParam String message) {

18// .prompt(...) 设置用户输入

19// .stream() 开启流式

20// .content() 返回 Flux<String>,一个字一个字往外吐

21return chatClient

22                .prompt(message)

23                .stream()

24                .content();

25    }

26}

你能看到,逻辑就一行链式调用: - .prompt(message)——把用户说的话喂给模型 - .stream()——打开流式开关 - .content()——返回 Flux<String>,也就是一个字符串流

返回值类型 Flux<String> 是 Reactor 的响应式流。前端用 EventSource 接住它,就能实现"打字机效果"。

第四步:极简前端的 HTML

把下面这个文件放到 src/main/resources/static/index.html,一个 input 加一个文本区,用 EventSource 接流:

1<!DOCTYPE html>

2<htmllang="zh">

3<head>

4    <metacharset="UTF-8">

5    <title>DocMind v0</title>

6</head>

7<body>

8    <h1>DocMind v0 —— 先聊起来</h1>

9    <inputid="question"type="text"placeholder="输入你的问题,回车发送"style="width:80%" />

10    <divid="answer"style="margin-top:20px; white-space:pre-wrap;"></div>

11

12    <script>

13const input = document.getElementById('question');

14const answer = document.getElementById('answer');

15

16        input.addEventListener('keydown'async (e) => {

17if (e.key !== 'Enter'return;

18const msg = input.value;

19            input.value = '';

20            answer.textContent = '';

21

22// 用 EventSource 就能接 SSE 流,注意这里要把中文编码一下

23const es = new EventSource('/api/chat?message=' + encodeURIComponent(msg));

24            es.onmessage = (event) => {

25                answer.textContent += event.data;

26            };

27            es.onerror = () => es.close();

28        });

29    </script>

30</body>

31</html>

启动你的 Spring Boot 服务,浏览器打开 http://localhost:8080,输入一句话,回车——你会看到 DeepSeek 的回答一个字一个字地蹦出来。这就是流式输出,这就是 DocMind 的骨架。

恭喜,你的第一个 AI 应用跑起来了。


05本章踩坑

每个章节我都会保留这个小节,把我自己踩过的坑、你大概率也会踩的坑直接摊开。现象 → 根因 → 解法,三行讲清。

坑一:DeepSeek 的 key 在哪申请?怎么配?为什么不能传 GitHub?

  • 现象
    :不知道上哪搞 key;key 该怎么写进配置;有人截图把 key 晒到网上。
  • 根因
    :DeepSeek 的 key 要先去它的开放平台注册。进去之后在"API Keys"页面创建。创建后这个 key 就是你的"密码",谁拿到谁就能替你花钱调模型。
  • 解法
    : 1. 访问 DeepSeek 开放平台 → 注册账号 → 「API Keys」→ 创建新的 key,复制保存(注意 key 只显示一次)。 2. 配置里用环境变量 ${DEEPSEEK_API_KEY},别把 key 直接写进 application.yml。 3. 在项目根目录建一个 .gitignore 文件,把 key、配置文件之类敏感的东西排除掉。绝不要把含 key 的配置推到 GitHub——这是 AI 初学者最容易犯、也最致命的错。

1# .gitignore

2target/

3*.yml

4application.yml

5.env

坑二:Spring AI 版本和 Spring Boot BOM 绑定,不匹配必报错

  • 现象
    :启动报错,比如 No qualifying bean of type 'ChatClient.Builder' 或者一堆依赖找不到的诡异异常。
  • 根因
    :Spring AI 的版本和 Spring Boot 版本是绑定的,不能随便配。Spring AI 1.0.0 对应 Spring Boot 3.4.0+。你如果用了 Spring AI 1.0.0 却配了 Boot 3.2,就会因为少了某块自动配置而报一堆错,报错还不在点子上。
  • 解法
    :记住对应关系,Spring AI 1.0.0 就配 Boot 3.4.0 及以上。用 spring-boot-starter-parent 做 parent,让 BOM 帮你锁版本,别自己手动拼。

坑三:国内调 DeepSeek 要不要代理?

  • 现象
    :总担心国内网络访问不了 DeepSeek,想挂代理或者换服务器,结果越弄越复杂。
  • 根因
    :很多人把 DeepSeek 跟 OpenAI(国外服务)搞混了。DeepSeek 是国产模型,API 就在国内,服务器也在国内。
  • 解法
    :直连即可,国内网络直接可用,不需要代理。这一点比调 OpenAI 省心一百倍。如果你之前为了 OpenAI 配过代理,记得确认没把代理指到手疼——否则反而可能把请求代理到国外绕一圈,更慢。

06下一章预告

这一章,我们让一个"能聊天的壳"跑了起来,走通了 前端 → 后端 → 大模型 的管子。但你大概也感觉到了:这还只是一个 Chatbot,离 Agent 还差得远

别急。还记得开头那个 Agent 定义的三个零件吗?

下一章,我们就来把它补全。《第2章:Agent 的三个零件——模型、工具、记忆》。我们会把"工具"这个概念拆开——让 Agent 自己去调用函数、去检索你的文档,让那个"能聊"的壳,长出手和脑子。

先聊起来,再学会干活。路一步一步走,Agent 也是。

我们,第2章见。