在上一篇踩坑记中,基于我对编程框架的理解,直觉上认为,RAGAS 中的 Metric 作为最终执行层,理论上为了更好地拓展性,都是可以重写里的执行代码,这样面对各种独特的检测需求时,会非常的灵活。
最近终于有时间去检验这个想法。
这次重写的内容是 RAGAS 自带的 faithfullness(答案忠诚度)。
faithfullness
还是推荐各位不太理解概念的,先读一下这篇文章高级RAG(四):RAGAs评估-CSDN博客。
答案忠诚度,是指程序给出的答案,是否可以在背景知识中找到,能找到多少,找到的越多,分数越高,最高1分,最低0分。
源码很长,就不单独上了,想要深入了解的可以自己去 GitHub 查看。
在源码中可以看到,FaithFullness
类是继承自 MetricWithLLM
类的,里面只是将 _score_batch
方法进行了重写,而重写的内容也非常简单,就是将答案分成了几个部分,然后分别针对这些内容去判断是否是上下文中包含的内容,如果是则算1分,不是算0分,然后算平均值,就得到了这一条数据的忠诚度。
逻辑很简单,只是这个分数都是通过 GPT 的答案来的,也算是某种意义上的自适应了。
而要改写的话也很简单,因为计算逻辑很简单,也很有效,我们肯定就不需要再去改了。只需要修改 Prompt 中的内容,让得到的结果更符合我们需要的。
所以接下来要去了解一下 Faithfullness
的 Prompt 中都写了什么。
Prompt
这里的 Prompt 分成了两部分。
第一部分是将你的问题和回答传入,GPT 将答案解构成多个部分。
question: Who was Albert Einstein and what is he best known for?
answer: He was a German-born theoretical physicist, widely acknowledged to be one of the greatest and most influential physicists of all time. He was best known for developing the theory of relativity, he also made important contributions to the development of the theory of quantum mechanics.
statements in json:
{{
"statements": [
"Albert Einstein was born in Germany.",
"Albert Einstein was best known for his theory of relativity."
]
}}
然后是第二部分,将你解构后的内容与上下文对比,让 GPT 来判断,这部分的答案是否符合上下文的含义。
Context:
Photosynthesis is a process used by plants, algae, and certain bacteria to convert light energy into chemical energy.
statement_1: Albert Einstein was a genius.
Answer:
[
{{
"statement_1": "Albert Einstein was a genius.",
"reason": "The context and statement are unrelated"
"verdict": "No"
}}
]
符合的话,verdict
就是 Yes
,否则就是No
。
从 Prompt 示例中可以看到,举得例子都是偏问答类、解释类的,核心面向的场景就是问答。
但是我们自己的产品或功能,如果直接套用这个示例的话,很可能会变得不伦不类,得不到想要的回答。
重写
比如,我们公司有一个功能,是想要利用 GPT 来给用户反馈进行分类和打标签,大致的内容可能类似如下:
问题:可以帮我将下面的内容打上符合的标签或者确认所属模块吗?
用户反馈:简书的关注列表是按我最近关注的来排序的,经常让我错失一些非常棒的更新。
返回的标签或模块:["关注列表", "功能优化", "BUG"]
我想利用原生的 faithfullness
去生成第一部分(即,将答案解构成多个描述)会得到如下结果:
{
"statements": [
"将下面的内容打上符合的标签",
"确认所属模块"
]
}
emmm,看得出来,这是把我的问题给梳理成了多段了,并没有把我的答案梳理成多段。可见,在这个场景下,使用原生的 faithfullness
并不能得到我们想要的结果。
而出现这个问题的原因用很简单——Prompt中的示例在作怪。
第一部分的示例很明显是将问题+答案整合在一起,产生了一个返回结果,而我们只需要修改这个示例,让示例中的解构内容符合我们的期望,就可以避开这个问题。
question: Please help me label this sentence or distinguish the modules it belongs to?
answer: "Bugs", "Work List Module"
statements in json:
{{
"statements": [
"This sentence seems to be describing a bug.",
"This sentence seems to belong to the work list module"
]
}}
这样,再下一次生成解构内容时,就会参考我们示例中的逻辑进行拆分。
这里其实暗含了一个不好理解的点,就是用户反馈都没有放进去,就直接放答案,那GPT怎么理解这个答案是怎么来的呢?
其实这个就牵扯到了第二部分了,第一部分GPT其实不需要理解上下文,只需要按照我们示例的格式去将内容解构,而上下文是在第二部分发挥作用。
当然,这部分马上介绍,先贴一下修改后得到的结果。(源码中不止这一个地方要改,详细内容仔细翻看一下源码。)
{
"statements": [
"这句话似乎描述了一个关注列表",
"这句话似乎属于功能优化模块",
"这句话似乎描述了一个BUG"
]
}
OK,非常符合我的预期。继续。
按照相同的逻辑,只需要修改第二部分的示例为如下内容(源码不止一个地方)
Context:
It's obviously unreasonable that deleting posts cannot be done in bulk.
statement_1: This sentence seems to be describing a design issue.
statement_2: This sentence seems to belong to the work list module.
Answer:
[
{{
"statement_1": "This sentence seems to be describing a design issue.",
"reason": "The context seems to be complaining about the lack of corresponding functions, so it is a functional design issue",
"verdict": "Yes"
}},
{{
"statement_2": "This sentence seems to belong to the work list module.",
"reason": "The context mainly points out the issue of post management, not the work list.",
"verdict": "No"
}}
]
再次 Debug 查看第二段 Prompt 返回的内容如下:
[
{
"statement_1": "这句话似乎描述了一个关注列表",
"reason": "The context is complaining about the sorting of the follow list, so it is related to the follow list module",
"verdict": "Yes"
},
{
"statement_2": "这句话似乎属于功能优化模块",
"reason": "The context is not discussing functional optimization, but rather the issue with the follow list sorting",
"verdict": "No"
},
{
"statement_3": "这句话似乎描述了一个BUG",
"reason": "The context is not explicitly mentioning a bug, but rather a dissatisfaction with the follow list sorting",
"verdict": "No"
}
]
诶,可以看到,已经可以返回我们期望的内容了,但是中间还有一个小问题,就是把功能优化识别成了模块,所以判定为错了。针对这个我们,我们可以在第一步的时候给出 Prompt 去告知 GPT 模块通常只能有一个,就会大大减少这种情况的出现,当然,作为文档,这里就不去纠结了。
总之,通过改写 faithfullness
,终于可以利用 RAGAS 去正确的判断打标签这个功能的效果了。
后记
虽然自己已经比较确定 Metric 是可以自定义的,但是没想到过程如此顺利,只需要简单的修改 Prompt ,保证改写后的输出内容和此前一致,无需修改计算规则,即可顺利的得到结果。
而且从 RAGAS 这里,还看到了一些自适应的影子,由 GPT 去判断 GPT 的效果,哈哈,感觉大家都想到一块去了。这里面还能做特别多的拓展,留待后续慢慢研究了。