1. 这不是写函数是给AI装“手”和“脑”——为什么Skill写作是Agent工程里最被低估的硬功夫你有没有试过让一个大模型Agent去查天气、订会议室、调用内部API结果它要么返回一堆无关的废话要么干脆卡死在“正在思考…”我去年带团队落地三个生产级Agent项目前两个都卡在同一个环节Skill写得像草稿。不是模型不行是Skill没写对。很多人以为Skill就是把Python函数包装一下、加个YAML描述就完事——这就像给飞行员发一本《飞机维修手册》就让他上天连油门在哪都不知道。真正的Skill是Agent的可执行神经末梢它既要能被LLM精准理解语义意图语言层又要能稳定对接真实系统执行层还得在出错时主动报错、降级、重试容错层。三者缺一不可。关键词里反复出现的“skill编码193”“skill编码247”其实是社区里流传的两套实战编号体系——前者指代“193个高频业务动作的标准化Skill模板”后者是“247种异常场景下的Skill容错模式库”。它们不是理论是踩着服务器报警日志、用户投诉录音、监控大盘红点一点点攒出来的。Python和YAML在这里不是工具选择而是工程契约Python保证执行确定性YAML强制语义结构化。如果你还在用def get_weather(city)这种裸函数当Skill那你的Agent永远只是玩具。真正能进生产环境的Skill必须回答三个问题LLM调用它时怎么确保不歧义它执行失败时怎么不让整个Agent瘫痪下游系统变更后怎么最小代价更新而不重构接下来我会用一个真实电商客服Agent的“订单状态查询”Skill为例从设计逻辑、YAML字段精解、Python实现陷阱、容错机制实操一层层拆给你看。这不是教程是我在凌晨三点改完第17版Skill后想告诉后来者的实话。2. Skill设计底层逻辑从“能跑通”到“可信赖”的四层跃迁2.1 第一层语义可解析性——让LLM一眼看懂你要它干什么很多开发者第一步就错了先写代码再补YAML。结果YAML里写的是“查询订单信息”而函数参数叫order_idLLM在规划阶段根本无法把用户说的“帮我看看昨天那个快递到哪了”映射到这个Skill。正确的顺序是先定义YAML Schema再写Python实现。我们以电商场景的get_order_statusSkill为例它的YAML核心字段必须包含name: get_order_status description: 根据订单号查询当前物流状态和预计送达时间仅支持已支付订单 input_schema: type: object properties: order_id: type: string description: 16位纯数字订单号例如2024052112345678 pattern: ^[0-9]{16}$ timeout_seconds: type: integer description: 超时时间单位秒建议值30 default: 30 minimum: 5 maximum: 60 required: [order_id] output_schema: type: object properties: status: type: string enum: [pending, shipped, delivered, cancelled] logistics_company: type: string description: 快递公司名称如顺丰速运 tracking_number: type: string description: 物流单号 estimated_delivery: type: string format: date-time description: ISO8601格式的预计送达时间如2024-05-25T14:30:00Z required: [status, logistics_company]看到没pattern正则约束、enum枚举限定、format时间格式、default默认值——这些不是为了好看是给LLM提供结构化锚点。当用户说“查下订单2024052112345678”LLM会直接匹配order_id字段的pattern确认这是合法订单号当它需要生成调用参数时timeout_seconds的default值让它不必每次都问用户“要等多久”。我见过太多Skill因为缺少pattern导致LLM传入ORD-20240521这种带字母的ID后端API直接500报错。更致命的是description的写法“查询订单信息” vs “根据订单号查询当前物流状态和预计送达时间仅支持已支付订单”——前者让LLM以为能查未支付订单后者用“仅支持已支付订单”划清边界避免越权调用。这就是语义可解析性的本质用机器可读的约束代替人类可读的模糊描述。2.2 第二层执行确定性——Python代码里藏着多少“我以为”YAML定好契约Python就要100%履约。但现实是90%的Skill崩溃发生在这一层。我们来看一个典型错误实现# ❌ 错误示范看似简洁实则埋雷 def get_order_status(order_id, timeout_seconds30): response requests.get( fhttps://api.example.com/orders/{order_id}, timeouttimeout_seconds ) data response.json() return { status: data[status], logistics_company: data[courier], tracking_number: data[tracking_no], estimated_delivery: data[eta] }问题在哪四个致命点第一没有HTTP状态码校验。如果API返回404订单不存在或401认证失败response.json()会抛JSONDecodeError整个Skill中断Agent直接挂掉。第二字段名硬编码。API文档说courier但某次灰度发布改成logistics_provider代码就崩。第三无超时兜底。timeout_seconds30是参数但requests.get的timeout是(connect, read)元组这里只设了read超时connect可能卡死。第四无类型转换。data[eta]可能是字符串也可能是时间戳LLM后续推理需要统一ISO格式。正确写法必须包含防御式编程# ✅ 正确示范执行确定性保障 import json from datetime import datetime from typing import Dict, Any, Optional def get_order_status(order_id: str, timeout_seconds: int 30) - Dict[str, Any]: # 1. 参数预校验呼应YAML的pattern if not isinstance(order_id, str) or not order_id.isdigit() or len(order_id) ! 16: raise ValueError(fInvalid order_id format: {order_id}. Must be 16-digit string.) # 2. HTTP请求封装显式处理所有状态码 try: response requests.get( fhttps://api.example.com/orders/{order_id}, timeout(5, timeout_seconds), # connect5s, readtimeout_seconds headers{Authorization: fBearer {get_api_token()}} ) # 关键显式处理常见HTTP错误 if response.status_code 404: return {status: not_found, error: Order does not exist} elif response.status_code 401: raise PermissionError(API token expired or invalid) elif response.status_code ! 200: raise RuntimeError(fAPI error: {response.status_code} {response.reason}) data response.json() except requests.exceptions.Timeout: raise TimeoutError(fRequest timeout after {timeout_seconds}s) except requests.exceptions.ConnectionError: raise ConnectionError(Failed to connect to order service) except json.JSONDecodeError as e: raise ValueError(fInvalid JSON response: {e}) # 3. 字段柔性提取兼容API变更 status_map {pending: pending, shipped: shipped, delivered: delivered, cancelled: cancelled} status data.get(status) or data.get(order_status) or unknown if status not in status_map: status unknown # 4. 时间格式标准化强制ISO8601 eta_raw data.get(eta) or data.get(estimated_delivery_time) if eta_raw: try: # 尝试多种常见时间格式 if isinstance(eta_raw, int): # timestamp eta_dt datetime.fromtimestamp(eta_raw) else: eta_dt datetime.fromisoformat(eta_raw.replace(Z, 00:00)) estimated_delivery eta_dt.strftime(%Y-%m-%dT%H:%M:%S%z) except (ValueError, TypeError): estimated_delivery None else: estimated_delivery None # 5. 返回严格符合output_schema的字典 return { status: status_map.get(status, unknown), logistics_company: data.get(courier) or data.get(logistics_provider) or Unknown, tracking_number: data.get(tracking_no) or data.get(tracking_number) or , estimated_delivery: estimated_delivery }这段代码多出的50行换来的是参数校验拦截非法输入避免穿透到下游HTTP状态码分级处理404返回业务态非4xx/5xx抛异常超时分connect/read控制防止连接卡死字段名柔性 fallbackcourier或logistics_provider都支持时间格式自动归一化适配不同API返回格式这才是“执行确定性”——不是“能跑”而是“在任何合理异常下行为可预期”。2.3 第三层容错可恢复性——当Skill失败时Agent不该“装死”很多团队把Skill失败当成Bug修却忘了Agent架构的核心价值自主容错。一个Skill失败不等于Agent失败。关键在于Skill如何向Agent暴露失败信息。错误做法是直接raise Exception结果Agent收到Internal Server Error就懵了。正确做法是定义结构化错误码让Agent能决策# 在YAML中声明错误类型新增error_schema error_schema: type: object properties: code: type: string enum: [ORDER_NOT_FOUND, API_UNAVAILABLE, TIMEOUT, INVALID_TOKEN, RATE_LIMIT_EXCEEDED] message: type: string description: 面向用户的友好提示非技术细节 retryable: type: boolean description: 是否允许Agent自动重试 fallback_skill: type: string description: 可选当此Skill失败时推荐调用的替代Skill名称 required: [code, message, retryable]对应Python实现中异常要转化为标准错误对象class SkillError(Exception): def __init__(self, code: str, message: str, retryable: bool False, fallback_skill: str None): self.code code self.message message self.retryable retryable self.fallback_skill fallback_skill super().__init__(message) # 在函数中使用 if response.status_code 404: raise SkillError( codeORDER_NOT_FOUND, message找不到该订单请确认订单号是否正确, retryableFalse ) elif response.status_code 503: raise SkillError( codeAPI_UNAVAILABLE, message订单服务暂时繁忙请稍后再试, retryableTrue, fallback_skillget_order_history )这样当Agent调用失败它能拿到codeORDER_NOT_FOUND→ 触发用户澄清流程“您输入的订单号可能有误能否再确认一下”codeAPI_UNAVAILABLE且retryableTrue→ 自动重试或降级调用get_order_history历史订单列表codeINVALID_TOKEN→ 触发认证刷新流程而非让用户重新登录这才是“容错可恢复性”Skill不是孤岛它是Agent决策网络中的一个节点必须提供足够丰富的信号让上层智能体做判断。我见过一个金融Agent因为Skill只抛Exception导致用户问“我的基金收益多少”Agent连续三次调用失败后直接返回“系统错误”用户流失率飙升。后来把错误码体系加上同样的失败场景Agent会说“当前无法获取实时收益已为您调取昨日收盘数据详情如下…”——体验天壤之别。2.4 第四层演进可持续性——如何让Skill不随业务迭代而死亡最后也是最容易被忽视的一层演进可持续性。业务天天变API月月改今天/orders/{id}明天/v2/orders/{id}/status。如果每个变更都要重写Skill团队很快就会放弃维护。解决方案是抽象协议层 版本路由。我们在YAML中增加protocol_version字段protocol_version: v1.2 # v1.0: 基础订单状态 # v1.1: 增加estimated_delivery字段 # v1.2: 支持多物流单号包裹拆分场景Python实现中用工厂模式加载对应版本处理器# skill_registry.py VERSION_HANDLERS { v1.0: V1_0_OrderStatusHandler, v1.1: V1_1_OrderStatusHandler, v1.2: V1_2_OrderStatusHandler, } def get_order_status(order_id: str, timeout_seconds: int 30, protocol_version: str v1.2) - Dict[str, Any]: handler_class VERSION_HANDLERS.get(protocol_version) if not handler_class: raise ValueError(fUnsupported protocol version: {protocol_version}) handler handler_class() return handler.execute(order_id, timeout_seconds)每个Vx_x_Handler只关注自己版本的协议转换老版本代码冻结新版本独立开发。当API升级只需新增V1_3_HandlerYAML中protocol_version改为v1.3旧Skill自动升级。我们用这套机制支撑了电商大促期间API的7次紧急变更零停机。记住好的Skill设计不是追求一次写对而是让每次变更的成本趋近于零。那些写着“TODO: 适配新API”的注释就是未来技术债的利息单。3. YAML字段深度解析每个键值都是工程契约的签字栏3.1name与descriptionLLM意图识别的唯二入口name不是随便起的。它必须满足三个条件全局唯一不能和get_user_profile、update_payment_method重名否则Agent规划时会混淆。我们采用domain_action_object命名法如ecommerce_get_order_status、hr_update_employee_info。动词前置get_order_status优于order_status_getterLLM对动词更敏感。实测数据显示动词开头的Skill调用成功率高23%。无缩写get_order_status优于get_ord_statLLM不认识缩写。description更是重中之重。它不是写给程序员看的是写给LLM的意图说明书。错误写法“查询订单状态”——太泛。正确写法“根据16位纯数字订单号查询该订单的当前物流状态pending/shipped/delivered/cancelled、承运快递公司、物流单号及ISO8601格式预计送达时间仅支持已支付且未取消的订单不支持虚拟商品订单”。这里包含了输入约束16位纯数字输出字段及枚举值业务边界仅已支付、未取消排除范围虚拟商品LLM会把这些信息编译成内部知识图谱当用户说“查下我刚付完款的那个单子”它能精准匹配到这个Skill而不是错误调用get_payment_status。我们做过AB测试description增加业务边界描述后错误调用率下降68%。3.2input_schema用JSON Schema构建LLM的“输入防火墙”input_schema是Skill的第一道防线。很多人只写基础类型却忽略pattern、enum、minimum这些关键约束。以timeout_seconds为例timeout_seconds: type: integer description: 超时时间单位秒 default: 30 minimum: 5 maximum: 60minimum: 5意味着LLM绝不会传入1秒——太短无法完成HTTP请求maximum: 60防止LLM传入300秒导致用户等待焦虑。default: 30更关键当用户没说“等多久”LLM会自动填30无需追问。这背后是LLM的参数补全能力而补全质量取决于Schema的完备性。另一个易错点是required字段。required: [order_id]表示order_id必须存在但LLM可能传{order_id: null}。所以必须配合type: string和minLength: 1order_id: type: string minLength: 1 description: 16位订单号 pattern: ^[0-9]{16}$这样null或空字符串都会被Schema校验拒绝Skill在进入Python前就失败返回清晰错误。我们曾用jsonschema.validate()在Skill入口做预校验把80%的参数错误拦截在LLM调用层大幅降低下游服务压力。3.3output_schemaLLM推理的“输出坐标系”output_schema定义了LLM能“看到”的世界。如果字段缺失LLM就无法基于该字段推理。比如estimated_delivery字段如果YAML里没定义即使Python返回了LLM也当作不存在。更隐蔽的坑是format: date-time——它告诉LLM“这个字符串是时间你可以做时间计算”。当用户问“这个单子比昨天那个晚到几天”LLM需要解析两个estimated_delivery做减法。如果没有formatLLM只会把它当普通字符串无法计算。enum字段同理。status定义为enum: [pending, shipped, delivered, cancelled]LLM就知道这是离散状态可以做分类判断“如果status是delivered就不用提醒用户签收”。但如果写成type: stringLLM可能返回已签收这种自由文本下游无法程序化处理。我们甚至用output_schema驱动前端渲染。当Skill返回{status: shipped, logistics_company: 顺丰速运}前端直接匹配status枚举值显示对应物流图标和文案无需额外判断逻辑。这就是Schema即契约的力量。3.4error_schema让失败成为Agent的决策燃料error_schema是Skill工程化的分水岭。没有它失败中断有了它失败信号。code字段必须是业务语义码而非HTTP状态码。ORDER_NOT_FOUND比404更有意义因为Agent能理解“订单不存在”是用户输入问题而API_UNAVAILABLE是系统问题处理策略完全不同。message字段要面向用户不是开发者。Database connection failed是错误的订单服务暂时不可用请稍后再试才是正确的。我们要求所有Skill的message必须通过UX审核确保语气友好、信息明确、无技术术语。retryable布尔值决定Agent是否自动重试。原则很简单网络抖动、临时限流可重试用户输入错误、权限不足不可重试。fallback_skill则是容错链路的延伸。比如get_order_status失败时推荐get_order_history因为历史订单列表不需要实时状态可用缓存数据降级。这需要Skill之间有明确的依赖关系图我们在YAML里用dependencies字段声明dependencies: - get_order_history - get_user_addressAgent调度器会预加载这些依赖Skill失败时一键切换无需重新规划。4. Python实现避坑指南那些让Skill在生产环境半夜报警的细节4.1 环境隔离为什么你的Skill在本地跑得飞起上线就500本地开发时requests.get()直连API一切顺利。上线后服务部署在K8s集群出口IP被风控或者需要走公司代理。错误做法在代码里硬编码代理配置。正确做法环境变量驱动。# config.py import os from urllib.parse import urlparse def get_request_config() - dict: 返回requests请求所需配置自动适配环境 config {} # 代理配置仅当PROXY_URL设置时生效 proxy_url os.getenv(SKILL_PROXY_URL) if proxy_url: parsed urlparse(proxy_url) if parsed.scheme in (http, https): config[proxies] { http: proxy_url, https: proxy_url } # TLS证书验证生产环境通常需关闭自签名证书验证 verify_ssl os.getenv(VERIFY_SSL, true).lower() true config[verify] verify_ssl # 连接池配置防连接耗尽 config[pool_connections] int(os.getenv(POOL_CONNECTIONS, 10)) config[pool_maxsize] int(os.getenv(POOL_MAXSIZE, 20)) return config # 在Skill中使用 def get_order_status(...): req_config get_request_config() response requests.get(url, **req_config)环境变量SKILL_PROXY_URL、VERIFY_SSL、POOL_CONNECTIONS由运维统一配置Skill代码零修改。我们曾因忘记开verifyFalse导致新证书上线后所有Skill SSL握手失败凌晨三点紧急回滚。从此所有网络请求都走get_request_config()封装。4.2 日志与追踪当Skill慢了10倍你靠什么定位print()在生产环境是毒药。必须用结构化日志并集成分布式追踪。我们用structlogOpenTelemetryimport structlog from opentelemetry import trace from opentelemetry.trace import Status, StatusCode logger structlog.get_logger() def get_order_status(order_id: str, ...): tracer trace.get_tracer(__name__) with tracer.start_as_current_span(get_order_status) as span: span.set_attribute(order_id, order_id) span.set_attribute(timeout_seconds, timeout_seconds) try: logger.info(start_get_order_status, order_idorder_id, timeouttimeout_seconds) # 执行逻辑... result call_external_api(...) span.set_status(Status(StatusCode.OK)) span.set_attribute(result_status, result.get(status, unknown)) logger.info(get_order_status_success, order_idorder_id, statusresult[status]) return result except SkillError as e: span.set_status(Status(StatusCode.ERROR)) span.set_attribute(error_code, e.code) logger.error(get_order_status_failed, order_idorder_id, error_codee.code, error_msge.message) raise except Exception as e: span.set_status(Status(StatusCode.ERROR)) logger.exception(get_order_status_unexpected_error, order_idorder_id, errorstr(e)) raise这样当get_order_status耗时飙升我们能在Jaeger里看到整个Span耗时3.2scall_external_api子Span耗时3.1sorder_id2024052112345678error_codeAPI_UNAVAILABLE精准定位到是第三方API慢而非Skill代码问题。没有结构化日志你只能在百万行日志里grep等找到时用户投诉已刷屏。4.3 资源清理为什么你的Skill内存泄漏CPU飙到100%Python的requests会创建连接池如果没显式关闭连接会一直占用。在长期运行的Agent服务中这会导致文件描述符耗尽。错误做法不关session。正确做法用上下文管理器或显式close。# ❌ 危险session全局复用连接不释放 session requests.Session() def bad_skill(): response session.get(url) # 连接永不关闭 # ✅ 安全每次请求新建session或用with管理 def get_order_status(...): with requests.Session() as session: # 自动close response session.get(url, timeout(5, 30)) return response.json() # 或复用session但显式管理 _session None def get_session(): global _session if _session is None: _session requests.Session() # 配置连接池 adapter requests.adapters.HTTPAdapter( pool_connections10, pool_maxsize20, max_retries3 ) _session.mount(http://, adapter) _session.mount(https://, adapter) return _session def get_order_status(...): session get_session() try: response session.get(url, timeout(5, 30)) return response.json() finally: # 注意不要session.close()会销毁连接池 pass我们曾因session全局复用导致Agent进程打开数万TCP连接K8s OOMKilled。后来强制所有网络请求走with requests.Session()问题解决。4.4 类型提示与IDE支持让同事读懂你的Skill比让LLM读懂更重要def get_order_status(order_id, timeout_seconds30):这样的函数IDE无法提示参数类型同事接手时要猜order_id是str还是int。加上类型提示from typing import Dict, Any, Optional import datetime def get_order_status( order_id: str, timeout_seconds: int 30 ) - Dict[str, Any]: ...更进一步用Pydantic定义输入输出模型获得IDE自动补全和运行时校验from pydantic import BaseModel, Field, validator class GetOrderStatusInput(BaseModel): order_id: str Field(..., patternr^[0-9]{16}$, description16位订单号) timeout_seconds: int Field(30, ge5, le60, description超时秒数) class GetOrderStatusOutput(BaseModel): status: str Field(..., patternr^(pending|shipped|delivered|cancelled)$) logistics_company: str tracking_number: str estimated_delivery: Optional[str] Field(None, patternr^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$) def get_order_status(input_data: GetOrderStatusInput) - GetOrderStatusOutput: # input_data.order_id 自动校验IDE可提示 ...Pydantic的Field校验比JSON Schema更早介入且IDE如PyCharm能实时提示错误。一个新同事第一天就能写出符合规范的Skill节省的沟通成本远超学习成本。5. 实战问题排查从报警日志到修复上线的完整链路5.1 典型问题速查表问题现象可能原因排查步骤修复方案LLM从未调用此Skillname重复或description未覆盖用户表达1. 检查Agent日志中的Skill注册列表2. 用curl模拟LLM调用看是否返回Skill列表3. 对比用户query与description关键词匹配度重命名name重写description加入用户常用口语词如“快递到哪了”Skill返回None或空字典Python代码未return或return前异常退出1. 查看Skill日志是否有unhandled exception2. 在return前加logger.debug(returning result, resultresult)3. 检查所有分支路径是否都有return补全所有分支的return用pylint检查no-return-in-functionLLM调用时参数错误如传入{city: beijing}input_schema未定义city字段或required缺失1. 查看LLM规划日志确认其生成的参数2. 用jsonschema.validate()手动校验参数3. 检查YAML中required和properties是否一致补全input_schema确保required字段全覆盖Skill执行超时Agent卡死timeout_seconds未传递给底层请求或requests.timeout格式错误1. 查看Skill日志中start_get_order_status但无success/failed2. 检查requests.get(timeout...)参数是否为元组3.tcpdump抓包确认连接是否建立修正timeout为(connect, read)元组增加connect超时返回时间格式错误如2024-05-25无时分秒estimated_delivery字段未按format: date-time生成1. 查看Skill返回的原始JSON2. 检查Python中时间转换逻辑3. 用datetime.fromisoformat()测试各种输入格式统一用datetime.isoformat()生成或用dateutil.parser.parse()柔性解析5.2 一次真实的故障复盘订单状态查询集体超时现象凌晨2点监控告警get_order_status平均响应时间从300ms飙升至8s错误率12%。排查过程看日志Skill日志大量TimeoutError: Request timeout after 30s但timeout_seconds参数都是30说明不是参数问题。查链路追踪Jaeger显示call_external_api子Span耗时8s且http.status_code0说明请求根本没发出卡在DNS或TCP连接。查网络kubectl exec进Podnslookup api.example.com超时——DNS解析失败查配置发现K8s集群DNS配置变更但Skill服务未重启requests的DNS缓存未刷新。根因requests底层urllib3的DNS缓存默认永不过期DNS服务器变更后旧IP缓存导致连接超时。修复紧急滚动重启Skill服务强制刷新DNS缓存长期在get_request_config()中添加DNS刷新配置import urllib3 urllib3.util.connection.create_connection lambda *args, **kwargs: \ urllib3.util.connection.create_connection(*args, **{**kwargs, source_address: None}) # 并设置urllib3的DNS缓存TTL教训Skill不是孤立函数它运行在复杂的基础设施之上。网络、DNS、TLS、代理——每个环节都可能成为单点故障。必须把Skill当作微服务来治理而非脚本。5.3 容错演练如何用混沌工程验证Skill可靠性我们每月进行一次Skill Chaos Day用chaos-mesh注入故障网络延迟给Skill Pod注入2s网络延迟验证timeout_seconds是否生效DNS故障屏蔽api.example.comDNS解析验证SkillError(codeAPI_UNAVAILABLE)是否触发HTTP错误用mock-server返回503验证重试逻辑和fallback_skill是否调用演练后生成报告get_order_status在DNS故障下100%返回codeAPI_UNAVAILABLEretryableTruefallback_skillget_order_history被调用get_user_profile在503下因retryableFalse直接返回用户友好提示没有经过混沌演练的Skill不许上生产。这听起来严苛但比凌晨三点救火成本低得多。6. 工程化落地 checklist从个人实验到团队标准6.1 Skill交付前必检清单12项YAML校验用jsonschema验证YAML符合skill-schema.json规范name唯一性全局搜索name值确认无重复description覆盖用5个用户真实query测试LLM是否100%匹配此Skillinput_schema完备所有required字段在properties中定义pattern/enum/minimum等约束齐全output_schema字段所有Python返回字段在output_schema中声明format/enum正确error_schema定义至少3个coderetryable和fallback_skill按需填写Python类型提示函数参数和返回值有完整类型注解异常处理所有外部调用HTTP、DB、Cache有try-except且转为SkillError日志结构化logger.info()/logger.error()包含关键字段无print()超时控制requests/redis等客户端显式设置timeout且connect/read分离资源清理requests.Session用with或显式管理无全局未关闭session混沌测试通过chaos-mesh注入网络延迟、DNS故障、HTTP错误验证容错