本文介绍了如何通过魔笔权限组实现细粒度的控制百炼 RAG 应用的知识库检索范围。
方案概览
要实现基于魔笔的细粒度知识库检索,可以分为6步:
获取用户权限组列表:在应用中通过
{{mobi.currentUser.groups}}
获取当前登录用户的权限组列表。查询数据库权限组对应的知识库 ID:可以使用内置数据库或者您自己的数据库存储权限组 ID 和知识库 ID 的映射关系,通过这张映射表可以快速将第一步获取的权限组列表映射为知识库 ID 列表。
创建知识库:如果第二步中的表中没有当前权限组的知识库 ID,说明该权限组还未建立知识库,需要通过调用百炼的创建知识索引的接口创建一个新的知识库。
存储新的映射关系:将第三步中接口返回的新知识库 ID 与权限组的映射关系持久化存储到表中,方便下一次的检索。
限定 RAG 应用的检索范围:在百炼应用的集成操作中,将第二步获取的知识库 ID 列表(如果是新创建的知识库,则为第三步返回的知识库 ID)作为知识库检索范围的知识库 ID 列表参数。即可实现指定知识库范围进行检索。
限定知识库上传文档:在知识库管理页面,根据第二步获取的知识库 ID 列表(如果是新创建的知识库,则为第三步返回的知识库 ID)列出该用户可见的知识库列表和文档,用户上传文档的时候选择的知识库 ID 只能从这个列表中进行选择上传。
前期准备
搭建百炼应用,参考百炼文档知识检索增强(RAG),为了使用百炼应用的知识库检索范围参数,不能配置该应用的知识库。
发布百炼应用后,获得 API-KEY、应用 ID。
创建一个非结构化类目,用于存储所有用户的文档,记录该类目的 ID。
步骤一:获取用户权限组列表
新建全局变量
currentUserGroups
。通过
{{mobi.currentUser.groups}}
获取用户的权限组列表,可以按需过滤指定的权限组。将过滤后的权限组名称列表设置到变量
currentUserGroups
中。// 获取用户权限组名称 const currentGroups = mobi.currentUser.groups.map(obj=>obj.name).filter( name => name !== "DEFAULT_END_USER" && name !== "END_USER" ); currentUserGroups.setValue(currentGroups)
{{mobi.currentUser.groups}}
只有在发布的应用中才能获取登录用户的权限组,在应用的设计器中无法获取设计时用户的权限组。
步骤二:查询数据库权限组对应的知识库 ID
建立一张名为
group_index_mapping
的表记录权限组和知识库的映射关系。CREATE TABLE group_index_mapping ( id INT AUTO_INCREMENT PRIMARY KEY, group_id VARCHAR(32) NOT NULL UNIQUE, index_id VARCHAR(32) NOT NULL UNIQUE, INDEX idx_group_id (group_id) );
创建集成操作根据
group_id
查询index_id
。
步骤三:创建知识库
创建 HTTP 集成操作或者阿里云 OpenAPI 集成操作调用阿里云百炼创建知识索引接口。阿里云百炼创建知识索引接口可以参考CreateIndex - 创建索引。
调用创建知识库的集成操作
globalCreatedIdx
。使用权限组名称作为新知识库的名称。globalCreateIdx.trigger({ "Name": groupId, "StructureType": "unstructured", "SinkType": "DEFAULT", "EmbeddingModelName": "text-embedding-v2", "RerankModelName": "gte-rerank-hybrid", "RerankMinScore": "0.2", "SourceType": "DATA_CENTER_CATEGORY", "CategoryIds": [""] })
步骤四:存储新的映射关系
创建集成操作将步骤三中返回的新知识库 ID 与权限组名称的映射关系插入到映射表中。
汇总步骤一到步骤四的前端函数。
const idxObjs = []; // 存储当前用户的知识库对象数组 const idxIds = []; // 存储当前用户的知识库 id 数组 let selectedKey = 0; const currentGroups = currentUserGroups.value; //1.获取当前用户的权限组 for (let i = 0; i < currentGroups.length; i++) { selectedKey = i + 1; const groupId = currentGroups[i]; console.log(groupId); const indexId = await SelectIndexId.trigger({ "groupId": groupId }); // 2.查询权限组对应的知识库 id if (indexId.data.length === 0) { // 3.如果不存在该权限组对应的知识库 id,则调用百炼接口创建知识库 const currentIdx = await globalCreateIdx.trigger({ "Name": groupId, "StructureType": "unstructured", "SinkType": "DEFAULT", "EmbeddingModelName": "text-embedding-v2", "RerankModelName": "gte-rerank-hybrid", "RerankMinScore": "0.2", "SourceType": "DATA_CENTER_CATEGORY", "CategoryIds": [""] }); // 接口返回的新知识库 id const idxId = currentIdx.data.body.Data.Id; // 4.持久化存储新的映射关系 await InsertIndexMapping.trigger({ "groupId": groupId, "indexId": idxId, }); idxObjs.push({ "name": groupId, "id": idxId, "selectedKey": String(selectedKey) }); idxIds.push(idxId); } else { // 如果存在,则直接获取该知识库 id const idxId = indexId.data[0].index_id; idxObjs.push({ "name": groupId, "id": idxId, "selectedKey": String(selectedKey) }); idxIds.push(idxId); } } // 存储到全局变量 currentIdxIds,作为后续知识库检索范围 currentIdxIds.setValue(idxIds); // 返回对象数组,用于设置知识库管理页面的展示 return idxObjs;
步骤五:限定 RAG 应用的检索范围
新增全局变量
currentIDxIDs
。将步骤二或步骤三中获取到的该用户可见的知识库 ID 数组存储到全局变量中
currentIDxIDs
。在对话页使用的百炼应用集成操作中,使用该变量设置知识库检索范围的知识库 ID 列表参数。
步骤六:限定知识库上传文档
阿里云百炼的类目创建存在上限(1000个),详情请参考数据导入操作说明。因此对于大规模的用户类目隔离存在一定的限制,因此推荐使用单类目作为知识库的转存类目,并删除类目管理页面,屏蔽底层的类目管理细节。并且在知识库管理页面新增上传文档到指定知识库的逻辑:
新增上传按钮 uploadButton。文件上传至魔笔的系统文件存储,上传成功后执行前端函数。
新增 ApplyFileUploadLease 集成操作,实现申请文件上传租约接口,该接口可以参考ApplyFileUploadLease - 申请文档上传租约,配置如下:
新增 UploadRemoteFile 集成操作,集成资源选择 file/系统内置集成/系统内置文件集成,该资源为系统内置资源,不需要创建。集成操作的配置如下:
新增 UploadFile 集成操作,实现申请文件上传到预置类目接口,该接口可以参考AddFile - 添加文档,配置如下:
新增 SubmitIndexAddDocumentsJob 集成操作,实现将文档上传到指定知识库的接口,该接口可以参考SubmitIndexAddDocumentsJob - 提交索引追加任务,配置如下:
创建前端函数 uploadBailianFile 绑定到 uploadButton 的上传系统成功事件,用来执行流程中的步骤2-6。
// 1.点击上传按钮 uploadButton 触发前端函数 // 2.计算MD5 const base64String = uploadButton.values[0].base64Data; const wordArray = CryptoJS.enc.Base64.parse(base64String); const md5Hash = CryptoJS.MD5(wordArray); const md5Hex = md5Hash.toString(CryptoJS.enc.Hex); // 3.调用百炼接口申请文档上传租约,获取租约文档 ID const response = await ApplyFileUploadLease.trigger({ "Md5": md5Hex, "FileName": uploadButton.values[0].name, "SizeInBytes": uploadButton.values[0].size }); const fileUploadLeaseId = response.data.Data.FileUploadLeaseId; // 租约参数 const url = response.data.Data.Param.Url; const contentType = response.data.Data.Param.Headers["Content-Type"]; const bailianExtra = response.data.Data.Param.Headers["X-bailian-extra"]; // 4.上传至百炼服务器临时存储 const responseRemote = await UploadRemoteFile.trigger({ "url": url, "contentType": contentType, "bailianExtra": bailianExtra, "fileId": uploadButton.values[0].fileId }); // 5.调用百炼接口将文档上传到预置类目 const respnseUpload = await UploadFile.trigger({ "fileUploadLeaseId": fileUploadLeaseId }); const fileId = respnseUpload.data.body.Data.FileId; // 6.上传文档到指定知识库 const submitResp = await SubmitIndexAddDocumentsJob.trigger({ "DocumentIds": [fileId] }); // 刷新文档列表 const responseListFile = await globalListFileByIdx.trigger(); return responseListFile
前端函数中 CryptoJS 库需要引入第三方库https://g.alicdn.com/code/lib/crypto-js/4.2.0/crypto-js.min.js。