当前位置: 首页 > news >正文

第58篇|AI 失败态:网络失败、Key 缺失、模型失败如何提示

第58篇|AI 失败态:网络失败、Key 缺失、模型失败如何提示

第 58 篇收束第 12 天内容:AI 能力必须把失败态讲清楚。Key 缺失、登录未完成、网络失败、响应为空、模型未开通,它们都不应该只变成一句“失败了”。

项目把前置检查放在页面层,请求异常放在服务层,模型不可用时再尝试候选模型。这样用户看到的是明确提示,开发者排查时也能顺着层级定位。

这一篇继续围绕 21 天「智能相机开发实战」训练营展开。阅读时可以先看界面效果,再顺着函数名回到 DevEco Studio 定位实现,最后把成功态、取消态和失败态串成一个可复现闭环。

本篇目标

  • 区分 Key 缺失、选择为空、登录未完成和网络失败。
  • 理解requestJson如何包装请求错误。
  • 掌握视频模型候选重试的判断。
  • 保证失败后 busy 状态能恢复。

对应源码位置

  • entry/src/main/ets/pages/Index.ets
  • entry/src/main/ets/services/VolcengineArkService.ets

一、失败态先从前置检查开始

很多失败不需要真的发请求。没有选择照片、没有配置 Key、入口关闭、华为账号未准备好,这些都可以在页面层提前拦截。

提前拦截能减少无意义网络请求,也能给用户更具体的下一步,比如“请先选择照片”或“请先配置火山方舟 API Key”。

AI 能力区需要清楚展示可恢复失败态

二、页面层负责用户可见提示

generateSmartDescriptionsForMovieSelection先检查是否可生成、是否有选中照片、是否配置 Key、是否完成身份准备。每个失败点都更新不同状态文案。

这种写法比统一 catch 后显示“生成失败”更友好,用户知道该补照片、填 Key,还是等待登录。

页面层在请求前处理 Key 缺失和选择为空

return this.aiSynthesisEntryVisible && this.hasMovieSelection() && !this.aiInsightBusy && !this.videoTaskBusy && !this.videoExportBusy && !this.systemShareBusy; } private async generateSmartDescriptionsForMovieSelection(): Promise<void> { if (!this.canGenerateSmartDescriptionsFromSelection()) { return; } const sourceRecords = this.getSelectedVideoRecords(); if (sourceRecords.length === 0) { this.videoTaskStatusText = '请先选择照片'; return; } if (this.arkApiKey.trim().length === 0) { this.videoTaskStatusText = '请先配置火山方舟 API Key'; return; } if (!await this.ensureHuaweiIdentityForAiSynthesis('movie')) { return; } this.aiInsightBusy = true; this.videoTaskStatusText = `正在生成 ${sourceRecords.length} 张照片的智能描述...`; let nextRecords = this.galleryRecords.slice(); try { for (let index = 0; index < sourceRecords.length; index += 1) { const sourceRecord = sourceRecords[index]; const latestRecord = nextRecords.find((record: GalleryMoment) => record.id === sourceRecord.id) ?? sourceRecord; this.videoTaskStatusText = `正在生成智能描述 ${index + 1}/${sourceRecords.length}`; const insight = await VolcengineArkService.analyzeMoment( this.getAbilityContext(), latestRecord, this.arkApiKey.trim() ); nextRecords = nextRecords.map((record: GalleryMoment) => { if (record.id !== sourceRecord.id) { return record; } const nextRecord = this.buildAiReadyRecord(record, insight.aiCaption, insight.videoPrompt); if (this.getRecordUserNote(record).length === 0) { nextRecord.userNote = insight.aiCaption; } return nextRecord;

前置检查越清楚,真正进入服务层的请求就越干净。

三、请求层包装状态码、空响应和传输错误

requestJson检查 HTTP 状态码、空响应和 JSON 解析前的响应文本。任何异常都会被包装成带上下文的错误。

页面层不需要知道底层是 500、空响应还是连接超时,它只展示服务层抛出的可读错误。开发者看日志时仍然能看到具体状态码和响应体。

requestJson 统一包装请求错误

}; const requestOptions: http.HttpRequestOptions = { method: method, header: header as Object, expectDataType: http.HttpDataType.STRING, readTimeout: 60000, connectTimeout: 60000 }; if (bodyText) { requestOptions.extraData = bodyText; } try { const response = await request.request(url, requestOptions); const responseText = typeof response.result === 'string' ? response.result : JSON.stringify(response.result); if ((response.responseCode as number) < 200 || (response.responseCode as number) >= 300) { throw new Error(`Ark request failed (${response.responseCode}): ${responseText}`); } if (!responseText || responseText.length === 0) { throw new Error('Ark response body is empty.'); } const parsedData = JSON.parse(responseText) as T; return { data: parsedData, rawText: responseText }; } catch (error) { const message = error instanceof Error ? error.message : JSON.stringify(error); throw new Error(`Ark request transport failed: ${message}`); } finally { request.destroy(); }

请求层的错误文案要对开发者有用,页面层的状态文案要对用户有用,两者不要混成一团。

四、模型不可用时可以尝试候选模型

视频生成模型可能因为未开通、端点不支持或模型名变化而失败。shouldRetryVideoModel用关键词识别这类错误,允许项目尝试候选模型。

这不是为了掩盖问题,而是为了在可恢复的模型失败下继续给用户机会。真正不可恢复的错误则停止重试,把原因反馈给页面。

shouldRetryVideoModel 识别可重试的模型错误

private static buildVideoModelCandidates(videoModel: string): Array<string> { const candidates: Array<string> = []; VolcengineArkService.appendUniqueModel(candidates, VolcengineArkService.normalizeVideoModel(videoModel)); VolcengineArkService.appendUniqueModel(candidates, VolcengineArkService.DEFAULT_VIDEO_MODEL); VolcengineArkService.appendUniqueModel(candidates, 'doubao-seedance-1-0-pro-fast-250610'); VolcengineArkService.appendUniqueModel(candidates, 'doubao-seedance-1-0-pro-250528'); VolcengineArkService.appendUniqueModel(candidates, 'doubao-seedance-1-0-lite-i2v-250428'); return candidates; } private static shouldRetryVideoModel(message: string): boolean { const lowerMessage = message.toLowerCase(); return message.includes('ModelNotOpen') || message.includes('InvalidEndpointOrModel') || message.includes('模型') || message.includes('未开通') || message.includes('不存在') || message.includes('不支持') ||

第 58 篇的重点是分层:页面层拦截使用条件,服务层处理传输错误,模型层做候选重试。层级清楚,失败态就不会乱。

五、失败恢复后要回到稳定状态

失败态处理不能只看错误文案,还要看状态是否恢复。页面层在请求前会设置 busy 状态,请求结束后必须在finally中恢复。否则用户看到的不是一次失败,而是按钮一直不可用、页面一直“生成中”。

服务层兜底也要避免把技术响应写给用户。第 56 篇的sanitizeProductText可以作为失败态的下游保护:即使上游返回了模型元数据、JSON 片段或空响应,最终进入产品字段的也应该是用户能理解的短文案。

sanitizeProductText 过滤技术响应,避免异常内容进入产品文案

try { for (let index = 0; index < sourceRecords.length; index += 1) { const sourceRecord = sourceRecords[index]; const latestRecord = nextRecords.find((record: GalleryMoment) => record.id === sourceRecord.id) ?? sourceRecord; this.videoTaskStatusText = `正在生成智能描述 ${index + 1}/${sourceRecords.length}`; const insight = await VolcengineArkService.analyzeMoment( this.getAbilityContext(), latestRecord, this.arkApiKey.trim() ); nextRecords = nextRecords.map((record: GalleryMoment) => { if (record.id !== sourceRecord.id) { return record; } return this.buildAiReadyRecord(record, insight.aiCaption, insight.videoPrompt); }); } } finally { this.aiInsightBusy = false; }

这段流程说明了失败态的另一半:无论成功还是失败,忙碌状态都要释放。否则下一次点击、下一次重试和下一篇训练营里的验证步骤都会被旧状态挡住。

工程检查清单

  • Key 缺失和照片未选择在页面层提前提示。
  • 请求层统一包装状态码、空响应和传输错误。
  • 模型未开通或不支持时允许候选重试。
  • 失败后所有 busy 状态都能恢复。
  • 用户文案和开发者日志各自服务不同对象。

今日练习

  1. 删除本地 Key 后触发智能描述,记录页面提示。
  2. 模拟空响应,推演requestJson会抛出什么错误。
  3. shouldRetryVideoModel补充一个你遇到过的模型错误关键词。

训练营后面的文章会继续按“真实页面效果 -> 源码定位 -> 状态闭环 -> 可验证结果”的节奏推进。每一篇都尽量让你能拿着代码直接回到项目里复现,而不是只停留在概念说明。

http://www.cnnetsun.cn/news/2788743.html

相关文章:

  • 实战应用:基于快马平台构建智能桌面助手宠物,集成提醒与信息展示
  • 萤石 ERTC 如何灵活支撑摄像头接入多人视频会议?
  • 物联网操作系统技术讲座深度解析:从理论到实战的竞赛赋能
  • iOS越狱终极指南:从iOS 17到iOS 26.5全面解锁iPhone隐藏功能
  • 基于GPS同步的分布式逆变器谐波电压补偿技术解析
  • 无线通信基础:频率、波长与天线设计的核心关系
  • 免费文案提取神器2026推荐:视频字幕+图片文字提取保姆级教程
  • CSDN AI数字营销不是万能药,但不用它=自动淘汰(20年招生顾问的3条铁律)
  • FPGA高级设计实战:从时序收敛到系统级优化的工程进阶指南
  • VisualCppRedist AIO:企业级运行时依赖管理系统的5大技术创新
  • 2026年PDF压缩到最小全方案:保姆级教程+免费工具+Adobe专业设置
  • 2026年AI编程工具全方位推荐:权威评测与选型指南
  • UltraEdit自定义VHDL语法高亮:提升硬件描述语言开发效率
  • 终极指南:如何用Carrot插件实时预测Codeforces评级变化
  • 硬件厂商如何用CSDN AI引擎实现线索成本下降63%?——基于27家头部厂商的A/B测试实证报告
  • ABB 工业机器人激光切割工作站离线编程仿真研究
  • OpenClaw保姆级配置教程(适用于Windows)
  • Miniforge 完全入门指南:从零撸到环境自由
  • 安规电容X与Y:定义、选型与EMC设计实战指南
  • Altium Designer ERC警告“Compiler Net has no driving source”的根源与解决指南
  • AI Agent友好型工具设计的5大底层原则
  • 当记忆密码成为数字枷锁:用自动化工具找回被遗忘的压缩包密码
  • 思源宋体CN终极指南:7种字重开源字体高效应用
  • 8051单片机跳转指令全解析:LJMP、AJMP、SJMP与JMP@A+DPTR的区别与应用
  • 补码原理深度解析:从模运算到硬件实现,统一计算机加减法
  • 正交矩阵:从几何定义到工程应用的核心原理与避坑指南
  • 抖音批量下载神器:3分钟实现效率革命,智能解放你的双手
  • uCOS-II在AVR Mega16上的移植实践:从Mega128裁剪到资源优化
  • SIMD 优化实战:为什么很多代码用了 AVX 还是没有变快
  • 别再用临时变量了!用Python的异或运算(^)实现变量交换,又快又省内存