RAGas 回答相关性 Answer Relevance

Peng Xia

回答相关性是指生成的答案应该针对所提出的实际问题进行作答。
以下从正面和反面分别给出一些例子:

  • 正面例子
    • 问题为“苹果有哪些营养价值?”,
    • 答案“苹果富含维生素C、纤维素和抗氧化物质,维生素C有助于增强免疫力,纤维素能促进肠道蠕动,抗氧化物质有利于身体健康”。该
    • 答案直接针对问题,全面阐述了苹果的营养价值,无多余或不相关内容,回答相关性高。
  • 反面例子
    • 同样问题“苹果有哪些营养价值?”
    • 答案是“苹果在很多地方都有种植,吃起来很甜,有些人喜欢把它做成苹果派。不过,它对健康有一定好处”。
    • 此答案没有直接阐述苹果的营养价值,只是提及种植地、口感和食用方式等不相关信息,且对营养价值的表述模糊,没有有效回答问题,回答相关性低。

如果答案能以恰当的方式直接回答问题,我们就认为该答案具有相关性。需要特别指出的是,我们对答案相关性的评估并不考虑其真实性,但是会对不完整或包含冗余信息的答案进行扣分。为了评估答案相关性,对于给定的答案,我们促使大语言模型(LLM)基于生成个潜在问题,具体如下:

1
2
根据给定答案生成一个问题。
答案:[答案]

然后,我们使用 embedding 模型获取所有问题的嵌入向量。对于每个,我们计算它与原始问题之间的相似度 ,即相应嵌入向量之间的余弦相似度。问题的答案相关性得分的计算方式如下:

这个指标用于评估生成的答案与初始问题或指令的契合程度。

这是测试数据

1
2
3
4
5
6
7
query = "美国最高法院关于堕胎的裁决对全球有什么影响?"
answer = "美国最高法院关于堕胎的裁决具有重要的全球影响。该裁决导致在堕胎访问受到限制的州,三分之一的生育年龄女性和女孩无法获得堕胎服务。那些州的母婴健康支持也较弱,母亲死亡率较高,儿童贫困率也较高。此外,裁决的影响超出了国界,由于美国在全球的地缘政治和文化影响力,这一裁决也产生了跨国影响。全球的组织和活动家担心这一裁决可能会激励其他国家出台反堕胎的立法和政策。裁决还妨碍了某些非洲国家的进步法律改革和堕胎指南的实施。此外,该裁决在国际政策领域造成了寒蝉效应,使得反堕胎的力量能够削弱人权保护。"
docs = [
"- 2022年,美国最高法院作出裁决,推翻了50年的判例法,取消了宪法堕胎权。\n- 这一裁决产生了巨大影响:三分之一的生育年龄女性和女孩现在生活在堕胎服务几乎完全无法获得的州。\n- 这些堕胎法律最为严格的州,母婴健康支持最为薄弱,母亲死亡率较高,儿童贫困率较高。\n- 美国最高法院的裁决还通过美国在全球的地缘政治和文化影响力,超越国界产生了影响。\n- 全球的SRR组织和活动家对这一裁决可能为其他国家的反堕胎立法和政策攻击铺路表示担忧。\n- 观察者还注意到该裁决对某些非洲国家的进步法律改革产生了影响,导致堕胎指导方针的 adoption 和执行停滞不前。\n- 该裁决在国际政策领域产生了寒蝉效应,助长了反堕胎的国家和非国家行为体破坏人权保护的势头。",
"美国最高法院的堕胎裁决不仅在国内引发了激烈的辩论和讨论,也在全球范围内引发了广泛关注。许多国家将美国视为法律和社会问题的领导者,因此这一裁决可能会影响其他国家对堕胎的政策和态度。",
"这一裁决还可能对国际组织和非政府组织(NGO)产生影响,尤其是那些致力于生育权和妇女健康问题的团体。根据裁决的结果,可能会出现资金、倡导工作和与美国同行的合作发生变化,进而在全球范围内引发生育正义斗争的连锁反应。"
]

实现介绍

指标原理
通过大语言模型(LLM)根据给定答案生成多个潜在问题,将这些问题与原始问题进行相似度计算,利用相似度均值来衡量答案和原始问题的契合程度,进而评估回答相关性。具体计算方式为,其中是答案相关性得分,是生成的潜在问题数量,是原始问题与生成的潜在问题的相似度。

背景原因
在评估RAG系统时,需要全面考量多个维度,回答相关性是其中重要的一环。传统评估方式存在局限性,难以准确衡量生成答案与问题的匹配程度。该指标聚焦答案是否直接、恰当回应问题,且不考虑答案真实性,仅针对答案不完整或含冗余信息的情况进行扣分,以此更精准地评估RAG系统生成答案的质量。

实现流程:

  1. 生成潜在问题:将给定答案输入LLM,利用“Generate a question for the given answer. answer: [answer]”的提示,让LLM基于答案生成个潜在问题
  2. 获取问题嵌入向量:借助OpenAI API的text - embedding - ada - 002模型,获取原始问题和生成的个潜在问题的嵌入向量。
  3. 计算相似度并得出得分:计算每个潜在问题与原始问题嵌入向量之间的余弦相似度,最后按照公式计算出答案相关性得分 ,完成对回答相关性的评估。

生成潜在问题

使用chain异步生成伪问题

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
import asyncio
import numpy as np
from typing import List
from langchain_core.documents import Document
from langchain_openai import ChatOpenAI
from langchain_openai import OpenAIEmbeddings
from sklearn.metrics.pairwise import cosine_similarity
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field

'''
输出格式形如:
{
"sentence_and_statements":
}
'''

class Generated_Query(BaseModel):
query: str = Field(description="生成的问题内容。")
answer_sufficiency: str = Field(description='对于生成的问题, 分析回答是否是清晰地回答。')
answer_sufficiency_result: int = Field(description='如果回答清晰地回答生成的问题,为1,否则为0。')

query_parser = PydanticOutputParser(pydantic_object=Generated_Query)

llm = ChatOpenAI(
base_url='http://localhost:5551/v1',
api_key='EMPTY',
model_name='Qwen2.5-7B-Instruct',
temperature=0.5,
)

generate_query_prompt = (
"为给定的回答生成一个问题,并分析判断回答是否是清晰地回答。"
"如果回答清晰地回答生成的问题,为1,否则为0。"
"清晰的回答是直接回答问题、不带有歧义和模糊处理的回答;反之,含糊的的回答是指回避、含糊或模棱两可的回答。\n"
"输出格式:\n{output_format_instructions}\n\n"
"[回答-开始]\n{answer}\n[回答-结束]\n"
)
generate_query_prompt = PromptTemplate(
template=generate_query_prompt,
partial_variables={
"output_format_instructions":query_parser.get_format_instructions()
}
)

generate_query_chain = generate_query_prompt | llm | query_parser
1
generate_query_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": {"query": {"description": "生成的问题内容。", "title": "Query", "type": "string"}, "answer_sufficiency": {"description": "对于生成的问题, 分析回答是否是清晰地回答。", "title": "Answer Sufficiency", "type": "string"}, "answer_sufficiency_result": {"description": "如果回答清晰地回答生成的问题,为1,否则为0。", "title": "Answer Sufficiency Result", "type": "integer"}}, "required": ["query", "answer_sufficiency", "answer_sufficiency_result"]}
[回答-开始] [33;1m[1;3m{answer}[0m [回答-结束]
1
2
3
4
5
6
7
num_gen = 10
tasks = [
generate_query_chain.ainvoke({'answer':answer})
for i in range(num_gen)
]

generated_querys = await asyncio.gather(*tasks)
1
generated_querys
[Generated_Query(query='美国最高法院关于堕胎的裁决对哪些方面产生了影响?', answer_sufficiency='回答详细描述了美国最高法院关于堕胎的裁决对美国国内各州、母婴健康、儿童贫困以及全球地缘政治和文化影响力等方面的影响。', answer_sufficiency_result=1),
 Generated_Query(query='美国最高法院关于堕胎的裁决对美国国内和国际的影响是什么?', answer_sufficiency='回答详细讨论了美国最高法院关于堕胎裁决的国内影响,包括对生育年龄女性和女孩堕胎服务的限制、母婴健康状况以及儿童贫困率的影响。同时,回答也提到了裁决的国际影响,包括对其他国家反堕胎立法和政策的影响,以及在国际政策领域对人权保护的潜在削弱。回答内容丰富且具体,直接回应了问题。', answer_sufficiency_result=1),
 Generated_Query(query='美国最高法院关于堕胎的裁决对美国国内和国际产生了哪些影响?', answer_sufficiency='回答详细描述了美国最高法院关于堕胎的裁决对美国国内的影响,包括堕胎服务的限制、母婴健康状况以及儿童贫困率的变化,同时也提到了这一裁决对国际社会的影响,例如对其他国家立法的影响和在国际政策领域的寒蝉效应。', answer_sufficiency_result=1),
 Generated_Query(query='美国最高法院关于堕胎的裁决对美国国内和国际产生了哪些影响?', answer_sufficiency='回答详细描述了美国最高法院关于堕胎裁决对美国国内的影响,包括对生育年龄女性和女孩堕胎服务获取的影响,以及对母婴健康的负面影响。同时,回答也提到了裁决的国际影响,包括对其他国家制定反堕胎立法和政策的影响,以及对国际政策领域中人权保护的潜在削弱。', answer_sufficiency_result=1),
 Generated_Query(query='美国最高法院关于堕胎的裁决对美国国内和国际产生了哪些影响?', answer_sufficiency='回答详细地描述了美国最高法院关于堕胎的裁决对美国国内和国际的影响,包括对生育年龄女性和女孩堕胎服务的限制、母婴健康状况、地缘政治和文化影响力以及国际政策领域的影响。', answer_sufficiency_result=1),
 Generated_Query(query='美国最高法院关于堕胎的裁决对美国国内和全球产生了哪些影响?', answer_sufficiency='回答详细说明了美国最高法院关于堕胎的裁决对美国国内的影响,如导致堕胎访问受限州的女性无法获得堕胎服务,母婴健康状况恶化等,同时也指出了裁决的全球影响,包括对其他国家的立法和国际政策领域的影响。', answer_sufficiency_result=1),
 Generated_Query(query='美国最高法院关于堕胎的裁决对美国国内和全球产生了哪些影响?', answer_sufficiency='回答详细地描述了美国最高法院关于堕胎的裁决对美国国内的影响,包括对生育年龄女性和女孩获得堕胎服务的限制,以及对母婴健康、儿童贫困率的影响。此外,回答还提到了裁决的全球影响,包括对其他国家立法和政策的影响,以及在国际政策领域造成的寒蝉效应。', answer_sufficiency_result=1),
 Generated_Query(query='美国最高法院关于堕胎的裁决对美国国内和国际产生了哪些具体影响?', answer_sufficiency='回答详细地讨论了美国最高法院关于堕胎的裁决对美国国内的影响,包括对生育年龄女性和女孩堕胎服务的限制,以及对母婴健康、儿童贫困的影响。此外,回答还讨论了裁决的国际影响,包括对其他国家反堕胎立法的影响,以及在国际政策领域对人权保护的潜在负面影响。', answer_sufficiency_result=1),
 Generated_Query(query='美国最高法院关于堕胎的裁决对美国国内和国际有什么影响?', answer_sufficiency='回答详细地讨论了该裁决对美国国内的影响,包括对生育年龄女性和女孩堕胎服务的限制、母婴健康状况以及儿童贫困率的影响。此外,回答还探讨了裁决的国际影响,包括其对全球组织和活动家的影响,以及对某些非洲国家法律改革的负面影响。', answer_sufficiency_result=1),
 Generated_Query(query='美国最高法院关于堕胎的裁决对美国国内和全球产生了哪些影响?', answer_sufficiency='回答详细地描述了美国最高法院关于堕胎的裁决对美国国内的影响,包括对生育年龄女性和女孩堕胎服务的限制、母婴健康状况以及儿童贫困率的影响。同时,回答还提到了裁决的全球影响,包括地缘政治和文化影响力、对其他国家反堕胎立法的潜在影响以及在国际政策领域造成的寒蝉效应。', answer_sufficiency_result=1)]

生成数据如下:

1
2
3
4
5
[
Generated_Query(query='美国最高法院关于堕胎的裁决对哪些方面产生了影响?', answer_sufficiency='回答详细描述了美国最高法院关于堕胎的裁决对美国国内各州、母婴健康、儿童贫困以及全球地缘政治和文化影响力等方面的影响。', answer_sufficiency_result=1),
Generated_Query(query='美国最高法院关于堕胎的裁决对美国国内和国际的影响是什么?', answer_sufficiency='回答详细讨论了美国最高法院关于堕胎裁决的国内影响,包括对生育年龄女性和女孩堕胎服务的限制、母婴健康状况以及儿童贫困率的影响。同时,回答也提到了裁决的国际影响,包括对其他国家反堕胎立法和政策的影响,以及在国际政策领域对人权保护的潜在削弱。回答内容丰富且具体,直接回应了问题。', answer_sufficiency_result=1),
Generated_Query(query='美国最高法院关于堕胎的裁决对美国国内和国际产生了哪些影响?', answer_sufficiency='回答详细描述了美国最高法院关于堕胎的裁决对美国国内的影响,包括堕胎服务的限制、母婴健康状况以及儿童贫困率的变化,同时也提到了这一裁决对国际社会的影响,例如对其他国家立法的影响和在国际政策领域的寒蝉效应。', answer_sufficiency_result=1)
]
  • query 字段将用来计算相似度
  • answer_sufficiency_result 字段用于评判回答有效性

获取问题嵌入向量

基于 OpenAIEmbeddings 使用 api 获取 embedding

1
2
3
4
5
6
7
# Set desired model
openai_embedding = OpenAIEmbeddings(
model="bge-m3",
base_url='http://localhost:9997/v1',
api_key='cannot be empty',
# dimensions=1024,
)
1
vectors = openai_embedding.embed_documents([query]+[item.query for item in generated_querys])

计算相似度并得出得分

1
2
3
real_query_vec = vectors[0]
gen_query_vec = vectors[1:]
similarity = cosine_similarity([real_query_vec], gen_query_vec)
1
similarity
array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]])

相似度有点高, 因为query也是之前用大模型批量生成用来训练embedding模型的

1
2
3
4
5
6
7
answer_sufficiencies = [item.answer_sufficiency_result for item in generated_querys]
answer_sufficiencies = np.array(answer_sufficiencies)

scores = similarity * answer_sufficiencies
aveaged_score = np.mean(scores)

print(aveaged_score)
1.000000000000001

完整代码

1
2
3
4
5
import asyncio
import nest_asyncio

# 应用 nest_asyncio 以支持 Jupyter 笔记本中的异步操作
nest_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
129
130
131
132
133
134
135
136
137
"""
# 回答相关性 Answer Relevance


## 指标原理
通过大语言模型(LLM)根据给定答案生成多个潜在问题,将这些问题与原始问题进行相似度计算,利用相似度均值来衡量答案和原始问题的契合程度,进而评估回答相关性。具体计算方式为$AR=\frac{1}{n}\sum_{i = 1}^{n}sim(q,q_{i})$,其中$AR$是答案相关性得分,$n$是生成的潜在问题数量,$sim(q,q_{i})$是原始问题$q$与生成的潜在问题$q_{i}$的相似度。

## 背景原因
在评估RAG系统时,需要全面考量多个维度,回答相关性是其中重要的一环。传统评估方式存在局限性,难以准确衡量生成答案与问题的匹配程度。该指标聚焦答案是否直接、恰当回应问题,且不考虑答案真实性,仅针对答案不完整或含冗余信息的情况进行扣分,以此更精准地评估RAG系统生成答案的质量。

## 实现流程
1. **生成潜在问题**:将给定答案$a_{s}(q)$输入LLM,利用“Generate a question for the given answer. answer: [answer]”的提示,让LLM基于答案生成$n$个潜在问题$q_{i}$。
2. **获取问题嵌入向量**:借助OpenAI API的text - embedding - ada - 002模型,获取原始问题$q$和生成的$n$个潜在问题$q_{i}$的嵌入向量。
3. **计算相似度并得出得分**:计算每个潜在问题$q_{i}$与原始问题$q$嵌入向量之间的余弦相似度$sim(q,q_{i})$,最后按照公式$AR=\frac{1}{n}\sum_{i = 1}^{n}sim(q,q_{i})$计算出答案相关性得分$AR$ ,完成对回答相关性的评估。
"""

import asyncio
import numpy as np
from typing import List, Dict
from langchain.schema.runnable import Runnable
from langchain_openai import ChatOpenAI
from langchain_openai import OpenAIEmbeddings
from sklearn.metrics.pairwise import cosine_similarity
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field


class Generated_Query(BaseModel):
query: str = Field(description="生成的问题内容。")
answer_sufficiency: str = Field(description='对于生成的问题, 分析回答是否是清晰地回答。')
answer_sufficiency_result: int = Field(description='如果回答清晰地回答生成的问题,为1,否则为0。')


class Answer_Relevance(Runnable):
"""
评估回答和问题的相关程度, 好的回答能够推导出对应的问题
"""

def __init__(self, llm: ChatOpenAI, embedding_model: OpenAIEmbeddings):
self.llm = llm
self.embedding_model = embedding_model

self.query_parser = PydanticOutputParser(pydantic_object=Generated_Query)

generate_query_prompt = (
"为给定的回答生成一个问题,并分析判断回答是否是清晰地回答。"
"如果回答清晰地回答生成的问题,为1,否则为0。"
"清晰的回答是直接回答问题、不带有歧义和模糊处理的回答;反之,含糊的的回答是指回避、含糊或模棱两可的回答。\n"
"输出格式:\n{output_format_instructions}\n\n"
"[回答-开始]\n{answer}\n[回答-结束]\n"
)
self.generate_query_prompt = PromptTemplate(
template=generate_query_prompt,
partial_variables={
"output_format_instructions": self.query_parser.get_format_instructions()
}
)
self.generate_query_chain = self.generate_query_prompt | self.llm | self.query_parser

def generate_pseudo_query(self, answer: str, num_of_gen: int = 10):
"""
异步生成多个伪问题
:param answer: 回答文本
:param num_of_gen: 生成问题的数量
:return: 伪问题列表
"""
tasks = [
self.generate_query_chain.ainvoke({'answer': answer})
for _ in range(num_of_gen)
]
loop = asyncio.get_event_loop()
pseudo_querys = loop.run_until_complete(asyncio.gather(*tasks))

return pseudo_querys

def compute_score(self, real_query: str, pseudo_querys: List[Generated_Query]):
"""
计算回答和问题的相关性得分
:param real_query: 实际问题
:param pseudo_querys: 伪问题列表
:return: 计算的相关性得分
"""
data = [real_query] + [item.query for item in pseudo_querys]
vectors = self.embedding_model.embed_documents(data)

# 分别提取实际问题和伪问题的向量
real_query_vec = vectors[0]
pseudo_query_vec = vectors[1:]

# 计算余弦相似度
similarity = cosine_similarity([real_query_vec], pseudo_query_vec)

# 获取伪问题的回答充分性结果
answer_sufficiencies = [item.answer_sufficiency_result for item in pseudo_querys]
answer_sufficiencies = np.array(answer_sufficiencies)

# 计算最终的相关性得分
scores = similarity.flatten() * answer_sufficiencies
aveaged_score = np.mean(scores)

return aveaged_score

def invoke(self, inputs: Dict[str, List[str]], num_of_gen: int=10) -> float:
"""
异步评估回答与问题的相关度
:param inputs: 包含问题 `query` 和回答 `answer` 的字典
:param num_of_gen: 生成伪问题的数量
:return: 计算的相关性得分
"""

query = inputs["query"]
answer = inputs["answer"]

pseudo_querys = self.generate_pseudo_query(answer, num_of_gen)
score = self.compute_score(query, pseudo_querys)

return score

async def ainvoke(self, inputs: Dict[str, List[str]], num_of_gen: int = 10) -> float:
"""
异步评估回答与问题的相关度
:param inputs: 包含问题 `query` 和回答 `answer` 的字典
:param num_of_gen: 生成伪问题的数量
:return: 计算的相关性得分
"""
query = inputs["query"]
answer = inputs["answer"]

# 生成伪问题
pseudo_querys = await asyncio.to_thread(self.generate_pseudo_query, answer, num_of_gen)

# 计算相关性得分
score = await asyncio.to_thread(self.compute_score, query, pseudo_querys)
return score


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
llm = ChatOpenAI(
base_url='http://localhost:5551/v1',
api_key='EMPTY',
model_name='Qwen2.5-7B-Instruct',
temperature=0.5,
)

openai_embedding = OpenAIEmbeddings(
model="bge-m3",
base_url='http://localhost:9997/v1',
api_key='cannot be empty',
)

tool = Answer_Relevance(llm, openai_embedding)

inputs = {
"query":"美国最高法院关于堕胎的裁决对全球有什么影响?",
"answer":"美国最高法院关于堕胎的裁决具有重要的全球影响。该裁决导致在堕胎访问受到限制的州,三分之一的生育年龄女性和女孩无法获得堕胎服务。那些州的母婴健康支持也较弱,母亲死亡率较高,儿童贫困率也较高。此外,裁决的影响超出了国界,由于美国在全球的地缘政治和文化影响力,这一裁决也产生了跨国影响。全球的组织和活动家担心这一裁决可能会激励其他国家出台反堕胎的立法和政策。裁决还妨碍了某些非洲国家的进步法律改革和堕胎指南的实施。此外,该裁决在国际政策领域造成了寒蝉效应,使得反堕胎的力量能够削弱人权保护。",
}
score = tool.invoke(inputs)
print(score)
Comments