为LangChain链设置基本测试,涉及验证其结构完整性和预期流程。这种方法不同于评估LLM输出的质量,后者通常使用专门的评估方法。我们将使用流行的Python测试框架 pytest 和模拟技术,以便在测试期间将链与实际的LLM API调用隔离。示例场景:一个简单的摘要链设想我们有一个简单的LangChain链,设计用于接收一段文本并将其总结为三个要点。它可能包含:一个 PromptTemplate 来指导LLM。一个LLM模型实例(例如,来自OpenAI)。一个 OutputParser (也许是 SimpleJsonOutputParser 或自定义的)来构建要点。以下是我们链的结构,使用LangChain表达式语言(LCEL):# 假设已导入必要的模块:ChatOpenAI, PromptTemplate, StrOutputParser, JsonOutputParser 等。 # 假设 OPENAI_API_KEY 已在环境变量中设置 from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from operator import itemgetter # 简化示例 - 实际链可能使用 JsonOutputParser 以获得更好的结构 prompt_template = ChatPromptTemplate.from_template( "Summarize the following text into exactly three concise bullet points:\n\n{text}\n\nFormat the output as a numbered list." ) model = ChatOpenAI(model="gpt-3.5-turbo") # 或您偏好的模型 output_parser = StrOutputParser() # 本示例的简单解析器 # 定义链 summary_chain = ( {"text": itemgetter("text")} | prompt_template | model | output_parser ) # 示例用法(不属于测试本身) # input_text = "Large Language Models are transforming industries by enabling natural language interaction..." # result = summary_chain.invoke({"text": input_text}) # print(result)设置测试环境首先,请确保您已安装 pytest 和 pytest-mock:pip install pytest pytest-mock langchain langchain_openai python-dotenv我们将把测试组织在一个单独的文件中,例如 test_summary_chain.py。我们还需要一种安全管理API密钥的方法;使用环境变量(以及可能通过 python-dotenv 加载的 .env 文件用于本地测试)是常见做法。模拟LLM交互在测试中直接调用LLM API会使测试变慢、成本高昂,并可能导致非确定性结果。对链结构进行单元和集成测试的主要思想是模拟LLM调用。我们希望验证我们的提示是否正确构建,并且链能适当地处理LLM的预期响应格式。Python的内置 unittest.mock 库(或 pytest-mock 提供的 mocker fixture)非常适合此目的。编写测试我们来创建 test_summary_chain.py:import pytest from unittest.mock import MagicMock # 用于创建模拟对象 from operator import itemgetter # 假设您的链定义在名为 `summary_chain_module.py` 的文件中 from summary_chain_module import summary_chain, prompt_template, model, output_parser # 测试提示模板格式化 def test_prompt_template_formatting(): """验证提示模板是否正确插入文本。""" sample_text = "This is sample input text." expected_prompt_value = "Summarize the following text into exactly three concise bullet points:\n\nThis is sample input text.\n\nFormat the output as a numbered list." # 创建一个等效的 RunnablePassthrough 用于测试模板部分 prompt_part = {"text": itemgetter("text")} | prompt_template result = prompt_part.invoke({"text": sample_text}) # ChatPromptTemplate 的结果具有 'to_string()' 方法 assert result.to_string() == expected_prompt_value # 使用模拟LLM测试整个链 def test_summary_chain_with_mock_llm(mocker): # 使用 pytest-mock 的 'mocker' fixture """验证链是否正确处理模拟的LLM响应。""" sample_text = "This is the text to be summarized." mock_llm_output = "1. First point.\n2. Second point.\n3. Third point." # 模拟链中 'model' 实例的 'invoke' 方法 # 我们找到 'model' 在哪里使用(在 summary_chain_module 中)并在那里进行修补。 mock_model_invoke = mocker.patch('summary_chain_module.model.invoke') mock_model_invoke.return_value = MagicMock(content=mock_llm_output) # 如果需要,模拟 AIMessage 结构,这里 StrOutputParser 直接期望字符串或带有 .content 的 AIMessage # 调用实际的链 result = summary_chain.invoke({"text": sample_text}) # 断言提示已正确传递给模拟的模型 # 模拟函数第一次调用的第一个参数 call_args = mock_model_invoke.call_args[0][0] expected_prompt = prompt_template.invoke({"text": sample_text}) assert call_args == expected_prompt # 断言最终输出是经解析器处理的模拟LLM输出 assert result == mock_llm_output # 测试输出解析器逻辑(如果它更复杂) # 对于 StrOutputParser,没有太多可测试的,但如果使用 JsonOutputParser: # def test_output_parser(): # mock_llm_response_content = '{"summary": ["Point 1", "Point 2", "Point 3"]}' # # 假设 json_output_parser = JsonOutputParser(...) # # parsed_output = json_output_parser.parse(mock_llm_response_content) # # assert parsed_output == {"summary": ["Point 1", "Point 2", "Point 3"]}运行测试导航到包含 test_summary_chain.py 的目录,并在终端中运行 pytest:pytest您应该会看到指示测试通过或失败的输出。解读和后续步骤这些测试成功验证了:提示构建: test_prompt_template_formatting 确保我们的模板正确地整合了输入变量。链集成(模拟): test_summary_chain_with_mock_llm 确认输入通过提示模板流向(模拟的)模型,并且(模拟的)模型的输出由输出解析器正确处理。它检查链内的连接和数据流。请记住,这些测试不评估 mock_llm_output 是否是良好的摘要。这需要前面讨论过的评估策略,例如使用评估数据集,与参考摘要进行比较(例如ROUGE分数),或采用基于LLM的评估方法。这种做法构成了测试的一个基本层面,确保您的链在进行更复杂的质量评估之前结构是健全的。它在开发周期的早期就能捕获逻辑、解析和组件集成中的错误。