开始进入到ragas的打分环节了,首先进行个人认为最重要的faithfulness,也就是可信度/忠诚度,可能大家更喜欢叫它幻觉率,因为它评分的核心指标是检索文档和回答答案的相关性,所以答案有没有胡编乱造主要看这个指标。不过在正式跑ragas框架前,需要先写个适配器,因为ragas框架只认Langchain合同,而我们写的是requests调接口的方式,所以进入到model.py文件,开始接着上次的代码,继续写个SystemChat类;
class SystemChat(BaseChatModel):
@property
def _llm_type(self)->str:
return "System-chat"
def _generate(self,message,stop=None,run_manager=None,**kwargs):
prompt="\\n".join(str(m.content) for m in messages)
text=ask_llm(prompt)
return ChatResult(generations=[ChatGeneration(message=AIMessage(content=text))])
写完了适配器,咱们就可以进入到打分环节了,先新建一个faithfulness.py文件
import json,os,sys
import pandas as pd
from ragas import EvaluationDataset,evaluate,RunConfig
from ragas.metrics import Faithfulness
from ragas.llms import LangchainLLMWrapper
#路径锚定,不管从哪里跑,data都指向所在目录
BASE_DIR=os.path.dirname(os.path.abspath(__file__)))
DATA_DIR=os.path.join(BASE_DIR,"data")
sys.path.insert(0,BASE_DIR)
from db import es
from models import SystemChat
MAX_CONTEXTS=20#配置参数,取问答接口引用的前几个切片,这里取前20个
ES_INDEX="xxxx"#es数据库实际使用的索引
上面把基础配置和需要引用的库给写好,接下来需要先做一个很重要的事,把被测系统引用的正文从ES换回来,当然这里这样写是基于被测系统在引用文档时没有把引用文章正文带出来或者带的是个文档壳而正文以附件形式存在检索文档中的情况,如果检索文档中直接含有正文则更好,下面针对的是检索时没带的情况;
def fetch_contexts(df_res):
"""reference如果只有nid的情况下"""
all_nids=sorted({ref["nid"] for refs_str in df_res["references"]
for ref in json.loads(refs_str)})
#使用nid换回正文
res=es.search(
index=ES_INDEX#指定索引里查
body={
"size":len(all_nids),
"query":{"nested":{"path":"doc_contents","query":{"terms":{"doc_content.nid":all_nids}}}},
"_source":["doc_name","doc_contents.content"],
},
)
nid2content={}
for hit in res["hits"]["hits"] :#遍历es返回的每一条主文档记录
for part in hit["_source"].get("doc_contents",[])#遍历该主文档下的所有切片
if part.get("nid") in all_nids:#过滤,将切片的nid作为key,content作为value,清理数据空格并截断长度
nid2content [part["nid"]]=str(part.get("content","")).strip()[:2000]
return nid2content
总结上面这段代码就是"批量取书"的过程,把文档的正文取出来后,就可以把题目、答案、检索文档正文等打包好进入到faithfulness的打分阶段了;
def judge_faithfulness
samples=[] #收集评分的材料
for i in range (len(df_res)):
row=df_res.iloc[i] #取出答题记录(问题/答案/检索文档)
ref_nids=[] #系统的切片清单
for ref in json.loads(row["references"]): #遍历这道题引用的每个切片
n=str(ref["nid"])
if n not in ref_nids:
ref_nids.append(n)
ref_nids=ref.nids[:MAX_CONTEXTS] #取前20个引用切片
contexts=[nid2content[n] for n in ref_nids if n in nid2content[n] #切片有正文的直接装订起来
samples.append({
"user_input":row["question"],
"response":row["response"],
"reference":row["answer"],
"retrieved_contexts":contexts,
})
dataset=EvaluationDataset.from_list(samples)#材料打包起来成ragas的评测集
print(f"已加载{len(dataset)}条,开始打分…")
judge_llm=LangchainLLMWrapper(ZhiwenChat()) #裁判模型就用出题时的模型,套langchain壳给ragas识别
run_config=RunConfig(timeout=1200,max_retries=0,max_workers=2)#单题限时最多20分钟,超时不重试,同时开两路打分加快速度
result=evaluate (dataset=dataset,metrics=[Faithfulness()],llm=judge_llm,run_config=run_config)#开判
detail=result.to_pandas() #总分拆分成每题明细
detail.to_csv(DATA_DIR+"/eval_detail_faithfulness.csv",index=False,encoding="utf-8-sig")
return result
if __name__=="__main__":
df_res=pd.read_csv(DATA_DIR+"/data_result_zhiwen.csv")
nid2content=fetch_contexts(df_res)
judge_faithfulness(df_res,nid2content)
df_detail=pd.read_csv(DATA_DIR+"/eval_detail_faithfulness.csv")
n_nan=df_detail["faithfulness"].isna().sum()
if n_nan:
print (f"\\n超时缺考{n_nan}题(NaN不计分,报分时注明有效题数")
这里算是把整套打分的流程给写完了,但是实际情况可能更复杂,当然取正文的情况也可能更容易,一切都根据看客们项目实际情况来定夺代码该怎么写,业务场景该怎么处理,下一篇写回答相关性这个指标的处理方式。
哦对了ragas框架的这些指标总分就是1分,要是这些评测集总分打出来能有0.8分以上,就是不错的分数了😄
网硕互联帮助中心


评论前必须登录!
注册