10th NVIDIA Sky Hackathon 参赛记录——构建适用性更广的RAG应用

本次Hackathon我们提交的作品,是一款基于RAG技术的国家标准检查工具。它提供了两个功能:

  1. 输入国家标准文件 + 用户的问题,它会根据国家标准,解答用户在标准方面的疑惑。
  2. 输入国家标准文件 + 用户设计方案/产品参数表等,它会针对设计方案中的细节,搜寻与之对应的国家标准并进行比对检查,指出设计方案中与国家标准不符的问题点,或者合规方面的风险点,并给出对应国家标准。

在此之中,我们遇到的问题是,用户的提问与国家标准的表述语言之间经常存在较大差别。例如关于隐私保护国家标准,用户的提问是:作为外卖软件,能否收集用户的身份证号。而与之对应的国家标准是:用户信息收集要遵循最小必要原则。这时候直接进行RAG匹配,可能无法匹配到最相关的结果。

为了解决这个问题,我们引入了LLM对用户提问进行增强,同时进行多轮retrieve。

LLM增强的Prompt为:

decomposition_prompt = ChatPromptTemplate.from_template("""
现在你是一个国家标准制定者。
给你一段产品设计方案,请尝试针对产品中的功能或参数或技术细节,指定国家标准,要求提供准确参数或规定。
例如一个接口要求用户提供地理位置、生日等信息,可能有如下标准进行约束:
["收集的个人信息的类型应与实现产品或服务的业务功能有直接关联;直接关联是指没有上述个人信息的参与,产品或服务的功能无法实现;", "自动采集个人信息的频率应是实现产品或服务的业务功能所必需的最低频率;", "间接获取个人信息的数量应是实现产品或服务的业务功能所必需的最少数量。"]
请以List[str]的格式给出回答,注意要使用中文。列表中不要有多余的引导词、标号等。除了列表以外,不要有任何多余的说明或解释。例如: ["收集的个人信息的类型应与实现产品或服务的业务功能有直接关联;直接关联是指没有上述个人信息的参与,产品或服务的功能无法实现;", "自动采集个人信息的频率应是实现产品或服务的业务功能所必需的最低频率;", "间接获取个人信息的数量应是实现产品或服务的业务功能所必需的最少数量。"]
以下是设计方案的一部分:
{scheme}
""")

最终实现多轮retrieve的代码为:

check_prompt = ChatPromptTemplate.from_template("""
现在你是一位国家标准检察官。
现在请对照国家标准,检查用户提供的产品设计方案或者参数表等,是否违反了国家标准,或者有合规方面的风险。
如果有,请指出产品具体违反(或可能违反)了哪一条国家标准,例如一个API接口,可能存在以下违规:
    1. 根据隐私保护标准5.2条规定,作为一款外卖软件不应收集用户的身份证号码。
    2. 根据隐私保护标准6.2条规定,自动采集个人信息的频率应是实现产品或服务的业务功能所必需的最低频率。因此作为一款外卖软件不应每分钟采集用户的地理位置信息。
注意,检查官是一项高度严谨的工作,因此你给出的规定必须来自于国家标准,并给出具体条目编号。如果没有合规风险,请直接说没有风险;
如果国家标准中没有提到,请不要自行编造规定;
对于设计方案中没有提到的细节,请不要在答案中进行提及;
请以中文列表形式返回答案。
以下是用户的产品设计或参数:
{scheme}
以下国家标准可能和用户的产品设计相关:
{standard}
""")

summary_prompt = ChatPromptTemplate.from_template("""
请针对以下问题列表进行归纳总结,去除掉重复的问题,并给出一个新的问题列表,格式与原问题列表保持一致。
问题列表:
{problem_list}
直接输出总结结果即可,不要提及你进行了总结。使用中文给出答案。
""")


from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_community.document_loaders.text import TextLoader
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.document_loaders.word_document import Docx2txtLoader
import json

def compare_schema_with_standard(schema_path: str,
                                 embedder: NVIDIAEmbeddings,
                                 standard_store: FAISS = None,
                                standard_store_path: str = None):
    instruct_llm = ChatNVIDIA(model="01-ai/yi-large")
    if schema_path.endswith(".md"):
        loader = UnstructuredMarkdownLoader(schema_path)
    elif schema_path.endswith(".txt"):
        loader = TextLoader(schema_path)
    elif schema_path.endswith(".pdf"):
        loader = PyPDFLoader(schema_path)
    elif schema_path.endswith(".doc") or schema_path.endswith(".docx"):
        loader = Docx2txtLoader(schema_path)
    else:
        raise ValueError("schema file format not supported")

    print("正在加载国家标准......")
    # 加载standard store
    if standard_store is None:
        standard_store = FAISS.load_local(standard_store_path, embedder, allow_dangerous_deserialization=True)

    print("正在加载设计方案")
    # 加载设计方案
    schema_pages = loader.load()
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000, chunk_overlap=100,
        separators=["\n\n", "\n", ".", ";", ",", " ", "。", ";", ",", "!"]
    )
    schema_chunks = text_splitter.split_documents(schema_pages)

    problems = ""
    # 针对每个分片进行合规检验
    for chunk in schema_chunks:
        # 对设计方案进行decomposition,增加匹配成功概率
        print("正在提取方案所涉及法条")
        decomposition_chain = {"scheme": lambda x: chunk.page_content} | decomposition_prompt | instruct_llm | StrOutputParser()
        decomposition_str = decomposition_chain.invoke("")
        # decomposition_str = '["电动自行车的最高时速应保持在30km/h以内;", "电动自行车的电机功率不得超过500w;", "电动自行车的电机额定转速至少为500r/min;", "电动自行车的电动机型式应为轮圈电机;", "电动自行车的电池电压应为72V;", "电动自行车的电池最大电流不得超过50A;", "电动自行车的电池容量应为24Ah;", "电动自行车的电池应支持扩展;", "电动自行车的充电器电压应为72V;", "电动自行车的喇叭响度应为20db;", "电动自行车应 Equip 左右各一个反射器;", "电动自行车的车身重量应为50kg;", "电动自行车在干态25km/h同时使用前后制动时,制动距离应为8m以内。"]'
        print(decomposition_str)
        try:
            decomposition_list = json.loads(decomposition_str)
        except Exception as e: # 冗余设计,避免输出的不是list[str],增强代码健壮性
            print("cannot load as json")
            decomposition_list = decomposition_str.replace("\"", "").split(',')
        print(decomposition_list)

        # 对decomposition之后的检查项逐一进行retrieve
        retriever = standard_store.as_retriever()
        retrieved_standards = set()
        for decomposition_item in decomposition_list:
            invoked = retriever.invoke(decomposition_item)
            for doc in invoked:
                retrieved_standards.add(doc.page_content)
                
        # print(retrieved_standards)
        # 针对每一个分片进行retrieve+check
        print("正在检查方案设计")
        check_chain = {"scheme": lambda x: chunk.page_content,
                       #"standard": (lambda x: decomposition_str + chunk.page_content) | retriever | RunnableLambda(lambda x: ''.join([y.page_content for y in x]))} | check_prompt | instruct_llm | StrOutputParser() 
                       "standard": (lambda x: '\n'.join(retrieved_standards))} | check_prompt | instruct_llm | StrOutputParser() 
        problems += check_chain.invoke("")
        
    # 如果设计文档被分片了,对所有分片的合规检测结果进行总结
    if len(schema_chunks) > 1:
        summary_chain = {"problem_list": lambda x: problems} | summary_prompt | instruct_llm | StrOutputParser()
    return problems

# print(compare_schema_with_standard("ebike_schema.md", embedder, standard_store_path="standard_index"))

        

最终经过测试,可以准确检索到国家标准中对应的规则。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值