在文章利用 langchian 搭建通用的表格数据分析工具 | 年轻人起来冲 中,我们针对 RAG 回答统计、分析类问题的能力弱的问题,我们通过对问题分类,使用生成 pandas 代码的方式完成回答。但是这个方式存在一个问题,即无法处理多个表格,本文扩展使用场景,将其扩展到可以使用多表格的领域
问题背景在没有扩展前,基于 RAG 的表格文档处理方式将用户查询转为 2 个方向:
向量查询:基于向量查询检索相关上下文,然后使用 llm 回答问题 生成代码:基于表格部分内容 + llm 生成代码,通过执行代码获取答案 flowchart LR
A[用户提问] --> B{llm问题分类}
C[最终回复]
B --> D[向量查询] --> F{llm生成回复} -->C
B --> E[llm生成代码] --> G{执行代码} -->C
因为在生成代码部分,需要利用表格内容构建 prompt,这就意味着,不同的表格由于表头不同,导致 prompt 不同,因此以上过程无法处理多个表格。
因此本文针对不同表格动态构建 prompt,可以同时针对不同表格实现 “精确查询”,其过程如下:
flowchart LR
A[用户提问] --> B{llm问题分类}
subgraph one[所有表格]
direction LR
E[llm生成代码] --> G{执行代码}
end
C[最终回复]
B --> D[向量查询] --> F{llm生成回复} -->C
B --> one -->I{llm选择答案} -->C
关键代码 1. 向量检索生成分支该分支是常规的 rag 分支,构建索引,然后将检索到的上下文和问题一起提供给 llm,生成回答
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 vector_prompt=PromptTemplate.from_template(""" 根据下面的上下文内容回答问题。如果你不知道答案,就回答不知道,不要试图编造答案 优化输出效果,采用无序列表输出,表头加粗处理 {context} 问题:{question} """ )retriever=indexs.as_retriever() from operator import itemgettervector_chain = ( {"context" : itemgetter("question" )|retriever|format_docs, "question" : RunnablePassthrough()} | vector_prompt | llm | StrOutputParser() )
2. 生成 pandas 分支这一分支包含 2 个过程,首先对每个文档生成 pandas 查询代码,执行得到结果后,通过 llm 评估每个文档回答问题的程度,取评分高的回答输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 system_string="你正在使用pandas处理DataFrame,请根据三个引号分隔的问题输出pandas命令,其中`print(df.head())`的结果如下:\n" system_string+="{excle_head}\n" system_string+= "请使用1行代码完成需求,目的是通过pandas检索出符合条件的行(包括所有列)\n" system_string+= "只输出代码,不要输出其他任何信息,也不能写任何注释\n" system_string+= "确保代码可运行\n" pandas_prompt = ChatPromptTemplate.from_messages([("system" , system_string), ("human" , "'''{question}'''" )]) pandas_chain = {'excle_path' :RunnablePassthrough(), 'question' :RunnablePassthrough()}|{'excle_head' :read_excle, 'question' :RunnablePassthrough()}|pandas_prompt | \ llm | StrOutputParser() | _sanitize_output | \ run_python|postprocess referee_prompt=PromptTemplate.from_template(""" 根据问题及回答,评估回答响应了问题的程度,如果打分是0-10分\n 问题:{question}\n 回答: {answer}\n 直接给出分数即可,不需要解释 """ )referee_chain = (referee_prompt | llm | StrOutputParser() ) try : answers=[] for excle_path in excles_path: answer = pandas_chain.invoke({'excle_path' :excle_path, 'question' :question}) score=referee_chain.invoke({"question" :question,"answer" :answer}) answers.append((answer,int (score.replace('分' ,'' )))) if len (answers)>0 : answers=sorted (answers,key=itemgetter(1 ),reverse=True ) finnly_answer=answers[0 ] if finnly_answer[1 ]>0 : return finnly_answer[0 ] else : return '无法回答该问题' else : return '无法回答该问题' except Exception as e: print ('出现错误:{e}' ) return '无法回答该问题'
3. 问题分类过程这是对用户输入问题进行分类,然后选择向量查询还是生成 pandas 分支的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def route (info ): print (info) if "精准查询" in info["class" ]: return answer_by_pandas(question=info['question' ],excles_path=excles_path) elif "普通查询" in info["class" ]: return answer_by_vector(question=info['question' ],excles_path=excles_path) else : return answer_by_vector(question=info['question' ],excles_path=excles_path) class_prompt=ChatPromptTemplate.from_template("""你是一名问题归类员,你的任务识别 以下由三个引号包含的问题,然后将其分类为:“普通查询”、“精准查询”,当提问涉及准确的时间、地点、人物、序号时,归类为精准查询, 否则归类为普通查询。 直接输出“普通查询”、“精准查询”之一,不要输出其他任何信息 问题:```{question}``` """ )class_chain=class_prompt|llm|StrOutputParser() full_chain={"class" :class_chain,"question" : RunnablePassthrough()}|RunnableLambda(route)
效果展示使用时,第一步先勾选需要处理的表格,然后提问题