1. 为什么要在 Spring AI 里把 PostgresML 嵌入 endpoint 改到 TaoToken
如果你正在用 Spring AI 接 PostgresML 做嵌入模型,大概率会遇到一个很现实的问题:本地 PostgreSQL 装了 pgml 扩展,EmbeddingModel也能注入,但真正跑embed()的时候,要么卡在模型下载,要么报连接超时,要么返回维度对不上。核心检索词先摆出来——Spring AI 集成 PostgresML 嵌入模型,本质是让 Spring Boot 应用通过 JDBC 调数据库里的pgml.embed()函数,把文本转成向量。它适合谁?适合本地已经有 PostgreSQL、已经装了 pgml 扩展、想用数据库内计算省掉单独部署推理服务的开发者。
但 PostgresML 的嵌入计算依赖 Hugging Face 模型权重,默认走的是公网模型仓库。很多内网环境、CI 环境、或者只是想快速验证链路的场景,模型拉不下来就直接卡死。这时候把 endpoint 改到 TaoToken,用统一的 API 入口承接嵌入请求,链路会清爽很多:Spring AI 侧仍然是EmbeddingModel接口,底层请求指向 TaoToken 的兼容端点,返回维度、调用日志都能正常核对。
我试过的做法是:保留 PostgresML 作为向量存储和检索层,把嵌入生成这一步的出口切到 TaoToken。这样数据库里PG_VECTOR类型照用,pgvector 的距离计算照跑,只是向量的来源换了个更稳定的通道。下面从环境准备开始,一步步给出可复制的配置。
先明确整体链路:Spring Boot 应用 → Spring AIEmbeddingModel→ HTTP 请求到 TaoToken 端点 → 返回float[]向量 → 写入 PostgreSQL 的vector列。PostgresML 扩展负责数据库内的向量类型和索引,TaoToken 负责嵌入计算。两者职责分开,排障时定位更快。
你需要准备的东西:一个能跑的 PostgreSQL 12+(建议 14 以上),已执行CREATE EXTENSION IF NOT EXISTS vector;和CREATE EXTENSION IF NOT EXISTS pgml;;一个 Spring Boot 3.x 项目,JDK 17+;以及一个 TaoToken 的 API Key。Key 在控制台生成,地址是 https://taotoken.net/api-keys ,生成后复制保存,后面配置里要用。
这里要提醒一句:pgml 扩展的安装对权限有要求,数据库用户得有CREATE EXTENSION权限。生产环境建议手动执行扩展创建,别依赖自动配置,否则权限不足时报错信息很难看懂。我踩过的坑就是自动建扩展失败,日志只给一句模糊的 SQL 异常,排查了半天才发现是角色权限问题。
2. TaoToken 前置准备:Key、Base URL 与模型 ID 三件套
在动 Spring AI 配置之前,先把 TaoToken 侧的三件套确认清楚,这是后面所有配置的基础。所谓三件套就是 Base URL、API Key、Model ID,缺一个请求都跑不通。
Base URL 用 https://taotoken.net/api ,注意这个地址不带任何查询参数,直接作为请求前缀。API Key 从控制台拿,格式通常是一串以特定前缀开头的字符串,复制时别带空格。Model ID 是你要调用的嵌入模型标识,比如常见的文本嵌入模型名称,具体以 TaoToken 文档里列出的可用模型为准,文档入口在 https://taotoken.net/doc 。
为什么强调这三件套?因为 Spring AI 的OpenAiEmbeddingModel或兼容配置里,baseUrl、apiKey、options.model三个属性必须同时正确。少一个,要么 401,要么 404,要么返回空向量。我见过最常见的错误就是 baseUrl 末尾多写了/v1或者少写了路径,导致请求打到错误的路由上。
你可以先用 curl 快速验证 Key 是否可用,避免把问题带进 Spring 项目里。下面这条命令把文本发给嵌入端点,看返回结构里有没有data[0].embedding数组:
curl -X POST https://taotoken.net/api/embeddings \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "your-embedding-model-id", "input": "Spring AI 集成 PostgresML 嵌入模型" }'返回里如果能看到一个浮点数组,长度就是向量维度,记下这个数字,后面核对 Spring AI 返回维度时要用。如果返回 401,检查 Key 是否复制完整;如果返回 404,检查 baseUrl 和路径拼接是否正确;如果返回模型不存在,去文档里核对 Model ID 拼写。
这一步别跳过。很多人在 Spring 配置里反复改,其实问题出在 Key 或模型 ID 上。先用 curl 把通道打通,再进代码层,排障成本会低很多。验证通过后,把 Base URL、Key、Model ID 三个值记在手边,下一节的配置文件直接填。
另外提一下 Coding Plan 的场景:如果你不只是做一次嵌入验证,而是要长期跑编码类 Agent 或者批量向量化任务,可以了解下 https://taotoken.net/coding-plan ,它更适合持续调用的工作负载。单次验证用按量 Key 就够了。
3. 可复制配置:application.yml 与 EmbeddingModel 片段
这一节是核心,给出能直接抄的配置。Spring AI 从 1.0 开始对模型配置做了统一抽象,嵌入模型可以通过spring.ai.model.embedding指定,再用对应厂商的前缀配参数。我们把出口指向 TaoToken 的兼容端点。
先看application.yml,路径是src/main/resources/application.yml。注意缩进用空格,别用 Tab:
spring: datasource: url: jdbc:postgresql://localhost:5432/mydb username: postgres password: your_db_password driver-class-name: org.postgresql.Driver ai: model: embedding: openai openai: embedding: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} options: model: your-embedding-model-id dimensions: 1536这里有几个关键点。第一,spring.ai.model.embedding设成openai,因为 TaoToken 提供的是 OpenAI 兼容协议,Spring AI 用OpenAiEmbeddingModel来对接最顺。第二,base-url填https://taotoken.net/api,不要在后面加/v1,Spring AI 会自己拼/embeddings路径。第三,api-key用环境变量注入,别硬编码在文件里,避免提交到仓库泄露。第四,dimensions要和你实际用的模型输出维度一致,填错了写入vector(n)列时会报维度不匹配。
如果你更习惯用 properties 格式,等价写法如下,路径是src/main/resources/application.properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb spring.datasource.username=postgres spring.datasource.password=your_db_password spring.ai.model.embedding=openai spring.ai.openai.embedding.base-url=https://taotoken.net/api spring.ai.openai.embedding.api-key=${TAOTOKEN_API_KEY} spring.ai.openai.embedding.options.model=your-embedding-model-id spring.ai.openai.embedding.options.dimensions=1536依赖方面,Maven 的pom.xml里需要引入 Spring AI 的 OpenAI starter 和 PostgreSQL 驱动:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency>Gradle 对应写法:
implementation 'org.springframework.ai:spring-ai-starter-model-openai' implementation 'org.postgresql:postgresql'接下来是EmbeddingModel的使用片段。Spring Boot 会自动装配一个EmbeddingModelBean,你直接注入即可。下面这个 Controller 暴露一个 POST 接口,接收文本列表,返回向量:
import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/embed") public class EmbeddingController { private final EmbeddingModel embeddingModel; public EmbeddingController(EmbeddingModel embeddingModel) { this.embeddingModel = embeddingModel; } @PostMapping public List<float[]> embed(@RequestBody List<String> texts) { return embeddingModel.embed(texts); } }注意embed方法返回的是List<float[]>,每个float[]长度就是向量维度。如果你要写入 PostgreSQL 的vector列,需要把float[]转成 pgvector 能识别的字符串格式,比如[0.1,0.2,...]。这一步在下一节验证时会给出具体写法。
如果你不想用自动装配,想手动构造EmbeddingModel,可以这样写,把 Base URL 和 Key 显式传进去:
import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.OpenAiEmbeddingOptions; import org.springframework.ai.openai.api.OpenAiApi; OpenAiApi openAiApi = OpenAiApi.builder() .baseUrl("https://taotoken.net/api") .apiKey(System.getenv("TAOTOKEN_API_KEY")) .build(); OpenAiEmbeddingOptions options = OpenAiEmbeddingOptions.builder() .model("your-embedding-model-id") .build(); OpenAiEmbeddingModel model = new OpenAiEmbeddingModel(openAiApi, options);手动构造的好处是可以在多模型场景下灵活切换,比如一个 Bean 用嵌入模型,另一个 Bean 用对话模型。但大多数情况下自动装配就够了,配置更少,出错面更小。
4. 验证请求:跑通一次向量化并核对维度与日志
配置写完,启动 Spring Boot 应用,然后发一个真实请求验证。先确认应用启动日志里没有报EmbeddingModel装配失败,也没有 baseUrl 解析异常。启动成功后,用 curl 打你暴露的接口:
curl -X POST http://localhost:8080/api/embed \ -H "Content-Type: application/json" \ -d '["Spring AI 集成 PostgresML 嵌入模型", "向量维度核对"]'返回应该是一个 JSON 数组,里面两个元素,每个元素是一个浮点数组。数一下数组长度,比如返回 1536 个浮点数,那就和配置里的dimensions: 1536对上了。如果长度不一致,说明模型实际输出维度和配置不符,要么改配置,要么换模型。
接着把向量写进 PostgreSQL,验证 pgvector 链路。先在数据库里建表,注意vector的维度要和实际输出一致:
CREATE TABLE IF NOT EXISTS doc_embedding ( id BIGSERIAL PRIMARY KEY, content TEXT NOT NULL, embedding vector(1536) );然后在 Java 侧把float[]转成 pgvector 字符串再插入。下面是一个 JdbcTemplate 的写法:
import org.springframework.jdbc.core.JdbcTemplate; import java.util.Arrays; import java.util.stream.Collectors; public void saveEmbedding(JdbcTemplate jdbc, String content, float[] vector) { String vecStr = Arrays.stream(toBoxed(vector)) .map(String::valueOf) .collect(Collectors.joining(",", "[", "]")); jdbc.update("INSERT INTO doc_embedding(content, embedding) VALUES (?, ?::vector)", content, vecStr); } private Double[] toBoxed(float[] arr) { Double[] boxed = new Double[arr.length]; for (int i = 0; i < arr.length; i++) boxed[i] = (double) arr[i]; return boxed; }插入成功后,用一条 SQL 验证向量确实落库了,并且能做距离查询:
SELECT id, content, embedding <=> '[0.1,0.2,...]'::vector AS distance FROM doc_embedding ORDER BY distance LIMIT 5;<=>是 pgvector 的余弦距离操作符,能跑出结果说明向量类型、索引、查询链路都通了。到这一步,Spring AI 调 TaoToken 生成向量、写入 PostgreSQL、pgvector 检索,整条链路验证完毕。
日志方面,建议在application.yml里把 Spring AI 的日志级别调到 DEBUG,方便看请求和响应:
logging: level: org.springframework.ai: DEBUG org.springframework.web.client: DEBUG调完之后,控制台会打印出请求的 URL、请求体、响应状态。重点看 URL 是不是https://taotoken.net/api/embeddings,请求体里model和input是否正确,响应状态是不是 200。如果看到 401,回去查 Key;看到 404,查 baseUrl 拼接;看到响应体里data为空,查模型 ID。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
这一节把实际会撞到的报错逐个拆开,给出定位思路。这些报错信息你大概率会在日志里原样看到,对照着查就行。
401 Unauthorized。日志里通常是401 Unauthorized: [no body]或者带一句invalid api key。原因就三类:Key 没配、Key 配错、Key 失效。先确认环境变量TAOTOKEN_API_KEY在当前 shell 里能echo出来,再确认application.yml里引用的是${TAOTOKEN_API_KEY}而不是写死的旧值。如果 Key 是从控制台复制的,注意别把首尾空格带进去。重新生成一个 Key 再试是最快的排除法,生成入口在 https://taotoken.net/api-keys 。
local proxy failed。这个报错通常出现在请求根本没发出去的时候,日志里会有Connection refused或Failed to connect。检查两件事:一是base-url是不是写成了https://taotoken.net/api/带尾斜杠,某些 HTTP 客户端拼接时会出双斜杠导致路由异常;二是本机网络能不能正常访问taotoken.net,用curl -I https://taotoken.net/api看返回头。如果公司网络有出口限制,需要走内网出口策略,这个得找网络管理员,不是代码问题。
reading choices 相关报错。典型信息是Cannot deserialize value of type ... from Array value或者reading choices字段解析失败。这个多半是把嵌入模型和对话模型的响应结构搞混了。嵌入接口返回的是data[].embedding,对话接口返回的是choices[].message。如果你用OpenAiChatModel去调嵌入端点,或者反过来,就会在反序列化时报这个错。确认spring.ai.model.embedding=openai且注入的是EmbeddingModel而不是ChatModel。
OAuth 相关报错。日志里出现OAuth、token endpoint、invalid_client这类字样,说明请求被路由到了需要 OAuth 认证的路径上。TaoToken 的 API 用的是 Bearer Token,不是 OAuth 流程。出现这个报错通常是 baseUrl 配错了,打到了别的服务上。核对base-url是不是https://taotoken.net/api,别写成带oauth或authorize的路径。
维度不匹配。报错信息类似expected 1536 dimensions, not 768。这是配置里的dimensions和模型实际输出不一致。解决办法:先用 curl 单独调一次嵌入接口,数返回数组长度,然后把这个数字填到dimensions和建表时的vector(n)里,两处必须一致。
pgml 扩展未找到。报错extension "pgml" is not available。说明数据库没装 pgml,或者当前用户没权限创建。用超级用户执行CREATE EXTENSION IF NOT EXISTS pgml;,然后确认SELECT * FROM pg_extension WHERE extname='pgml';能查到记录。生产环境别开create-extension=true自动建,手动建更可控。
把上面这些报错和对应动作整理成一张对照表,排障时直接查:
| 报错关键词 | 最可能原因 | 处理动作 |
|---|---|---|
| 401 Unauthorized | Key 缺失或错误 | 重新生成 Key,检查环境变量 |
| local proxy failed | baseUrl 拼接或网络出口 | 去掉尾斜杠,curl 测连通性 |
| reading choices | 模型类型用混 | 确认注入 EmbeddingModel |
| OAuth / invalid_client | baseUrl 打到错误路径 | 核对为 https://taotoken.net/api |
| dimensions mismatch | 维度配置不符 | curl 数长度,同步改配置和建表 |
| extension pgml not available | 扩展未装或权限不足 | 超级用户手动 CREATE EXTENSION |
6. 语义一致 CTA:接入文档、模型对话与长期编码
链路跑通之后,如果你要继续深入,几个入口按场景分流。想核对 TaoToken 支持哪些嵌入模型、参数怎么传,看接入文档 https://taotoken.net/doc ,里面有完整的模型列表和请求示例。想先在网页上试一下模型对话效果,确认返回风格,用模型对话入口 https://taotoken.net/chat 。如果你是要长期跑编码类 Agent、批量向量化或者持续调用的工作负载,Coding Plan 更合适,入口在 https://taotoken.net/coding-plan 。
回到本篇的主线:Spring AI 集成 PostgresML 嵌入模型,关键动作就三步——把base-url指向https://taotoken.net/api,把 Key 和 Model ID 配进application.yml,然后用一次真实请求核对返回维度和数据库写入。这三步做完,向量化链路就通了。剩下的检索、索引、距离计算,交给 PostgreSQL 和 pgvector 就行。
最后留一个实用技巧:把嵌入请求的响应时间也打日志里。如果单条文本嵌入超过 2 秒,考虑批量提交,一次传多条文本,减少往返次数。批量大小从 16 或 32 开始试,太大反而会触发请求体限制。这个参数没有万能值,按你的文本长度和网络状况调。