RAGas 有效文档准确率 Useful Context Precision
在RAG系统评估场景下,“计算RAG返回有效文档的平均top-k命中率” 旨在衡量系统检索相关信息的能力,在只有一次查询的情况下,具体解释如下:
背后直觉 :在实际运用RAG系统进行单次查询时,用户希望能高效获取有效文档。如果系统能在返回结果的前几个位置(如top-1、top-2)就展示有效文档,说明其检索精准度高、效率好;若有效文档位置靠后,获取有效信息的效率就会降低。计算平均top-k命中率,就是为了综合评估系统在不同检索深度下找到有效文档的能力,帮助我们了解系统此次查询中检索有效信息的表现,数值越高代表系统表现越好。
实现方式
数据准备 :假设RAG系统针对此次查询返回5个文档。通过大模型分析每个文档对得出答案是否有帮助,为每个文档赋予命中结果 ( ), 取值为0或1 ,1表示该文档有助于得出答案,0表示无助于得出答案。
计算top-k命中率
top-1命中率 : ,即第一个文档的命中结果就是top-1命中率。若 ,则top-1命中率为1;若 ,则top-1命中率为0。
top-2命中率 : ,即前两个文档命中结果的平均值。比如 , ,则 。
…
计算平均top-k命中率 :计算得到的top-1~5命中率的加权平均值。
1 2 3 4 5 6 7 8 query = "美国最高法院关于堕胎的裁决对全球有什么影响?" answer = "美国最高法院关于堕胎的裁决具有重要的全球影响。该裁决导致在堕胎访问受到限制的州,三分之一的生育年龄女性和女孩无法获得堕胎服务。那些州的母婴健康支持也较弱,母亲死亡率较高,儿童贫困率也较高。此外,裁决的影响超出了国界,由于美国在全球的地缘政治和文化影响力,这一裁决也产生了跨国影响。全球的组织和活动家担心这一裁决可能会激励其他国家出台反堕胎的立法和政策。裁决还妨碍了某些非洲国家的进步法律改革和堕胎指南的实施。此外,该裁决在国际政策领域造成了寒蝉效应,使得反堕胎的力量能够削弱人权保护。" docs = [ "- 2022年,美国最高法院作出裁决,推翻了50年的判例法,取消了宪法堕胎权。\n- 这一裁决产生了巨大影响:三分之一的生育年龄女性和女孩现在生活在堕胎服务几乎完全无法获得的州。\n- 这些堕胎法律最为严格的州,母婴健康支持最为薄弱,母亲死亡率较高,儿童贫困率较高。\n- 美国最高法院的裁决还通过美国在全球的地缘政治和文化影响力,超越国界产生了影响。\n- 全球的SRR组织和活动家对这一裁决可能为其他国家的反堕胎立法和政策攻击铺路表示担忧。\n- 观察者还注意到该裁决对某些非洲国家的进步法律改革产生了影响,导致堕胎指导方针的 adoption 和执行停滞不前。\n- 该裁决在国际政策领域产生了寒蝉效应,助长了反堕胎的国家和非国家行为体破坏人权保护的势头。" , "美国最高法院的堕胎裁决不仅在国内引发了激烈的辩论和讨论,也在全球范围内引发了广泛关注。许多国家将美国视为法律和社会问题的领导者,因此这一裁决可能会影响其他国家对堕胎的政策和态度。" , "这一裁决还可能对国际组织和非政府组织(NGO)产生影响,尤其是那些致力于生育权和妇女健康问题的团体。根据裁决的结果,可能会出现资金、倡导工作和与美国同行的合作发生变化,进而在全球范围内引发生育正义斗争的连锁反应。" ]
代码实现 判断文档有效性 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 import asyncioimport numpy as npfrom typing import List from langchain_core.documents import Documentfrom langchain_openai import ChatOpenAIfrom langchain_openai import OpenAIEmbeddingsfrom sklearn.metrics.pairwise import cosine_similarityfrom langchain_core.prompts import PromptTemplatefrom langchain_core.output_parsers import PydanticOutputParserfrom pydantic import BaseModel, Fieldclass Usefulness_Verdication (BaseModel ): reasoning: str = Field(description="思考上下文是否有助于得出给定的回答。" ) result: int = Field(description="判决结果,如果上下文有助于得出给定的回答,则结果为1,否则为0。" ) verdication_parser = PydanticOutputParser(pydantic_object=Usefulness_Verdication) llm = ChatOpenAI( base_url='http://localhost:5551/v1' , api_key='EMPTY' , model_name='Qwen2.5-14B-Instruct' , temperature=0.5 , ) verdict_prompt = ( "给出问题、回答和上下文,验证上下文是否有助于得出给定的回答。" "你需要先分析内容作为判决理由,如果有用,则判决结果为1,如果没用,则判决结果为0。\n\n" "输出格式:\n{output_format_instructions}\n" "[问题-开始]\n{query}\n[问题-结束]\n\n" "[回答-开始]\n{answer}\n[回答-结束]\n\n" "[上下文-开始]\n{context}\n[上下文-结束]\n\n" ) verdict_prompt = PromptTemplate( template=verdict_prompt, partial_variables={ "output_format_instructions" :verdication_parser.get_format_instructions() } ) verdict_usefulness_chain = verdict_prompt | llm | verdication_parser verdict_prompt.pretty_print()
给出问题、回答和上下文,验证上下文是否有助于得出给定的回答。你需要先分析内容作为判决理由,如果有用,则判决结果为1,如果没用,则判决结果为0。
输出格式:
The output should be formatted as a JSON instance that conforms to the JSON schema below.
As an example, for the schema {"properties": {"foo": {"title": "Foo", "description": "a list of strings", "type": "array", "items": {"type": "string"}}}, "required": ["foo"]}
the object {"foo": ["bar", "baz"]} is a well-formatted instance of the schema. The object {"properties": {"foo": ["bar", "baz"]}} is not well-formatted.
Here is the output schema:
1 {"properties": {"reasoning": {"description": "思考上下文是否有助于得出给定的回答。", "title": "Reasoning", "type": "string"}, "result": {"description": "判决结果,如果上下文有助于得出给定的回答,则结果为1,否则为0。", "title": "Result", "type": "integer"}}, "required": ["reasoning", "result"]}
[问题-开始]
[33;1m[1;3m{query}[0m
[问题-结束]
[回答-开始]
[33;1m[1;3m{answer}[0m
[回答-结束]
[上下文-开始]
[33;1m[1;3m{context}[0m
[上下文-结束]
1 2 3 4 5 6 7 8 9 10 11 12 13 tasks = [ verdict_usefulness_chain.ainvoke( { "query" :query, 'answer' :answer, "context" :a_doc, } ) for a_doc in docs ] verdications = await asyncio.gather(*tasks) verdications
[Usefulness_Verdication(reasoning='上下文提供了美国最高法院关于堕胎裁决的具体信息,包括裁决内容、对美国国内的影响(如女性和女孩无法获得堕胎服务、母婴健康支持薄弱等)、以及对全球的影响(如其他国家可能受到的立法和政策影响、对某些非洲国家法律改革的阻碍、国际政策领域的寒蝉效应等)。这些信息直接支持了回答中提到的裁决对全球的影响,因此上下文对得出给定的回答有帮助。', result=1),
Usefulness_Verdication(reasoning='上下文提到美国最高法院的堕胎裁决在全球范围内引发了广泛关注,并且许多国家将美国视为法律和社会问题的领导者。这与回答中提到的裁决可能激励其他国家出台反堕胎立法和政策以及裁决在全球范围内的跨国影响是一致的。上下文有助于理解回答中的全球影响。', result=1),
Usefulness_Verdication(reasoning='上下文补充了回答中的信息,特别是关于裁决如何可能影响国际组织和非政府组织(NGO),尤其是那些致力于生育权和妇女健康问题的团体。上下文提到可能会出现资金、倡导工作和与美国同行的合作发生变化,这进一步解释了裁决的跨国影响。因此,上下文提供了额外的信息,有助于理解裁决对全球的影响。', result=1)]
计算加权平均命中率 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 results = [item.result for item in verdications] final_score = np.nan denominator = sum (results) if denominator <=0 : final_score = 0 else : numerator = sum ( [ (sum (results[: i + 1 ]) / (i + 1 )) * results[i] for i in range (len (results)) ] ) final_score = numerator / denominator final_score
1.0
封装 1 2 3 4 5 import asyncioimport nest_asyncionest_asyncio.apply()
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 import asyncioimport numpy as npfrom typing import List , Dict from langchain_core.documents import Documentfrom langchain.schema.runnable import Runnablefrom langchain_openai import ChatOpenAIfrom langchain_openai import OpenAIEmbeddingsfrom sklearn.metrics.pairwise import cosine_similarityfrom langchain_core.prompts import PromptTemplatefrom langchain_core.output_parsers import PydanticOutputParserfrom pydantic import BaseModel, Fieldclass Usefulness_Verdication (BaseModel ): reasoning: str = Field(description="思考上下文是否有助于得出给定的回答。" ) result: int = Field(description="判决结果,如果上下文有助于得出给定的回答,则结果为1,否则为0。" ) class Useful_Context_Precision (Runnable ): """ 该类用于评估回答中上下文的相关性和有用性。其核心任务是验证给定上下文是否有助于 得出回答,并计算有用性判决的平均精度。 """ def __init__ (self, llm: ChatOpenAI ): """ 初始化 Useful_Context_Precision 类,设置 LLM 和判决解析器。 :param llm: 用于生成上下文判决的 LLM 模型实例 """ self .llm = llm self .verdication_parser = PydanticOutputParser(pydantic_object=Usefulness_Verdication) verdict_prompt = ( "给出问题、回答和上下文,验证上下文是否有助于得出给定的回答。\n" "你需要先分析内容作为判决理由,如果有用,则判决结果为1,如果没用,则判决结果为0。\n\n" "输出格式:\n{output_format_instructions}\n" "[问题-开始]\n{query}\n[问题-结束]\n\n" "[回答-开始]\n{answer}\n[回答-结束]\n\n" "[上下文-开始]\n{context}\n[上下文-结束]\n\n" ) self .verdict_prompt = PromptTemplate( template=verdict_prompt, partial_variables={ "output_format_instructions" : self .verdication_parser.get_format_instructions() } ) self .verdict_usefulness_chain = self .verdict_prompt | self .llm | self .verdication_parser def verdict_document_usefulness (self, query: str , answer: str , document_list: List [str ] ) -> List [Usefulness_Verdication]: """ 异步判断一组文档中每个文档对给定问题和回答的上下文是否有用。 :param query: 提出的问题 :param answer: 对问题的回答 :param document_list: 文档列表,包含与问题相关的上下文 :return: 返回每个文档的上下文有用性的判决结果列表 """ tasks = [ self .verdict_usefulness_chain.ainvoke({ "query" : query, "answer" : answer, "context" : a_doc, }) for a_doc in document_list ] loop = asyncio.get_event_loop() verdications = loop.run_until_complete(asyncio.gather(*tasks)) return verdications def calculate_average_precision (self, results: List [int ] ) -> float : """ 计算并返回平均精度(AP)得分。 :param results: 判决结果的列表,其中 1 表示有用,0 表示无用 :return: 平均精度得分 """ final_score = np.nan denominator = sum (results) if denominator <= 0 : final_score = 0 else : numerator = sum ([ (sum (results[:i + 1 ]) / (i + 1 )) * results[i] for i in range (len (results)) ]) final_score = numerator / denominator return final_score def invoke (self, inputs: Dict [str , List [str ]] ) -> float : """ 执行用于计算有用上下文精度的评估。 :param inputs: 输入字典,包含以下字段: - "query": 提出的问题 - "answer": 对问题的回答 - "document_list": 文档列表 :return: 计算得到的精度得分 """ query = inputs["query" ] answer = inputs["answer" ] document_list = inputs["document_list" ] verdications = self .verdict_document_usefulness(query, answer, document_list) results = [item.result for item in verdications] score = self .calculate_average_precision(results) return score
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 llm = ChatOpenAI( base_url='http://localhost:5551/v1' , api_key='EMPTY' , model_name='Qwen2.5-14B-Instruct' , temperature=0.5 , ) tool = Useful_Context_Precision(llm) inputs = { "query" :query, "answer" :answer, "document_list" :docs, } tool.invoke(inputs)
1.0