通用文档智能问答系统商业化实战

一、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}
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐