总体流程代码(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.步骤总结

  1. 使用Langchain解析文档,得到切块的文档数据和未切块的数据
  2. 将未切块的完整文档数据存入mongodb数据库中,得到文档id
  3. 使用阿里Embedding模型将切块的数据向量化
  4. 将向量化的数据存入数据库中
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐