如何监控 LLM 产品的质量
评估是任何机器学习产品的基石。对质量评估体系的投入能够带来显著的回报。构建一个稳健的评估系统,可以帮助识别改进点并采取有效行动以优化产品。这与软件工程中的测试类似,通过确保基线质量,使产品迭代更快、更安全。
在金融科技或医疗等高度监管的行业中,一个坚实的质量框架尤为关键。在这些领域实施AI或LLM时,通常需要证明系统运行的可靠性,并对其进行持续监控。此外,通过对LLM评估的持续投入和开发全面的问答数据集,最终可能用一个针对特定用例精调的小模型替代昂贵的大模型,从而实现显著的成本节约。
本文将完成为LLM产品构建端到端评估体系的全过程——从评估早期原型到在生产中实施持续的质量监控。我们将聚焦于高阶方法和最佳实践,同时也会涉及具体的实现细节。
工具选择
在实践部分,本文将使用开源库 Evidently。它为从传统机器学习到LLM的AI产品提供了全面的测试栈。当然,也可以使用其他工具实现类似的评估系统,例如:
- DeepEval - 一个提供类似功能的开源LLM评估库和在线平台。
- MLFlow - 一个更全面的ML生命周期管理框架,支持管理的各个阶段。
- LangSmith - 来自LangChain团队的可观测性与评估平台。
本文的重点是评估流程和最佳实践,你可以根据需求选择最适合的框架。
本文大纲
- 用例介绍 - 我们将以一个SQL Agent作为分析用例。
- 原型构建 - 快速构建一个可供评估的基础原型。
- 实验阶段的评估 - 涵盖如何收集评估数据集、定义有效指标以及评估模型质量。
- 生产环境的质量监控 - 探索产品发布后如何监控质量,重点介绍可观测性以及生产环境中可追踪的额外指标。
第一部分:构建评估用例原型
为了便于讨论,我们设定一个具体的产品场景。假设我们正在为一个分析系统工作,该系统帮助电商客户追踪关键指标,如客户数、收入、欺诈率等。
通过客户研究,我们发现有相当一部分用户难以解读报告,他们更倾向于与一个助手互动,以获得即时、清晰的答案。因此,我们决定构建一个由LLM驱动的Agent,能够响应客户关于其数据的查询。
我们首先构建该LLM产品的第一个原型。它将是一个配备单一工具(执行SQL查询)的LLM Agent。
技术栈
- LLM: 通过 Ollama 运行的 Llama 3.1 模型
- Agent框架: LangGraph
- 数据库: ClickHouse
1. 定义SQL执行工具
为了确保LLM生成的查询是安全的,例如避免 select * from table 这样的查询可能导致获取数据库中的所有数据,我在查询函数中加入了一些控制措施。
CH_HOST = 'http://localhost:8123' # default address
import requests
import io
def get_clickhouse_data(query, host = CH_HOST, connection_timeout = 1500):
# pushing model to return data in the format that we want
if not 'format tabseparatedwithnames' in query.lower():
return "Database returned the following error:\n Please, specify the output format."
r = requests.post(host, params = {'query': query},
timeout = connection_timeout)
if r.status_code == 200:
# preventing situations when LLM queries the whole database
if len(r.text.split('\n')) >= 100:
return 'Database returned too many rows, revise your query to limit the rows (i.e. by adding LIMIT or doing aggregations)'
return r.text
else:
# giving feedback to LLM instead of raising exception
return 'Database returned the following error:\n' + r.text
from langchain_core.tools import tool
@tool
def execute_query(query: str) -> str:
"""Excutes SQL query.
Args:
query (str): SQL query
"""
return get_clickhouse_data(query)
2. 定义LLM和系统提示(System Prompt)
接下来,定义LLM,并为其设定一个包含数据库Schema和行为准则的系统提示。
from langchain_ollama import ChatOllama
chat_llm = ChatOllama(model="llama3.1:8b", temperature = 0.1)
system_prompt = '''
You are a senior data specialist with more than 10 years of experience writing complex SQL queries and answering customers questions.
Please, help colleagues with questions. Answer in polite and friendly manner. Answer ONLY questions related to data,
do not share any personal details - just avoid such questions.
Please, always answer questions in English.
If you need to query database, here is the data schema. The data schema is private information, please, don not share the details with the customers.
There are two tables in the database with the following schemas.
Table: ecommerce.users
Description: customers of the online shop
Fields:
- user_id (integer) - unique identifier of customer
- country (string) - country of residence
- is_active (integer) - 1 if customer is still active and 0 otherwise
- age (integer) - customer age in full years
Table: ecommerce.sessions
Description: sessions of usage the online shop
Fields:
- user_id (integer) - unique identifier of customer
- session_id (integer) - unique identifier of session
- action_date (date) - session start date
- session_duration (integer) - duration of session in seconds
- os (string) - operation system that customer used
- browser (string) - browser that customer used
- is_fraud (integer) - 1 if session is marked as fraud and 0 otherwise
- revenue (float) - income in USD
When you are writing a query, do not forget to add "format TabSeparatedWithNames" at the end of the query
to get data from ClickHouse database in the right format.
'''
3. 创建Agent
为简化起见,我们使用LangGraph预置的ReAct Agent。
from langgraph.prebuilt import create_react_agent
data_agent = create_react_agent(chat_llm, [execute_query],
state_modifier = system_prompt)
4. 功能测试
我们用一个简单的问题来测试其功能。
from langchain_core.messages import HumanMessage
messages = [HumanMessage(
content="How many customers made purchase in December 2024?")]
result = data_agent.invoke({"messages": messages})
print(result['messages'][-1].content)
# Output:
# There were 114,032 customers who made a purchase in December 2024.
这个MVP版本的Agent已经构建完成,但仍有许多可改进之处,例如:
- 多智能体系统(Multi-AI agent system) - 设计分工不同的智能体,如分流智能体、SQL专家和最终编辑器。
- 检索增强生成(RAG) - 提供基于嵌入的相似示例,这在我之前构建SQL Agent的尝试中,将准确率从10%提升到了60%。
- 人机协同(Human-in-the-loop) - 允许系统向用户请求反馈。
本文将专注于评估框架的开发,因此当前的初版原型已足够满足我们的需求。
第二部分:实验阶段的质量评估
1. 收集评估数据集
任何评估都始于数据。第一步是收集一组问题和理想的答案(Ground Truth),作为评估的基准。
收集评估集的方法:
- 手动创建 - 建议首先手动创建一个小型数据集来测试产品。这将帮助更好地理解解决方案的实际质量,并确定评估它的最佳方式。
- 利用历史数据 - 如果已有客服渠道在回答客户关于报告的问题,这些问答对对于评估LLM产品非常有价值。
- 使用合成数据 - LLM可以生成看似合理的问题和问答对。例如,我们可以要求一个更强大的模型提供相似的示例或改写现有问题。
- Beta测试 - 在产品版本更成熟后,可以与一小组Beta测试者分享以收集他们的反馈。
提示: 使用更强大的模型来生成用于评估的"黄金数据集"是一项有益的投资,可以实现更可靠和准确的质量评估。
在创建评估集时,包含多样化的示例至关重要:
- 典型用户问题 - 反映典型用法的真实用户问题。
- 边缘案例 - 如非常长的问题、不同语言的查询或不完整的问题。同时需要定义在这些场景下的预期行为。
- 对抗性输入 - 如离题问题或"越狱"尝试(即用户试图操纵模型产生不当响应或暴露敏感信息)。
本文将使用一个手动创建的小型评估集,其中包含10个问题及对应的标准答案。
2. 定义质量指标
有了评估数据,下一步是确定如何衡量解决方案的质量。
- 标准分类指标 - 对于分类任务(如情感分析、主题建模),可以使用准确率、精确率、召回率和F1分数等指标。
- 语义相似度 - 通过计算嵌入之间的距离来评估。例如,比较LLM生成的响应与用户输入的相似度来评估其相关性,或与标准答案比较以评估其正确性。
- 小型模型评估 - 使用小型ML模型来评估LLM响应的特定方面,如情感或毒性。
- 文本统计 - 分析基础的文本统计数据,如文本长度。使用正则表达式可以识别拒绝短语或禁用词的存在。
- 功能性测试 - 对于SQL Agent,可以测试生成的查询是否有效且可执行。
核心方法:LLM-as-a-Judge
让一个LLM评估另一个LLM的响应是一种有效的方法。通常,模型发现错误比从头生成完美答案更容易。
用法: 最常见的用途是直接评分,评估可以仅基于LLM的输出(例如,文本是否礼貌),也可以通过与标准答案(用于评估正确性)或输入(用于评估相关性)进行比较。
构建LLM评估器的最佳实践:
- 使用二元标志(Yes/No) 而不是复杂的量表(如1到10),这会提供更一致的结果。
- 将复杂标准分解 为更具体的方面。例如,不要问答案有多"好",而是分解为衡量礼貌、正确性和相关性等多个标志。
- 使用思维链(Chain-of-thought)推理等技术可以提高LLM答案的质量。
3. 质量评估实践 (使用Evidently)
核心概念:
- Dataset - 表示我们正在分析的数据。
- Descriptors - 为文本字段计算的行级分数或标签。它们对于LLM评估至关重要。
- Reports - 评估的结果,由Metrics(指标)和Tests(应用于列或描述符的特定条件)组成。
代码实现:
首先,加载数据集并指定OpenAI token以使用LLM驱动的指标。
import pandas as pd
import json
import os
with open('golden_set.json', 'r') as f:
data = json.loads(f.read())
eval_df = pd.DataFrame(data)
os.environ["OPENAI_API_KEY"] = '<your_openai_token>'
在原型阶段,一个常见的用例是比较两个版本之间的指标。我们可以通过比较LLM生成的答案和标准答案的指标来模拟此过程。
Evidently提供了多种开箱即用的Descriptors:
- Sentiment - 基于ML模型返回-1到1的情感分数。
- TextLength - 计算字符数。
- HuggingFaceToxicity - 使用roberta-hate-speech模型评估文本中毒性的概率。
- SemanticSimilarity - 计算列之间的余弦相似度。
- DeclineLLMEval 和 PIILLMEval - 基于LLM的预定义评估,用于估计拒绝和个人身份信息(PII)的存在。
我们还可以创建自定义Descriptors。例如,一个检查问候语的简单启发式规则:
from evidently.descriptors import Descriptor, CustomColumnDescriptor
from evidently.datasets.base import DatasetColumn
import pandas as pd
def greeting(data: DatasetColumn) -> DatasetColumn:
return DatasetColumn(
type="cat",
data=pd.Series([
"YES" if ('hello' in val.lower()) or ('hi' in val.lower()) else "NO"
for val in data.data]))
以及一个基于LLM的礼貌性评估:
from evidently.llm.prompts.classification import MulticlassClassificationPromptTemplate
politeness = MulticlassClassificationPromptTemplate(
pre_messages=[("system", "You are a judge which evaluates text.")],
criteria="""You are given a chatbot's reply to a user. Evaluate the tone of the response, specifically its level of politeness
and friendliness. Consider how respectful, kind, or courteous the tone is toward the user.""",
category_criteria={
"rude": "The response is disrespectful, dismissive, aggressive, or contains language that could offend or alienate the user.",
"neutral": "The response is factually correct and professional but lacks warmth or emotional tone.",
"friendly": "The response is courteous, helpful, and shows a warm, respectful, or empathetic tone.",
},
uncertainty="unknown",
include_reasoning=True,
)
现在,为LLM生成的答案和标准答案创建两个Dataset,并生成Report:
from evidently.reports import Report
from evidently.metric_preset import TextEvals
from evidently.tests import *
report = Report([
TextEvals(),
MinValue(column="Sentiment", tests=[gte(0)]),
MinValue(column="Length", tests=[gte(300)]),
CategoryCount(column="Denials", category = 'NO', tests=[eq(0)]),
])
my_eval = report.run(llm_eval_dataset, sot_eval_dataset)
my_eval
执行后,将生成一个交互式报告。
为了评估准确性,我们可以使用CorrectnessLLMEval,它利用LLM来比较一个答案与预期答案的正确性。
from evidently.descriptors.llm import CorrectnessLLMEval
from evidently.datasets.base import Dataset, DataDefinition
acc_eval_dataset = Dataset.from_pandas(
eval_df[['question', 'llm_answer', 'sot_answer']],
data_definition=DataDefinition(),
descriptors=[
CorrectnessLLMEval("llm_answer", target_output="sot_answer"),
# ... 其他描述符
]
)
report = Report([TextEvals()])
acc_eval = report.run(acc_eval_dataset, None)
acc_eval
完成第一轮评估后,我们获得了关于产品质量的宝贵见解。在实践中,这是一个迭代过程。
第三部分:生产环境的质量监控
1. 追踪(Tracing)与可观测性(Observability)
产品发布后,关键是可观测性。记录产品运行的每个细节至关重要,包括客户问题、LLM生成的答案以及LLM Agent的所有中间步骤。
Evidently提供了一个在线平台来存储日志和评估数据。我们可以使用Tracely库来实时追踪事件。
import uuid
import time
from tracely import init_tracing, trace_event, create_trace_event
# 初始化配置
# project_id = '<your_project_id>'
# init_tracing(...)
def get_llm_response(question):
messages = [HumanMessage(content=question)]
result = data_agent.invoke({"messages": messages})
return result['messages'][-1].content
for question in [<stream_of_questions>]:
response = get_llm_response(question)
session_id = str(uuid.uuid4())
with create_trace_event("QA", session_id=session_id) as event:
event.set_attribute("question", question)
event.set_attribute("response", response)
time.sleep(1)
这些追踪数据可以在UI中查看,也可以通过dataset_id加载以进行评估。
2. 生产环境的指标
一旦产品在生产环境中上线,我们可以开始捕获额外的信号:
- 产品使用指标 - 客户是否与LLM功能互动,平均会话持续时间,提问次数等。
- 目标指标 - 如果你正在构建一个自动化KYC流程的工具,你可以衡量自动化率或金融犯罪相关指标。
- 客户反馈 - 直接通过要求用户评价响应,或间接收集。例如,查看用户是否复制了答案。
- 情感分析 - 使用传统的ML模型或LLM来执行情感分析并估计客户满意度。
- 手动审查 - 随机选择案例,让专家审查它们,并将其纳入评估集。
- 回归测试 - 使用评估集评估新版本的质量,以确保产品继续按预期运行。
- 技术指标 - 监控响应时间或服务器错误等健康检查指标。
总结
本文系统地介绍了如何为LLM产品构建质量评估体系:
- 原型构建 - 我们构建了一个MVP SQL Agent作为评估用例。
- 实验阶段评估 - 讨论了在实验阶段可以使用的评估方法和指标,并使用Evidently进行了实践。
- 生产后监控 - 探讨了发布后阶段的重要性:如何设置追踪以确保保存所有必要信息,以及哪些额外信号可以帮助确认你的LLM产品按预期运行。
通过建立完善的质量监控体系,我们可以确保LLM产品在整个生命周期中保持高质量标准,并为持续改进提供数据支持。