L2-6 通用文档智能问答系统商业化实战
·
通用文档智能问答系统商业化实战
一、logger.py
import os
import sys
from loguru import logger
def setup_logger(log_dir="logs"):
"""
配置日志
Args:
log_dir: 日志目录
"""
# 确保日志目录存在
os.makedirs(log_dir, exist_ok=True)
# 移除默认处理器
logger.remove()
# 添加控制台处理器
logger.add(
sys.stderr,
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
level="INFO"
)
# 添加文件处理器
logger.add(
os.path.join(log_dir, "doc_qa_{time:YYYY-MM-DD}.log"),
rotation="00:00", # 每天轮换
retention="30 days", # 保留30天
format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}",
level="DEBUG"
)
return logger
二、models.py
import os
import inspect
from langchain_openai import ChatOpenAI
from langchain_community.document_compressors import DashScopeRerank
from langchain_community.embeddings import DashScopeEmbeddings
ALI_TONGYI_API_KEY_OS_VAR_NAME = "DASHSCOPE_API_KEY"
ALI_TONGYI_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
ALI_TONGYI_MAX_MODEL = "qwen-max-latest"
ALI_TONGYI_DEEPSEEK_R1 = "deepseek-r1"
ALI_TONGYI_DEEPSEEK_V3 = "deepseek-v3"
ALI_TONGYI_REASONER_MODEL = "qvq-max-latest"
ALI_TONGYI_EMBEDDING_MODEL = "text-embedding-v3"
ALI_TONGYI_RERANK_MODEL = "gte-rerank-v2"
def get_lc_model_client(api_key=os.getenv(ALI_TONGYI_API_KEY_OS_VAR_NAME), base_url=ALI_TONGYI_URL,
model=ALI_TONGYI_MAX_MODEL, temperature=0.7, max_tokens=8000, verbose=False, debug=False):
"""
通过LangChain获得指定平台和模型的客户端,设定的默认平台和模型为阿里百炼qwen-max-latest
也可以通过传入api_key,base_url,model三个参数来覆盖默认值
verbose,debug两个参数,分别控制是否输出调试信息,是否输出详细调试信息,默认不打印
"""
function_name = inspect.currentframe().f_code.co_name
if (verbose):
print(f"{function_name}-平台:{base_url},模型:{model},温度:{temperature}")
if (debug):
print(f"{function_name}-平台:{base_url},模型:{model},温度:{temperature},key:{api_key}")
return ChatOpenAI(api_key=api_key, base_url=base_url, model=model, temperature=temperature, max_tokens=max_tokens)
def get_ali_embeddings():
"""通过LangChain获得一个阿里通义千问嵌入模型的实例"""
return DashScopeEmbeddings(
model=ALI_TONGYI_EMBEDDING_MODEL,
dashscope_api_key=os.getenv(ALI_TONGYI_API_KEY_OS_VAR_NAME)
)
def get_ali_rerank(top_n=10):
'''
通过LangChain获得一个阿里重排序模型的实例
:return: 阿里通义千问嵌入模型的实例
'''
return DashScopeRerank(
model=ALI_TONGYI_RERANK_MODEL,
dashscope_api_key=os.getenv(ALI_TONGYI_API_KEY_OS_VAR_NAME),
top_n=top_n
)
三、combine_client.py
import logging
from typing import Iterable
from loguru import logger
from knowledge import MyKnowledge
from models import ALI_TONGYI_MAX_MODEL, get_lc_model_client
from langchain_openai import ChatOpenAI
from langchain.chains.retrieval import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import AIMessageChunk
from langchain_core.runnables import AddableDict
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
# # 这行代码配置了日志记录的基本设置。它调用 logging.basicConfig(),这会对日志记录进行基本配置,例如设置日志记录格式、日志文件等。
# # 这里没有提供具体参数,所以使用默认配置,这通常包括在控制台输出日志消息。
# logging.basicConfig()
#
# # 这行代码获取名为 "langchain.retrievers.multi_query" 的日志记录器,并将其日志级别设置为 INFO。
# # 这样,任何由这个记录器产生的 INFO 级别及以上的日志消息(INFO、WARNING、ERROR、CRITICAL)都会被输出。
# logging.getLogger("langchain.retrievers.multi_query").setLevel(logging.INFO)
#
# # 这行代码获取名为 "langchain.retrievers.re_phraser" 的日志记录器,并将其日志级别设置为 INFO。
# # 同样,任何由这个记录器产生的 INFO 级别及以上的日志消息都会被输出。
# logging.getLogger("langchain.retrievers.re_phraser").setLevel(logging.INFO)
# 知识库问答指令
qa_system_prompt = """你是一名知识问答助手,
你将使用检索到的上下文来回答问题。如果你不知道答案,就说你没有找到答案。 "
"\n\n"
"{context}"
"""
qa_prompt = ChatPromptTemplate.from_messages(
[
("system", qa_system_prompt),
("placeholder", "{chat_history}"),
("human", "{input}"),
]
)
normal_prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个帮助人们解答各种问题的助手。"),
("placeholder", "{chat_history}"),
("human", "{input}"),
]
)
def streaming_parse(chunks: Iterable[AIMessageChunk]):
for chunk in chunks:
yield AddableDict({'answer': chunk.content})
class CombineClient(MyKnowledge):
"""
负责和大模型进行交互,并支持聊天历史记录;负责和知识库进行交互
"""
__chat_history = ChatMessageHistory()
def get_chain(self, collection, model, max_length, temperature):
"""
根据具体的对话场景,返回一个链
:param collection: 用户选择的知识库
:param model: 选择的model
:param max_length: 模型参数,最大文本长度
:param temperature: 模型参数,温度
:return: 一个可以处理会话历史的链
"""
retriever = None
logger.info(f"collection: {collection}")
if collection:
retriever = self.get_retrievers(collection)
logger.debug(f"[{collection}]检索器为: {retriever}")
# 只保留3个记录
logger.info(f"len: {self.__chat_history.messages}####:{len(self.__chat_history.messages)}")
if len(self.__chat_history.messages) >= 6:
self.__chat_history.messages = self.__chat_history.messages[-6:]
logger.info(f"self.__chat_history.messages: {self.__chat_history.messages}")
chat = get_lc_model_client(model=model, max_tokens=max_length, temperature=temperature)
if retriever:
# 创建一个问答链
question_answer_chain = create_stuff_documents_chain(chat, qa_prompt)
# 创建一个检索增强生成链(RAG),将检索器和问答链结合,使得模型在生成回答时可以参考检索到的内容。
rag_chain = create_retrieval_chain(retriever, question_answer_chain)
logger.info(f"产生一个RAG链......")
logger.debug(f"rag_chain: {rag_chain}")
else:
# 如果没有检索器,则采用普通的提示(prompt),通过聊天和流式解析来生成回答。
rag_chain = normal_prompt | chat | streaming_parse
logger.info(f"产生一个普通问答链......")
logger.debug(f"normal_chain: {rag_chain}")
# 创建对话历史链
chain_with_history = RunnableWithMessageHistory(
rag_chain,
lambda session_id: self.__chat_history,
input_messages_key="input",
history_messages_key="chat_history",
output_messages_key="answer",
)
''' 需要注意:output_messages_key,如果是无知识库的情况下是从AIMessageChunk的Content取,
知识库是返回 AddableDict('answer') '''
logger.debug(f"当前的处理链: {chain_with_history}")
return chain_with_history
def invoke(self, question, collection, model=ALI_TONGYI_MAX_MODEL, max_length=256, temperature=1):
return self.get_chain(collection, model, max_length, temperature).invoke(
{"input": question},
{"configurable": {"session_id": "unused"}},
)
def stream(self, question, collection, model=ALI_TONGYI_MAX_MODEL, max_length=256, temperature=1):
return self.get_chain(collection, model, max_length, temperature).stream(
{"input": question},
{"configurable": {"session_id": "unused"}},
)
def clear_history(self) -> None:
self.__chat_history.clear()
def get_history_message(self):
return self.__chat_history.messages
四、custom_loader.py
from loguru import logger
from unstructured.file_utils.filetype import FileType, detect_filetype
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.document_loaders import BaseLoader
from langchain_community.document_loaders \
import (CSVLoader, TextLoader, UnstructuredWordDocumentLoader, PyPDFLoader, UnstructuredMarkdownLoader)
class MyCustomLoader(BaseLoader):
"""
文档加载和分割模块
"""
# 支持加载的文件类型
file_type = {
FileType.CSV: (CSVLoader, {'autodetect_encoding': True}),
FileType.TXT: (TextLoader, {'autodetect_encoding': True}),
FileType.DOC: (UnstructuredWordDocumentLoader, {}),
FileType.DOCX: (UnstructuredWordDocumentLoader, {}),
FileType.PDF: (PyPDFLoader, {}),
FileType.MD: (UnstructuredMarkdownLoader, {})
}
# 初始化方法,设置文档加载器和文本分割器
def __init__(self, file_path: str):
loader_class, params = self.file_type[detect_filetype(file_path)]
logger.info(f"本文档[{file_path}]需使用文档加载器: {loader_class}")
self.loader: BaseLoader = loader_class(file_path, **params)
self.text_splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", " ", ""],
chunk_size=500,
chunk_overlap=200,
length_function=len,
)
def lazy_load(self):
# 文档的切分加载
return self.loader.load_and_split(self.text_splitter)
def load(self):
# 加载
return self.lazy_load()
五、knowledge.py
import os
import shutil
import hashlib
from typing import Optional
from loguru import logger
from custom_loader import MyCustomLoader
from models import get_lc_model_client, get_ali_embeddings, get_ali_rerank
from langchain_chroma import Chroma
from langchain.indexes import SQLRecordManager
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import ContextualCompressionRetriever, RePhraseQueryRetriever, EnsembleRetriever
from langchain.retrievers.document_compressors import LLMChainFilter, CrossEncoderReranker
from langchain_core.document_loaders import BaseLoader
from langchain_core.embeddings import Embeddings
from langchain_core.indexing import index
# 设置知识库 向量模型 重排序模型的路径
KNOWLEDGE_DIR = './chroma/knowledge/'
embedding_model = get_ali_embeddings()
class MyKnowledge:
"""
知识库管理模块
"""
# 向量化模型
__embeddings = embedding_model
logger.info(f"当前嵌入模型: {__embeddings.model}")
__retrievers = {}
__llm = get_lc_model_client()
def upload_knowledge(self, temp_file):
"""
处理原始文档的上传,并负责启动文档索引过程
"""
file_name = os.path.basename(temp_file)
file_path = os.path.join(KNOWLEDGE_DIR, file_name)
# 如果文件不存在就copy
if not os.path.exists(file_path):
os.makedirs(os.path.dirname(file_path), exist_ok=True)
shutil.copy(temp_file, file_path)
import gradio as gr
return None, gr.update(choices=self.load_knowledge())
def load_knowledge(self):
# exist_ok=True目标目录已存在的情况下不会抛出异常。
# 这意味着如果目录已经存在,os.makedirs不会做任何事情,也不会报错
os.makedirs(os.path.dirname(KNOWLEDGE_DIR), exist_ok=True)
# 知识库默认为空
collections = [None]
logger.info(f"当前知识库文件列表: {os.listdir(KNOWLEDGE_DIR)}")
for file in os.listdir(KNOWLEDGE_DIR):
# 将知识库进行添加
collections.append(file)
# 得到知识库的路径
file_path = os.path.join(KNOWLEDGE_DIR, file)
logger.info(f"文件路径: {file_path}")
# 简单化处理知识库名称,由文件名md5编码得到
collection_name = get_md5(file)
logger.info(f"知识库名称: {collection_name}")
logger.info(f"self.__retrievers: {self.__retrievers}")
if collection_name in self.__retrievers:
continue
# 创建对应加载器
loader = MyCustomLoader(file_path)
# 检索
self.__retrievers[collection_name] = create_indexes(collection_name, loader, self.__embeddings)
logger.info(f"检索器详情: {self.__retrievers}")
return collections
def get_retrievers(self, collection):
collection_name = get_md5(collection)
logger.info(f"知识库名字md5: {collection_name}")
if collection_name not in self.__retrievers:
logger.info(f"self.__retrievers:: {self.__retrievers:}")
return None
retriever = self.__retrievers[collection_name]
logger.info(f"get_retrievers中: {retriever}")
"""
ContextualCompressionRetriever:在上下文中压缩和优化检索结果
结合了基础压缩器(base_compressor)和基础检索器(base_retriever),以减少不相关信息,返回更为精炼的检索结果。
"""
compression_retriever = ContextualCompressionRetriever(
# 初始化一个 LLMChainFilter实例。该实例会使用大语言模型来执行复杂的文本过滤逻辑。
base_compressor=LLMChainFilter.from_llm(self.__llm),
# 利用语言模型对查询进行重述或重新表述,以提取问题的关键元素,从而优化检索过程。
base_retriever=RePhraseQueryRetriever.from_llm(retriever, self.__llm)
)
# 用于对检索结果进行重新排序
rerank_retriever = get_ali_rerank(top_n=3)
# 结合了基础检索器和压缩器的功能,先从数据库中检索候选文档,然后对这些文档进行压缩或过滤,以返回最相关的结果。
final_retriever = ContextualCompressionRetriever(
base_compressor=rerank_retriever,
base_retriever=compression_retriever
)
logger.debug(f"最终检索器为: {final_retriever}")
return final_retriever
# 创建索引
def create_indexes(collection_name: str, loader: BaseLoader, embedding_function: Optional[Embeddings] = None):
# 初始化Chroma数据库
db = Chroma(collection_name=collection_name,
embedding_function=embedding_function,
persist_directory=os.path.join('./chroma', collection_name))
# 初始化记录管理器:管理与文档相关的元数据和检索信息,并将这些数据存储在一个SQL数据库中
record_manager = SQLRecordManager(
f"chromadb/{collection_name}",
db_url="sqlite:///db/record_manager_cache.db"
)
"""在文档被索引到Chroma数据库之前,SQLRecordManager会管理这些文档的索引记录。它确保文档的索引状态被正确记录,避免重复索引或遗漏。"""
logger.info(f"record_manager: {record_manager}")
# 初始化数据库表结构
record_manager.create_schema()
"""
在你开始加载和索引文档之前,调用create_schema() 方法,确保用于存储文档元数据和索引信息的数据库表已经存在。
如果是第一次运行这个脚本,create_schema()方法会自动创建这些表。对于后续运行,如果表已经存在,则该方法不会重复创建,而是直接通过。
"""
logger.info("准备进行文件的加载切分....")
documents = loader.load()
logger.info(f"文档切分数量: {len(documents)}....")
logger.debug(f"documents: {documents}")
# 将加载的文档索引到数据库中
r = index(documents, record_manager, db, cleanup="full", source_id_key="source")
logger.info(f"文档索引结果为: {r}")
"""
num_added: 0 表示没有新文档被添加到数据库中。
num_updated: 0 表示没有文档被更新。
num_skipped: 8 表示有8个文档被跳过,没有被索引到数据库中。 提高索引效率
num_deleted: 0 表示没有文档被删除。
"""
'''
混合检索,将稀疏检索器(如BM25)与密集检索器(如嵌入相似性)相结合。
稀疏检索器擅长根据关键字查找相关文档,而密集检索器擅长根据语义相似性查找相关文档。
'''
ensemble_retriever = EnsembleRetriever(
# 返回最相似的3个文档
retrievers=[db.as_retriever(search_kwargs={"k": 3}), BM25Retriever.from_documents(documents)]
)
# logger.info(f"ensemble_retriever: {ensemble_retriever}")
return ensemble_retriever
def get_md5(input_string):
# 创建一个 md5 哈希对象
hash_md5 = hashlib.md5()
# 需要确保输入字符串是字节串,因此如果它是字符串,则需要编码为字节串
hash_md5.update(input_string.encode('utf-8'))
# 获取十六进制的哈希值
return hash_md5.hexdigest()
六、main.py
# 导入Gradio库,用于创建交互式Web应用程序
import gradio as gr
import nltk
nltk.download('punkt')
nltk.download('averaged_perceptron_tagger')
from loguru import logger
from logger import setup_logger
# 从llm模块中导入MyLLM类,这是自定义的大型语言模型接口
from combine_client import CombineClient
from models import ALI_TONGYI_MAX_MODEL, ALI_TONGYI_DEEPSEEK_R1, ALI_TONGYI_DEEPSEEK_V3
# 定义一个包含两个LLM模型名称的列表,供用户选择
LLM_MODELS = [ALI_TONGYI_MAX_MODEL, ALI_TONGYI_DEEPSEEK_R1, ALI_TONGYI_DEEPSEEK_V3]
# 实例化MyLLM类,用于后续的模型调用和处理
llm = CombineClient()
setup_logger()
# 定义submit函数,用于处理用户提交查询后处理页面展示信息
def submit_show(query, chat_history):
# logger.debug(f'query:{query}')
# logger.debug(f'chat_history:{chat_history}')
# 如果查询为空字符串,返回空字符串和当前的聊天记录
if query == '':
return '', chat_history
# 如果查询不为空,将查询添加到聊天记录中,并返回更新后的聊天记录
chat_history.append([query, None])
# show_his = ('', chat_history)
# logger.debug(f"('', chat_history):{show_his}")
return '', chat_history
# 定义llm_reply函数,用于生成模型回复
def llm_reply(collection, chat_history, model, max_length=256, temperature=1):
question = chat_history[-1][0]
logger.info(f'用户查询问题:{question}')
# 使用流式生成方法从模型中获取回复
response = llm.stream(question, collection, model=model, max_length=max_length, temperature=temperature)
chat_history[-1][1] = ""
logger.debug(f"chat_history:{chat_history}")
# 逐块处理模型生成的回复
for chunk in response:
logger.debug(f"chunk:{chunk}")
if 'context' in chunk:
# 如果块中包含上下文信息,则打印出来
for doc in chunk['context']:
logger.debug(f"doc:{doc}")
if 'answer' in chunk:
# 如果块中包含答案,则将其追加到聊天记录的最后一个条目中
chunk_content = chunk['answer']
logger.debug(f"chunk_content:{chunk_content}")
if chunk_content is not None:
chat_history[-1][1] += chunk_content
# 返回更新后的聊天记录
yield chat_history
logger.debug(f"chat_history:{chat_history}")
# 创建一个Gradio Blocks应用,设置fill_height为True
with (gr.Blocks(fill_height=True) as demo):
# 在应用中添加一个HTML元素,显示标题
gr.HTML("""<h1 align="center">通用文档分析助手(本程序正常运行需要科学上网)</h1>""")
# 创建一个新的行布局
with gr.Row():
# 创建一个占比为 4 的列布局
with gr.Column(scale=4):
# 创建一个下拉菜单,用于选择LLM模型
model = gr.Dropdown(
choices=LLM_MODELS,
value=LLM_MODELS[0],
label="LLM Model",
interactive=True,
scale=1
)
# 创建一个聊天机器人界面
chatbot = gr.Chatbot(show_label=False, scale=3, show_copy_button=True)
# 创建一个占比为 1 的列布局,显示进度
with gr.Column(scale=1, show_progress=True) as column_config:
# 创建一个滑块,用于设置生成回复的最大长度
max_length = gr.Slider(1, 8000, value=4000, step=100, label="模型回复最大长度", interactive=True)
# 创建一个滑块,用于设置生成回复的温度
temperature = gr.Slider(0, 1.9, value=0.7, step=0.1, label="温度", interactive=True)
# 创建一个按钮,用于清除聊天记录
clear = gr.Button("清除")
# 创建一个下拉菜单,用于选择知识库
collection = gr.Dropdown(choices=llm.load_knowledge(), label="知识库")
# 创建一个文件上传控件,支持多种文件类型
file = gr.File(label="上传文件", file_types=['doc', 'docx', 'csv', 'txt', 'pdf', 'md'])
# 创建一个文本框,用于用户输入
user_input = gr.Textbox(placeholder="Input...", show_label=False)
# 创建一个按钮,用于提交用户输入
user_submit = gr.Button("提交")
# 绑定 clear 按钮的点击事件,清除模型历史记录,并更新聊天机器人界面
clear.click(fn=llm.clear_history, inputs=None, outputs=[chatbot])
user_input.submit(fn=submit_show,
inputs=[user_input, chatbot], # 一个是用户输入 一个当前的聊天记录
outputs=[user_input, chatbot] # 一个是为了清空用户输入的文本框 另一个是更新后的聊天记录,将新的用户查询添加到聊天记录中。
).then(
fn=llm_reply,
inputs=[collection, chatbot, model, max_length, temperature],
# collection: 用户选择的知识库。
# chatbot: 当前的聊天记录(已经包含用户的新查询)。
# model: 用户选择的 LLM 模型。
# max_length: 用户设置的生成回复的最大长度。
# temperature: 用户设置的生成回复的温度。
outputs=[chatbot]
# 更新后的聊天记录,将模型生成的回复添加到聊天记录中。
)
# 绑定用户输入文本框的提交事件,
# 先调用submit函数,
# 然后调用llm_reply函数,
# 并更新聊天机器人界面
user_submit.click(fn=submit_show,
inputs=[user_input, chatbot],
outputs=[user_input, chatbot]
).then(
fn=llm_reply,
inputs=[collection, chatbot, model, max_length, temperature],
outputs=[chatbot]
)
# 绑定提交按钮的点击事件,先调用submit函数,
# 然后调用llm_reply函数,并更新聊天机器人界面
# 绑定文件上传控件的上传事件,调用upload_knowledge函数,并更新文件控件和知识库下拉菜单
file.upload(fn=llm.upload_knowledge, inputs=[file], outputs=[file, collection])
# 绑定知识库下拉菜单的更改事件,调用clear_history函数,并更新聊天机器人界面 也就是换一个知识库就清空当前的页面
collection.change(fn=llm.clear_history, inputs=None, outputs=[chatbot])
# 绑定应用加载事件,调用clear_history函数,并更新聊天机器人界面
demo.load(fn=llm.clear_history, inputs=None, outputs=[chatbot])
# 启动 Gradio 应用
demo.launch()
七、rags_eval.py
from models import get_lc_model_client, get_ali_embeddings
from datasets import Dataset
from combine_client import CombineClient
from ragas import evaluate
from ragas.llms import LangchainLLMWrapper
from ragas.embeddings import LangchainEmbeddingsWrapper
from ragas.metrics import (
answer_relevancy,
faithfulness,
context_recall,
context_precision,
)
llm = CombineClient()
llm.load_knowledge()
response = llm.invoke("请假流程", "人事管理流程.docx")
print("大模型答复:", response['answer'])
truth = '''1.普通员工申请请假/加班/调休:由直属领导、事业部总监、人事行政主管审批。抄送考勤组、请假部门、人事行政经理;
2.主管及以上人员申请请假/调休/请假≥3天:由直属领导、事业部总监、人事行政经理、总经理审批。抄送考勤组、请假部门、人事行政主管、股东会成员;
3.原则上所有请假/加班/调休必须在钉钉系统进行申请并批准后方可执行;因紧急特殊原因请假/调休,可以电话获得部门经理同意后24小时内申请,逾期未申请相关审批人员可不予准假,否则视为旷工。
4.常规请假需提前1个工作日在钉钉系统进行申请,请假3天及以上人员需提前3天申请,并注明请假时间、类型、事由,未按要求提前申请可不予准假,请假审批通过后方可休假,否则按旷工处理;'''
questions = [response['input']] # 问题
answers = [response['answer']] # 回答
contexts = [[doc.page_content for doc in response['context']]] # 文档
ground_truth = [truth] # 真实答案
data_samples = {
"question": questions,
"answer": answers,
"contexts": contexts,
"ground_truth": ground_truth
}
dataset = Dataset.from_dict(data_samples)
llms = get_lc_model_client()
embedding = get_ali_embeddings()
vllm = LangchainLLMWrapper(llms)
vllm_e = LangchainEmbeddingsWrapper(embedding)
result = evaluate(
dataset,
llm=vllm,
embeddings=vllm_e,
metrics=[
context_precision,
context_recall,
faithfulness,
answer_relevancy,
],
)
print(result)
# {'context_precision': 1.0000, 'context_recall': 1.0000, 'faithfulness': 1.0000, 'answer_relevancy': 0.7289}
更多推荐



所有评论(0)