免费语义相似度(STS)API 完全教程:零成本给文本装上"像不像"的尺子(2026-10-02 验证)
语义相似度(Semantic Textual Similarity,简称 STS)解决的是一个极其朴素但极其高频的问题:两段文字,到底像不像? 本文给出 2026 年仍可零成本调用的 6 条路线、一套可直接抄的阈值标定方法,以及 7 个让相似度分数从"能用"变成"可信"的实战坑位。所有额度均核对厂商公开页,验证时间 2026-10-02。
一、为什么 STS 是全站被问最多却最少被单独讲清的能力
在 免费 Embedding API 完全教程 里我们讲过怎么把文本变成向量,在 免费 Reranker(重排序)API 完全教程 里讲过怎么把候选结果重新排序。但真实项目里最常撞上的问题既不是"怎么向量化"也不是"怎么排序",而是这三问:
- 用户提交的工单,和历史上那 8000 条是不是重复?
- FAQ 库里 300 条标准问法,哪一条和用户这句话匹配到可以直接返回答案?
- 抓回来的两篇新闻,是同一件事还是两件事,要不要合并?
这三问的共性是:不需要排序,只需要一个 0 到 1 的分数,然后卡一个阈值。 这正是 STS 的主场。站内 128 篇文章里,Embedding、向量数据库、RAG 相关教程已覆盖"怎么算出向量",但从向量到"可决策的分数"这一步始终缺一篇专讲——本文补上这个缺口。
二、三条技术路线:先想清楚你在为哪一档精度付费
| 路线 | 原理 | 典型延迟 | 精度 | 免费可得性 | 适用场景 |
|---|---|---|---|---|---|
| A. Bi-encoder(双塔) | 两段文本各编码为向量,算余弦相似度 | 单条 30~120ms,可批量 | 中(分数普遍虚高 5~15%) | 极好,几乎所有免费 embedding 都能用 | 大规模去重、召回、聚类 |
| B. Cross-encoder(交叉编码) | 两段文本拼一起送入模型,直接输出相关分 | 单对 80~300ms,无法批量预编码 | 高 | 一般,多数免费层按对计费且额度小 | 精排、判重终裁、阈值边界复核 |
| C. LLM 判分 | 让大模型直接输出相似分或"是否等价" | 0.5~3s | 高,但方差大 | 好(多家免费 LLM 可用),但 token 成本高 | 复杂语义、否定句、需要可解释理由 |
一个务实的组合是:A 做粗筛,B 做终裁,C 只做抽样复核。 单靠 A 卡阈值,是 90% 的"相似度看起来很高但其实不是一回事"事故的根因。想系统了解各向量模型的免费额度差异,可参考 2026 免费 Embedding 向量模型 API 全景对比。
三、2026 年 6 条真能零成本跑通的路线(额度实测)
| 方案 | 免费额度(2026-10-02 核对) | 关键模型 | 计费口径 | 备注 |
|---|---|---|---|---|
| Cloudflare Workers AI | 每日 10,000 Neurons,无信用卡、无时限 | @cf/baai/bge-large-en-v1.5、bge-m3 |
按 Neurons 折算,输入 token 数决定消耗 | 边缘节点,国内延迟可接受,批量最划算 |
| Jina AI Embeddings | 免费层 1,000 次/天 | jina-embeddings-v3(8192 上下文、89 语言) |
按请求次数 | 多语言效果好,长文档友好 |
| NVIDIA NIM | 免费层 1,000 次/天 | nvidia/nv-embed-v2(4096 维) |
按请求次数 | MTEB 长期前列,长上下文适合整篇文档 |
| Hugging Face Inference Providers | 免费用户 $0.10/月额度(官方定价页,可能调整) | 任意 Hub 上的 embedding / text-ranking 模型 | 按算力时间计费,超额度需购积分 | 2025 年 7 月起 hf-inference 主要聚焦 CPU 类任务(embedding、text-ranking、text-classification),恰好是 STS 的主力 |
| Google Gemini Embedding | 免费层按 RPM/TPM/RPD 限流,各模型额度以 AI Studio 控制台实时显示为准 | gemini-embedding-001 |
按 token | 多语言与跨语种相似度表现好 |
| 本地 sentence-transformers | 完全免费,无额度上限 | all-MiniLM-L6-v2、bge-m3、bge-reranker-v2-m3 |
只花自己的算力 | 唯一适合"每天百万对"级别的方案 |
补充说明两点,避免踩坑:其一,Hugging Face 的免费额度是金额制而非次数制,$0.10 在 CPU 类 embedding 上大约能撑住几千次短文本调用,但一旦路由到 GPU 类模型会瞬间耗尽,务必在计费页看消耗明细;其二,Gemini 免费层的各模型限额已不再统一公布在文档页,改为在 AI Studio 项目控制台按项目实时显示,所以任何"每天 N 次"的二手数字都要以你自己的控制台为准。
如果这些免费额度对你所在业务量级不够用,可以在 免费 API 专区 按分类横向比较各家方案,或者通过 注册 OneAPI 统一调用平台 把多家免费端点聚合成一个 OpenAI 兼容入口,用同一个 Key 做统一路由与配额管理。
四、核心教学:从零算出一个"可信"的相似度分数
下面这段 Python 是本文唯一保留的完整示例,因为它就是核心教学内容本身:批量向量化、归一化、余弦矩阵、阈值过滤四步一次做完。任何支持 OpenAI 兼容 /v1/embeddings 的免费端点(Cloudflare AI Gateway、Jina、NIM、本地部署)都能直接替换 base_url 使用。
import numpy as np, requests
BASE = "https://api.jina.ai/v1" # 换成任意 OpenAI 兼容 embedding 端点
HEAD = {"Authorization": "Bearer YOUR_KEY"}
def embed(texts):
r = requests.post(f"{BASE}/embeddings", headers=HEAD,
json={"model": "jina-embeddings-v3", "input": texts}, timeout=30)
r.raise_for_status()
return np.array([d["embedding"] for d in r.json()["data"]], dtype=np.float32)
def l2norm(x):
return x / (np.linalg.norm(x, axis=1, keepdims=True) + 1e-9)
def similarity(pairs, threshold=0.85):
left = l2norm(embed([p[0] for p in pairs]))
right = l2norm(embed([p[1] for p in pairs]))
sims = np.sum(left * right, axis=1) # 归一化后点积 == 余弦相似度
return [(a, b, float(s), s >= threshold) for (a, b), s in zip(pairs, sims)]
pairs = [
("账号无法登录,提示密码错误", "登录时显示密码不正确"),
("如何修改绑定手机号", "换绑手机怎么操作"),
("发票开错了能重开吗", "物流一直没更新"),
]
for a, b, s, ok in similarity(pairs):
print(f"{s:.3f} {'相似' if ok else '不相似'} {a[:14]} <-> {b[:14]}")
三个容易被忽略的细节:第一,必须做 L2 归一化,否则点积不等于余弦相似度,不同长度文本之间分数不可比;第二,左右两侧各自编码但同批请求,把两段文本拼在一次 input 里能让向量分布更一致;第三,分数要保留原始值,不要一上来就四舍五入到两位小数,阈值边界复核时需要小数点后三位。
五、阈值怎么定:不要抄网上的 0.8,要用自己的数据标
任何"通用最佳阈值"的说法都是错的——阈值取决于模型、文本长度、领域,甚至中英文混排比例。正确做法是自建一个 60~120 对的小评测集,用下面的方法标定:
| 步骤 | 具体做法 | 产出 |
|---|---|---|
| 1. 采样 | 从真实业务里抽 200 条文本,两两组合后按分数排序,取高分/中分/低分各 40 对 | 120 对候选 |
| 2. 标注 | 人工只回答"是不是同一件事/同一个问题",二分类,不打分 | 正负标签 |
| 3. 扫描 | 阈值从 0.60 到 0.98,步长 0.01,逐档算精确率与召回率 | PR 曲线 |
| 4. 选点 | 去重场景优先精确率(≥0.95),FAQ 匹配优先召回率(≥0.90),按业务取舍 | 一个数字 |
| 5. 回归 | 每周补 20 条新样本重跑,阈值漂移超过 0.03 就重新标定 | 版本化阈值 |
站内对各家向量服务的免费额度做过横向实测,具体可用模型与限速可在 免费 API 专区 的分类目录中查到。经验区间仅供参考:bge-large-en-v1.5 类模型在中文客服短文本上,精确率 0.95 通常落在 0.880.93;跨语种(中英互比)同一精确率下阈值要下调 0.040.07。换模型必重标,这是最容易被跳过、也最容易出事故的一步。
六、7 个让分数失真的坑(每个都有真实事故模式)
- 长度不对称:一句 8 字的问法 vs 一段 300 字的文档,余弦分会系统性偏低。解法是长文本先做切片,取各片最大相似度而非整篇编码。
- 否定句陷阱:"支持退款" 与 "不支持退款" 的相似度常高达 0.95 以上。bi-encoder 对否定词几乎不敏感,这类判定必须走 cross-encoder 或 LLM 复核。
- 数字与专名:"订单 88213" 与 "订单 88214" 看起来极像但语义完全不同。解法是对数字、ID、型号做正则抽取并单独比较,不一致直接判否。
- 分数虚高基线:不同模型分数基线不同,同一模型换个版本基线也会漂移。解法是加一个"锚点对"集合(已知不相似的 10 对)每次跑一遍,观察基线漂移。
- 批量截断:一次传 200 条文本时部分免费端点会静默截断,返回向量数少于请求数。解法是断言
len(vectors) == len(texts),不等则分片重试。 - 缓存穿透:同一句话因空格、全半角、换行差异被当成不同文本重复计费。解法是入库前统一做 normalize(去多余空白、全角转半角、繁转简)。
- 中英混排 tokenizer 偏差:中英夹杂句子的向量常被英文 token 主导。解法是混排比例超过 30% 的样本单独走一路阈值。
七、工程化:批量、缓存、退避与多模型回退
生产环境跑 STS,真正的成本不在算法而在额度管理。核心做法有四条:
更多免费服务的接入细节汇总在 免费 API 专区。1. 本地向量缓存:以 normalize 后的文本哈希为 Key 存向量,命中率通常能到 60%85%,直接按比例削减 API 消耗。
2. 批量合并请求:把 50200 对文本合并成一次 embedding 调用,请求次数从 N 降到 N/批大小,这对"按次数计费"的免费层(1,000 次/天那类)是决定性的。
3. 429 指数退避 + 抖动:命中限流时按 1s/2s/4s 退避并加随机抖动,避免同批任务同步重试造成二次限流。
4. 多模型回退链:主模型额度耗尽自动切备用(如 Jina → Cloudflare → 本地 bge-m3),并把"模型名"写入结果元数据,方便后续发现阈值漂移。
这套配额管控的具体实现,我在 免费 API 成本与配额管控实战 里写过完整的退避与预算方案,这里不重复。若你希望用一层统一网关管住多家免费端点的配额与路由,可以查看 免费向量数据库 API 实力榜单 配套的检索侧方案,或者直接读 用免费 Embedding API 构建 RAG 知识库 把相似度计算接进完整检索链路。
八、什么时候该放弃 STS 改用别的办法
不是所有问题都适合卡相似度阈值。以下三种情况建议换路线:
| 情况 | 为什么 STS 不合适 | 替代方案 |
|---|---|---|
| 需要判断"逻辑是否等价"(如两段代码、两条合同条款) | 相似度只看语义接近,不看逻辑约束 | 结构化解析 + 规则比对,或 LLM 判分带理由 |
| 需要精确匹配(型号、SKU、法币金额) | 向量会把 0.5 和 5.0 判得很近 | 抽取 + 精确匹配,向量只做候选召回 |
| 需要跨模态(图文是否一致) | 本文所有方案都是纯文本模型 | 走 CLIP 类多模态 embedding,另行评测 |
若你只是想快速验证"我的场景到底需不需要 STS",可以先在 免费 API 专区 挑一个免费 embedding 端点,用第五节的 120 对评测集跑一遍标定,再决定是否投入工程化。
九、动手清单
- 选路线:日调用量 1 万对以内用免费 API(Cloudflare 或 Jina 起步最顺),以上直接本地部署 bge-m3。
- 建评测集:120 对,人工二分类,这是唯一不可省略的一步。
- 跑标定:按第五节流程产出你自己的阈值,别抄 0.8。
- 加缓存与批量:先把请求次数压到最低,再谈额度够不够。
- 埋监控:记录每日基线锚点对分数、阈值命中分布、各模型消耗占比。
- 定复核策略:阈值上下 0.03 的边界样本,走 cross-encoder 或 LLM 二次判定。
按这六步做完,你就有了一条零成本、可解释、可回归的文本相似度流水线。全部六条路线的实时可用状态与额度变动,会持续更新在 免费 API 专区。更多免费能力的接入方式与额度对比,都可以到 免费 API 专区 按分类检索;想把这些端点收敛成一个 Key、一套配额、一份账单,注册 OneAPI 统一调用平台 后即可用 OpenAI 兼容协议直接切换上游。