动手之前:环境、选型和一次性的决定
约 2506 字大约 8 分钟
2026-09-10
今天是这个系列第一篇「真动手」的文章,但还不写业务代码。干什么呢?搭环境,以及做掉几个一旦定下来就不太好改的决定。这类决定的特点是:改它们的成本会随着项目推进指数级上涨,所以值得在第一天多花半小时想清楚。
我按顺序过:工程骨架、数据库、模型、语料盘点。
一、工程骨架:Java AI 的版本大坑
先说最气人的坑,因为它坑人的方式特别有迷惑性。
网上教程和 Java AI 1.1.x 的差异,大到能让你怀疑自己不会写 Java。 1.0 正式版之前那些 M4、RC 版本的教程满天飞:包名不一样、ChatClient 的用法不一样、starter 的名字也不一样。你要是拿一篇老教程对着 1.1.x 的依赖写,报错信息还特别拧巴——不是「找不到类」就是「方法不存在」,很难第一时间意识到是版本问题。
土办法判断教程新旧
看依赖名。1.1.x 的 starter 命名是 spring-ai-starter-* 这种格式(比如后面要用的 spring-ai-starter-vector-store-pgvector),看到老的 spring-ai-pgvector-store 之类写法直接关掉。
我的建议就一条:只认 1.1.x 官方 reference 文档,教程一律只当思路参考。
骨架本身没什么花样,一个普通的 Spring Boot 工程,Java 17 起步,把 BOM 引进来管版本:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.1.x 具体小版本看当时最新</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>用 BOM 的好处是后面加什么 starter 都不用写版本号,也不怕各个组件版本错配。
二、数据库:把 PGVector 装起来
第二块地基是数据库。我假设你已经有 Postgres 使用经验,这里只讲 PGVector 特有的部分。
装法很简单,PGVector 是个 Postgres 扩展,两种路线:
- 本地开发:直接用官方镜像。
pgvector/pgvector:pg17这个镜像自带扩展,docker 一行命令起来,没有任何安装过程,推荐所有人从这条开始 - 已有 Postgres 实例:编译安装扩展,然后
CREATE EXTENSION vector;一次性激活
装好之后可以快速自检一下,连上库执行:
CREATE EXTENSION IF NOT EXISTS vector;
SELECT '[1,2,3]'::vector <=> '[3,2,1]'::vector; -- 能返回一个数字就说明一切正常这里提前剧透一个后面会反复出现的细节:<=> 是余弦距离算子,PGVector 里还有 <->(欧氏)和 <#>(内积)。你建索引用哪种算子,查询就必须用哪种,混用的话要么索引不生效要么结果错乱。现在记不住没关系,第 07 篇会把它和索引一起讲透,这里先埋个种子。
有个被问得很多的问题顺手答了:「我的博客量级,PGVector 扛得住吗?」放心。个人博客算上切片撑死几十万条向量,PGVector 配合 HNSW 索引在千万级以内都活得很滋润。真到千万级以上再考虑 Milvus 也不迟——而且到时候你该操心的也不是向量库,是别的。
三、模型决定:这才是重头戏
环境搭建十分钟就完事,模型选型才是今天真正要想清楚的。三个决定:Embedding、Rerank、生成模型。
先说这些决定将来落在哪:relax-admin 的「知识库配置 → AI 引擎」页就是给它们准备的——每一类模型一条配置,API 地址、key、模型名、参数都从那页下发到后端。所以今天想清楚的每个「选型决定」,明天就是那一页里的一条记录。
📷 图片位(待补充)
应放什么图:「知识库配置 → AI 引擎」页面截图,展示 Embedding / Rerank / 生成模型三块配置卡片,最好有一条已经填好真实配置的(key 打码),让读者对「模型选型最终长这样」有个实体感。
Embedding:中文场景只有一条硬规矩
硬规矩就一条:必须选多语种模型,且从第一天起就把模型名和版本记进数据。
为什么强调「记进数据」?上一篇讲过,向量只有在同一个模型的坐标系里才能比较。你今天用 A 模型灌了全库,半年后看到 B 模型评测分更高,手一滑换了——新向量进的是 A 坐标系的库,检索立刻变成玄学,而且这种错不报异常,就是默默变差,等用户反馈「怎么越来越不准了」你才知道。每条向量记录都带上它是谁算的,是未来能安全换模型的唯一保票,第 06 篇落地时会看到表结构里专门有这个字段。
选型上,国内可选的多语种 Embedding 不少(DashScope 的 text-embedding-v3/v4、BGE 系列等等),中文效果都有基本盘,按 API 稳定性和价格挑一个就行。维度建议 1024(主流,表达力和存储成本平衡)。
别过度纠结
Embedding 模型之间评测榜上差的那一两分不重要——语料切分质量对效果的影响,比这几个模型之间的差距大一个数量级。挑个顺手的赶紧进入正题。
Rerank:要单独选,但可以晚点定
注意 Rerank 是另一个模型,不是复用 Embedding 的。它俩干的活完全不同:Embedding 是「各自把文字变成向量再比距离」,Rerank 是「把问题和段落拼在一起读一遍打分」,后者准得多但慢得多,所以只能放在漏斗末端(第 10 篇细讲)。选型看有没有现成的 API 服务(gte-rerank 这类),有就用,没有等做到那一篇再说。
生成模型:随便选,但要想清楚一件事
生成模型反而最不重要,主流的都能胜任「照着资料回答」这个任务,中途换也没多少迁移成本。真正要想的是上下文窗口的预算意识:回答质量差很多时候不是模型笨,是你往它窗口里塞的垃圾证据太多。这个话题属于第 12 篇,这里只提醒一句:别因为「反正窗口 128k」就无脑多塞,上下文不是越多越好。
四、模型接入的通用姿势
Java AI 接模型服务基本是「OpenAI 兼容协议」的天下,配置长这样(以 OpenAI 风格的 endpoint 为例,各家云厂商几乎都提供兼容模式):
spring:
ai:
openai:
api-key: ${AI_API_KEY}
base-url: ${AI_BASE_URL}
embedding:
options:
model: 你选的embedding模型两个实用建议:
- key 永远走环境变量,别硬编码——博客仓库是要推到 GitHub 的,key 泄露的快乐你不想体验
- Embedding 的调用从第一天起就按「会失败」来写代码:这些服务有速率限制,批量灌库时一万个切片无脑并发发请求,几秒内就会收获一堆 429。分批、限速、失败重试,这三件事不是优化项,是基本盘,第 06 篇会给完整实现
五、语料盘点:动手前先摸清家底
最后一步准备经常被跳过,但它很重要:把你博客的文章盘一遍。
我自己盘的时候重点看了三类东西:主体是规规矩矩的 Markdown,这没问题;有没有从别的平台搬过来的老文章、正文里嵌着 HTML 标签的;还有那种只有一两百字的碎碎念,信息密度约等于没有。盘完的分类结论:第一类直接进管道,第二类要在解析环节加清洗规则,第三类干脆不进库——垃圾语料不占存储,占的是检索结果的位置,它混进 top5,真正有用的段落就被挤下去了。
顺手定下元数据的最低集合,这是全系列第一批「不可逆决定」:每篇文章入库时至少要带 slug(文章唯一标识,改版更新时靠它定位旧数据)、title、category、publish_time。以后你会在无数地方感谢这几个字段。
再多说一步数据形态的事:relax-admin 的「知识库配置 → 知识库列表」页里,知识库分两种来源——上传型(手动/接口上传文档,走「文档上传」页)和 Gitee 同步型(绑定仓库定时拉取,走「增量同步」页)。我们博客是后者:仓库 relax-blog.git,每 6 小时一次 git diff 增量检测。第 15 篇会把这个同步机制讲透,这里只要知道:语料盘点盘的就是那个仓库里的 Markdown。
📷 图片位(待补充)
应放什么图:「知识库列表」页截图,重点展示「博客知识库」卡片(Gitee 拉取型、6 小时增量同步的标识)和一个上传型知识库的对比,体现两种来源类型的差异。
今天的产出清单
照例收个尾,检查一下今天应该落地的东西:
都打上勾了?那准备工作齐活。下一篇开始进入正餐的第一道菜:解析。听起来最没有技术含量的一步,我会告诉你为什么「解析质量决定 RAG 效果的上限」,以及 Markdown 这种看似人畜无害的格式里,藏着多少不为人注意的坑。
我们下篇见。