问题背景:
笔者在使用通义点金平台搭建知识库的过程中遇到了如下问题:
通义点金的文档库在解析带表格的文档时,会将每个表格的数据以markdown的格式存储到独立的chunk中,chunk类型为table,并且该chunk仅包含表格本身的内容,不会包含表格前后的说明文字,也就是会丢失表格的辅助信息甚至是表格名称(如果表名没有在表格内)。
上述问题会导致,针对包含大量相似结构表格的文档进行问答时,大模型的回答会出现错乱,例如把A表的数据误认为B表中的同名数据了;根本原因是在召回以及召回后的分析中,大模型无法识别某个字段到底属于那张表。
例如下面这个表格chunk就仅包含表格内容,无表名等信息。
解决方法:
总体思路:
利用点金平台的API对table类型的chunk进行修订,将物理意义上的前一个chunk和后一chunk中的内容合并到table类型的chunk中。这样可以使table类型的chunk中包含到表名以及前后的各种解释性文字。
将上述逻辑封装成一个工具程序,输入为文档库id和文档名前缀,工具会扫描指定文档库中的所有文档并找出符合文档名前缀的文档,对这些文档中所有类型为table的chunk进行修订。
具体步骤:
(1)调用“获取⽂档库内⽂档信息(/api/library/listDocument)”API获取指定文档库id下的所有文档。
(2)根据输入的文件名前缀过滤全量文档列表得到待处理的文档列表。
(3)针对待处理列表逐条进行修订:
i.调用“查看⽂档对应的⽂档块(/api/library/getDocumentChunk)”API 获取当前文档的全量chunk列表
ii.按页面顺序对chunk进行升序排序(页面 + 起始纵坐标)。
iii.扫描全量chunk列表,对于table类型的chunk生成新的chunkText:将前序和后继chunk的chunkText拼接到当前chunkText
iv.调用“更新⽂档块(/api/library/updateDocumentChunk)”API 更新第iii步修订的chunk。
部分代码参考:
#对文档中的表格chunk做上下文补充 def updateFileTable(library_id: str = None, file_list: list = None): docInfoList = serchFileList(library_id=library_id, file_list=file_list) #查询待处理文件列表 for docInfo in docInfoList: updateSingleFileTbale(library_id=library_id, docId=docInfo['docId']) #逐个文档进行修订
def serchFileList(library_id: str = None, file_list: list = None): doc_list = [] result = LLM_Dianjin_RAG_Interface.search_file(library_id=library_id) #查询指定文档库下的所有文档列表(封装了 /api/library/listDocument API) for record in result: if file_list is not None: # 判断是否需要根据文件名过滤 match = False for file_name in file_list: if record['title'].startswith(file_name): match = True if not match: continue if record['statusCode'] != 'Completed': continue # 如果文件状态不是Completed,跳过该记录 doc_info = {"docId": record['docId'], "title": record['title']} doc_list.append(doc_info) return doc_list
def updateSingleFileTbale(library_id: str = None, docId: str = None): #获取全量文档块(内部封装了/api/library/getDocumentChunk) fileChunks = getFileChunks(library_id=library_id, docId=docId) my_utils.log('ori table', f'fileChunks:{fileChunks}', 'debug') #对文档块进行重排序(按页码和起始纵坐标排序) fileChunks.sort(key=lambda x: (x['pos'][0]['page'], x['pos'][0]['axisArray'][1])) my_utils.log('sorted table', f'fileChunks:{fileChunks}', 'debug') #生成待更新数据 updateChunkList = [] preChunk = None nextChunk = None for index,chunk in enumerate(fileChunks): #获取当前chunk的前续和后继chunk if index > 0: preChunk = fileChunks[index - 1] if index < len(fileChunks) - 1: nextChunk = fileChunks[index + 1] else: nextChunk = None if chunk['chunkType'] == 'table': #仅修订table类型的chunk chunkText = appendTableChunk( curChunkInfo=chunk, preChunk=preChunk, nextChunk=nextChunk) #拼接chunkText updateChunkList.append({ "chunkId": chunk['chunkId'], "chunkText": chunkText, }) #更新文档块 return updateDocumentChunk(library_id=library_id, chunkInfoList=updateChunkList) #更新对应的chunk(内部封装了/api/library/updateDocumentChunk)
实现效果
示例1
原chunk信息:不包含表名等信息。
更新后的chunk信息:可以看到在原表格内容的前面拼接了前续chunk的内容,即表名和部分说明信息;在原表格内后面可以看到拼接了后续chunk的表格内容。
示例2:
原chunk信息:不包含表后的说明信息
更新后的chunk信息:补充了表后的说明信息。
后记:
1、通义点金近期的更新已经把table的标题加进到chunk中,基本不会出现表格的chunk中无表名的情况了,但是表前的说明文字暂然未能添加到表格的chunk中,如果需要自动化添加仍可以参考本文所述的工具逻辑。
2、通义点金近期的更新后,chunk查询的默认排序已经是按页面顺序排了,chunk列表的排序步骤可以略掉了。