与传统软件测试相比,测试集成大型语言模型(LLM)的应用会引入一些复杂性。虽然单元测试和集成测试等标准做法仍然很有用,但LLM输出的概率性和通常主观的性质需要专门的策略。与每次对相同输入产生相同输出的确定性代码不同,LLM即使在温度设为零等受控参数下接收到相同的提示,也可能生成略有不同的文本。此外,为摘要或创意写作等任务定义“正确性”并非总是直接明了。
本节考察了有效测试LLM应用的方法,以确保它们不仅功能完备,而且可靠、一致,并达到预期的质量标准。
测试LLM应用的独特挑战
在概述测试策略之前,了解所涉及的具体挑战会很有帮助:
- 非确定性: LLM的输出在不同调用之间可能有所不同,这使得直接的字符串比较在许多情况下无法可靠地用于测试断言。
- 主观性和质量评估: 评估生成文本的相关性、连贯性、语气或安全性通常需要判断,这很难完全自动化。
- 对外部服务的依赖: 应用依赖LLM API,这些API可能会出现延迟、停机、行为变化或速率限制,影响测试的可靠性。
- 成本: 测试期间每次LLM API调用都会产生费用,这可能使广泛的测试套件变得昂贵。
- 对输入的敏感性: 提示、上下文甚至格式的微小变化都可能导致非常不同的输出,这需要仔细设计测试用例。
- 缺少明确的真实数据: 对于许多生成任务,没有一个单一的“正确”答案可供比较,这使得断言逻辑变得复杂。
测试LLM应用的策略
全面的测试策略将传统方法与专为LLM调整的技术结合起来。
单元测试
侧重于单独测试应用中的各个组件。这是您可以最直接应用传统测试的地方。
- 提示模板: 验证您的函数是否正确地使用用户输入和上下文来格式化提示。
- 输出解析: 测试您从LLM原始文本输出中提取结构化数据(例如,JSON、列表)的逻辑。确保它能够优雅地处理格式错误或意外的格式。
- 数据验证: 测试应用于解析输出的任何验证规则(例如,使用Pydantic模型)。
- 工具集成逻辑: 如果使用代理或工具,请测试负责调用这些工具并处理其结果的函数。
对于单元测试,强烈建议模拟LLM API调用。这包括用一个返回预定义响应的替代函数来替换实际的API调用。模拟使您无需承担API成本、网络延迟或处理LLM的非确定性即可测试组件的逻辑。Python的unittest.mock库常用于此目的。
集成测试
验证应用的不同组件是否协同工作正确。这通常涉及对LLM API进行实际(或仔细控制的)调用。
- 从提示生成到解析: 测试从生成提示模板、填充它、将其发送到LLM(可能使用代表性输出进行模拟)以及解析响应的流程。
- RAG流程: 在检索增强生成系统中,测试文档检索、将上下文插入提示、LLM生成和最终输出格式化之间的互动。
- 代理工具使用: 测试代理接收输入、决定使用工具、调用工具、处理工具输出并生成最终响应的顺序。
集成测试比单元测试更复杂,也可能更慢、成本更高,但它们对于发现组件之间边界的问题必不可少。
功能测试和验收测试
根据要求评估应用的端到端行为。从用户的角度来看,应用是否达到了其预期目的?
- 任务完成: 应用能否成功执行其设计的核心任务(例如,根据提供的文档回答问题、准确总结文本、生成代码片段)?
- 评估集(“黄金集”): 创建一组精心策划的代表性输入和预期输出(或输出特性)的数据集。这些输入应涵盖常见用例、边缘情况和潜在的故障模式。定期使用此数据集运行应用,以跟踪性能并发现回归。
- 例如,问答机器人的评估集可能包括知识库中已知答案的问题、需要信息综合的问题,以及旨在测试歧义处理或超出范围主题的问题。
- 质量门: 根据评估集定义重要指标(见下文)的可接受阈值。如果性能低于这些阈值,则使构建失败或发出警报。
回归测试
每当您修改提示、更新模型、更改解析逻辑或重构代码时,请运行您的测试套件(特别是评估集),以确保您没有无意中破坏现有功能或降低输出质量。考虑到LLM的敏感性,即使是微小的提示调整也可能产生意想不到的后果。
蜕变测试
当您没有特定的预期输出,但可以定义相关输入之间输出的预期关系时,这项技术非常有用。
- 例子: 如果您的应用用于文本摘要,向其提供相同输入文本的略微改写版本,应会得到语义相似的摘要。如果您要求一个摘要,然后又要求对相同文本进行更简洁的摘要,则第二个输出确实应该更短并保留核心意义。
- 测试不变性: 如果某些输入修改不应改变输出(例如,在代码生成中更改变量名不应改变程序的逻辑),蜕变测试可以验证这一点。
性能和成本测试
- 延迟: 测量API调用所花费的时间以及应用的总端到端响应时间。
- Token用量: 监控每次互动消耗的输入和输出token数量,以估计和控制成本。
- 负载测试: 模拟并发用户,以了解您的应用在负载下的扩展能力和性能,特别是关于API速率限制方面。
安全测试
- 提示注入: 测试是否存在漏洞,即恶意用户输入可能操纵底层提示,导致LLM忽略原始指令或泄露敏感信息。
- 数据处理: 确保敏感数据不会在不必要和未经允许的情况下无意中包含在发送到第三方API的提示中。检查输出不会泄露私人信息。
- 工具安全: 如果代理使用与外部系统互动的工具,请确保这些互动已正确进行身份验证和授权。
评估指标
自动化评估LLM输出质量具有挑战性,但对于可扩展的测试来说是必要的。
- 精确匹配(EM): 衡量与参考答案完全匹配的生成输出百分比。适用于具有精确、简短答案的任务(例如,事实提取)。
- F1分数: 精确率和召回率的调和平均值,常用于评估输出中与参考相比特定关键词或实体的存在/缺失。
- ROUGE(面向召回率的摘要评估替补): 测量生成文本与一个或多个参考摘要之间的重叠(n-gram、词序列)。常见变体包括ROUGE-1、ROUGE-2和ROUGE-L(最长公共子序列)。适用于摘要。
- BLEU(双语评估替补): 类似于ROUGE,但侧重于精确率(生成文本中在参考中出现的程度)。常用于机器翻译。
- 语义相似度: 使用文本嵌入模型(如Sentence-BERT)将生成的输出和参考/预期输出都转换为向量。计算这些向量之间的余弦相似度。相似度得分越高(越接近1)表示语义重叠越大。向量A和B之间余弦相似度的公式为:
Similarity(A,B)=∥A∥∥B∥A⋅B
- LLM作为评判者: 使用一个独立的、通常更强大的LLM来根据预定义标准评估生成的输出。您向评估LLM提供输入、生成的输出,以及一个提示,要求它评估相关性、连贯性、安全性或遵守指令的情况。这需要对评估器本身进行细致的提示工程。
该图表说明了不同的评估方法,它们将生成的输出与参考或标准进行比较,以生成质量得分。
没有单一的指标是完美的。通常,自动化指标和定期人工审查的结合能提供对应用质量的最佳评估。
实施测试工作流程
- 开发评估集: 在开发过程早期创建多样化的输入和预期输出/特性集。
- 编写单元测试: 覆盖核心逻辑,特别是解析和数据处理,使用模拟。
- 实施集成测试: 验证组件互动,可能使用专用测试模型或受控的API调用。
- 自动化功能测试: 定期使用自动化脚本运行您的评估集。选择合适的指标进行评估。
- 与CI/CD集成: 将自动化测试(单元、集成、评估集上的功能检查)添加到您的持续集成管道中。如果测试失败或质量指标大幅下降,则使构建失败。
- 生产监控: 持续记录输入、输出、性能指标和成本。使用监控来识别问题和可能需要调查或优化提示/逻辑的潜在问题输出。
测试LLM应用需要调整传统软件工程实践,以考虑语言模型的独特之处。通过将单元测试与模拟、集成测试、评估集、适当的指标和持续监控结合起来,您可以对LLM驱动的应用的可靠性和质量建立信心。这是一个迭代过程;随着应用的演进和您对其行为的更多了解,预计会不断优化您的测试和评估策略。