NestJs将上传的文本转换为向量数据并存储到向量数据库
本文介绍了文件管理的总体流程代码实现,主要包括文档上传、处理和向量化存储三个核心步骤。首先通过控制器接收上传文件,使用Langchain库读取和拆分文档内容;然后将原始文本存入数据库并获取ID;接着利用阿里云Embedding模型将文档标题和内容分批次向量化;最后将向量数据存入Milvus向量数据库,按用户ID创建独立集合进行存储。整个过程实现了文档从上传到向量化存储的完整流程,为后续的语义检索和
总体流程代码(controller)
@Controller('filemanagement')
export class FilemanagementController {
constructor(
@InjectModel(Filemanagement.name)
private readonly FilemanagementModel: Model<Filemanagement>,
private readonly filemanagementService: FilemanagementService
) { }
//上传文件
@Post('uploadkb')
@UseGuards(AuthGuard)
@UseInterceptors(uploadFileInterceptor)
async uploadFile(
@Req() req: { user: { token: string } },
@UploadedFiles() files: { file: Express.Multer.File[] }) {
const userId = req.user.token
const documentIds: Types.ObjectId[] = []
//处理上传到文档(for遍历处理多文件)
for (const file of files.file) {
//1.读取文档
const {rawText, splitDocument} = await this.filemanagementService.readFile(file, 'UB')
//2.上传数据库
const docID = await this.filemanagementService.uploadFile(file, userId, rawText, 'UB')
//3.向量,存储向量数据库
const originalname = await this.filemanagementService.vectorStorage(file, splitDocument, userId, docID)
console.log(originalname)
documentIds.push(docID)
}
return {
result: documentIds
}
}
}
1.读取文档,使用Langchain库的File Loaders 来读取docx或pdf文档
先安装依赖
npm install @langchain/community @langchain/core mammoth #docx
npm i @langchain/community @langchain/core pdf-parse #odf
1.2使用loader解析文档
const nike10kPdfPath = "../../../../data/nke-10k-2023.pdf";
const loader = new PDFLoader(nike10kPdfPath);
const docs = await loader.load();
得到的docs是数组,代表文档每一页的数据,将每一页的数据合并成一个完整的字符串
const first = docs[0]
for (const doc of docs) {
first.pageContent += '\n' + doc.pageContent
}
1.3使用textsplitters来拆分上述得到的一整块文档,按预设拆分成小块
let docOutput: Document<Record<string, any>>[] = []
//按照字符拆分
if (uploadFile === 'UB') {
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 800, //每段最大字符数
chunkOverlap: 100,//每段之间的重叠字符数
});
docOutput = await splitter.splitDocuments([first]);
}
最后docOutput得到拆分成小块后的文档
2.将未拆分的文本first存入数据库得到文本id
const docID = await this.filemanagementService.uploadFile(file, userId, rawText, 'UB')
3.使用阿里云Embedding向量模型进行数据向量化
3.1先安装openai 的sdk
npm install --save openai
3.2创建openai实例
export class FilemanagementService {
private client: MilvusClient
private openai: OpenAI
constructor(
//注入模型
@InjectModel(Filemanagement.name)
private filemanagementModel: Model<Filemanagement>,
private configService: ConfigService,
) {
//向量数据库连接
this.client = new MilvusClient({
address: this.configService.get('MILVUS_ADDRESS') as string
});
//通义千问
this.openai = new OpenAI({
apiKey: this.configService.get('QWEN_API_KEY') as string,
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1'
})
}
//......
}
3.3切块文本数组转换成切块向量数组生成器
// 使用阿里云向量模型将切块文档数组转换成向量数组
async QwenEmbedding(splitDocument: Document<Record<string, any>>[] | [{ pageContent: string }]) {
const completion = await this.openai.embeddings.create({
model: 'text-embedding-v2',
input: splitDocument.map(item => item.pageContent),
dimensions: 1536
})
return completion.data
}
4.将上述得到的拆分后的docOutput和docID进行向量转换
4.1判断向量数据库是否有以userId为集合名的集合,若无则创
//集合名称
const collectionName = `_${userId}`
//1.判断集合是否创建
const queryCollection = await this.client.hasCollection({ collection_name: collectionName })
if (!queryCollection.value) {
//创建集合,每个用户一个集合,集合名词+用户唯一标识
await this.createCollection(collectionName)
}
4.2将文档标题进行向量化
//2.向量文档的标题
const vectorsDocTitle = await this.QwenEmbedding([{ pageContent: file.originalname }])
4.3将切块的文档向量化,但是
作为字符串列表时最多支持 25 条
所以切分出的文本块数组splitDocument不能一次性全丢进去,需要进一步切分,以25条为一份切块并传入QwenEmbedding函数
//分批处理,千问embedding一次最多25行
const batchSize = 25
for (let i = 0; i < splitDocument.length; i += batchSize) {
const batchDocument = splitDocument.slice(i, i + batchSize)
const vectorsDocText = await this.QwenEmbedding(batchDocument)
await this.insertData(collectionName, file.originalname, docID, batchDocument, vectorsDocTitle, vectorsDocText)
}
最终得到向量化的数组vectorsDocText,其最长不超过25条
5.将该向量数组存入向量数据库
存储格式
var res = await client.insert({
collection_name: "quick_setup",
data: data,//data为数组,存放多条数据
})
代码实例
将所需要的数据组装后一条条加入group中
//插入向量数据库
async insertData(
collectionName: string,
originalname: string,
docId: Types.ObjectId,
splitDocument: Document<Record<string, any>>[],
vectorsDocTitle: OpenAI.Embeddings.Embedding[],
vectorsDocText: OpenAI.Embeddings.Embedding[]
) {
const group = splitDocument.map((item, index) => ({
docId: docId.toString(),
docTitle: originalname,
docText: item.pageContent,
embedDocTitle: vectorsDocTitle[0].embedding,
embedDocText: vectorsDocText[index].embedding
}))
try {
const res = await this.client.insert({
collection_name: collectionName,
data: group
})
if (res.status.error_code === 'Success') {
return '插入数据成功'
} else {
throw new BadRequestException(`插入向量数据库失败:${res}`)
}
} catch (error) {
throw new BadRequestException(`插入向量数据库失败:${error}`)
}
}
完成
6.步骤总结
- 使用Langchain解析文档,得到切块的文档数据和未切块的数据
- 将未切块的完整文档数据存入mongodb数据库中,得到文档id
- 使用阿里Embedding模型将切块的数据向量化
- 将向量化的数据存入数据库中
更多推荐
所有评论(0)