飞书机器人×AI Agent:车载HMI自动化测试平台落地实践 “跑一个工况测试把结果发我群里。”——这句话从提需求到真正看到报告以前至少要折腾三个人测试工程师手动搭环境、执行脚本、截图留证、整理Excel然后等开发有空了再去群里口头同步。如果我们把这些动作全部交给一个“听得懂人话”的机器人让它在飞书群里自己接下指令、自己调度测试设备、自己跑完车载HMI全流程用例再把结果整理成表格卡片推回来这件事就成了。本篇文章要讲的就是我自己落地这套“飞书机器人 × AI Agent”组合的车载HMI自动化测试平台全过程从架构选型、系统分层到每个核心模块的实现细节以及那些不跑一遍根本意识不到的坑。这套平台的核心思路一句话就能说清把飞书当成人机交互入口把 AI Agent 当成大脑把车载HMI自动化测试框架当成手脚。用户不需要学命令行、不需要打开测试平台网页、甚至不需要记住测试用例编号直接在企业IM里用自然语言下指令机器人就会自动拆解任务、调用对应的测试工具链、实时播报进度、最终生成结构化报告。整套体系我自己跑了几个月现在团队里测试同学、开发同学、项目经理都在用日均执行任务量比之前手动操作翻了四倍左右。下面直接进入正题说一下整体设计和落地细节。1. 系统定位与整体设计思路1.1 传统车载HMI测试流程的痛点车载HMIHuman Machine Interface测试和普通App测试差距非常大。它不光是点按钮、看页面还要联动CAN总线信号模拟、ECU状态切换、仪表盘UI刷新、语音交互、触控响应等一系列硬件在环操作。最典型的一个场景测试“车辆速度变化时仪表盘数字刷新是否正常”这个用例需要先通过CAN工具发送车速信号比如0 → 30km/h → 60km/h → 0同时在每个节点截图验证仪表盘指针和数字显示。手动执行的话一个人一天最多跑几十条这样的用例而且极度枯燥——盯着仪表盘截图、比对像素、记录日志一个不留神就看漏了。传统流程还有两个附加问题。其一环境准备非常重CANoe或者PCAN的硬件连接、电源序列上电、HMI模拟器启动、诊断仪的会话建立每一步都要人工确认。其二结果反馈链路长测试执行完还要整理截图、录屏、日志和结论写出一份让人看得懂的报告再通过IM或者邮件发出去。等到开发真正看到问题的时候可能已经过去大半天了。这些痛点叠加在一起让我意识到测试平台必须“主动服务”而不是“被动等待”——于是有了让机器人自己在群里跑测试的想法。1.2 为什么是飞书机器人 AI Agent 的组合我最早考虑的其实不是AI Agent而是传统关键词匹配机器人用户输入命令字比如“跑TC001”机器人解析后调测试框架API。这个方案简单但有个硬伤——团队成员根本记不住用例编号和参数格式。他们真正想要的是说一句“帮我把空调温度从22度调到26度看看触控响应有没有卡顿”然后机器就能自己去找对应用例、组合参数、执行并给结论。后来决定引入大模型来做意图理解这正好点到 AI Agent 的核心能力让模型不仅会“聊天”还会“做事”。一个Agent的基本结构是大模型负责理解意图、规划步骤、决定调用哪个工具工具层负责真实执行执行结果再反馈给模型做汇总和汇报。这个链路放在自动化测试平台上是天然的——测试平台就是工具飞书就是交互界面。选飞书主要是基于实际环境公司内部IM就是飞书所有人在一个IM里沟通不需要额外安装客户端。而且飞书开放平台提供了完整的机器人能力和消息卡片能力支持长连接事件订阅回调不需要暴露公网端口这对内网部署非常友好。更关键的是飞书消息卡片支持表格、按钮交互、分栏展示测试报告可以直接结构化呈现不需要跳转网页。1.3 设计目标与边界切分这个平台我在第一版就明确了几条边界避免变成“什么都做”的大杂烩只负责HMI相关测试任务的解析、调度、执行和结果回传不承担测试用例的编辑器功能不强行全自动化环境搭建电源上电、CAN通道初始状态校验这类高危动作保留人工确认位不做大而全的测试管理平台原有Jira、禅道等缺陷系统继续存在机器人只做“执行与汇报”环节系统单机可部署不引入K8s等重调度组件保证一台服务器能跑起来。边界切好以后整个架构做起来就很顺了。接下来拆解一下核心架构。2. 核心架构解析2.1 系统分层架构整个平台分为七层每一层职责尽量单一避免互相耦合。实际部署时全部跑在一台物理服务器上通过Docker Compose管理容器整体拓扑如下接入层负责与飞书服务器建立长连接接收消息事件、回调事件负责发送消息卡片与文件消息。Agent层核心大脑基于大型语言模型做意图识别、任务拆解、工具选择并通过函数调用Function Calling机制触发后续动作。调度层负责任务排队、并发控制、设备锁管理、链路追踪。调度层是Agent和测试执行之间的“防抖”模块避免大模型直接操作硬件。工具层把测试平台能力封装成一个个可被Agent调用的工具比如“查询可用设备”“执行指定用例”“读取当前执行状态”“获取测试报告数据”。执行层车载HMI自动化测试框架负责真实控制CAN卡、电源、HMI模拟器等硬件执行用例。数据层存储每次执行的记录、日志、截图、结构化结果并提供检索API。展示层基于飞书消息卡片动态生成测试摘要、表格、按钮操作使用户能直接在IM内完成查看和操作。一句话概括Agent 不直接碰硬件调度层也不直接回消息所有交互都通过统一的API网关走。这样做的好处是每一层可以独立升级替换。比如今天接入层绑的是飞书明天换成钉钉或企业微信只需要重写接入层适配器Agent和调度层完全不用动。同理执行层今天用的自研HMI脚本明天换成基于CANoe的框架也只隔离在工具层内部。2.2 飞书机器人接入细节飞书机器人的接入门槛其实很低麻烦的是权限和事件订阅。我在开放平台创建了一个企业自建应用拿到App ID和App Secret然后申请了以下关键权限权限用途im:message接收单聊和群聊消息im:message.send_as_bot以机器人身份发送消息im:resource获取消息中的图片资源截图回传用im:chat获取群信息、成员列表判断谁有权限下指令权限申请后需要发布应用版本否则权限不生效这个细节坑了不少人。事件订阅方式我强烈建议用长连接WebSocket而不是Webhook回调。Webhook要求暴露一个公网可访问的HTTPS地址在内网环境还得做内网穿透而且飞书的回调机制要求请求后在3秒内响应处理逻辑稍复杂就容易超时。长连接是飞书主动向你的服务端发起WebSocket连接事件被动推给你无需暴露地址断线后SDK自动重连对部署环境非常友好。飞书官方Python SDKfeishu或叫larksuite-oapi集成了长连接模式大概二十行代码就能跑起来。机器人的交互能力我用了两类一类是纯文本消息适合简单指令确认另一类是交互卡片包含按钮和表格。卡片的高级功能后面实操部分细讲。2.3 AI Agent 内部结构与关键机制如果你接触过 AI Agent应该知道现在比较主流的架构基本逃不开“感知—规划—行动—反馈”这个循环。落到这个项目里我的Agent内部结构如下System Prompt定义了机器人的角色边界、可使用的工具清单、输出格式规范。这是最重要的部分一个措辞不严谨的Prompt会让大模型去调用不存在的工具或者用错误格式返回。大模型推理使用Long Context窗口能力较强的模型我这边用的是一个私有化部署的中等参数模型配合云端模型做互补机密场景走私有化常规场景走云端。Function Calling机制模型输出结构化的工具调用请求而不是自由文本这样能保证稳定性。工具层会严格校验参数参数缺失就自动反馈给模型补充。状态记忆由于单轮对话有上下文窗口限制长任务的状态不能全靠对话记录所以Agent在执行完一步后会把状态写入数据层的task_state表下次继续时就从这个表中恢复。这里要特别说一下AI Agent token 是什么意思。Token是模型理解和生成文本的最小单元大致相当于一个字或小半句话。一次完整执行链路可能消耗几千甚至上万token——因为要把工具返回的结果全部给模型重新阅读一遍。如果不做上下文管理一个跑了半小时的测试任务会把模型输入撑爆费用也水涨船高。我的策略是所有工具返回的数据进入模型前先做摘要提取只保留关键字段和结论截图和完整日志不直接进token而是以文件消息的形式推送到飞书群。这个策略后面还会细讲。2.4 端到端指令流转链路整个平台能跑通核心是一条清晰的调用链路。以一条真实指令为例“帮我在模拟器上跑一遍仪表盘基础显示用例结果发群里”。用户在飞书群输入消息飞书服务器通过长连接推送到接入层。接入层判断这是不是机器人可处理的消息比如判断是否机器人或群会话中是否包含关键词。消息送入Agent层大模型解析出意图目标是为仪表盘基础显示测试动作是执行用例结果是发送到群聊。Agent通过Function Calling调用search_testcases工具查找匹配用例列表再调用check_device_available查询当前是否有空闲的HMI模拟器确认有资源后调用execute_test_task创建任务。调度层收到创建任务请求后将任务写入队列并分配设备锁防止其他任务抢用同一台设备。执行层拿到任务后开始控制CAN卡发送信号、控制HMI模拟器截屏跑完每一条用例后生成结构化结果。执行结果回传到调度层并通知Agent层已经完成。Agent层汇总执行摘要调用send_feishu_card工具将结果以摘要卡片完整表格的形式发送到原群。如果中途有失败项Agent还会自动判断是否需要重试重试完成后再次发送结果。这个链路里最容易被忽略的是第4步“工具选择”。大模型不是万能的它可能选错工具甚至自己编造一个工具名所以在工具层做了一个“工具描述长度限制Schema校验”每次模型返回的调用请求如果不符合预定义Schema直接拒绝并要求重新生成。这比让模型自己“发挥”可靠得多。3. 实操过程与核心环节实现3.1 从零搭建飞书机器人服务先说一下具体的代码实现。我的机器人服务用 Python FastAPI 飞书官方SDKlarksuiteoapi编写。初始化长连接的方式如下from larksuiteoapi import Config, new_ws_client from larksuiteoapi.event import EventDispatcherHandler config Config(your_app_id, your_app_secret, log_levelINFO) def on_message(ctx, event): # 处理消息 return {} dispatcher EventDispatcherHandler(config) dispatcher.register_event(im.message.receive_v1, on_message) ws_client new_ws_client(config, event_handlerdispatcher) ws_client.start()这样启动后机器人就以长连接模式接入飞书只要进程不退出就能持续接收消息。注意飞书SDK的版本差异有些老版本的长连接API名不一样优先用新版本v2.x。收到消息后第一步不是直接送给大模型而是先做基础过滤忽略机器人自己发的消息如果是群聊且没有机器人可以选择忽略或者按配置响应判断消息类型是文本还是图片等。我当时在这里踩过一个坑——飞书SDK返回的event结构里消息内容是一个JSON字符串里面还有一层结构直接当字典取文本会拿不到。后来统一封装了一个parse_message_text函数来处理。机器人回复消息有两种方式同步回复和异步主动发消息。对于慢任务测试要跑几分钟到几十分钟我选择了“先回复收到再异步发送结果”的模式避免长连接处理超时。这也是飞书事件响应的最佳实践——服务器必须在3秒内给出ack否则飞书会重试推送事件。3.2 Agent 层的工具化封装与函数调用Agent层是整个平台的智商所在。我用的是OpenAI兼容的接口协议这样可以灵活切换不同的模型服务商。核心代码如下from openai import OpenAI client OpenAI(base_urlhttp://your-llm-server/v1, api_keyunused) tools [ { type: function, function: { name: execute_test_task, description: 创建并执行一条车载HMI测试任务, parameters: { type: object, properties: { testcase_ids: {type: array, items: {type: string}}, device_name: {type: string}, priority: {type: string, enum: [high, normal, low]} }, required: [testcase_ids] } } }, # ...其他工具定义 ] resp client.chat.completions.create( modelyour-model, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_msg} ], toolstools, tool_choiceauto )模型如果判断需要调用工具会在返回内容里带一个tool_calls字段你只需解析里面的function.name和arguments然后执行对应逻辑。执行完再把结果作为新的消息回传给模型让模型把结果整理成适合飞书展示的文本或卡片数据。这里最关键的是System Prompt的写法。我第一版写得太宽泛模型经常自己脑补测试结论后来我把Prompt改成了非常强约束的结构明确“你只能使用提供的工具禁止编造未提供的工具”明确“工具调用结果必须真实如果数据缺失要明确说无法获取”明确“如果用户指令模糊必须先调用ask_clarification工具向用户确认不要自己猜”明确“收到测试执行结果后必须以Summarize格式输出摘要不得添加未提供的统计信息”。这样约束下来模型输出质量提升非常明显。建议读者在构建自己的Agent时把System Prompt当作工程代码来维护而不是一段随手写的“聊天人格”。3.3 车载HMI测试执行层的接入HMI自动化测试执行层是整条链路里最“硬核”的部分。我的执行层基于Pytest构建了一套扩展框架硬件控制封装成Fixture用例以Python函数形式编写。硬件方面用了三件套一个CAN卡PCAN、一个可编程电源、一个HMI模拟器实际是一个带触摸屏的开发板运行着仪表盘应用。核心思路是把测试用例拆成“信号激励UI验证数据采集”三段。比如一个基础显示测试用例def test_display_basic_speed(): # 1. 发送车速信号 can_bus.send_signal(VehicleSpeed, value30) sleep(2) # 2. 截图HMI界面 hmi.screenshot(speed_30.png) hmi.ocr_verify(30, region(1200, 400, 1320, 460)) # 3. 记录日志 can_bus.send_signal(VehicleSpeed, value0)车载HMI自动化有一个难点是UI元素的识别。传统App自动化有包名、控件ID、xpath可以用但HMI很多场景是闭源图形界面没有控件树可查。我采用的方案是截图 OCR 像素级模拟对比三管齐下对于数字显示这类精确内容用OCR识别数字文本对于告警图标这类图形用预先采集的基准图做像素相似度比对对于需要验证“动画流畅度”的场景录制屏幕后逐帧分析时间戳差值。执行层做好后封装成工具API给Agent调用。这里要注意接口的幂等性同一个测试任务如果因为超时被重复调度不允许同一台设备同时跑同一个用例。我在任务表里加入了status字段排队中/执行中/完成/失败调度器每次取任务前先更新设备锁记录设备被锁时其他任务直接排队等待不并行。3.4 测试结果汇总与飞书表格消息卡片现在到了很多人感兴趣的“飞书机器人发送表格”。飞书有两种发送表格的方式一种是以文件消息发送Excel或CSV另一种是把表格渲染进消息卡片。这两种我都用了适用场景不太一样。文件消息适合测试结果数据量大、需要开发自己筛选分析的场景。Post一个file类型的消息把生成的Excel或CSV文件上传即可飞书可以直接在聊天窗口里预览。消息卡片适合在群里快速总览的场景比如用例总数、通过率、失败项列表。消息卡片的结构化展示是这套平台体验的关键。下面是一个典型卡片JSON片段{ config: {wide_screen_mode: true}, elements: [ { tag: div, text: { tag: lark_md, content: **测试完成** 结果通过 38/40 } }, { tag: div, text: { tag: lark_md, content: 执行时长12分36秒 设备HMI-01 } } ] }如果需要更复杂的表格飞书消息卡片支持table标签但布局和字段数量有限制。我实际开发中发现卡片里直接塞一个大表格容易在手机上显示得很窄所以我的做法是摘要卡片里只放总览和失败用例列表完整数据以CSV文件消息发送两相结合。实现上Agent会先调用一个fetch_test_summary工具从数据层拿汇总数据然后用一段生成卡片的Python函数构造消息最终通过im.message.create接口发到群里。注意自定义卡片要用interactive消息类型不是text。3.5 上下文窗口与Token成本控制前面说过token是很多人刚接触Agent时的盲区。我可以提供一个实际数据一次包含“查询设备→执行任务→获取结果→汇总发送”的完整链路如果全部用原始内容传输差不多会消耗3000到5000个token。在测试任务频繁触发的场景下一天下来token开销不小而且还会拖慢响应速度。我的处理策略有三板斧工具返回内容压缩给每个工具定义summary_fields只有网络地址、核心数值、结果代码等字段进入模型上下文长文本直接截断为前200字符。分阶段上下文重建对于执行时间很长的任务不把完整的中间过程串进同一个模型会话而是等到执行阶段结束后重新发起一个“汇总会话”只把最终结构化数据和用户原始指令传入。核心代码思路是任务执行期间模型会话已经结束执行结果暂存在数据库中拿到结果后新起一次completion。这个方案的token开销平均下降了60%。设置模型输出长度上限调用参数中显式设置max_tokens不允许模型生成过长的自夸文本强制简洁输出。同时我在Agent层做了一层“成本保护”如果单个任务token消耗超过预设阈值自动切换到更小参数模型来完成汇总工作。小模型在这个职责下效果完全够用。4. 常见问题与排查技巧实录4.1 飞书机器人收不到消息这个问题排在踩坑榜第一位。统计下来90%的原因是应用权限没配置好或没发布版本。飞书开放平台里修改权限之后必须到“版本管理”中创建一个新版本并发布权限才会真正生效。第二个原因是事件订阅没配置成功——如果用长连接需要确认启动日志里出现了“ws connected”字样否则事件不会推送。第三点群聊中必须把机器人拉进群且需要机器人或使用关键词否则机器人收不到。收不到消息时先用飞书提供的调试工具模拟发送事件确认SDK能收到再去查业务逻辑不要一上来就翻业务代码。4.2 Agent调用了不存在的工具或参数格式错误大模型在Function Calling场景下偶尔会“幻觉”。最常见的情况是模型自己发挥了一个工具名比如我明明定义了execute_test_task它却输出成execute_task然后参数也随便填。我的对策是工具名设计为“动词名词”的明确组合减少歧义在SDK侧做严格校验工具名不在白名单内则直接报错并把可用的工具列表重新塞入下一条消息参数校验失败时不直接结束对话而是把错误信息反馈给模型要求它重新生成正确的调用请求。实测下来第二次生成的成功率极高。另外要设置重试上限比如一个工具连续三次调用失败就终止会话并让用户联系管理员防止模型死循环。4.3 长任务执行状态丢失与并发冲突第一次上线时遇到一个经典问题模型等待执行结果期间WebSocket连接断了几十秒执行完成后Agent层进程重启了结果状态丢了最终群里没人收到报告。后来我引入了状态持久化机制。所有长期运行任务在调度层都有独立的状态记录并且守护进程在重启后会扫描status执行中但超时未完成的任务主动标记为失败并发通知。设备并发锁是另一个容易被忽略的细节两辆车的路测任务可能同时到达调度器必须给每台HMI设备建立互斥锁否则会看到两台测试机画面被同时操控的诡异现象。4.4 车载HMI执行层的环境稳定性坑车载环境硬件状态比纯软件复杂得多。最常见的问题是CAN卡驱动崩溃——长时间运行后PCAN驱动偶发失去响应导致测试卡在“等待信号确认”环节。我的方案是增加硬件看门狗执行层每个用例开始前先调用can_bus.ping()确认链路健康如果响应超时就自动重启CAN卡驱动并把异常记入日志重试当前用例。还有电源序列的问题。有些HMI设备对上下电时序敏感快速反复启动容易导致系统异常我给执行层做了严格的功率状态机——空闲时下电、测试时上电、结束后延迟5秒再下电。这套状态机写在调度层里的好处是统一管理不允许工具绕过。5. 一些后续可以继续扩展的方向这套平台目前在一台服务器上跑得很稳但后面要扩展的路其实还不少。比如多车型并行测试——现在设备锁是单机内的如果未来实验室上了多台HMI台架就得给调度层引入分布式锁或者直接迁移到Kubernetes管理让多个执行Pod可以同时跑用例。AI侧也有升级空间。现在的Agent只是被动接指令下一步可以做成主动巡检每天早上自动跑一遍冒烟用例发现失败直接通知到人而不是等人来问。另外可以结合历史缺陷库做智能分析当测试失败时Agent自动对比历史日志和缺陷说明推荐最可能的原因和修复建议。这些能力基于现在的工具层和数据结构完全可以加上去只是需要在Prompt层增加对应的任务模板。还有一件事是个人建议不要一上来就追求全自动。我整个开发和上线周期里前两周都在做人机确认机制——比如机器人执行高危操作前必须等待用户回“确认”才会继续。自动化不是把人的判断力去掉而是把重复劳动去掉人只需要在关键节点做决策。这个思路放到任何自动化平台都是通用的。