基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-10-25 17:30

向量相似度测试如何摆脱网络和密钥

dev.to · AI#向量嵌入#自动化测试#持续集成
Editor brief · 编辑速览

文章用 TypeScript 实现文本向量相似度评分,并为测试提供确定性的本地替代方案。生产环境调用真实嵌入服务,测试无需网络或 API 密钥,便于在缺少 secrets 的分支 CI 中运行。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

给文本相似度检查接上 embedding,测试就可能多出网络请求、API key 和不稳定因素。尤其是外部贡献者提交的 fork,CI 拿不到密钥,测试可能连执行的机会都没有。

作者在 evalgate 这个 CI 评估工具中采用了一种拆分:生产环境用真实 embedding,离线测试用确定性的本地向量,共用后面的打分和阈值判断。这套 TypeScript 实现值得借鉴,但要先分清:它能验证评分流程,不能替你证明语义判断准确。

把可替换的部分收窄到 embed 函数

两段文本分别变成向量,再用余弦相似度比较方向,是整套方案的基础。余弦相似度忽略向量长度,返回值在 -1 到 1 之间,1 代表方向相同。

作者的实现加了两个保护:只遍历两个向量中较短的长度,避免长度不一致时访问越界;分母为零时返回 0,避免零向量产生 NaN。

真正影响可测试性的设计,是评分器不直接依赖 OpenAI 或某个 SDK。它只要求 provider 能提供 embed():有就调用,没有就使用本地实现。

下面这段代码把向量来源和评分逻辑接在一起:

const embed = ctx.provider.embed
  ? (t: string) => ctx.provider.embed!(t)
  : (t: string) => Promise.resolve(localEmbedding(t));

const [outVec, expVec] = await Promise.all([embed(output), embed(expected)]);
const cos = cosineSimilarity(outVec, expVec);
// 将 [-1, 1] 映射到 [0, 1],让阈值更直观。
const score = (cos + 1) / 2;
const passed = score >= threshold;

要看清的是 const score = (cos + 1) / 2。这里的分数经过了线性转换,threshold = 0.8 实际对应原始余弦相似度 0.6,并不表示「有 80% 的概率语义相同」。

Promise.all 并发生成两个向量。之后的余弦计算、分数转换和通过判断,都不再关心向量来自哪个服务。这让离线测试可以覆盖真实的评分逻辑,而不用把整个评分器替换成一个固定结果。

离线向量为什么稳定,又为什么不懂语义

本地实现使用的是哈希词袋:把单词映射到固定数量的桶,统计每个桶出现了多少次,再做 L2 归一化。可以把它理解为一张固定长度的词频表,只是多个词可能挤进同一个位置。

作者选择 FNV-1a 作为稳定哈希函数,初始值为 0x811c9dc5,每轮异或字符编码后,用 Math.imul 乘以 0x01000193,保持 32 位整数运算,最后通过 hash >>> 0 转成无符号整数。

下面这段代码完成分词、计数和归一化,依赖前述 fnv1a():

export function localEmbedding(text: string, dims = 64): number[] {
  const vec = new Array<number>(dims).fill(0);
  const words = text
    .toLowerCase()
    .replace(/[^a-z0-9\s]/g, " ")
    .split(/\s+/)
    .filter(Boolean);
  for (const word of words) {
    const idx = fnv1a(word) % dims;
    vec[idx] = (vec[idx] ?? 0) + 1;
  }
  const norm = Math.sqrt(vec.reduce((s, v) => s + v * v, 0)) || 1;
  return vec.map((v) => v / norm);
}

要特别检查 .replace(/[^a-z0-9\s]/g, " "):它只保留英文字母、数字和空白,中文会被清除。默认的 dims = 64 则决定向量有 64 个桶。

在实现和参数固定时,同一输入总会得到同一向量,不需要网络、密钥或随机数。这足以测试评分器能否运行、阈值是否生效,以及一些预先设计好的输入是否通过。

但它没有理解文本。作者用猫的例句说明:The cat sat on the mat 和 A feline rested on the rug 意思接近,表面词汇却几乎不重合,本地实现会给出较低相似度。

这套替代实现验证的是评分流程,不是语义质量。

我的判断

我赞成把外部依赖收敛到 embed() 这个接口。相比直接 mock 整个评分器,它保留了更多真实逻辑;相比每次测试都调用真实服务,它更适合需要离线执行、拿不到密钥的 CI。

不过,原文没有提供测试耗时、误判率或与其他方案的对照数据。「测试更可信」是有工程逻辑支撑的经验判断,不能当成已经量化验证的结论。

还有几个边界需要补齐。首先,作者说无共同词的文本得分接近零,放到最终 score 上并不成立:即使原始余弦为 0,映射后也是 0.5。哈希碰撞还可能让不同词产生重叠。因此,「无关文本一定失败」取决于具体输入和阈值。

其次,纯中文输入会变成零向量。按原文的零分母处理,最终得分仍是 0.5;两个相同的空字符串也一样。不能把「相同字符串通过」当成无条件保证。

最后,截取较短向量虽然避免了异常,也可能掩盖 provider 返回维度错误。生产中缺少 embed() 时自动降级,同样需要谨慎,因为作者自己明确反对用哈希向量承担生产语义判断。原文没有展示如何强制这个边界。

离线用例通过,只能说明这部分逻辑通过了验证。 真实服务的鉴权、返回格式和语义效果,仍要单独检查。

能拿来做什么

  • 给 Node 或 TypeScript 项目的相似度评分器抽出 embed(text) 接口,让余弦计算和阈值判断可以脱离服务 SDK 独立执行。
  • 在离线测试中明确覆盖词汇重复、无关英文、空字符串和纯中文输入,按映射后的分数设计断言,避免误把 0.5 当成零相似度。
  • 为真实 provider 保留单独的集成检查,验证向量维度、调用行为和代表性语义样例;这些问题无法由哈希词袋代替验证。
  • 如果采用自动回退,在生产配置中明确要求真实 embed() 可用,避免配置缺失后继续输出看似正常的语义评分。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
AI 写九成代码,人工审查如何分级
下一篇
让 Agent 在浏览器中交付可用文件

资讯速览

dev.to · AI · 2026-10-25 17:30

AI 评分 8.0 / 10

相关标签

#向量嵌入#自动化测试#持续集成

继续阅读

  • LINE Agent 从原型走向生产的工程实践
  • AI 写九成代码,人工审查如何分级
  • 让 Agent 在浏览器中交付可用文件
  • Agent 将重启授权扩大为删容器操作
  • 语音 Agent 的数字字段该如何验收