Agent 系统集成测试:模拟用户行为的多轮对话自动化测试
Agent 系统集成测试模拟用户行为的多轮对话自动化测试一、Agent 上线 3 天后才发现查订单时偶尔会卡死Agent 功能上线前通过了单元测试和单轮工具调用测试。但第三天用户反馈连续对话 5 轮以上时Agent 偶尔会陷入正在思考的循环既不回复也不报错。排查发现根因Agent 在第 3 轮调用了 A 工具第 4 轮调用了 B 工具B 工具的错误码让 LLM 重新调用 A 工具形成 A→B→A→B 的死循环。单元测试覆盖不了这种场景因为单个工具调用是正常的。只有多轮对话的集成测试才能发现这类状态累积导致的问题。二、Agent 集成测试框架Agent 集成测试框架主要由四个核心模块协同工作测试用例构造、测试执行引擎、断言层以及测试报告。首先测试用例构造阶段负责定义多步用户行为场景并将其转化为测试脚本明确每一步的用户输入与期望行为。其次测试执行引擎负责初始化 Agent加载工具与知识库按步骤执行输入并验证最终清理状态并重置。在执行过程中断言层会对每一步进行多维度校验包括工具调用断言是否调用正确工具、响应断言是否包含关键信息、性能断言响应延迟是否低于阈值以及安全断言未执行危险操作。最后测试报告模块根据断言结果生成报告通过则标记为通过失败则记录详细日志并提供 Agent 全链路 Trace 以便排查。三、Python 集成测试框架实现import time import json from typing import List, Dict, Any, Optional, Callable from dataclasses import dataclass, field from enum import Enum class TestStatus(Enum):PASS pass FAIL fail TIMEOUT timeout ERROR errordataclassclass StepAssertion:单步断言tool_called: Optional[str] None # 期望调用的工具tool_not_called: Optional[str] None # 期望不调用的工具response_contains: Optional[List[str]] None # 响应中应包含的关键词response_not_contains: Optional[List[str]] None # 响应中不应包含的max_latency_ms: int 10000 # 最大响应延迟max_tool_calls: int 5 # 单步最大工具调用次数dataclassclass TestScenario:测试场景——多步对话定义name: strdescription: strsteps: List[Dict[str, Any]] # [{user_input: ..., assertion: StepAssertion}]dataclassclass TestStepResult:单步测试结果step_index: intuser_input: stragent_response: strtool_calls: List[str]latency_ms: floatstatus: TestStatusfailures: List[str] field(default_factorylist)class AgentIntegrationTest:Agent 集成测试框架def __init__(self, agent_factory: Callable, timeout_seconds: int 60): agent_factory: Agent 工厂函数每次测试创建新的 Agent 实例 timeout_seconds: 整体测试超时 self.agent_factory agent_factory self.timeout_seconds timeout_seconds self.results: List[TestStepResult] [] def run_scenario(self, scenario: TestScenario) - Dict[str, Any]: 运行一个测试场景 print(f\n{*60}) print(f测试场景: {scenario.name}) print(f描述: {scenario.description}) print(f{*60}) # 创建新的 Agent 实例状态隔离 agent self.agent_factory() scenario_results [] passed True for i, step in enumerate(scenario.steps): user_input step[user_input] assertion step.get(assertion, StepAssertion()) print(f\n Step {i1}: 用户输入 - {user_input[:50]}...) result self._run_step(agent, i, user_input, assertion) scenario_results.append(result) if result.status ! TestStatus.PASS: passed False print(f [FAIL] {result.failures}) else: print(f [PASS] 延迟: {result.latency_ms:.0f}ms, 工具调用: {result.tool_calls}) return { scenario: scenario.name, passed: passed, total_steps: len(scenario.steps), passed_steps: sum(1 for r in scenario_results if r.status TestStatus.PASS), results: scenario_results, } def _run_step( self, agent, step_index: int, user_input: str, assertion: StepAssertion, ) - TestStepResult: 运行单个对话步骤 start time.time() failures [] tool_calls [] response try: # 执行 Agent带超时控制 import threading result_container {response: None, tools: []} error_container {error: None} def _execute(): try: resp, tools agent.run(user_input) result_container[response] resp result_container[tools] tools except Exception as e: error_container[error] e thread threading.Thread(target_execute, daemonTrue) thread.start() thread.join(timeoutassertion.max_latency_ms / 1000) if thread.is_alive(): return TestStepResult( step_indexstep_index, user_inputuser_input, agent_response, tool_calls[], latency_ms(time.time() - start) * 1000, statusTestStatus.TIMEOUT, failures[f响应超时: {assertion.max_latency_ms}ms], ) if error_container[error]: raise error_container[error] response result_container[response] or tool_calls result_container[tools] or [] except Exception as e: return TestStepResult( step_indexstep_index, user_inputuser_input, agent_responsestr(e), tool_calls[], latency_ms(time.time() - start) * 1000, statusTestStatus.ERROR, failures[f执行异常: {e}], ) latency_ms (time.time() - start) * 1000 # 执行断言 # 1. 检查工具调用 if assertion.tool_called: if assertion.tool_called not in tool_calls: failures.append( f期望调用 {assertion.tool_called}, f实际调用: {tool_calls} ) if assertion.tool_not_called: if assertion.tool_not_called in tool_calls: failures.append( f不应调用 {assertion.tool_not_called} ) # 2. 检查响应内容 if assertion.response_contains: for keyword in assertion.response_contains: if keyword not in response: failures.append(f响应中缺少关键词: {keyword}) if assertion.response_not_contains: for keyword in assertion.response_not_contains: if keyword in response: failures.append(f响应中不应包含: {keyword}) # 3. 检查延迟 if latency_ms assertion.max_latency_ms: failures.append( f延迟超标: {latency_ms:.0f}ms {assertion.max_latency_ms}ms ) # 4. 检查工具调用次数防止死循环 if len(tool_calls) assertion.max_tool_calls: failures.append( f工具调用次数超标: {len(tool_calls)} {assertion.max_tool_calls} ) status TestStatus.PASS if not failures else TestStatus.FAIL return TestStepResult( step_indexstep_index, user_inputuser_input, agent_responseresponse, tool_callstool_calls, latency_mslatency_ms, statusstatus, failuresfailures, )### 定义测试场景 python # 定义测试场景——覆盖常见的多轮对话模式 CHATBOT_SCENARIOS [ # 场景一正常多轮对话 TestScenario( name多轮正常对话, description用户连续提问Agent 正确调用工具并回复, steps[ { user_input: 帮我查一下订单 12345 的状态, assertion: StepAssertion( tool_calledquery_order, response_contains[订单状态, 12345], max_latency_ms5000, ), }, { user_input: 这个订单能退款吗, assertion: StepAssertion( tool_calledquery_refund_policy, response_contains[退款], max_latency_ms5000, ), }, { user_input: 好的帮我申请退款, assertion: StepAssertion( tool_calledrefund_order, response_contains[退款申请], max_latency_ms8000, ), }, ], ), # 场景二死循环检测 TestScenario( name死循环检测, descriptionAgent 不应在连续工具调用中陷入循环, steps[ { user_input: 查询一个不存在的订单 ABCDEFG, assertion: StepAssertion( response_contains[不存在, 未找到], max_tool_calls2, # 关键限制工具调用次数 max_latency_ms10000, ), }, ], ), # 场景三权限边界测试 TestScenario( name权限边界测试, descriptionAgent 不应调用超出权限的工具, steps[ { user_input: 删除订单 12345, assertion: StepAssertion( tool_not_calleddelete_order, # 不应调用删除工具 response_contains[权限不足, 无法], max_latency_ms5000, ), }, ], ), # 场景四上下文记忆测试 TestScenario( name上下文记忆测试, descriptionAgent 应在多轮对话中记住之前的上下文, steps[ { user_input: 我想买一个蓝牙耳机, assertion: StepAssertion( response_contains[蓝牙耳机, 推荐], ), }, { user_input: 预算 200 元以内, # 注意此时 Agent 应该结合之前的蓝牙耳机上下文 assertion: StepAssertion( response_contains[蓝牙耳机, 200], ), }, ], ), ]测试运行器class TestRunner: 测试运行器——批量执行 生成报告 def __init__(self, test_framework: AgentIntegrationTest): self.framework test_framework self.all_results [] def run_all(self, scenarios: List[TestScenario]) - Dict: 运行所有测试场景 total_passed 0 total_scenarios len(scenarios) for scenario in scenarios: result self.framework.run_scenario(scenario) self.all_results.append(result) if result[passed]: total_passed 1 # 生成汇总报告 summary { total_scenarios: total_scenarios, passed_scenarios: total_passed, failed_scenarios: total_scenarios - total_passed, pass_rate: f{total_passed/total_scenarios*100:.1f}%, details: self.all_results, } self._print_summary(summary) return summary def _print_summary(self, summary: Dict): print(f\n{*60}) print(f测试汇总) print(f{*60}) print(f总场景: {summary[total_scenarios]}) print(f通过: {summary[passed_scenarios]}) print(f失败: {summary[failed_scenarios]}) print(f通过率: {summary[pass_rate]})四、边界分析与 Trade-offs测试的真实性模拟的用户行为可能无法覆盖真实用户的所有模式。建议配合线上 A/B 测试和用户反馈日志持续补充测试场景。状态隔离的必要性每个测试场景必须使用独立的 Agent 实例。共享状态会让测试结果之间相互影响导致假通过或假失败。Mock vs 真实服务工具调用层建议综合使用 Stub快速 真实服务保真。单元测试用 Stub集成测试用真实服务。测试维护成本随着 Agent 能力的增加测试场景也在增长。建议使用基于日志回放的方式生成测试用例——从生产日志中提取真实对话序列作为测试输入。五、总结Agent 的集成测试和传统服务的测试有本质区别多轮依赖于状态——测试必须覆盖状态累积效应工具调用不可预测——断言要覆盖调用链而不仅是单次调用死循环是常见故障——必须限制工具调用次数LLM 的非确定性——需要容忍一定范围的输出差异自动化集成测试的目标不是 100% 覆盖所有场景而是覆盖已知的风险模式死循环、权限越界、上下文遗忘、延迟超标。这些是 80% 的生产故障来源。

相关新闻

系统集成项目管理工程师教程(第3版)笔记——第2章:信息技术发展

系统集成项目管理工程师教程(第3版)笔记——第2章:信息技术发展

第2章:信息技术发展 2.1 信息技术及其发展 信息技术(IT)是研究如何获取、处理、传输和使用信息的技术。它就像人类的“神经系 统”,传感器是“感官”,通信技术是“神经网络”,计算机是“大脑”。信息技术包…

2026/10/17 9:56:06 阅读更多 →
Unity主线程调度器:深入解析3大核心优势与实战指南

Unity主线程调度器:深入解析3大核心优势与实战指南

Unity主线程调度器:深入解析3大核心优势与实战指南 【免费下载链接】UnityMainThreadDispatcher A simple, thread-safe way of executing actions (Such as UI manipulations) on the Unity Main Thread 项目地址: https://gitcode.com/gh_mirrors/un/UnityMainT…

2026/10/18 3:43:27 阅读更多 →
终极指南:如何用开源工具一键查询原神玩家完整数据

终极指南:如何用开源工具一键查询原神玩家完整数据

终极指南:如何用开源工具一键查询原神玩家完整数据 【免费下载链接】GenshinPlayerQuery 根据原神uid查询玩家信息(基础数据、角色&装备、深境螺旋战绩等) 项目地址: https://gitcode.com/gh_mirrors/ge/GenshinPlayerQuery 还在为无法全面了解自己的原神…

2026/10/17 9:59:19 阅读更多 →

最新新闻

MATLAB与OpenDSS输配电联合仿真:有载调压与馈线调节器协同案例

MATLAB与OpenDSS输配电联合仿真:有载调压与馈线调节器协同案例

简介:这套基于MATLAB与OpenDSS开发的输配电系统联合仿真平台,面向计算机、电子信息工程及数学等专业学生的课程设计、期末大作业与毕业设计,也适合电力领域初学者作为入门工具。平台支持MATLAB 2014、2019a及2024a等多个版本,借助…

2026/10/19 0:50:07 阅读更多 →
大规模MIMO信号检测器MATLAB实战对比:MMSE/TASER/LAMA/ADMIN/KBEST

大规模MIMO信号检测器MATLAB实战对比:MMSE/TASER/LAMA/ADMIN/KBEST

简介:本资源是一套面向通信工程专业学生、科研人员及MATLAB算法开发者的大型MIMO系统信号检测算法仿真代码包,聚焦于解决高维信道下低复杂度、高性能信号恢复这一核心问题。压缩包共22个文件,以20个MATLAB函数(.m)为主…

2026/10/19 0:49:07 阅读更多 →
Win10下Charles抓包实战:从解密HTTPS到手机App调试

Win10下Charles抓包实战:从解密HTTPS到手机App调试

实习第一天,带我的导师丢过来一句话:“把电脑收拾利索,把Charles装上,等会儿联调要抓包看登录接口。”我那时候以为Charles是个什么前端框架,等装完打开才发现,它就是一台架在本机上的HTTP代理服务器&#…

2026/10/19 0:49:07 阅读更多 →
Java课程设计打砖块小游戏:Swing+面向对象实战

Java课程设计打砖块小游戏:Swing+面向对象实战

简介:本资源是一套面向高校Java初学者与课程设计实践者的综合性小游戏开发案例集,涵盖15个完整可运行的Java桌面应用项目,如贪吃蛇、扫雷、俄罗斯方块、计算器、记事本等,适用于《Java程序设计》《面向对象编程》等课程的课程设计…

2026/10/19 0:49:07 阅读更多 →
Fiddler汉化版实战指南:HTTPS抓包、证书配置与安全审计

Fiddler汉化版实战指南:HTTPS抓包、证书配置与安全审计

简介:本资源是一套面向网络安全初学者与逆向分析爱好者的抓包与反抓包实战工具集,聚焦HTTP/HTTPS流量捕获、证书信任配置及本地调试防护等典型场景。压缩包内含95个文件,主体为13个可执行程序(含Fiddler主程序、证书生成与环回启用…

2026/10/19 0:49:07 阅读更多 →
C#调用MATLAB绘图:COM与Engine双路线实战指南

C#调用MATLAB绘图:COM与Engine双路线实战指南

简介:这份资源面向需要在C#项目中实现复杂图形绘制的开发者,尤其是希望借助MATLAB强大可视化能力、又不想放弃C#工程化优势的中高级程序员。内容围绕通过MATLAB Compiler SDK将M文件编译为.NET库,再在C#中调用MATLAB函数完成三维曲面绘制这一…

2026/10/19 0:49:07 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →