☰
MQTT-Explorer LLM 集成测试调试指南:CI 工作流步骤顺序修复与环境变量注入实践
2026/10/5 2:24:45 网站建设 项目流程
  • 开发工具
  • 物联网
  • 消息队列

【免费下载链接】MQTT-Explorer

An all-round MQTT client that provides a structured topic overview

项目地址:https://gitcode.com/gh_mirrors/mq/MQTT-Explorer
点击查看免费下载

LLM_TESTS_DEBUG.md 记录了 MQTT-Explorer 在接入 LLM(AI Assistant)集成测试时遇到的一个典型 CI 故障——GitHub Actions 工作流中.env.llm-tests环境文件因「先写文件、后 Checkout」的步骤顺序错误而被工作区覆盖丢失,以及后续的环境变量注入、测试开关与 jsdom 网络限制的排查与修复过程。读完本文,你将掌握该仓库 LLM 集成测试的完整调试思路、可复用的修复模式,以及本地开发与 CI/CD 中运行「离线测试 + 在线 LLM 实测」两套测试体系的实操方法。

一、问题背景:LLM 集成测试的运行前提

MQTT-Explorer 的 AI Assistant(LLM 集成)在 LLM_INTEGRATION.md 中已有完整设计:前端通过 WebSocket RPC 与后端通信,后端持有 API Key,代理所有 LLM 请求。为了保证这个功能的质量,仓库在 app/src/services/spec/ 目录下维护了三类测试:

  1. 单元测试(llmService.spec.ts):覆盖parseResponse()、getQuickSuggestions()、hasApiKey()等 LLM Service 方法;
  2. 提案校验测试(llmProposals.spec.ts):校验 Topic 格式、Payload 合法性、QoS 取值、Description 质量;
  3. 实时 LLM 集成测试(llmIntegration.spec.ts):真实调用 OpenAI/Gemini API,验证系统识别(zigbee2mqtt、Home Assistant、Tasmota)、提案质量、边界情况与问题生成质量。

其中第三类测试是**选择性(opt-in)**的,需要同时满足两个条件才会真正执行(见 app/src/services/spec/llmIntegration.spec.ts):

const shouldRunLLMTests = process.env.RUN_LLM_TESTS === 'true' const hasApiKey = !!process.env.OPENAI_API_KEY || !!process.env.GEMINI_API_KEY || !!process.env.LLM_API_KEY

也就是说:必须有RUN_LLM_TESTS=true环境变量,且至少注入一个 API Key。这看似简单,却正是本次调试的核心战场——如何把 API Key 安全、可靠地注入到 CI 环境并让测试真正跑起来。

二、核心问题:GitHub Workflow 步骤顺序缺陷

文档明确指出,.github/workflows/copilot-setup-steps.yml存在一个致命步骤顺序问题。修复前的执行流程是:

  1. 创建.env.llm-tests文件(写入 API Key 与测试开关)
  2. Checkout 代码 ←这一步会用仓库内容覆盖工作目录,导致刚创建的.env.llm-tests被整体清除
  3. 运行测试

GitHub Actions 的actions/checkout会重置工作目录,任何在 checkout 之前写入的未跟踪文件都会丢失。于是测试运行时环境文件已不存在,RUN_LLM_TESTS与 API Key 自然全部缺失,LLM 集成测试被静默跳过,而 CI 日志往往只会显示一个含糊的 "skipped",排查难度极高。

修复后的正确顺序是:

  1. 先 Checkout 代码
  2. 再创建.env.llm-tests(此时文件才能在工作区中持久存在)
  3. 运行测试

从当前仓库 .github/workflows/copilot-setup-steps.yml 可以看到修复后的实际形态——Checkout code步骤位于最前,紧随其后的Persist Secrets to Agent Environment步骤负责写入环境文件:

steps: - name: Checkout code uses: actions/checkout@v6 - name: Persist Secrets to Agent Environment run: | echo "export OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}" > .env.llm-tests echo "export RUN_LLM_TESTS=true" >> .env.llm-tests chmod 600 .env.llm-tests echo "✅ Created .env.llm-tests file" ls -la .env.llm-tests

修复中引入的四项关键改动

文档总结的改动点逐一对应到上述 YAML,每一项都有其明确目的:

改动目的实现位置
将「Persist Secrets」步骤移到「Checkout code」之后避免 checkout 覆盖工作区、丢失环境文件.github/workflows/copilot-setup-steps.yml
为环境变量添加export前缀使文件可通过source .env.llm-tests正确注入当前 shell,而不是以普通赋值形式存在同上echo "export OPENAI_API_KEY=..." > .env.llm-tests
追加RUN_LLM_TESTS=true显式启用 live 测试,避免「有 Key 但测试仍跳过」的隐性失效同上echo "export RUN_LLM_TESTS=true" >> .env.llm-tests
chmod 600将文件权限收紧为仅属主可读写,防止敏感 Key 被同环境其他用户读取同上chmod 600 .env.llm-tests
验证日志(ls -la+ echo)确认文件确实创建成功,为后续排障提供证据同上

值得注意的是,工作流环境块中还配置了TESTS_MQTT_BROKER_HOST: localhost、TESTS_MQTT_BROKER_PORT: 1883与OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }},其中 secrets 通过环境变量传入,再被第二步固化到.env.llm-tests中,形成「GitHub Secrets → 环境变量 → 文件 → shell source」的注入链。

三、环境变量注入机制验证:从手动创建到一键脚本

文档用一段最小化 bash 验证了.env.llm-tests的创建与 source 机制:

# 创建 .env 文件 echo 'export OPENAI_API_KEY=sk-your-key' > .env.llm-tests echo 'export RUN_LLM_TESTS=true' >> .env.llm-tests # Source 并验证 source .env.llm-tests echo $OPENAI_API_KEY # 输出 Key,说明注入成功

仓库在此基础上提供了两个一键脚本,把上述流程产品化:

1. 环境搭建脚本 scripts/setup-llm-env.sh

该脚本的核心逻辑是读取当前环境中的注入 secrets,按优先级写入$REPO_ROOT/.env.llm-tests:

  • 检测到OPENAI_API_KEY→ 写入export OPENAI_API_KEY='...'+export RUN_LLM_TESTS=true;
  • 检测到GEMINI_API_KEY→ 写入对应的 Gemini 配置 + 测试开关;
  • 检测到通用LLM_API_KEY→ 额外写入export LLM_PROVIDER='${LLM_PROVIDER:-openai}'(generic key 需要显式指定 provider);
  • 三种 Key 都不存在 → 打印手工创建指引并以退出码 1 结束,避免静默失败。

写入完成后同样执行chmod 600 "$ENV_FILE",并在末尾提示:永远不要把这个文件提交到版本控制(已加入.gitignore)。

2. 测试运行脚本 scripts/run-llm-tests.sh

该脚本负责完整的前置校验与执行:

  • set -e:任何一步失败立即中断,防止"半失败"状态被误判为成功;
  • 校验OPENAI_API_KEY/GEMINI_API_KEY/LLM_API_KEY至少存在其一,否则打印三种 Key 的用法并exit 1;
  • 提供方自动识别:检测到 OpenAI Key 时默认LLM_PROVIDER=openai,检测到 Gemini Key 时默认LLM_PROVIDER=gemini,通用 Key 默认走openai;
  • 强制export RUN_LLM_TESTS=true;
  • 进入app/目录执行yarn test。

这也解释了文档中「Provider auto-detection (OpenAI/Gemini)」这一已验证项的来源——两个脚本与测试入口 app/src/services/spec/llmIntegration.spec.ts 中的getProvider()逻辑相互印证:

const getProvider = (): 'openai' | 'gemini' | null => { if (process.env.OPENAI_API_KEY) return 'openai' if (process.env.GEMINI_API_KEY) return 'gemini' if (process.env.LLM_API_KEY && process.env.LLM_PROVIDER) { return process.env.LLM_PROVIDER as 'openai' | 'gemini' } return null }

三个层级(shell 脚本、测试文件、服务层)对提供方识别逻辑保持了一致,这也是「Provider auto-detection 已验证」能被端到端确认的原因。

四、测试检测与跳过行为的源码级验证

文档描述的「测试能正确检测 API Key、启用 live 执行(不跳过)、输出 provider 识别日志」可以直接在测试代码中找到对应实现(app/src/services/spec/llmIntegration.spec.ts):

before(function () { if (!shouldRunLLMTests) { console.log('Skipping LLM integration tests: RUN_LLM_TESTS not set to "true"') this.skip() } if (!hasApiKey) { console.warn('Skipping LLM integration tests: No API key found') this.skip() } if (!provider) { console.warn('Skipping LLM integration tests: Could not determine provider') this.skip() } console.log(`Running LLM integration tests with provider: ${provider}`) })

可见跳过逻辑是分层守卫的:RUN_LLM_TESTS未设置、无 API Key、无法识别 provider,三种情况任一成立都会this.skip(),并打印明确的原因日志。这解释了调试时的核心排查原则——先看测试是否在跑,再看跑的是离线还是在线。若 CI 日志中出现 skip 提示,第一反应应是检查RUN_LLM_TESTS与 Key 注入是否真正落到了执行测试的那个 shell 环境。

在线测试的判定标准与耗时预期可见 docs/LLM_TEST_RESULTS.md:离线测试 100 个用例全部通过、约 2 秒完成;在线 LLM 集成测试 11 个用例、约 20-30 秒完成,单用例耗时普遍在 1.5-2.3 秒区间(真实 API 往返),因此测试套件把this.timeout(60000)放宽到 60 秒以容纳 API 延迟。

五、当前限制:jsdom 环境中的网络错误

文档记录了当前已知限制——在 jsdom 测试环境中调用真实 LLM API 会失败:

Error: Cross origin null forbidden Error: LLM API call failed: Network Error

原因分析是明确的:

  1. 测试运行在 jsdom 模拟的 DOM 环境,而非真实浏览器;
  2. 测试代码通过axios直接发起 HTTP 请求(见 app/src/services/spec/llmIntegration.spec.ts),jsdom 对跨域请求施加 CORS 限制,导致Cross origin null forbidden;
  3. 因此实时 API 测试需要真正的 Node.js 环境或网络请求 mock。

这一点值得特别注意:生产架构中 LLM 请求是通过后端 WebSocket RPC 代理的(见 app/src/services/llmService.ts 中backendRpc.call(RpcEvents.llmChat, ...)),而测试为了独立于后端、直接验证 LLM 输出质量,选择了直连 API 的方式,所以在 jsdom 下必然受限。这是"测试直连"与"生产代理"两种路径的架构差异所致,不是缺陷而是设计取舍。

六、推荐的运行方案

本地开发:在 Node 环境中运行

文档给出的本地运行方式与仓库脚本一致:

source .env.llm-tests cd app && yarn test

更推荐直接使用一键脚本(脚本内部已处理 provider 识别与测试开关):

OPENAI_API_KEY=sk-your-key ./scripts/run-llm-tests.sh

手动方式的完整等价命令(与 app/src/services/spec/README.md 中的说明一致):

export OPENAI_API_KEY=sk-your-key export RUN_LLM_TESTS=true cd app && yarn test

执行后应能在控制台看到 provider 识别日志Running LLM integration tests with provider: openai,随后是 zigbee2mqtt 系统识别、提案质量校验、边界情况、问题生成四组在线用例的逐个执行与通过结果。

CI/CD 中的分层策略

文档建议 CI 采用分层策略,app/src/services/spec/README.md 给出了对应的 GitHub Actions 示例:

  • 常规 CI(默认):只跑离线测试,无需 API Key,快速、确定性、零成本;
  • 定时任务(如 nightly):通过secrets.OPENAI_API_KEY注入 Key 并设置RUN_LLM_TESTS: true,运行在线实测;
  • 网络与 mock 兜底:若必须在 jsdom 环境跑在线逻辑,考虑用 nock 或 msw mock HTTP 请求,或在有真实网络访问权限的 Job 中执行。

关键运维纪律(同样适用于本仓库任何含 Key 的流程):

  1. API Key 一律走 secrets 管理,严禁硬编码或提交;
  2. .env.llm-tests不纳入版本控制(.gitignore已排除);
  3. 定期轮换 Key 并在供应商控制台监控用量与设置计费告警(可参考 ENV_VARS_EXAMPLE.md 的 Security Recommendations)。

七、已验证工作项与调试结论

文档末尾的 Verified Working 清单,结合本次源码核对可以给出如下对应关系:

已验证项对应实现/证据
✅ 工作流创建.env.llm-tests(步骤顺序已修复).github/workflows/copilot-setup-steps.yml
✅setup-llm-env.sh创建环境文件scripts/setup-llm-env.sh
✅ 环境变量 source 机制export前缀 +source .env.llm-tests
✅ 测试检测 API Keyapp/src/services/spec/llmIntegration.spec.ts
✅ Provider 自动识别(OpenAI/Gemini)app/src/services/spec/llmIntegration.spec.ts 与 scripts/run-llm-tests.sh
✅ 无 API Key 时的正确跳过行为app/src/services/spec/llmIntegration.spec.ts 三层守卫

最终结论:LLM 测试基础设施现已正常工作。核心修复只需把「Persist Secrets」步骤移到「Checkout code」之后,配合export前缀、RUN_LLM_TESTS=true开关、chmod 600权限收紧与创建验证日志,即可让 API Key 稳定穿越 checkout 阶段并到达测试进程。而 jsdom 网络限制属于环境能力边界,不是基础设施故障——线上实测应放在 Node 环境或具备真实网络访问的 CI Job 中执行。

八、调试思路沉淀:三个可复用原则

  1. 步骤顺序先于脚本内容:GitHub Actions 中 checkout、缓存恢复、secrets 写入的先后顺序,决定了文件能否存活到后续步骤。凡是在 checkout 之前写工作区文件的做法,都应视为反模式。
  2. 跳过不是通过:测试静默 skip 时 CI 依然是绿色,但功能质量无人把关。RUN_LLM_TESTS=true显式开关 + skip 原因日志,是让「该测没测」变得可见的关键设计。
  3. 注入链每一环都要有证据:从 Secrets 环境变量到.env文件再到 shell source,每一环都应像工作流中的ls -la那样留有验证输出,排障时才能快速定位断点。

如需进一步了解 LLM 功能的完整配置(LLM_PROVIDER、OPENAI_API_KEY、GEMINI_API_KEY、LLM_API_KEY、LLM_NEIGHBORING_TOPICS_TOKEN_LIMIT及其优先级、Token 上下文截断对提案质量的影响),可继续阅读 LLM_INTEGRATION.md 与 ENV_VARS_EXAMPLE.md;在线测试的完整示例输出与验收标准见 docs/LLM_TEST_RESULTS.md。

  • 开发工具
  • 物联网
  • 消息队列

【免费下载链接】MQTT-Explorer

An all-round MQTT client that provides a structured topic overview

项目地址:https://gitcode.com/gh_mirrors/mq/MQTT-Explorer
点击查看免费下载

相关推荐

上一篇:使用 ag-ui Java Client 连接远程 AG-UI Agent:HttpAgent、SSE 事件流与传输层定制实战
下一篇:electron-vue 静态资源使用指南:理解 `static/` 目录与 `__static` 全局变量

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询