2026年7月2日 周四晚上19:30,报名腾讯会议了解“如何构建自进化的动态知识库(Brain)”(限30人)
免费POC, 零成本试错
FDE知识库

FDE知识库

学习大模型的前沿技术与行业落地应用


收藏

使用RAG技术构建企业级文档问答系统之QA抽取

发布日期:2024-07-20 12:43:19 浏览次数: 4613

1 概述

从本文开始,将开一个大坑,陆续介绍企业级文档问答系统构建的全流程,以及关键环节的优化手段。重点介绍算法流程。

构建一个基础版的RAG是非常简单的,甚至使用扣子、Dify等平台,熟练的情况下都用不了5分钟,即使使用Langchain、LlamaIndex等框架,搭建完整流程,代码也不会超过100行。但基础版的问答效果往往较差。

下面这张图是OpenAI介绍的RAG优化经验,这个准确率当然随不同的数据集会有不同,但基本上优化后的准确率比优化前有显著提升这个基本上是一致的。


问答系统构建完成后,总的流程是先对文档进行解析、切分,然后使用问题检索相关知识片段,最后将问题和知识片段输入LLM,生成答案。

在构建的过程中也是一样的,这三个环节是可以分别独立优化的,如下图所示:



本篇首先专注在如何获取QA数据,所谓的QA数据,就是“问题-回答”数据,理想情况下,如果包含回答所用到的文档片段是更好的。部分系统(如客服系统)是有这方面数据的,但绝大多数情况下是没有的,这时就需要首先构造一批问答数据,这是后续所有环节最重要的一步。

本系列将会使用中国银行所发布的《2024全球经济金融展望报告》作为文档,围绕针对这个文档的问答效果优化展开。

本文所介绍的方法,会使用千问官方的qwen-long模型,对《2024全球经济金融展望报告》这个文档抽取QA,这个模型足够便宜,抽取的结果质量也还不错。QA抽取包含如下3个步骤:

  • 短文档片段QA抽取:这部分模拟日常情况下,经常会询问细节性问题的使用场景

  • 长文档片段QA抽取:这部分模拟需要综合较多上下文才能回答的使用场景

  • QA质量打分:使用LLM再次对抽取的QA进行质量评估,这一步算是借鉴了微软phi-1.5模型Textbooks Are All You Need论文中的方法,就是借助模型对数据质量进行评估

整个过程花费不到1元,结果已经抽取好了,大家可以直接使用。

本文所对应代码已开源,地址在:https://github.com/Steven-Luo/MasteringRAG/blob/main/00_PDF%E8%A7%A3%E6%9E%90%E4%B8%8EQA%E6%8A%BD%E5%8F%96_v1.ipynb

2 准备环境

代码在Google Colab环境下进行了测试,正常情况下,安装Anaconda基本上会包含大部分所用到的包,再安装如下包即可:

pip install langchain langchain_community pypdf openai

为了便于大家复现,打印所安装的版本:

 , , , 

 module  (langchain, langchain_community, pypdf, openai):
()
langchain           0.2.8
langchain_community 0.2.7
pypdf               4.3.0
openai              1.35.14

设置API key

 

os.environ[] = 
os.environ[] =

3 文档解析与切分

   PyPDFLoader
   Document
   RecursiveCharacterTextSplitter
 
   uuid4

 (documents, filepath, chunk_size=, chunk_overlap=, seperators=[, ], force_split=):
 os.path.exists(filepath)   force_split:
()
 pickle.load((filepath, ))

    splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        separators=seperators
    )
    split_docs = splitter.split_documents(documents)
 chunk  split_docs:
        chunk.metadata[] = (uuid4())

    pickle.dump(split_docs, (filepath, ))

 split_docs

loader = PyPDFLoader()
documents = loader.load()


pattern = 
merged_docs = [Document(page_content=.join(re.sub(pattern, , doc.page_content)  doc  documents))]

splitted_docs = split_docs(documents, os.path.join(output_dir, ), chunk_size=, chunk_overlap=)
splitted_docs_large = split_docs(merged_docs, os.path.join(output_dir, ), chunk_size=, chunk_overlap=)
uuid2doc = {doc.metadata[]: doc.page_content  doc  splitted_docs}
uuid2large_doc = {doc.metadata[]: doc.page_content  doc  splitted_docs_large}

4 QA抽取

既然是构造QA,那最好是保留回答问题时所使用的上下文,方便后续环节的优化。

4.1 QA抽取Prompt

这一步核心的2个Prompt如下:

qa_gen_prompt_tmpl = 













{{document}}





qa_gen_prompt_tmpl_large_context = 







{{document}}

4.2 QA抽取代码

抽取核心代码,此处使用多线程加速抽取,考虑到网络请求异常情况会比较多,因此增加失败重试机制,同时考虑到这是一个耗时操作,并保存中间结果,以确保失败或者再次运行时,已经执行过的部分不会被重复执行:

   OpenAI
 
 
 
 
   tqdm
 

client = OpenAI(
    api_key=os.environ[],
    base_url=os.environ[]
)

 (prompt_tmpl, text):
    prompt = prompt_tmpl.replace(, text).strip()
 prompt

 (prompt, max_retry=, debug=, top_p=, temperature=):
 (prompt):
        completion = client.chat.completions.create(
            model=,
            messages=[

                {: , : prompt}
            ],
            top_p=top_p,
            temperature=temperature
        )
 completion.choices[].message.content

 max_retry > :
:
 do_chat(prompt)
   e:
            max_retry -= 
            sleep_seconds = random.randint(, )
 debug:
()
            time.sleep(sleep_seconds)
 


 (splitted_docs, prompt_tmpl, qa_ckpt_filename):
    qa_ckpt = {}
 os.path.exists(qa_ckpt_filename):
        qa_ckpt = (qa_ckpt_filename).readlines()
        qa_ckpt = [json.loads(line.strip())  line  qa_ckpt  line.strip() != ]
        qa_ckpt = {item[]: item  item  qa_ckpt}
()

    file_lock = threading.Lock()

    max_workers = 
 concurrent.futures.ThreadPoolExecutor(max_workers=max_workers)  executor:
        futures = {doc.metadata[]: executor.submit(chat, build_qa_prompt(prompt_tmpl, doc.page_content), , )  doc  splitted_docs  (doc.page_content.replace(, )) >=   doc.metadata[]   qa_ckpt}
 uuid  tqdm(futures):
            future = futures[uuid]
            result = future.result()
 result  :


            item = {: uuid, : result}
            qa_ckpt[uuid] = item


            file_lock.acquire()

:
 (qa_ckpt_filename, )  f:
                    f.write(json.dumps(item, ensure_ascii=) + )
   e:
(e)
:
                file_lock.release()
 qa_ckpt


detailed_qa_dict = gen_qa(splitted_docs, qa_gen_prompt_tmpl, os.path.join(output_dir, ))

large_context_qa_dict = gen_qa(splitted_docs_large, qa_gen_prompt_tmpl_large_context, os.path.join(output_dir, ))

4.3 抽取样例

[
    {
: ,
: ,
: 
    },
    {
: ,
: ,
: 
    },
    {
: ,
: ,
: 
    },
    {
: ,
: ,
: 
    },
    {
: ,
: ,
: 
    },
    {
: ,
: ,
: 
    },
    {
: ,
: ,
: 
    },
    {
: ,
: ,
: 
    }
]

4.4 后置处理

从上面的样例可以看出,结果是被json...包裹的,没有办法直接解析为JSON,使用正则表达式进行后置处理,提取JSON

 
   

 (text):
    pattern = 

    text = text.replace(, )
:
 json.loads(text)
:
        match = re.search(pattern, text, re.DOTALL)
:
            matched = match.group()
 json.loads(matched)
   e:
()

 []

 (qa_ckpt, uuid2doc_map):
    data = []

 key, value  tqdm(qa_ckpt.items()):
        text = value[]
        qa_list = convert2json(text)

 item  qa_list:
            question = item.get(, ).strip()
            answer = item.get(, ).strip()
            context = item.get(, ).strip()

 question ==   answer == :
(qa_list)

            data.append({
: key,
: question,
: answer,
: context,
: uuid2doc_map[key]
            })
    qa_df = pd.DataFrame(data)
 qa_df

qa_df = build_qa_df(detailed_qa_dict, uuid2doc)
qa_df.drop_duplicates(, inplace=)
qa_df[] = 
large_context_qa_df = build_qa_df(large_context_qa_dict, uuid2large_doc)
large_context_qa_df.drop_duplicates(, inplace=)
large_context_qa_df[] = 

qa_df = pd.concat([qa_df, large_context_qa_df])

5 QA质量检查

这部分就是对qa_df中的问题-回答对,再打一次分,然后过滤低分结果,Prompt如下:

qa_check_prompt_tmpl = 










{{question}}




{{answer}}

总体又是一个循环,与QA抽取部分非常相似,此处不再粘贴代码,需要的朋友们请访问代码仓库。

5.1 打分结果样例

5.2 3分样例

  • 问:报告中提到的主要经济体GDP增速变化趋势的图的名称是什么?

  • 答:主要经济体GDP增速变化趋势

  • 上下文:图2:主要经济体GDP增速变化趋势(%)

5.3 2分样例

  • 问:消费者借贷能力和意愿受到什么因素的影响?

  • 答:美国家庭债务余额拖欠率回升至3%

  • 上下文:美国家庭债务余额拖欠率回升至3%,消费者借贷能力和意愿将有所下降。

可以看出,低分问答对,质量确实相对较低

5.4 最终数据集构建

这部分首先保留4分及以上的问答对,然后随机挑选100条数据作为后续的测试集。至此,准备工作完成。

hq_qa_df = qa_df[qa_df[] >= ]
test_q = hq_qa_df.sample(, replace=)[].values.tolist()
hq_qa_df[] = 
hq_qa_df.loc[hq_qa_df[].isin(test_q), ] = 

hq_qa_df.to_excel(os.path.join(output_dir, ), index=)

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询

扫码登录
登录即表示您同意《53AI网站服务协议》
服务协议

欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。

在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。

一、 定义

本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。

会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。

知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。

二、 账号注册与登录

登录方式:本网站支持以下登录方式,您可根据实际情况选择:

微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。

手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。

账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。

实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。

未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。

三、 服务内容与规范

知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。

服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。

禁止行为:您在使用服务时不得实施以下行为:

利用技术手段批量爬取、下载、转存知识库内容;

将知识库内容用于商业目的或未经授权地向第三方传播;

干扰本网站正常运行或侵犯其他用户合法权益;

发布违法违规信息或从事违反公序良俗的活动。

四、 知识产权声明

权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。

有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。

侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。

五、 个人信息保护

我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。

您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。

您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。

六、 免责声明

内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。

不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。

第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。

七、 违约责任

如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。

如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。

八、 法律适用与争议解决

本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。

因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。

九、 其他

本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。

本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。

我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。


已查阅