做车载HMI测试这些年,我最深的体会是:测试真正难的从来不是"跑用例"本身,而是怎么让一堆跑不完的用例、改不完的需求、复现不了的偶发缺陷,在有限的人和时间内有序收场。当飞书机器人和AI Agent这两个词开始频繁出现在行业群里时,我第一反应是——它们能不能把我从"每天早上挨个问用例结果、下班前整理报告发群"的琐事里捞出来?答案是能。这篇文章记录的是我基于飞书机器人 × AI Agent,从零搭建的一套车载HMI自动化测试平台,涵盖整体架构、飞书机器人接入、Agent核心实现、测试执行器联动,以及落地过程中被真实环境逼着改出来的几轮优化。适合正在做车载HMI/智能座舱测试自动化、或者想给现有测试平台加一个智能入口的工程师参考。
我会尽量把踩过的坑和最终方案一起讲清楚,尤其是那些官方文档不会写的东西:飞书事件回调超时怎么处理、Agent的Token上下文怎么控制、大模型判断测试结果时怎么防止幻觉,这些都是真金白银换来的教训。
1. 车载HMI测试的真实痛点:我为什么非要把飞书机器人和AI Agent请进来
1.1 传统HMI自动化测试卡在哪几个环节
先说说背景。车载HMI的测试对象这几年膨胀得很厉害:仪表盘、中控大屏、HUD、副驾娱乐屏,一套车机里可能同时跑着QNX、Linux、Android Automotive三套系统。UI迭代又特别快,几乎每个版本都有布局调整、文案修改、深色模式适配。传统自动化平台在这个场景下其实能做的不少,但实际用起来总有几座大山:
- 入口分散:测试任务在JIRA里,用例在测试管理平台上,执行脚本散落在各台工控机,报告最后要人手工汇总。任何一次版本回归,光是把这些信息凑齐就要半天。
- 编排靠人:跑哪些用例、要不要注入车速信号、导航软件要不要先切到后台,这些依赖人肉判断。用例执行顺序一旦出错,整批结果全部作废。
- 结果割裂:截图、日志、CAN信号记录、覆盖率数据各存各的,出了问题很难把时间轴对齐,定位一个偶现黑屏可能要翻好几个小时的日志。
- 反馈链路长:测试执行完不是终点,还要整理报告、@对应开发、跟踪修复状态。这一步纯靠人工重复劳动,也是最容易漏的环节。
典型场景是这样的:测试工程师小A要验证某个版本的导航应用,他得先去JIRA看任务,再打开自动化平台选用例集,等跑完后翻日志和截图找问题,最后把结果整理成PPT发到群里,再挨个@人确认。整条链路里,人承担了大量"信息搬运"工作,真正的分析判断反而被压缩了。
1.2 AI Agent在测试链路里到底扮演什么角色
很多人一听说AI Agent做测试,第一反应是"让大模型去执行用例"。这个理解其实偏了。Agent在测试平台里的核心价值不是"智能执行用例",而是"把测试需求转成可执行的规划,再把结果组织成人能直接理解的信息"。
举个例子:你给Agent发一句"验证夜间模式下仪表盘导航转弯箭头显示是否正常"。传统平台听到这句话是懵的,因为它需要的是一个明确的用例ID。Agent会把这个需求拆成一系列动作:切换到夜间模式、通过CAN信号把车速拉到巡航状态、启动导航并设定路线、在转弯点截图、和基准图比对、输出差异分析。每一步对应一个工具调用,工具执行完的结果再汇总成结构化报告。
并且Agent还有个传统框架给不了的能力:失败原因解释。脚本断言失败只会告诉你"截图不匹配",Agent可以结合截图、日志、当前HMI状态做一个综合分析,给出"截图差异集中在左上角区域,疑似路况卡片遮挡,建议检查导航SDK的布局适配"。这种输出对开发定位问题的帮助是碾压级的。
1.3 飞书机器人为什么是天然的第一入口
入口放在IM里,不是拍脑袋决定的。车载测试团队的人员分布决定了这件事:硬件台架在实验室,软件团队在办公区,项目管理在飞书群里,测试报告和缺陷跟踪最终也要回到飞书。IM是所有人日常停留的地方,把测试平台接进去,用户不需要再打开一套新系统,在群里发一条消息或者点一个卡片按钮就能发起/查看一个任务。
飞书开放平台的成熟度也正好够用:机器人事件订阅、消息卡片、上传文件、多维表格API、获取用户身份。这些能力组合起来,足够支撑一个双向交互的测试入口——不仅能"发消息给你",还能"听你指令、识别你是谁、给你可点击的交互界面"。自定义webhook机器人只能单向发消息,做不了这个闭环,所以平台最终选择了企业自建应用的方式接入。这条路在授权审核上多花了一点时间,但后面用起来极度顺畅。
2. 平台整体架构:接入层、编排层、执行层是怎么做到解耦的
2.1 分层架构的总设计思路
整套平台跑起来之后,我习惯把架构描述成四个逻辑层:接入层、编排层、执行层、数据层。
接入层只有飞书机器人这个角色,负责接收群消息、卡片按钮回调、任务状态查询请求,并做最基础的意图判断和权限校验。编排层是AI Agent加任务状态机的组合,负责把一句话拆成多个步骤、选择工具、维护上下文、判断结果是否满足预期。执行层是各种测试执行器:ADB/SSH操作器、CAN信号模拟器、截图和视觉比对引擎。数据层存测试结果、Agent会话记录、报告文件和任务状态。
这个结构和很多人想象的"Agent直接调工具"不同,中间被刻意加了一层状态机。Agent负责"规划"和"解释",状态机负责"保证流程不会乱走路"。举个例子,Agent规划出5个步骤,状态机会按顺序分发,第2步超时或失败时,状态机会中断并触发补偿逻辑,而不是把异常丢给大模型现场发挥。测试平台需要的是稳定可复现的执行,这一层隔离非常值得做。
2.2 为什么消息接入要独立于Agent编排
这是我最早踩的一个大坑,差点让整个项目翻车。飞书对事件回调有一个硬性要求:服务器必须在3秒内返回应答,否则飞书会判定回调失败并不停重试。如果我把飞书消息直接同步抛给Agent去编排执行,一个稍微复杂的规划动作就要调用大模型接口,光等LLM返回通常就要好几秒,回调必然超时。
最终方案是在接入层和编排层之间加了一个事件队列。飞书事件到达后,机器人服务立刻返回一个空JSON确认,同时把事件内容写进队列,后台Worker消费队列后,再交给Agent编排。用户看到的是"已收到任务,正在编排",执行结果通过异步消息推回到群里。这套机制把飞书回调的时效性要求和Agent处理的耗时彻底解耦,后面再遇到任何长任务都没慌过。
2.3 执行层抽象:把"操作HMI"变成一套标准工具
如果Agent要直接操作不同设备,代码会很快变成一团乱麻。我做的第一件事是定义执行器接口:所有工具统一提供名称、描述、参数Schema和一个invoke方法,输入输出都是JSON结构。Agent只和这套接口打交道,不关心底层是ADB还是SSH还是CAN设备。
trait HmiTool { fn name(&self) -> &str; fn description(&self) -> &str; fn parameters(&self) -> Value; // JSON Schema async fn invoke(&self, params: Value) -> Result<ToolResult, ToolError>; } struct ToolResult { status: ToolStatus, // Success | Failed | Skipped summary: String, // 喂给大模型看的精简摘要 data: Value, // 完整结构化数据,用于报告 }这里的summary字段是为Token省钱设计的,后面在Agent上下文管理里会细讲。执行器抽象好之后,新增一种设备支持只需要实现一个trait,Agent编排逻辑一行不用改。
3. 飞书机器人模块:从群聊消息到可追踪的测试工单
3.1 接入选型:企业自建应用比自定义机器人强在哪
飞书机器人有两条路,一条是群里的自定义机器人,配置一个webhook地址就能发消息,五秒钟就能跑通。另一条是企业自建应用,要建应用、配权限、申请审核,麻烦得多。我刚开始也图省事试过自定义机器人,但很快发现它致命的问题:只能发、不能收。用户想发起测试任务、查状态、点按钮交互,全走不了webhook。自定义机器人拿不到@人的身份信息,更做不了基于用户角色的权限控制。
企业自建应用虽然开通麻烦,但是把能力补全了:可以订阅机器人消息事件,能拿到用户ID和群ID,可以发送消息卡片并接收按钮回调,还能调用云文档接口上传文件、创建多维表格。对测试平台来说,这些能力每一项都是刚需。我的建议是如果做demo可以先用自定义机器人,正式平台直接选自建应用,不要走弯路。
3.2 消息意图识别:规则优先,大模型兜底
飞书机器人收到的消息大致分两类:一类是格式明确的指令,比如"/run nav_regression_suite"或卡片按钮回调;另一类是自然语言描述,比如"帮我查一下昨天深色模式那批用例为啥挂了"。我在这块用了很务实的策略:能走规则的绝不动大模型。
固定格式指令直接用字符串解析和参数匹配,耗时几毫秒,不消耗Token。自然语言才交给AI Agent做意图识别和任务编排。这么做的原因很直接:测试平台的指令重复率极高,每次都让大模型处理纯粹是浪费,而且规则解析的稳定性远高于模型。实测下来,平台里大约70%的请求走了规则路径,只有剩下30%真正需要Agent的理解能力。
3.3 测试报告如何变成飞书表格和消息卡片
报告推送是用户感知最直观的部分,也是我花心思最多的部分。最初我只是把文本结果拼成一条普通消息发出去,用起来很痛苦:几十个用例的结果堆成一大段,手机上根本看不下去,想定位失败项还得翻半天。
后面改成组合方案:一条消息卡片加一个文件附件。卡片上只放汇总信息——通过率、失败数、阻塞数、总耗时、失败用例Top5列表,每个失败项后面带一个"查看详情"按钮。点击按钮回调触发查询,返回这条用例的截图、日志、Agent分析结论。明细数据则单独整理成Excel文件上传到会话里,用飞书的云文档能力直接在线预览。
这里有个细节:Excel文件的表头我用的是项目组已有的模板格式,用例编号、模块、预期结果、实际结果、缺陷关联、截图链接,每个字段都对齐了飞书文档表格的列类型。这样测试经理可以直接把表格导入已有的管理文档,不用二次加工。真正做到"报告即数据"。
4. AI Agent核心设计:用Rust把任务编排、工具调用和Token管理落地
4.1 为什么Agent运行时选Rust而不是Python
先回答一个绕不开的问题:为什么用Rust写Agent运行时,而不是生态更成熟的Python。一是部署体验,Rust一个静态二进制扔到台架工控机上就能跑,不装Python环境、没有依赖冲突,对于长期驻场的测试设备来说太重要了。二是并发稳定性,Agent运行时本质是大量异步任务同时在飞书接入、工具调用、LLM请求之间流转,Rust的async/await和所有权模型让这块代码写起来很踏实,不用担心共享状态的野问题。
当然Rust的短板也很明显:AI生态库少、模型SDK不全、写业务逻辑比Python慢。我的解法是核心运行时用Rust,间接执行的算子脚本仍旧用Python子进程。两边通过JSON over stdio通信,Rust负责调度和生命周期,Python负责算子里那些生态依赖重的逻辑,比如视觉模型推理、CAN协议解析。这个混合架构实际跑下来是最顺的,两边各取所长。
4.2 我用的是Plan-and-Execute而不是单体ReAct
AI Agent的主流架构里,ReAct模式和Plan-and-Execute是我实际对比过两条路线。ReAct的思路是让模型在每一轮都观察当前状态、决定下一步动作,灵活但有两个问题:Token消耗高,每步决策都要把历史搬给模型;行为不稳定,模型偶尔会反复尝试同一个失败动作。对测试场景来说,这两个问题都很致命,因为用例执行的路径应该是确定性和可解释的。
最终我选了Plan-and-Execute的变体:大模型只负责把用户需求翻译成一个有序的任务计划,后续步骤的执行由状态机驱动,每一步的工具选择在规划时就固定下来。Agent在单步骤遇到失败时,允许把失败信息和重试建议反馈给模型,让模型决定是重试、跳过还是终止。这样既保留了对异常情况的智能处理能力,又保证了正常路径的稳定执行。从效果看,平台上线三个多月,正常路径零随机乱跳。
4.3 Token管理:搞懂Agent的"短期记忆"才能控住成本
先说Token是什么。Token是LLM处理文本的最小单位,中文场景下大约一个字对应1到2个Token。模型有上下文窗口限制,超了就要截断或者报错。Agent对话过程中,每轮规划、每次工具返回结果都会累积进历史上下文,越滚越大,很快把窗口撑爆。
我在这里吃的教训最深。第一版实现里,我天真地把所有工具执行完整输出都塞给模型,结果跑了三十个用例之后,上下文窗口直接爆掉,平台报错瘫痪。后面做了三件事来管住Token:
- 历史截断:只保留最近N轮对话,更早的消息自动丢弃。对测试平台来说,Agent和用户的交互轮次不会很多,保留最近五轮基本够用。
- 工具结果压缩:就是前面说的
summary字段。工具执行完,先由代码自动生成一段精简摘要,完整数据存数据库,只有摘要进上下文。比如截图比对工具返回的是"相似度97.2%,差异区域2处,位于左上角"这一句,而不是整个Base64图片或几百行像素数据。 - 会话隔离:每个测试任务单独建一个Agent会话,任务结束后上下文归档,避免不同任务的上下文互相污染,也避免历史累积导致Token失控。
这三板斧下来,单任务Token消耗降低了差不多80%,平台响应速度和费用都健康了。
5. 工具执行层:Agent怎么真正驱动车载HMI和执行器
5.1 工具注册表与参数校验
Agent能调的工具不是硬编码在编排逻辑里的,而是维护在一张工具注册表里。每个工具包含四件套:名称、描述、参数Schema、执行器实现。Agent在规划时,会把用户需求、可用工具列表、工具描述一起丢给大模型,让模型挑合适的工具并输出JSON参数。Rust端用serde做参数反序列化,Schema不符的请求直接拒绝,不给模型瞎编的机会。
这里有一条经验:工具描述要写"人话"。同样一个"截图"工具,描述写成"截图当前HMI界面并返回图像文件路径"和"get_screenshot"对模型理解能力的影响完全不同。描述越具体,模型选错工具的概率越低。平台里每个工具的描述都经过了几轮措辞优化,这是0成本但收益极其明显的优化点。
5.2 通过SSH/ADB操作目标设备
车载HMI的操作执行器基本跑两条协议:Android Automotive设备走ADB,QNX/Linux设备走SSH。我在执行器里封装的几个核心动作包括:启动/停止应用、注入触摸和按键事件、读取当前焦点窗口、设置系统参数、执行shell命令、截屏。每个动作的底层都对应一串指令,但Agent看到的只是一些"语义化动作"。
这里很容易忽略的一个点是命令超时。车载设备的性能参差不齐,冷启动导航应用有时候能卡到几十秒,但如果设置无限期等待,一旦设备异常挂起,Agent就会卡死在那里。我的做法是所有远程命令统一加一个可配置超时,默认30秒,超时后自动杀掉命令并返回超时错误。错误信息会喂给Agent做重试判断,但执行框架本身不会无限等下去。
5.3 CAN信号模拟与HMI状态联动
HMI测试有大量场景依赖车辆信号。比如要验证仪表盘的车速显示,就得让整车的CAN网络把车速信号喂给仪表;要验证转向灯指示,就得模拟转向灯开关的状态。平台里集成了一个CAN信号模拟器,通过CAN卡和自研信号发生器,在测试环境里广播刹车、油门、挡位、转向灯、车速这些关键信号。
Agent侧的工具集于是多了set_speed、set_gear、set_turn_signal这类动作。执行器内部做一层状态同步:设置CAN信号的同时,会主动等待HMI端的状态刷新,再触发后续截图或操作,确保测试时序正确。这一层等待逻辑很关键,因为CAN信号到UI渲染天然有延迟,如果不等待直接截图,经常会捕获到过渡帧,造成假失败。
5.4 视觉验证:截图比对那部分如何防误判
视觉验证是车载HMI测试的核心,也是AI最容易帮倒忙的地方。平台里的视觉引擎分了三层:第一层用OpenCV做模板匹配和感知哈希粗比对,快速筛出明显一致的截图;第二层对差异区域做像素级定位,输出差异框和差异率;第三层才轮到AI介入——把差异区域的局部截图、相似度数值、预期描述一起给大模型,让模型判断差异是缺陷、是预期变化还是测试环境干扰。
这里要特别强调一点:不要让大模型直接看全图做判断。全图分辨率高、细节多,既费Token又容易让模型被背景干扰,最后给出一个泛泛的"看起来不太对"。局部裁剪加结构化指标的设计非常实用,模型看到的是"差异率3.2%,差异集中在左上角一个图标区域,预期该区域显示箭头图标",这种输入下模型输出准确率会高得多。
6. 落地过程中被真实环境教育的三件事
6.1 飞书回调3秒超时:先应答后异步是底线
前面提过事件队列的做法,这里展开说一下当时的排查过程。第一版联调时,群里发消息经常出现"消息已读但没有反应",飞书后台能看到大量回调重试记录。顺着链路一步步查,才定位到问题出在Agent规划调用了LLM接口,单次响应就要3到5秒,整条飞书回调必然超时。修复方案就是在接入层加队列,先响应后处理。这里想提醒后来者一点:飞书回调的3秒限制任何人都绕不过去,任何同步处理逻辑放进回调里都是定时炸弹。
6.2 长测试任务的会话保持与超时控制
一个完整的回归任务可能要跑三十分钟,中间会经历多次Agent规划、工具调用、结果汇总。这个过程中如果某个环节花了太长时间,用户会以为平台挂了。我们平台的做法是给长任务设计了一个进度反馈机制:Agent每完成一个步骤,就往飞书群推一条轻量进度消息,比如"计划5步,已完成2步:CAN信号注入完成,正在启动导航"。用户可以随时打断说"跳过这步",Agent会重新规划后续路径。
这个机制同时解决了会话保持问题。因为每条进度消息都携带Task ID,用户回复时可以通过Task ID直接追加指令,不需要重新描述一遍完整需求。
6.3 大模型幻觉导致的假失败,是最难调的一类问题
平台刚上线那两周,出现过几次特别迷惑的情况:测试结果明明都通过了,Agent在总结里却写"注意:导航界面疑似显示异常"。开发一看截图,界面完全正常。这就是典型的模型幻觉——它看到差异区域的局部截图,结合上下文自己脑补了一个"异常"。
我的解决方案分三步:第一,给Agent的判定通道增加置信度门槛,低于门槛统一标成"Review",不直接下PASS或FAIL的结论;第二,结构化数据优先于模型描述,工具返回里已有的数值指标(相似度、差异框坐标)直接透出在报告里,模型只负责解释,不负责创造指标;第三,所有"Review"状态的结论必须人工复核后才能关闭,AI结论永远只是辅助意见。这套机制之后,AI误判从"会改错结论"降级成了"只会多标记一条等人工看一眼",完全在可控范围。
6.4 消息风暴与并发限流
车载测试团队人数不多,但一个项目群里可能有几十个自动化任务同时在跑。每个任务每完成一步推一条进度消息,再加上报告推送和按钮回调,量积累起来也不小。更要命的是如果某个Agent实例卡住,LLM API持续重试,会对大模型接口方的限流策略造成压力。
平台做了两件事。一是会话级锁:同一个测试项目群同时只允许一个任务在演化状态,后面提交的任务排队等待,避免多个Agent同时操作同一台台架设备导致命令打架。二是全局限流器:对LLM API调用做令牌桶限流,超出额度时任务降级为重试队列,并主动通知用户"当前负载高,任务已排队"。这两层保护上线后再没出现过消息风暴把整个平台拖垮的情况。
写在最后
这套平台上线之后,我最大的感受是自动化测试终于不再只是"跑用例",而是一个能对话、能解释、能把人的注意力引导到最值得看的地方的助手。中间踩过的坑——尤其是Token管理、回调超时、模型幻觉这三块——如果重新来一遍,我会在设计阶段就把消息链路和Agent状态机画清楚,而不是先跑通Demo再回来补课。
最后分享一个小技巧:在设计AI Agent驱动的测试平台时,别让大模型做真正的断言,把硬性判定留给确定性算法(坐标、阈值、状态码、异常日志),让大模型专注于描述异常、组织步骤、解释结果。这样既能保住准确性,又能享受大模型带来的灵活交互,两全其美。