首页/新闻资讯/正文详情

agent学习Day9——异常兜底与重试护栏

发布时间:2026/9/22 13:08:10 来源:云帆数科 栏目:资讯中心
agent学习Day9——异常兜底与重试护栏
让接口不再裸奔全局异常兜底与指数退避重试一、裸奔的代价为什么路由不能没有异常兜底之前/analyze-basic-llm路由里只有一行return await llm_service.analyze_jd(req.jd_text)——纯裸奔零 try/except。service 层 catch 了异常也只是记日志再 raise最后全砸到 FastAPI 默认兜底上。裸奔的代价有四个状态码塌缩LLM 超时、返回坏 JSON、代码 bug全变成 500。客户端分不清我请求错了“上游挂了还是服务自己炸了”监控告警也瞎。格式不统一500 是{detail:...}字符串422 校验失败是{detail:[...]}数组前端没法一套逻辑处理。堆栈泄露开发模式 500 会吐 Python 堆栈生产环境是安全隐患。手写 try/except 不可持续异常类型一只手数不过来N 个接口 × M 种异常 N×M 个 try/except必漏必错。解决思路一句话一处定义异常映射全局生效。路由继续专心写业务所有异常交给分诊台统一翻译。二、三个文件搭起分诊台FastAPI 的app.exception_handler(异常类)是全局分诊台内部维护「异常类型 → 处理器」映射表异常冒泡到最外层按继承链最具体匹配。一处注册全局生效。落到三个文件app/core/exceptions.py——定义异常基类 统一响应模型classAppException(Exception):def__init__(self,code:int,message:str,detail:objectNone):self.codecode self.messagemessage self.detaildetailsuper().__init__(message)classServiceError(AppException):def__init__(self,message上游 LLM 超时/不可用,detailNone):super().__init__(code503,messagemessage,detaildetail)classErrorResponse(BaseModel):code:intmessage:strdetail:object|NoneNone# object 啥都能塞字符串、列表、字典AppException是基类所有业务异常继承它一个处理器收全部子类。detail: object | None是关键——422 时塞校验错误列表数组503 时只一句话字符串一个字段同时容得下。app/api/error_handlers.py——四个处理器asyncdefapp_exception_handler(request,exc:AppException):returnJSONResponse(status_codeexc.code,contentErrorResponse(codeexc.code,messageexc.message,detailexc.detail).model_dump())asyncdefvalidation_exception_handler(request,exc:RequestValidationError):returnJSONResponse(status_code422,contentErrorResponse(code422,message请求参数校验失败,detailexc.errors()).model_dump())asyncdefunhandled_exception_handler(request,exc:Exception):returnJSONResponse(status_code500,contentErrorResponse(code500,message服务器内部错误,detailNone).model_dump())AppException处理器收所有业务异常含ServiceError(503)RequestValidationError接住 422 校验Exception兜底处理器故意不给 detail 堆栈防泄露。app/main.py——注册关键一步不注册不生效app.add_exception_handler(AppException,app_exception_handler)app.add_exception_handler(StarletteHTTPException,http_exception_handler)app.add_exception_handler(Exception,unhandled_exception_handler)app.add_exception_handler(RequestValidationError,validation_exception_handler)路由层一行没动异常冒泡上来自动翻译。三、父类才能接住父类异常注册HTTPException处理器后打一个不存在的路径返回却是 Starlette 默认的{detail:Not Found}——我们的处理器没接住。根因Starlette 找不到路由时抛的是starlette.exceptions.HTTPException父类而 FastAPI 的HTTPException是它的子类。异常匹配规则是被抛的异常是不是注册类型的实例——父类实例不是子类实例匹配失败退回默认。修复注册父类。fromstarlette.exceptionsimportHTTPExceptionasStarletteHTTPException app.add_exception_handler(StarletteHTTPException,http_exception_handler)要接住所有 HTTP 异常得注册父类不能注册子类。这个坑把继承链匹配方向钉死了。四、瞬时故障自愈指数退避重试全局异常兜底解决出了错怎么兜底但有些错能自愈——LLM 调用遇 429 限流、超时、5xx过几秒可能就好了。无脑重试会放大故障惊群所以要有策略。重试三要素缺一不可分类重试只重试瞬时故障。RateLimitError/APITimeoutError/APIConnectionError/InternalServerError进重试分支AuthenticationErrorKey 无效直接抛绝不重试。指数退避等待 base * 2^attempt加min(..., max_delay)封顶。每次失败后等更久给对方越来越大的恢复窗口。随机抖动等待 random.uniform(0, 退避)把一批同步失败的请求重试时刻打散成一片消除周期性惊群。改造在LlmService.call_llm内forattemptinrange(max_retries):try:responseawaitself.client.chat.completions.create(...)contentresponse.choices[0].message.contentifcontentisNone:raiseValueError(LLM returned empty content)returncontentexcept(openai.RateLimitError,openai.APITimeoutError,openai.APIConnectionError,openai.InternalServerError)ase:ifattemptmax_retries-1:raiseServiceError(message上游 LLM 多次重试仍失败,detailstr(e))await_wait_with_backoff(attempt)continueexceptopenai.AuthenticationErrorase:raiseServiceError(messageLLM 认证失败请检查 API Key,detailstr(e))重试耗尽抛ServiceError(503)——正好被第二章的全局处理器接住统一返回{code:503,...}。3.2 的出口对接 3.1 的入口用一个异常类串起来。五、重试的边界该不该 vs 能不能最容易被绕晕的一点重试for循环只包住create调用。如果 LLM 成功返回了但内容是一段废话坏 JSONValueError是在call_llm已经return content之后、analyze_jd的parse_jd_analysis里才抛的——根本不在重试循环的射程内所以当前不会重试。这不是该不该重试的语义问题是能不能重试的结构问题。要让坏 JSON 也重试得两步①把解析移进call_llm的 try 内return 前②except加ValueError进可重试分支。分清这两层才算真懂重试边界。六、怎么验dependency_overrides 偷梁换柱异常和重试都得测但不能真调 LLM费钱慢。前面的博客讲过app.dependency_overrides这把钥匙测试时把真依赖换成假依赖——今天用它注入故意抛异常的假 serviceclassFakeTimeoutService:asyncdefanalyze_jd(self,jd_text):raiseServiceError(message上游 LLM 超时/不可用)app.dependency_overrides[get_llm_service]lambda:FakeTimeoutService()rclient.post(/api/v1/jd/analyze-basic-llm,json{jd_text:招 Python 工程师})# → 503 {code:503,message:上游 LLM 超时/不可用,detail:null}生产路由一行不动测试把真LlmService换成假货白嫖验证 503/500。重试则用unittest.mock替换底层client.chat.completions.create造前两次超时、第三次成功验证call_count3。坑TestClient默认raise_server_exceptionsTrue未接住的异常会在测试端重抛、盖住 500 兜底。验 500 必须TestClient(app, raise_server_exceptionsFalse)。七、小结全局异常兜底和指数退避重试是接口稳定性的两道护栏前者保证出了任何错都返回统一格式、不泄露堆栈后者保证瞬时故障能自愈、又不放大故障。两者通过ServiceError(503)一个异常类对接——重试耗尽抛它全局处理器接它。把 LLM 调用从裸奔等死升级成兜底 自愈是后端服务能扛真实流量的关键一步。

相关推荐

3步构建跨平台音频生态:AudioShare重新定义设备间音频共享体验
3步构建跨平台音频生态:AudioShare重新定义设备间音频共享体验

3步构建跨平台音频生态:AudioShare重新定义设备间音频共享体验 【免费下载链接】AudioShare 将Windows的音频在其他Android设备上实时播放。Share windows audio 项目地址: https://gitcode.com/gh_mirrors/audi/AudioShare 在万物互联的时代,音频… · 2026/8/5 7:09:07

嵌入式Linux串口与RS485驱动开发实战指南
嵌入式Linux串口与RS485驱动开发实战指南

1. 嵌入式Linux串口与485驱动开发概述在工业控制、智能家居、物联网终端等嵌入式应用场景中,串口通信始终扮演着不可替代的角色。作为最基础的通信接口之一,UART串口因其协议简单、可靠性高、成本低廉等优势,被广泛应用于设备间的数据交互。而… · 2026/9/21 5:08:50

macOS 27测试版深度解析:Apple Silicon与AI加速实战
macOS 27测试版深度解析:Apple Silicon与AI加速实战

1. 项目概述:macOS 27测试版的技术转向苹果最新发布的macOS 27第二个测试版标志着x86架构时代的终结。这个版本首次完全移除了对Intel处理器的支持,所有系统组件都针对Apple Silicon进行了重编译。我在开发者过渡套件上实测发现,系统内核中已… · 2026/7/22 14:59:10

踊跃的近义词2026最新
踊跃的近义词2026最新

搞定踊跃近义词查询,图解原理让代码不再报错 复制来的代码跑不通,报错信息满屏飘,盯着 KeyError 或 TypeError… · 2026/9/22 13:07:57

公众号如何赚钱最佳实践 3招搞定流量变现
公众号如何赚钱最佳实践 3招搞定流量变现

公众号如何赚钱最佳实践 3招搞定流量变现 面对满屏的报错日志,盯着那堆红色的 StackTrace 发呆,是不是觉得脑子要炸了?这种“报错一堆看不懂”的时刻,正是很多开发者从新手进阶到高手的分水岭。别急着复制粘贴去问搜索引擎,先静下心来,用… · 2026/9/22 13:07:51

3招搞定安全ppt课件:手写实现性能优化避坑指南
3招搞定安全ppt课件:手写实现性能优化避坑指南

3招搞定安全ppt课件:手写实现性能优化避坑指南 版本升级后 API 全变了,打开旧项目直接报错,这种崩溃感谁懂?别再死记硬背新文档,直接上手【手写实现】核心逻辑,才是解决安全ppt课件性能优化的根本路子。… · 2026/9/22 13:07:45

石墨表格API重构性能优化实战与面试必问
石墨表格API重构性能优化实战与面试必问

石墨表格API重构性能优化实战与面试必问 上周刚接手一个内部数据中台项目,打开石墨表格SDK文档时我直接愣住。版本从v2.0升到v3.5后,原本熟悉的 sheet.read()… · 2026/9/22 13:07:20

剑冢boss源码拆解:从入门到精通的实战路径
剑冢boss源码拆解:从入门到精通的实战路径

剑冢boss源码拆解:从入门到精通的实战路径 看了一堆教程还是不会写项目?别急,问题不在你笨,而在你一直在看“黑盒”代码,没摸过“白盒”逻辑。很多开发者卡在【剑冢boss】这类复杂模块的集成上,觉得配置改不对、参数调不通。其实,【剑冢bos… · 2026/9/22 13:07:20

2026最新山楂树之恋台词解析:面试被问原理别慌
2026最新山楂树之恋台词解析:面试被问原理别慌

2026最新山楂树之恋台词解析:面试被问原理别慌 面试官问:“讲讲进程间通信原理,你连山楂树之恋台词里的静秋和老三怎么传数据都说不清?” 别笑,很多应届生在面试现场就是卡在这个点上,明明背过八股文,一追问底层实现就脑子空白。… · 2026/9/22 13:07:07

5个电影海报图片处理坑,新手避坑指南
5个电影海报图片处理坑,新手避坑指南

5个电影海报图片处理坑,新手避坑指南 刚写完代码,一运行屏幕直接炸了。满屏红色的 StackTrace 滚得比弹幕还快,什么 NullPointerException 、 ImageIO.read() returned null 、… · 2026/9/22 0:00:07

注册微信公众账号:一文搞懂从0到1全流程
注册微信公众账号:一文搞懂从0到1全流程

注册微信公众账号:一文搞懂从0到1全流程 复制来的代码跑不通,报错信息满屏飞,到底卡在哪?别急,咱们先停下手里的调试。很多开发者觉得注册微信公众账号只是填个表单、传个身份证那么简单,真上手才发现坑深不见底。今天这篇 一文搞懂… · 2026/9/22 0:00:07

手写实现图片压缩网站核心:搞定WebP转换与质量调优
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站… · 2026/9/22 0:00:19

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码