NeuralSearch(神经搜索)是一种基于深度学习的语义检索方式,它不再依赖关键词的字面重合,而是把查询语句和候选文档都转换成高维向量,通过向量之间的距离衡量语义相近程度。在Node.js环境下,我们可以借助已有的JavaScript生态快速搭建一套轻量服务,用于内部文档、客服知识库或商品描述的智能检索。

一、NeuralSearch的核心原理
传统搜索系统通常使用倒排索引,把文档切词后记录词项出现位置,查询时做词面匹配。这种方式遇到同义词、上下位词就无能为力。神经搜索使用预训练语言模型(如sentence-transformers中的模型)将一句话编码为一个固定长度向量,语义相近的句子在向量空间中距离更近。
假设我们把“苹果手机怎么截屏”和“iPhone如何截图”分别向量化,虽然字面上没有重叠词,但模型给出的两个向量余弦相似度会很高。检索时,系统对所有文档向量与查询向量算相似度,取Top-K返回。这就是NeuralSearch区别于关键词搜索的根本逻辑。
1.1 向量与相似度计算
向量通常表示成长度为384或768的浮点数组。最常用的是余弦相似度,公式为两个向量点积除以各自模长乘积,结果越接近1越相似。在Node.js里可以手写一个函数实现,不需要引入重依赖。
function cosineSimilarity(a, b) {
let dot = 0, normA = 0, normB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
上面的代码对两个同维度数组做遍历,时间复杂度是线性的,对几百条文档的离线库完全够用。如果文档量到百万级,就要用专门的向量数据库或近似最近邻库,但原型阶段不需要过早优化。
二、Node.js服务基础结构
我们用Express搭建HTTP服务,用子进程或HTTP方式调用Python的sentence-transformers推理服务,因为目前JS端成熟的中文句向量模型较少。Node负责业务编排、向量存储和查询接口。
2.1 项目初始化与依赖
新建目录后执行npm初始化,安装express即可。向量暂存在内存数组,后续可换Redis或SQLite。注意Node版本需在18以上,方便使用fetch原生API。
npm init -y npm install express
目录中准备一个documents.json存放原始文本,例如产品问答。服务启动后先加载文本,调用推理接口拿到向量,再提供/search查询。这种分离让Node不需直接跑模型,降低部署难度。
2.2 调用向量生成接口
假设本地Python服务暴露了http://127.0.0.1:5000/embed接口,接收JSON文本列表返回向量。Node侧用fetch调用,注意错误处理。
async function getEmbeddings(texts) {
const resp = await fetch('http://127.0.0.1:5000/embed', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ texts: texts })
});
if (!resp.ok) {
throw new Error('embedding request failed');
}
const data = await resp.json();
return data.vectors;
}
该函数把一批文本发出去,拿到二维数组。生产环境要加超时与重试,避免模型服务阻塞拖垮Node。我们也可以把批大小控制在32以内,减轻推理端压力。
三、文档入库与检索实现
系统启动阶段把文档转向量并缓存,查询阶段实时算查询向量再遍历比较。下面给出完整可运行的服务骨架。
3.1 启动加载代码
读取文件、请求向量、存为内存结构。每项包含原文、向量,方便返回结果时带出文本。
const express = require('express');
const fs = require('fs');
const app = express();
app.use(express.json());
let docs = [];
async function init() {
const raw = JSON.parse(fs.readFileSync('./documents.json', 'utf-8'));
const texts = raw.map(item => item.content);
const vectors = await getEmbeddings(texts);
docs = raw.map((item, i) => ({
id: item.id,
content: item.content,
vector: vectors[i]
}));
console.log('loaded', docs.length, 'docs');
}
function cosineSimilarity(a, b) {
let dot = 0, normA = 0, normB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
这段代码在init里完成数据预热。如果documents.json有上千条,启动会慢几秒,可接受。若担心阻塞,可改成懒加载或定时刷新。
3.2 查询接口
接收用户输入,取向量后遍历docs算分,按分数倒序取前5条。用Express的路由处理。
app.post('/search', async (req, res) => {
try {
const query = req.body.query;
if (!query) {
return res.status(400).json({ error: 'query required' });
}
const qVec = (await getEmbeddings([query]))[0];
const scored = docs.map(d => ({
id: d.id,
content: d.content,
score: cosineSimilarity(qVec, d.vector)
}));
scored.sort((a, b) => b.score - a.score);
res.json(scored.slice(0, 5));
} catch (e) {
res.status(500).json({ error: e.message });
}
});
init().then(() => {
app.listen(3000, () => console.log('server on 3000'));
});
接口返回带分数的结果,前端可直接展示。这种实现没有复杂依赖,适合demo或内部工具。如果要并发高,可以把docs向量放进TypedArray提升计算速度。
四、方案对比与落地建议
与Elasticsearch关键词搜索比,NeuralSearch在语义漂移上表现更好,但首次推理有网络开销。我们可以在Node端加一层短缓存,相同问题不重复请求模型。
4.1 优缺点分析
优点很明显:用户用自然语言问,系统真能“懂”。比如搜“电脑蓝屏怎么办”能召回“系统崩溃处理指南”。缺点是要维护一个模型服务,且向量维度高导致内存占用不小。
| 维度 | 关键词搜索 | NeuralSearch |
|---|---|---|
| 语义理解 | 弱 | 强 |
| 部署成本 | 低 | 中 |
| 召回速度 | 极快 | 较快 |
上表概括了核心差异。实际项目常做混合检索:先用向量捞语义候选,再用关键词精排,兼顾准确与召回。
4.2 后续演进
当文档量增长,可把向量存到专用库如Milvus或pgvector,让数据库做近邻搜索。Node只拼查询语句与结果包装,整体架构更稳。另外可以微调模型适配业务语料,进一步提升垂直领域效果。
总体看,Node.js实现NeuralSearch并不复杂,关键是理清向量生成、存储、相似度计算三条线。按本文骨架改造,你就能拥有会语义理解的搜索接口。
Node.jsNeuralSearch向量检索修改时间:2026-08-11 09:33:42