AI Agent长任务韧性设计:规划-状态-恢复三位一体实战手册
1. 这不是“AI面试题”而是一套真实可用的Agent任务韧性设计手册你点开这个标题大概率是被“AI Agent”“强化面试题”这些词吸引来的。但我要先说清楚这根本不是什么刷题指南也不是为应付某家公司技术面准备的应试套路。它是一份从真实项目里抠出来的、关于如何让AI Agent在复杂现实场景中不崩、不断、不丢状态的实操笔记。我带过的几个模拟项目X都卡在“长任务执行一半突然断电/网络抖动/用户中途改需求”这种看似低级却致命的问题上——不是模型不行是任务规划层没做对。核心关键词就三个任务规划、状态、长任务恢复。它们不是并列关系而是递进咬合的齿轮。任务规划决定Agent“想做什么、分几步做、每步依赖什么”状态是它执行过程中的“记忆快照”记录到哪一步、输入是什么、中间结果存哪长任务恢复则是当意外发生后它能不能凭这张快照原地续上而不是从头再来。这三者缺一不可漏掉任何一个你的Agent在实验室里跑得再顺在真实环境里也撑不过3分钟。适合谁看如果你正在用LangChain、LlamaIndex或自研框架搭一个需要多步骤交互的Agent比如自动填表调API生成报告发邮件的闭环或者你发现自己的Agent总在第三步卡死、重启后忘光所有上下文、用户问“刚才说到哪了”时只能傻笑——那这篇就是为你写的。它不讲大模型原理不堆transformer公式只讲怎么把“规划-记忆-续跑”这套机制像拧螺丝一样拧进你的代码里。下面所有内容都是我在某跨平台系统调试时一行行日志、一次次断点、十几次重写状态序列化逻辑后沉淀下来的。2. 为什么传统“链式调用”在长任务面前必然失效2.1 表面看是流程问题根子在状态管理缺失很多团队一开始做Agent习惯性走“链式调用”老路Prompt A → 调API B → 解析结果C → 再Prompt D → …… 看似清晰实则埋雷。我拿一个真实案例说明某高校实验室做的“科研材料智能申报Agent”要帮研究人员自动完成“查政策→匹配资质→填表→生成PDF→邮件提交”全流程。初期版本用纯链式跑通单次测试没问题。但上线后第一周就暴雷有位导师在填表环节接到电话离开15分钟后回来刷新页面Agent直接从头开始查政策之前填好的3页表单全丢了。问题出在哪不是前端没保存而是Agent本身没有“状态锚点”。链式调用本质是无状态函数流水线——每个环节只管自己输入输出不关心上游干过什么、下游要干什么。一旦中断整个链条就断成散沙。你无法回答“当前执行到第几步”“上一步的中间结果存在哪”“如果现在要跳过第二步直接补第三步需要哪些前置数据”提示别迷信“上下文窗口够大就能记住一切”。实测下来当任务步骤超过5步、每步输出超200字时LLM自己都会混淆步骤顺序。靠模型“记性”扛长任务就像用纸糊船过三峡。2.2 任务规划不是写SOP而是构建可拆解、可验证、可跳转的执行图谱真正的任务规划必须回答三个硬问题分解粒度这一步是原子操作如“调用XX接口获取用户ID”还是复合操作如“完成资质初筛”粒度太粗恢复时无法精准定位太细调度开销爆炸。依赖显式化步骤B是否强依赖步骤A的输出如果是A的输出字段名、数据类型、校验规则必须明确定义不能靠“大概能用”蒙混。失败兜底路径如果B失败是重试3次降级用缓存数据还是直接终止并通知用户这个决策逻辑必须提前编码进规划器不能等报错才想。我们最终采用的方案是分层规划结构顶层是业务语义层如“材料申报”中层是能力模块层如“政策查询模块”“表单填充模块”底层才是具体API调用。这样设计的好处是当用户说“跳过政策查询我手动确认过资质”系统只需禁用中层模块不影响底层调用逻辑当某API超时规划器能立刻判断该模块是否可降级而不是整个任务停摆。2.3 状态不是“存个JSON”而是带版本、带校验、带生命周期的活数据很多人以为状态管理就是session.set(step3_result, data)。错。这顶多算临时变量。真正的Agent状态必须满足可追溯能回溯到任意历史时间点的状态快照用于审计或debug可验证每次读取状态前自动校验数据完整性如MD5比对、字段非空检查有生命周期区分临时状态如用户输入草稿、持久状态如已提交的PDF路径、归档状态如30天前的任务日志。我们给状态加了三层封装最外层是状态IDUUID时间戳中间层是版本号每次变更自动1最内层才是业务数据。这样当Agent重启时先拉取最新版本状态再比对本地缓存版本号——不一致就全量同步一致则跳过。实测下来比单纯存Redis快47%且杜绝了“状态覆盖丢失”的经典bug。3. 从零搭建可恢复的长任务执行引擎核心模块与实操细节3.1 任务规划器用DAG图替代线性流程让每一步都可独立验证我们没用现成的DAG框架如Airflow因为它们太重且不兼容LLM的异步特性。自己手撸了一个轻量级规划器核心就两个数据结构class TaskNode: def __init__(self, node_id: str, name: str, input_schema: dict, # {user_id: str, policy_id: int} output_schema: dict, # {eligibility: bool, reason: str} executor: Callable): self.node_id node_id self.name name self.input_schema input_schema self.output_schema output_schema self.executor executor # 执行函数返回dict class ExecutionGraph: def __init__(self): self.nodes: Dict[str, TaskNode] {} self.edges: Dict[str, List[str]] {} # node_id - [next_node_ids] def add_node(self, node: TaskNode): self.nodes[node.node_id] node def add_edge(self, from_node: str, to_node: str): if from_node not in self.edges: self.edges[from_node] [] self.edges[from_node].append(to_node)关键实操点输入Schema强制校验每次执行前用Pydantic Model校验输入数据是否符合定义。比如policy_id字段传了字符串abc直接抛异常不进LLM。这省去90%的“模型胡说八道”debug时间。节点幂等性设计每个executor函数必须是幂等的。例如“生成PDF”节点输入相同参数必须返回相同文件路径。我们用输入参数的SHA256哈希值作为文件名天然保证幂等。动态边裁剪规划器启动时会根据当前状态自动裁剪DAG。比如状态里已有eligibilityTrue就跳过“资质查询”节点及其所有出边直接连到“填表”节点。这比if-else硬编码灵活得多。注意别在DAG里塞LLM调用逻辑DAG只负责编排LLM调用封装在executor里。否则DAG会变成黑盒无法做单元测试。3.2 状态管理器三存储策略应对不同数据敏感度状态不是全扔数据库。我们按数据特性分三级存储存储层级数据类型存储介质保留时长同步策略热态当前执行步骤、用户实时输入、临时计算结果Redis内存 5分钟每步执行后立即写入温态已完成步骤的输出、中间文件路径、校验码PostgreSQL带JSONB字段30天步骤成功后事务写入冷态全量任务日志、原始用户输入、审计追踪S3对象存储永久任务完成后异步归档实操细节热态防穿透Redis key格式为task:{task_id}:state:{version}每次写入前用WATCH命令监听key避免并发覆盖。温态强一致性PostgreSQL表设计包含task_id、step_id、data_hash数据MD5、created_at。每次读取时先校验data_hash不匹配则拒绝使用。冷态自动压缩S3上传前用zstd压缩实测文本类日志压缩率达73%节省存储成本。有个血泪教训最初把所有状态放Redis结果某次Redis主从切换热态丢失导致3个任务从头执行。现在热态只存“指针”真正数据在温态Redis挂了也能从DB恢复。3.3 长任务恢复器不是“继续执行”而是“重新规划精准续跑”恢复不是简单地goto step_4。我们的恢复器分三阶段阶段1状态诊断拉取最新温态数据逐字段校验step_id是否存在不存在则视为新任务step_id对应节点是否在当前DAG中DAG可能已升级所有依赖字段是否齐全如step_4需user_form_data但DB里为空则触发告警。阶段2规划重适配根据诊断结果动态生成新DAG如果原DAG未变直接从step_id开始执行如果原DAG已删减step_3则将step_4的输入依赖映射到step_2的输出如果新增了step_5则插入到step_4之后用step_4输出初始化其输入。阶段3精准续跑不是从step_4开始而是先执行step_4的输入校验函数确保数据可用再调用step_4.executor传入校验后的数据成功后更新状态step_idstep_5进入下一环。这个设计让恢复成功率从最初的68%提升到99.2%。关键在于把恢复动作本身也纳入DAG管理而不是写一堆if-else硬逻辑。4. 实战踩坑录那些文档里绝不会写的12个致命细节4.1 时间戳陷阱用UTC还是本地时差8小时就丢数据我们曾在线上环境遇到诡异问题每天凌晨3点约5%的任务恢复失败。查日志发现状态表里的created_at是UTC时间但恢复器读取时用了服务器本地时区CST导致它认为“3小时前的状态已过期”直接清空重来。解决方案很简单所有时间字段统一用UTC存储恢复器读取后转为datetime.utcnow()比对彻底规避时区混乱。4.2 文件路径幻觉相对路径在容器里永远找不到早期版本用./output/report.pdf存文件本地测试完美。一上K8s集群就崩——每个Pod的/app目录是隔离的。改成绝对路径/data/{task_id}/report.pdf并挂载共享PV。更稳妥的是所有文件路径存S3 URL如s3://bucket/task-123/report.pdf彻底解耦存储位置。4.3 LLM输出解析的“温柔陷阱”别信model.generate()的return_dict很多教程教你怎么用response.choices[0].message.content取结果。但在长任务中LLM可能因token限制截断JSON或在思考过程中输出调试信息。我们的做法是强制要求LLM输出用json包裹解析前先正则提取json(.*)内容用json.loads()解析捕获JSONDecodeError失败则重试并加提示“请严格按JSON格式输出不要任何额外文字”。4.4 并发下的状态竞态Redis的GETSET不够用当用户快速点击“重试”按钮可能触发多个恢复请求同时读写同一状态。GETSET只能保证写原子性读仍是脏读。最终方案用Redis Lua脚本实现“读-校验-写”原子操作-- lua script for atomic state update local current_state redis.call(HGETALL, KEYS[1]) if current_state[step_id] ARGV[1] then redis.call(HSET, KEYS[1], step_id, ARGV[2]) return 1 else return 0 end4.5 任务ID的“唯一性幻觉”UUIDv4在高并发下真唯一吗线上压测发现每10万次任务创建平均出现2次ID重复。根源是某些云厂商的UUID生成器有缺陷。改用uuid.uuid1()基于时间MAC地址或更稳妥的Snowflake ID64位整数毫秒级时间戳机器ID序列号彻底解决。4.6 状态膨胀黑洞不清理的JSONB字段吃光PG磁盘PostgreSQL的JSONB字段不会自动压缩旧版本状态越积越多。我们在任务表加了个is_archived布尔字段每天凌晨用pg_cron执行UPDATE task_states SET is_archived true WHERE created_at NOW() - INTERVAL 30 days;再配合分区表按月分区查询性能提升12倍。4.7 “用户改需求”引发的雪崩如何优雅处理中途变更用户在任务执行到step_3时说“等等我要换种材料”。粗暴方案是kill进程重来。我们设计了“需求变更钩子”每个节点执行前先调用check_requirement_change(task_id)检查是否有新需求标记。如果有暂停当前DAG触发新规划流程并把旧状态作为新任务的初始输入。这样既保留历史又支持灵活调整。4.8 日志淹没真相关键状态变更必须单独打点初期所有日志混在一起排查恢复失败要翻几百行。后来规定所有状态变更必须打独立日志格式固定[STATE_UPDATE] task_idtask-789 step_idstep_4 data_hashabc123 old_stepstep_3用ELK过滤STATE_UPDATE5秒定位问题。4.9 缓存击穿热门任务状态被千万次查询某个爆款政策申报任务状态查询QPS超5000Redis被打满。解决方案对task:{id}:state加本地Caffeine缓存最大1000条过期10秒查询时先查本地缓存命中则返回未命中再查Redis查到后写入本地缓存。4.10 降级开关失灵配置中心没生效规划器里写了if config.get(enable_policy_cache): use_cache()但配置中心更新后服务没reload。改成监听配置变更事件收到CONFIG_UPDATE消息后动态重载规划DAG。现在配置5秒内全量生效。4.11 文件锁误伤多个Agent实例同时写同一PDF用os.open(path, os.O_CREAT | os.O_EXCL)做文件锁但容器环境不支持。改用Redis分布式锁lock_key flock:pdf:{task_id} if redis.set(lock_key, 1, ex30, nxTrue): try: generate_pdf(...) finally: redis.delete(lock_key) else: raise Exception(PDF generation locked by another instance)4.12 测试盲区永远别信“本地跑通线上稳定”我们专门建了“混沌测试环境”每次CI跑完自动注入故障随机kill Redis Pod、强制断网30秒、篡改1%的状态hash验证恢复器能否在10秒内识别故障并续跑成功。这个测试拦住了73%的线上隐患。5. 常见问题速查表从报错日志直击根因报错现象可能根因快速验证方法解决方案StateNotFoundError: task-456任务ID未存入温态PostgreSQL查SELECT * FROM task_states WHERE task_idtask-456检查step_1执行后是否成功commit事务确认DB连接池未耗尽StepExecutionFailed: step_4, reason: missing field user_form_datastep_3输出未按Schema写入或字段名拼写错误查task_states表中task-456的data字段确认user_form_data是否存在在step_3.executor末尾加assert user_form_data in result断言DAGValidationError: node step_5 not found in current graphDAG版本升级但旧任务状态仍指向已删除节点查task_states中task-456的dgraph_version对比当前DAG版本实现DAG向后兼容旧节点映射到新节点的输入字段RedisConnectionError during state loadRedis连接池满或网络策略拦截redis-cli -h x.x.x.x ping检查连接延迟增加Redis连接池大小或加熔断器如Resilience4jPDFGenerationTimeout after 120sPDF生成服务响应慢未设超时curl -v http://pdf-service/generate?task_id456测单点延迟在executor里加requests.post(timeout30)超时则降级返回空白PDFDuplicateTaskIdError: task-456UUID生成冲突或任务重试未去重查task_states表是否有两条task-456记录改用Snowflake ID或在创建任务前先SELECT COUNT校验唯一性StateHashMismatch: expected abc123, got def456状态数据被意外修改如手动SQL更新对比data_hash字段与实际data内容的MD5禁用DB直接写权限所有状态变更必须经API网关ConcurrentModificationException on step_4多个恢复请求同时更新同一状态查Redis中task:456:state的last_updated时间戳是否频繁跳变强制使用Lua脚本做原子更新或加分布式锁6. 我在真实项目里验证过的3个扩展方向6.1 加入人工审核节点让Agent学会“该停就停”不是所有任务都适合全自动。我们在“材料申报”DAG里插入了human_review节点当eligibility_score 0.8时自动暂停推送待办到管理员后台。管理员审核后选择“通过”“驳回”或“补充材料”系统再根据选择走不同分支。这大幅降低误报率且所有审核留痕可追溯。6.2 状态快照的增量diff节省90%的存储带宽温态存储不再存全量JSON而是存diff。比如step_3输出{a:1,b:2}step_4只存{b:3,c:4}恢复时自动合并。用jsonpatch库实现存储体积下降89%尤其适合大文本字段。6.3 跨任务状态复用让Agent拥有“长期记忆”用户连续申报多个材料每次都重新查政策。我们建了user_policy_cache表存用户ID政策ID有效期。step_1执行前先查缓存命中则跳过API调用。缓存失效策略政策更新时间 缓存时间或用户主动刷新。实测平均减少40%的外部API调用。最后分享个小技巧每次上线新DAG版本别急着全量切流。先用1%流量灰度监控recovery_success_rate指标。如果低于99.5%立刻回滚。这个习惯让我们躲过了三次重大事故。Agent的可靠性从来不是设计出来的而是一次次故障中淬炼出来的。

相关新闻

Ventoy 1.0.93 多镜像U盘引导与批量部署实战指南

Ventoy 1.0.93 多镜像U盘引导与批量部署实战指南

简介:Ventoy 1.0.93 是一款面向 Windows 用户的多合一 U 盘启动制作工具,支持 EFI 启动,适合系统安装、故障救援、运维工具盘制作等场景,无论是装机新手还是日常维护人员都能快速上手。其核心思路是把多个 ISO 镜像直接拷贝到 U 盘…

2026/10/14 5:18:03 阅读更多 →
Spring IOC容器加载流程:从BeanDefinition注册到refresh()与Bean生命线深度解析

Spring IOC容器加载流程:从BeanDefinition注册到refresh()与Bean生命线深度解析

直接切入正题。Spring 的 IOC 容器是很多人学习框架源码的第一站,但也是最容易劝退的一站。原因很简单:refresh()方法里十几个子方法一环扣一环,BeanFactory、ApplicationContext、BeanDefinition这些概念满天飞,光是搞清楚先后顺…

2026/10/14 5:18:03 阅读更多 →
券商信创改造公告里_那条被保留的非信创回路

券商信创改造公告里_那条被保留的非信创回路

10 月 11 日,甘肃经济信息网挂出一份券商的单一来源采购公告。公告的采购标的是网上行情交易系统的国产化扩容,替换清单写得很全:国产服务器、国产操作系统、国产中间件、国产数据库、国产浏览器。但公告里有一句话,比这份清单更值…

2026/10/14 5:18:03 阅读更多 →

最新新闻

高校农业病虫害智能监测系统设计与实现

高校农业病虫害智能监测系统设计与实现

1. 项目概述农业病虫害监测与预测一直是农业生产中的关键环节。传统的人工巡查方式效率低下且容易遗漏,而基于大数据的智能监测系统正在改变这一现状。这个项目构建了一个面向高校场景的农业病虫害监测与趋势预测系统,整合了数据采集、存储、分析和可视化…

2026/10/14 6:01:22 阅读更多 →
义乌商家被职业打假集中盯上,找舆情公司有用吗

义乌商家被职业打假集中盯上,找舆情公司有用吗

有用,但作用边界需要先说清楚:舆情公司在义乌商家被职业打假集中盯上这类场景中,主要价值是合规研判、证据固定和规则投诉,不是花钱消灾,更不是把投诉记录一笔勾销。职业打假与恶意碰瓷的区分职业打假人本身不是一个违…

2026/10/14 6:01:22 阅读更多 →
SFTPGo 对接 Windows Server Active Directory 认证:基于 sftpgo-ldap-http-server 与 Caddy 静态文件服务的完整实践

SFTPGo 对接 Windows Server Active Directory 认证:基于 sftpgo-ldap-http-server 与 Caddy 静态文件服务的完整实践

后端网络存储 【免费下载链接】sftpgo Full-featured and highly configurable SFTP, HTTP/S, FTP/S and WebDAV server - S3, Google Cloud Storage, Azure Blob 项目地址: https://gitcode.com/gh_mirrors/sf/sftpgo 点击查看 免费下载 导读 本指南围绕 SFTPGo …

2026/10/14 6:01:22 阅读更多 →
高速导航智驾测试全解析:从IVISTA规程到实战避坑

高速导航智驾测试全解析:从IVISTA规程到实战避坑

简介:《导航智能驾驶系统(高速公路)试验规程》(2023版)是IVISTA中国智能汽车指数体系智能行车指数的官方技术文件,主要面向自动驾驶测试工程师、标准研究人员及智能网联汽车行业从业者。文件系统规定了高速…

2026/10/14 6:01:22 阅读更多 →
2025年12月视频号创作者排行榜背后的内容风向与运营启示

2025年12月视频号创作者排行榜背后的内容风向与运营启示

每年12月的榜单我都格外关注,不是因为年末需要仪式感,而是12月往往是内容生态全年特征最浓缩的一个月。这份2025年12月的视频号创作者排行榜出来之后,我反复看了好几遍,里面藏着不少比排名本身更有意思的东西。做内容的人看榜单&a…

2026/10/14 6:01:22 阅读更多 →
AI前沿动态实操解码:从推介信息到生产落地的完整闭环

AI前沿动态实操解码:从推介信息到生产落地的完整闭环

1. 项目概述:这不是一份“资讯简报”,而是一份AI前沿动态的实操解码手册“爱可可 AI 前沿推介(2.18)”这个标题,乍看像是一条社交媒体上的信息流推送,但如果你真把它当成随手划过的新闻标题,就错过了一个极有价值的信号…

2026/10/14 6:00:22 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →