单元测试用于在隔离环境中验证 Agent 的小型、确定性逻辑单元。通过将真实 LLM 替换为内存中的伪实现(又称 fixture),你可以预先设定精确响应(文本、工具调用和错误),从而使测试快速、免费、可重复,并且无需 API 密钥。

使用 fakeModel 模拟聊天模型

fakeModel 是一个构建器风格的伪聊天模型,允许你预先设定精确响应(文本、工具调用、错误),并断言模型实际接收到的内容。它继承自 BaseChatModel,因此可以在任何需要真实模型的地方使用。

快速开始

创建模型,使用 .respond() 排队响应,然后调用模型。每次 invoke() 都会按顺序消费下一个排队响应:
如果调用次数超过了已排队的响应数量,它会抛出带有明确说明的错误:

工具调用响应

.respond() 支持工具调用,只需传入带有 tool_calls 的 AIMessage:
.respondWithTools() 是同样功能的简写形式。无需构造完整的 AIMessage,只需提供工具名称和参数:
id 字段是可选的。如果省略,将自动生成唯一 ID。
.respond() 和 .respondWithTools() 可以任意混合使用。这对于测试 Agent 循环特别有用,因为模型通常会在工具调用和文本响应之间交替。

模拟错误

在特定轮次抛出错误

向 .respond() 传入 Error 对象,会使模型在对应调用时抛出异常。错误可以出现在响应序列中的任何位置:

每次调用都抛出错误

.alwaysThrow() 会让每次调用都抛出异常,而不受响应队列影响。这对于测试错误处理和重试逻辑非常有用:

使用工厂函数生成动态响应

.respond() 还支持传入函数,根据输入消息动态生成响应。该函数接收完整消息数组,并返回 BaseMessage 或 Error:
工厂函数也可以返回错误:
每个函数都作为一个队列项,仅消费一次。如果希望同样的动态逻辑在多个轮次中复用,需要多次调用 respond() 将其加入队列。

结构化输出

对于使用 .withStructuredOutput() 的代码,可以通过 .structuredResponse() 配置伪返回值:
传递给 .withStructuredOutput() 的 schema 会被忽略。模型始终返回通过 .structuredResponse() 配置的值。这样可以让测试专注于应用逻辑,而不是解析逻辑。

断言模型接收到的内容

fakeModel 会记录每次调用,包括传入的消息和选项。这类似于传统测试框架中的 spy 或 mock:
即使模型抛出异常,调用记录也会保留:

与 bindTools 一起使用

LangChain Agent 和 LangGraph 等框架会在内部调用 model.bindTools(tools)。fakeModel 会自动处理这一点。绑定后的模型与原始模型共享同一个响应队列和调用记录,因此无需额外配置:

后续步骤

了解如何使用真实模型提供商 API 测试 Agent,请参阅 集成测试。