LanceDB向量数据库在Node.js中的实践:构建轻量级RAG系统
大多数RAG教程都在教你怎么调API,却很少讨论向量到底存在哪。ChromaDB要起服务、Pinecone要联网、Milvus太重——对于本地部署的GIS平台来说,这些都不是好选择。我们在GeoAI-UP项目中选择了LanceDB,一个基于Apache Arrow列式存储的嵌入式向量数据库。不需要单独的服务进程,数据直接持久化到本地文件系统,查询性能却能跟上内存数据库。这篇文章分享我们如何在TypeScript环境中落地这套方案,以及踩过的坑。为什么选LanceDB先看实际场景:GeoAI-UP是一个地理空间AI平台,用户会上传PDF政策文档、Word技术规范、Markdown报告,然后通过自然语言提问。比如"朝阳区的环境保护政策有哪些?"系统需要从已上传的文档中检索相关内容,再交给LLM生成答案。技术选型时我们有几个硬约束:必须本地运行:政府客户的数据不能出内网零运维成本:不能要求客户额外部署数据库服务支持元数据过滤:需要按文档类型、上传时间等字段筛选TypeScript原生支持:后端是Express + TypeScript技术栈对比了几种方案:方案是否需要服务本地存储TS支持元数据过滤ChromaDB需要(Python服务)✅一般✅Pinecone云端❌✅✅Milvus需要(Docker)✅一般✅LanceDB不需要✅优秀✅LanceDB的核心优势在于它是嵌入式的——就像SQLite一样,import之后就能用,数据存在本地.lancedb目录下。这对我们的打包部署非常友好:用户下载一个exe安装包,解压即用,不需要配置任何数据库连接。初始化与表结构管理LanceDB的初始化逻辑封装在LanceDBAdapter类中。关键代码:importlancedbfrom'@lancedb/lancedb';importtype{Table,Connection}from'@lancedb/lancedb';exportclassLanceDBAdapter{privateconnection:Connection|null=null;privatetable:Table|null=null;privateinitialized=false;privatedbPath:string;constructor(dbPath:string){this.dbPath=dbPath;}asyncinitialize():Promisevoid{if(this.initialized)return;console.log(`[LanceDBAdapter] Initializing at:${this.dbPath}`);// 确保目录存在if(!fs.existsSync(this.dbPath)){fs.mkdirSync(this.dbPath,{recursive:true});}// 连接到本地LanceDB(文件模式)this.connection=awaitlancedb.connect(this.dbPath);// 检查表是否存在consttableNames=awaitthis.connection.tableNames();if(tableNames.includes(KB_CONFIG.COLLECTION_NAME)){// 打开已有表并检查schemaconstexistingTable=awaitthis.connection.openTable(KB_CONFIG.COLLECTION_NAME);// 验证schema是否包含新字段try{consttestQuery=awaitexistingTable.query().limit(1).toArray();if(testQuery.length0){constfirstRow=testQuery[0]asany;if(!('totalChunks'infirstRow)||!('pageNumber'infirstRow)){console.log('[LanceDBAdapter] Detected old schema, will recreate table');awaitthis.connection.dropTable(KB_CONFIG.COLLECTION_NAME);needsRecreation=true;}else{this.table=existingTable;}}}catch(schemaError){needsRecreation=true;awaitthis.connection.dropTable(KB_CONFIG.COLLECTION_NAME);}}if(needsRecreation||!tableNames.includes(KB_CONFIG.COLLECTION_NAME)){// 创建新表,定义完整schemaconstemptyData=[{id:'',text:'',embedding:newArray(KB_CONFIG.EMBEDDING_DIMENSIONS