← 返回首页
RAG智能评估-LangSmith
发表时间:2025-05-19 14:01:56
RAG智能评估-LangSmith

1.什么是 LangSmith

LangSmith是LangChain的一个子产品,是一个大模型应用开发平台。它提供了从原型到生产的全流程工具和服务,帮助开发者构建、测试、评估和监控基于LangChain或其他 LLM 框架的应用程序。

2.使用 LangSmith 进行Rag评估

对于一个Rag系统的评估包含以下步骤: 1. 创建一个测试用的数据集 2. 定义一个问答系统 3. 使用 LangSmith 运行评估 4. 迭代 5. 改进系统

2.1 安装LangSmith

注意:截止到2024年12月,安装这个版本没有问题,因为每次版本更新会附带库和代码的更新。

pip install langsmith==0.1.137

2.2创建API key

打开 langsmith 官网, 地址: https://smith.langchain.com/

进行登录。

创建API Key ,选择Personal Access Token,弹出API key窗口时需要复制并保存好。

2.3 准备测试数据

导入测试数据有两种方法: - CSV文件导入 - 代码导入方式

CSV方式导入

上传.csv文件->选择并上传->预览数据。

点击create,生成了好多QA。

代码创建方式

下面的代码执行后,也可以在LangSmith中新建一个测试集。

import pandas as pd

# 读取csv文件
df = pd.read_csv("黑悟空.csv")

# 将dataFrame转换成列表元组的形式
example_inputs = list(df.itertuples(index=False, name=None))
# print(example_inputs)

from langsmith import Client
client = Client(api_key="lsv2_pt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
dataset_name = "example"

dataset = client.create_dataset(dataset_name=dataset_name)
for q, a in example_inputs:
    client.create_example(
        inputs={"问题": q}, outputs={"答案": a}, dataset_id=dataset.id
    )

2.4 使用LangSmith进行评估

测试数据准备好之后,就可以开始对系统进行评估了。LangSmith的后台可以生成评估的代码。我们可以参考官网提供的评估的代码修改优化如下(先使用不带有rag系统进行评估):

from langchain_openai import ChatOpenAI

chat_model = ChatOpenAI(
    openai_api_key="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    base_url="https://api.siliconflow.cn/v1",
    model="Qwen/Qwen2.5-7B-Instruct"
)

from langchain_core.prompts import ChatPromptTemplate
system_message = "你是一个机器人"
prompt = ChatPromptTemplate.from_messages([
    ("system", system_message),
    ("user", "{问题}")
])

from langchain_core.output_parsers import StrOutputParser
output_parser = StrOutputParser()

chain = prompt | chat_model | output_parser

import os

os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com"
os.environ["LANGCHAIN_API_KEY"] = "lsv2_pt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"


from langsmith import evaluate, Client
from langsmith.schemas import Example, Run

# 1. Create and/or select your dataset
client = Client()
dataset_name = "example"
dataset = client.clone_public_dataset("https://smith.langchain.com/public/a63525f9-bdf2-4512-83e3-077dc9417f96/d", dataset_name=dataset_name)

# 2. Define an evaluator
def is_concise_enough(root_run: Run, example: Example) -> dict:
    score = len(root_run.outputs["output"]) < 3 * len(example.outputs["答案"])
    return {"key": "is_concise", "score": int(score)}

# 3. Run an evaluation
# For more info on evaluators, see: https://docs.smith.langchain.com/concepts/evaluation#evaluators

# To evaluate an LCEL chain, replace lambda with chain.invoke
# To evaluate a LangGraph graph, replace lambda with graph.invoke
evaluate(
    lambda x: chain.invoke(x["问题"]),
    # chain.invoke
    # graph.invoke
    data=dataset_name,
    evaluators=[is_concise_enough],
    experiment_prefix="my first dataset experiment "
)

使用LangChain和LangSmith工具链,结合大语言模型(LLM)进行文本处理和评估: 1. 初始化模型与提示模板: 导入所需模块后,设置了一个简单的LangChain提示模板( ChatPromptTemplate ),包括系统消息和用户输入格式。然后通过 ChatOpenAI连接自定义的大模型服务(Qwen2___5-7B-Instruct ),并配置API密钥和服务 地址。 2. 定义环境变量: 使用 os.environ 设置LangSmith相关环境变量,用于启用LangChain的跟踪和连接LangSmith平台。这些变量包括API端点和访问密钥。 3. 数据集管理: 使用LangSmith的 Client 类创建或克隆一个公共数据集( test-code ),用于模型评估。数据集通过指定的URL导入。 4. 自定义评估函数: 定义了 is_concise_enough 函数来评估模型输出是否足够简洁。它通过比较模型输出与预期答案长度来确定得分,返回布尔值。 5. 设置评估器: 创建了一个LangChainStringEvaluator 实例,用于评估模型在特定任务(如逐步推理问答)上的表现。 6. 执行评估: 调用 evaluate 函数,对模型链进行评估,运行自定义数据集上的实验并存储结果。评估过程中使用链的输出与标准答案进行比较。

可以看到,在10个里面有3个符合长度限制,即“评估为简洁的”。注意:你的实验可能结果不同,因为大模型每次输出并不可控!

可以点击进去查看详细情况,包括输入、参考输出和实际输出。

现在我们可以定义一个rag系统,实际上就是将我们之前的rag系统放到代码中。

注意这里改为“{问题}”是因为之前定义数据集两个字段的时候使用了“问题”。同时可以采用三种不同的方式去进行评估。 1. 简洁度评估 2. 通过其它模型(可能是一个更大的模型进行回答是否准确评估,LangChainStringEvaluator 使用的是prompt的形式) 3. 使用余弦相似度评估

上面是第一种:简洁度评估的实现代码,我这里不再赘述。下来看第二种:通过其它模型的评估。

from langchain_community.vectorstores import FAISS  # 导入FAISS向量存储库
from langchain_huggingface import HuggingFaceEmbeddings  # 导入Hugging Face嵌入模型
from langchain_community.document_loaders import TextLoader  # 导入文本加载器
from langchain.text_splitter import RecursiveCharacterTextSplitter  # 导入递归字符文本分割器
from langchain_openai import ChatOpenAI  # 导入ChatOpenAI模型

# 使用 OpenAI API 的 ChatOpenAI 模型
chat_model = ChatOpenAI(
    openai_api_key="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    base_url="https://api.siliconflow.cn/v1",
    model="Qwen/Qwen2.5-7B-Instruct"
)

# 加载文本文件 "黑悟空.txt",编码格式为 'utf-8'
loader = TextLoader("黑悟空.txt", encoding='utf-8')
docs = loader.load()  # 将文件内容加载到变量 docs 中

# 把文本分割成 200 字一组的切片,每组之间有 20 字重叠
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20)
chunks = text_splitter.split_documents(docs)  # 将文档分割成多个小块

# 初始化嵌入模型,使用预训练的语言模型 'bge-large-zh-v1___5'
embedding = HuggingFaceEmbeddings(model_name='models/AI-ModelScope/bge-large-zh-v1___5')

# 构建 FAISS 向量存储和对应的 retriever
vs = FAISS.from_documents(chunks, embedding)  # 将文本块转换为向量并存储在FAISS中

retriever = vs.as_retriever()  # 创建一个检索器用于从向量存储中获取相关信息

from langchain.chains import RetrievalQA  # 导入RetrievalQA链

from langchain.prompts import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)

# 创建一个系统消息,用于定义机器人的角色
system_message = SystemMessagePromptTemplate.from_template(
    "根据以下已知信息回答用户问题。\n 已知信息{context}"
)

# 创建一个人类消息,用于接收用户的输入
human_message = HumanMessagePromptTemplate.from_template(
    "用户问题:{question}"
)

# 将这些模板结合成一个完整的聊天提示
chat_prompt = ChatPromptTemplate.from_messages([
    system_message,
    human_message,
])

"""使用LECL实现"""


# 格式化文档,将多个文档连接成一个
def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)


# 创建字符串输出解析器,用于解析LLM的输出
from langchain_core.output_parsers import StrOutputParser

output_parser = StrOutputParser()

from langchain_core.runnables import RunnablePassthrough

# 创建 rag_chain
rag_chain = (
        {"context": retriever | format_docs, "question": RunnablePassthrough()}  # 构建上下文字典
        | chat_prompt
        | chat_model
        | output_parser
)
# print(rag_chain.invoke("黑熊精自称为?"))

import os

os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com"
os.environ["LANGCHAIN_API_KEY"] = "lsv2_pt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

from langsmith import evaluate, Client
from langsmith.schemas import Example, Run

# 1. Create and/or select your dataset
client = Client()
dataset_name = "example"
dataset = client.clone_public_dataset("https://smith.langchain.com/public/a63525f9-bdf2-4512-83e3-077dc9417f96/d",
                                      dataset_name=dataset_name)


# 方式一:原始的代码:评估简洁度
# 2. Define an evaluator
def is_concise_enough(root_run: Run, example: Example) -> dict:
    score = len(root_run.outputs["output"]) < 3 * len(example.outputs["答案"])
    return {"key": "is_concise", "score": int(score)}


"""方式2:用更大的模型将原始答案与生成答案进行准确度评估"""
from langsmith.evaluation import LangChainStringEvaluator

eval_model = ChatOpenAI(
    model="Qwen/Qwen2.5-72B-Instruct",
    api_key="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    base_url="https://api.siliconflow.cn/v1",
)
evaluators = LangChainStringEvaluator("cot_qa", config={"llm": eval_model})

evaluate(
    lambda x: rag_chain.invoke(x["问题"]),
    # chain.invoke
    # graph.invoke
    data=dataset_name,
    #evaluators=[is_concise_enough],
    #evaluators=evaluators,
    evaluators=[evaluators],
    experiment_prefix="my first dataset experiment "
)

我们发现准确度效果明显改善很多。

第三种:使用余弦相似度评估

from langchain_community.vectorstores import FAISS  # 导入FAISS向量存储库
from langchain_huggingface import HuggingFaceEmbeddings  # 导入Hugging Face嵌入模型
from langchain_community.document_loaders import TextLoader  # 导入文本加载器
from langchain.text_splitter import RecursiveCharacterTextSplitter  # 导入递归字符文本分割器
from langchain_openai import ChatOpenAI  # 导入ChatOpenAI模型

# 使用 OpenAI API 的 ChatOpenAI 模型
chat_model = ChatOpenAI(
    openai_api_key="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    base_url="https://api.siliconflow.cn/v1",
    model="Qwen/Qwen2.5-7B-Instruct"
)

# 加载文本文件 "黑悟空.txt",编码格式为 'utf-8'
loader = TextLoader("黑悟空.txt", encoding='utf-8')
docs = loader.load()  # 将文件内容加载到变量 docs 中

# 把文本分割成 200 字一组的切片,每组之间有 20 字重叠
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20)
chunks = text_splitter.split_documents(docs)  # 将文档分割成多个小块

# 初始化嵌入模型,使用预训练的语言模型 'bge-large-zh-v1___5'
embedding = HuggingFaceEmbeddings(model_name='models/AI-ModelScope/bge-large-zh-v1___5')

# 构建 FAISS 向量存储和对应的 retriever
vs = FAISS.from_documents(chunks, embedding)  # 将文本块转换为向量并存储在FAISS中

retriever = vs.as_retriever()  # 创建一个检索器用于从向量存储中获取相关信息

from langchain.chains import RetrievalQA  # 导入RetrievalQA链

from langchain.prompts import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)

# 创建一个系统消息,用于定义机器人的角色
system_message = SystemMessagePromptTemplate.from_template(
    "根据以下已知信息回答用户问题。\n 已知信息{context}"
)

# 创建一个人类消息,用于接收用户的输入
human_message = HumanMessagePromptTemplate.from_template(
    "用户问题:{question}"
)

# 将这些模板结合成一个完整的聊天提示
chat_prompt = ChatPromptTemplate.from_messages([
    system_message,
    human_message,
])

"""使用LECL实现"""
# 格式化文档,将多个文档连接成一个
def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

# 创建字符串输出解析器,用于解析LLM的输出
from langchain_core.output_parsers import StrOutputParser
output_parser = StrOutputParser()

from langchain_core.runnables import RunnablePassthrough
# 创建 rag_chain 
rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}  # 构建上下文字典
    | chat_prompt
    | chat_model
    | output_parser
)
# print(rag_chain.invoke("黑熊精自称为?"))

import os

os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com"
os.environ["LANGCHAIN_API_KEY"] = "lsv2_pt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"


from langsmith import evaluate, Client
from langsmith.schemas import Example, Run

# 1. Create and/or select your dataset
client = Client()
dataset_name = "example"
dataset = client.clone_public_dataset("https://smith.langchain.com/public/a63525f9-bdf2-4512-83e3-077dc9417f96/d", dataset_name=dataset_name)


"""方式3:通过余弦相似度进行评估"""
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("models/AI-ModelScope/bge-large-zh-v1___5")
from sklearn.metrics.pairwise import cosine_similarity
def cal_cosine_similarity(root_run: Run, example: Example) -> dict:
    model_output = root_run.outputs["output"]
    reference_answer = example.outputs["答案"]

    embeddings = model.encode([model_output, reference_answer])

    similarity = cosine_similarity([embeddings[0]], [embeddings[1]])[0][0]

    score = similarity >= 0.75
    return {"key": "cosine_similarity", "score": int(score)}


# 3. Run an evaluation
# For more info on evaluators, see: https://docs.smith.langchain.com/concepts/evaluation#evaluators

# To evaluate an LCEL chain, replace lambda with chain.invoke
# To evaluate a LangGraph graph, replace lambda with graph.invoke
evaluate(
    lambda x: rag_chain.invoke(x["问题"]),
    # chain.invoke
    # graph.invoke
    data=dataset_name,
    evaluators=[cal_cosine_similarity],
    experiment_prefix="my first dataset experiment "
)

可以看到,使用余弦相似度进行评估(阈值为0.75),10个问题全部答对。