这阵子一直在收拾打卡/日报 Agent 这个项目的烂摊子。v1.0 版本从联调通过到正式投入日常使用,前后跑了大概六周时间,表面上功能是齐全的——定时打卡、日报自动生成、摘要推送、异常提醒,该有的都有了。但真正用起来才发现,问题远比比想象的多。
这个项目本质上是一个基于大模型工具调用能力的自动化 Agent,核心链路是:定时触发 -> 读取工作记录/沟通记录/代码提交 -> 调用大模型生成日报草稿 -> 推送到群聊 -> 人工确认归档。从架构上讲并不复杂,属于典型的"工作流编排 + LLM 工具调用"模式。但越简单的链路,越容易在细节上翻车。这六周里我陆续记下了二十多个问题点,筛掉一些偶发的、影响面小的,最后整理出一份 Bug 可改清单,按影响范围和修复成本排了优先级,准备在 v1.1 里集中处理。这篇文章就把整份清单的思路、拆解过程和修复方案完整记录下来,给正在做类似 Agent 项目的朋友一个参考。如果你也在维护自己的自动化工具,尤其是涉及定时任务、大模型输出解析、多 Agent 协作这些环节的,这份清单里的很多坑你早晚也会踩到。
1. 项目整体设计与 Bug 来源分析
1.1 v1.0 的核心架构和运行链路
先简单交代一下这个项目的技术底座。打卡/日报 Agent 采用的是"调度器 + 工作流 + 工具调用"的分层设计,底层跑在 Python 3.11 上,调度用的 APScheduler,工作流编排用的自研状态机,LLM 接入的是通用大模型 API,工具层封装了 Git 提交记录读取、文档检索、IM 消息发送、日历查询等接口。
整个执行链路是这样的:每天早上 9 点 30 分,调度器触发"早间打卡"任务,Agent 会拉取前一个工作日的代码提交、会议纪要、即时通讯消息等数据源,由大模型归纳成一段工作摘要,推送到团队群;每天晚上 18 点,触发"日报生成"任务,同样拉取当天的数据,生成结构化日报,交给用户确认后归档。
从技术角度看,v1.0 的重点不在算法也不在模型调优,而在于如何把大模型的能力稳定地嵌入到既有工作流里。整个项目的核心难点有三块:一是多数据源的采集与清洗,二是大模型输出的稳定性控制,三是状态流转的异常兜底。Bug 也主要集中在后两块——模型输出不稳定会引发解析错误,状态机流转设计不严谨会在边界条件下出现死锁或空转。
1.2 Bug 清单的来源和筛选方法
这份 Bug 清单不是凭空想出来的。我花了大概两周时间,做了三件事来收集和归类问题:
第一,全面复盘了六周内的所有运行日志。重点排查了报错堆栈、任务中断记录和异常推送记录,筛选出重复出现的错误模式。日志是最好的老师,很多偶发问题如果不通过日志回溯,根本不可能定位到根因。
第二,找了团队里实际的日活用户做了访谈。有人反馈"日报里经常出现昨天的内容",有人说"打卡提醒有时候收不到",还有人抱怨"Agent 生成的摘要和我实际做的工作对不上"。用户反馈往往没有技术细节,但能指出问题的表象,顺着表象去反查代码,往往能发现隐藏很深的逻辑缺陷。
第三,把 v1.0 里所有硬编码的阈值、超时时间、重试次数全部列了出来,逐个推演边界场景。很多 Bug 不是逻辑写错了,而是参数设计不合理——比如超时设置太短,大模型响应稍慢就触发重试,重试又导致任务堆积,最后整个工作流卡死。
筛选原则就两条:可复现性和影响面。只修能稳定复现、并且对用户有明显影响的问题。那些偶发一次、无法稳定复现的问题,先记录在案,不进入 v1.1 的修复清单。这不代表放任不管,而是避免在问题定位不清楚的情况下盲目改动代码,引入新的回归。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心 Bug 分类拆解与修复优先级划分
2.1 按影响维度把 Bug 分成四个象限
二十多个问题点如果一个个平铺开来处理,很容易迷失重点。我把它们按"影响频率"和"影响严重度"两个维度分成了四个象限:
第一象限是高频且严重的问题,这类必须第一时间修复。比如定时任务偶发跳过、日报内容出现串线、核心链路抛异常后静默失败——这些问题直接影响用户对工具的信任度,一天不修,团队就一天不敢真正依赖这个 Agent。
第二象限是高频但不太严重的问题,比如日报格式偶尔乱掉、摘要措辞不够精准、推送消息偶尔重复。这些问题不影响主流程跑通,但会持续消耗用户体验,属于"体验债",需要在迭代窗口内一并处理。
第三象限是低频但严重的问题,比如极端情况下状态机死锁导致任务队列阻塞、并发触发时数据竞争导致日报内容张冠李戴。这类问题不常见,但一旦发生,修复成本极高,而且往往伴随着数据正确性问题,需要从架构层面去加固。
第四象限是低频且不严重的问题,属于优化项。比如特定环境下重试机制不生效、日志打印等级混乱导致排查困难等。这些不着急,顺手能改就改,不强行排期。
象限分析做完之后,v1.1 的修复范围就很清晰了:集中处理第一和第二象限的问题,针对第三象限做防御性重构,第四象限选择性处理。
2.2 优先级排序的具体方法论与判断依据
排优先级的时候,我参考了一个很朴素的原则:** 先保证正确性,再保证可用性,最后才考虑体验优化。** 数据正确性是一切自动化工具的生命线。日报内容错了,比日报没生成更可怕——没生成用户知道自己手动补,生成错了用户可能直接照着错的内容汇报工作,那问题就大了。
所以我把"日报内容串线"、"打卡记录时间错乱"、"时区边界处理不当"这类数据正确性相关的问题,全部列为 P0 级别。修复方案确定后第一时间进开发。
P1 级别是"任务静默失败"、"重试风暴导致接口限流"这类可用性问题。它们不直接产生错误数据,但会让任务链路的可靠性大打折扣。一个定时任务如果 10 次里有 2 次不触发,用户就会觉得这个工具"不太靠谱",信任感一旦崩塌就很难重建。
P2 级别是格式、措辞、重复推送等体验问题。修复成本不高,但需要反复调试,放到功能迭代的间隙处理。
优先级排完之后,我又做了一轮"修复成本预估"。每个 Bug 都标注了预计改动范围、涉及模块、测试成本,然后按照"投入产出比"重新微调了顺序。有些 Bug 虽然影响很大,但修复需要动到底层架构,风险极高,这种就拆成两步走——先做防御性补丁保证线上稳定,再排期做架构重构。
2.3 附上 v1.1 修复排期总表
整个过程整理下来,最终进入 v1.1 修复清单的 Bug 一共有 16 个,分为 6 个批次:
| 批次 | 问题模块 | 核心问题描述 | 优先级 | 预计工时 |
|---|---|---|---|---|
| 第一批 | 定时调度 | 任务偶发跳过,无补偿机制 | P0 | 8h |
| 第一批 | 时间处理 | 跨日、跨周边界时间归属错误 | P0 | 4h |
| 第一批 | 数据隔离 | 多任务并发时上下文串线 | P0 | 12h |
| 第二批 | LLM 输出解析 | 模型返回非预期格式导致解析崩溃 | P1 | 6h |
| 第二批 | 重试机制 | 重试风暴导致上游接口限流 | P1 | 6h |
| 第二批 | 异常兜底 | 异常场景下静默失败,无告警 | P1 | 4h |
| 第三批 | 提示词工程 | 日报摘要泛化,缺少具体细节 | P2 | 8h |
| 第三批 | 推送逻辑 | 消息重复推送、顺序错乱 | P2 | 4h |
| 第三批 | 缓存策略 | 数据源缓存过期导致日报内容滞后 | P2 | 3h |
| 第四批 | 状态机 | 特定路径下状态流转死锁 | P0 | 16h |
| 第四批 | 会话管理 | Agent 长会话内存溢出,响应质量下降 | P1 | 10h |
| 第五批 | 日志系统 | 日志级别混乱,关键链路无追踪 ID | P2 | 5h |
| 第五批 | 配置管理 | 环境差异导致配置项失效 | P2 | 3h |
| 第五批 | 工具调用 | 部分工具入参校验缺失,异常输入未拦截 | P1 | 4h |
| 第六批 | 安全加固 | 工具调用白名单不够严格,存在注入风险 | P1 | 6h |
| 第六批 | 权限控制 | 用户操作审计日志缺失 | P3 | 4h |
批次划分遵循的规则是:P0 优先、模块内聚、联调成本最小化。第一批和第四批虽然都是 P0,但第四批涉及状态机重构,改动范围大,所以拆到后面单独处理,避免和其他修复互相干扰。每一批修完后,都要经过完整的回归测试再发版。
3. 关键 Bug 的根因剖析与修复方案设计
3.1 定时任务偶发跳过:问题不在调度器,在消息队列
这个 Bug 的表现是:APScheduler 的日志里显示任务已经触发,但 Agent 的实际执行链路里完全没有对应记录。一开始以为是调度器漏触发,排查了很久没有头绪。后来把调度器的执行日志和任务队列的消费日志放在一起对比,才发现问题出在任务消息的确认机制上。
整个流程是这样的:调度器到点触发任务,产生一条任务消息推送到 Redis 队列,工作进程从队列里拉取消息、执行任务、执行完成后确认删除。正常情况下这是个标准的生产者-消费者模型。但 v1.0 里为了实现"任务至少执行一次"的可靠性保障,给消息设置了 10 分钟的可见超时时间。如果任务执行时间超过 10 分钟,Redis 会认为消息没有被消费,把它重新放回队列。这时候如果前一个任务还在跑,后一个任务又起来了,两个任务并发执行,同一个任务的内容会被重复生成。
更隐蔽的是另一种情况:任务执行到最后一步更新状态时崩溃了,消息被 Redis 重新投递,但状态机里任务已经被标记为"执行中",不会被重新调度。这就造成了"任务实际没有完成,但永远不会再被拉起"的假死状态。
修复方案分两层:第一层,去掉"执行中"状态,改为"待执行 -> 执行中 -> 已完成/执行失败"。任务重新入队时,如果检测到同一任务 ID 已经在执行中且超时未完成,允许重新执行,而不是直接丢弃。第二层,把任务执行的幂等性做扎实,每个任务都有一个全局唯一的 execution_id,写入日报时带上这个 ID,确保重复执行不会产生重复数据。
3.2 时区与跨日边界:所有时间计算必须走统一时间服务
这是踩得最深的一个坑。打卡/日报 Agent 跑在 Docker 容器里,容器默认时区是 UTC。服务器上的时间是北京时间,但容器里取到的系统时间是 UTC。如果代码里直接用 datetime.now() 拿当前时间,就会比真实时间慢 8 个小时。
最早发现这个问题是因为日报归档的时间戳总是差 8 小时。一开始以为是显示问题,后来发现不仅是显示问题——每天晚上 18 点触发的日报任务,在容器里计算"今天"的范围时,用的是 UTC 时间的凌晨 4 点到下午 4 点。如果有同事在下午 5 点半提交了代码,这条提交记录就不会被算进"今天"的日报里,要等到第二天才会出现,而且会出现在第二天的日报里——因为相对 UTC 时间来说,下午 5 点半已经是"第二天"了。
这个 Bug 的修复思路看起来很简单:统一时区。但真正实施的时候发现,代码里散落着至少七处不同的时间获取方式——有的用系统时间,有的用数据库时间,有的硬编码了 +08:00 偏移。修的时候不是改一处,而是把所有时间获取都收敛到一个统一的 get_now() 服务函数里,强制走同一套时区逻辑,然后通过单元测试把所有边界场景覆盖掉。
注意:跨日边界还有一个容易忽视的点——用户自定义的工作时间段。v1.0 假设所有人的工作时间都是 9 点到 18 点,但实际团队里有早班同事 8 点就开始干活了,晚上还有加班到 22 点的。如果日报的时间范围写死,就会有人的工作内容永远进不了当天的日报。这个在 v1.1 里改成了可配置的时间段,默认读取用户的日历设置。
3.3 LLM 输出解析:不能靠提示词保证 JSON 格式稳定
大模型输出不稳定是 Agent 类项目绕不开的痛。v1.0 里日报生成的核心逻辑是:把整理好的工作数据拼到提示词里,让大模型输出一段总结 + 一个 JSON 数组(包含工作项、耗时、标签)。然后写了一个解析函数,把模型返回的文本里的 JSON 提取出来,转成结构化数据。
这个设计看起来没问题,但实际跑起来之后,至少遇到了三种模型输出的"非预期行为":
第一种是 JSON 外面包了 markdown 代码块标记。模型会返回 json ... 这样的格式,需要先剥离代码块标记才能解析。
第二种是 JSON 格式不完整。有时候模型生成到一半被截断,返回的 JSON 只有一个开头;有时候多了一个逗号或者少了一个括号。用 json.loads() 解析直接抛异常。
第三种是模型直接不按提示词走。提示词里明明写了"只输出 JSON",但模型偶尔会在 JSON 前面加一句"根据您的要求,以下是今天的日报总结"这类废话。
v1.0 对这些情况的处理方式是:解析失败就重试一次,重试还失败就跳过日报生成,发一条报错消息到群里。这个兜底逻辑本身没有大问题,但问题在于重试的代价太高——每重试一次,就要等大模型重新生成一遍完整内容,耗时可能超过 30 秒。如果模型反复不配合,整个任务链路会被拖得很长。
v1.1 的修复方案是三层防护:第一层,在提示词里加入格式约束,并要求模型使用 few-shot 示例,极大降低非预期输出概率;第二层,解析器不再依赖原生的 json.loads(),而是先做清洗——剥离 markdown 标记、修复尾逗号、括号补全、截断内容补全——清洗失败才进入重试;第三层,重试策略从"无限重试"改为"最多重试一次 + 降级方案",降级方案是放弃结构化 JSON,直接输出纯文本日报,保证用户至少能收到内容。
3.4 多 Agent 上下文串线:状态隔离是并发安全的底线
这是整个 v1.0 里最让我头疼的 Bug。现象是:团队里有三个同事几乎同时触发了日报生成任务,结果 A 的日报里出现了 B 的工作内容,C 收到的推送消息里混着 A 和 B 的数据。最离谱的一次,一个同事的日报里出现了另一个完全不同项目的内容。
定位了很久才发现根因:v1.0 的 Agent 上下文管理是全局单例的。为了省事,所有任务的上下文都挂在一份共享的全局变量上。单线程跑没问题,但一旦多个任务并发执行,共享变量就会被互相覆盖。A 任务写入上下文后,B 任务启动,把上下文改成了自己的,A 任务再往下走一步时读到的就是 B 的数据。
这个设计在单用户、单任务的场景下是够用的,但完全没考虑并发。修复方案也很直接:把上下文从全局单例改成"任务级隔离"——每个任务创建独立的上下文实例,任务结束时销毁。同时把 Agent 的工作目录、临时文件、日志输出路径全部按任务 ID 隔离,从根上杜绝串线问题。
这个 Bug 的修复本身不复杂,但它暴露了一个更深层的问题:在 Agent 类项目里,并发安全不只是线程安全的问题,更是状态管理的问题。Agent 本质上是"有状态的执行体",状态隔离做不好,功能再丰富也是空中楼阁。
3.5 状态机死锁:异常路径没有设计兜底,卡死只能重启
最后一批次的 P0 问题——状态机死锁。这个 Bug 的复现条件是:用户在日报生成过程中手动取消了任务,然后又重新触发了一次同样的任务。第二次任务启动后,状态机发现任务已经处于"已完成"状态,拒绝继续流转,但又没有把状态重置为"待执行",于是整个任务卡死。用户再触发第三次、第四次,结果还是一样卡死。
根因是状态机的流转设计只考虑了"正常路径",没有考虑"用户主动干预"这个分支。任务被取消后,状态停留在"生成中",重新触发时状态机认为任务还在执行中,直接忽略新的触发请求。由于没有超时机制和人工重置入口,这个任务就永远卡死了。
修复方案分三部分:第一,状态机增加"Cancelled"(已取消)状态,取消操作显式地把状态置为已取消,而不是直接中断;第二,增加状态"超时重置"机制,任何状态如果持续超过 30 分钟没有变化,允许被重新触发;第三,提供管理后台的"强制重置"接口,人工干预兜底。这三层加完之后,死锁问题才算从根本上解决。
4. Agent 类项目的通用 Bug 预防策略与排查经验
4.1 无法复现的 Bug 怎么处理:先建日志追踪,再谈修复
排查这个项目的过程中,我至少遇到了三个无法稳定复现的 Bug。它们的共同特点是:用户反馈有问题,但开发环境里怎么跑都复现不了。这时候最忌讳的做法是"我看着代码猜原因,然后直接改"。正确做法是先建立可观测性,把现场信息拿到手,再谈修复。
我这里的经验是把日志体系升级了一遍。v1.0 的日志是"散装"的,各个模块各自打印各自的日志,没有统一的 request_id(请求追踪 ID),也没有结构化的日志字段。排查问题时只能靠时间戳强行关联,效率极低。
v1.1 里把日志系统整体换成了结构化日志框架,核心改动有三个:
第一,引入全局追踪 ID。每个任务从调度触发到执行完成,全程携带同一个 tracing_id,所有日志、消息推送、数据库记录都带上这个 ID。排查问题时,一条命令就能拉出整个链路的完整日志。
第二,增加关键节点的"打点"日志。任务开始、数据源拉取完成、LLM 调用开始、LLM 返回、解析完成、推送成功,这几个关键节点的耗时和状态全部记录下来。这样定位性能问题和逻辑问题都有依据。
第三,增加错误现场的"快照"功能。捕获异常时,把当时的上下文信息(任务参数、数据源状态、模型返回原文)序列化到单独的错误日志中。这样即使 Bug 无法复现,也能从快照里分析相对完整的原因。
实践心得:如果 Bug 在线上反复出现但本地无法复现,很多情况是环境差异引起的——数据量级不一样、上游接口耗时不一样、第三方服务的响应内容不一样。这种情况下,与其纠结于复现,不如把"现场数据"抓得更全。有了完整的现场数据,大多数问题都可以静态分析出来。
4.2 模型输出的不确定性,要用工程手段消化
Agent 项目和传统后端开发的另一个核心差异是:传统后端的下游接口虽然有延迟,但行为是可预期的;而 Agent 的下游是大模型,它的行为是一个概率分布,同样的输入,每次输出都可能不一样。这种不确定性直接冲击了很多传统的可靠性设计假设。
比如说"重试"。传统接口重试的前提是幂等——我调一次和调一百次,结果是一样的。但大模型不是幂等的,同样的提示词,第一次返回了合法 JSON,第二次可能就返回了废话。v1.0 里"解析失败就重试"这个策略,本质上是用"增加不确定性"去对抗"不确定性",效果当然不好。
v1.1 的工程手段是:把"控制不确定性"提前到"生成阶段"而不是"解析阶段"。具体做法是:
第一,约束输出结构。不再让模型自由输出 JSON,而是采用"分步引导"的方式:先让模型输出工作项列表(自然语言),再用专门的格式化模型把列表转成 JSON。生成和格式化分离,降低单次输出的复杂度。
第二,建立输出验证器。模型返回的内容先经过一个独立的验证器做检查——不仅是检查 JSON 格式,还要检查字段是否存在、值的范围是否合理、日期是否合法。验证不通过直接触发重生成,而不是到解析阶段才报错。
第三,设计降级链。模型生成失败时,按顺序降级:先用缓存的上一次结果 + 标注"生成失败,内容可能滞后";缓存也没有,就直接使用数据源里的原始记录,不经过模型加工;原始记录也拿不到,才发送失败通知。
这套"生成 -> 验证 -> 降级"的链路搭完之后,日报生成的成功率从 92% 提升到了 99.5%。剩下的 0.5% 是模型服务本身不可用,属于基础设施故障,靠应用层代码没法完全规避。
4.3 打卡/日报类工具的回归测试:场景卡片化
Agent 项目的测试是出了名的难做。传统单元测试可以验证函数逻辑,但 Agent 的行为涉及大模型输出、工具调用、状态流转,链路太长,一个环节不稳定就会导致整个测试挂掉。如果回归测试不能自动化,那每次发版都要手动测一遍所有场景,人力成本完全扛不住。
我 v0.9 到 v1.0 的迭代里就吃过这个亏——手动回归测试花了整整一天,测试过程中还漏掉了一个时区边界问题,上线后才被发现。v1.1 之后,我痛定思痛,建了一套"场景卡片化"的回归测试方案:
把打卡/日报 Agent 的核心使用场景拆成卡片。卡片分为三类——正常流程卡片(正常打卡、正常生成日报、正常推送)、异常流程卡片(数据源拉取失败、LLM 输出异常、推送接口超时)、边界场景卡片(跨日、跨周、节假日、极端数据量)。每张卡片里定义了输入桩数据、预期输出和关键断言。
测试执行时,用 mock 数据替代真实的大模型调用和外部接口,把链路缩短到可控的范围内。比如测"日报生成"逻辑时,不真调大模型,而是把一段预先准备好的模型输出喂给解析器,断言解析结果符合预期。这样测试就是确定性的,跑一千次结果都一样。
这套测试框架跑下来,v1.1 的回归测试从人工一天缩短到了自动化 20 分钟。最关键的是,每次改完代码,跑一遍卡片集就能知道有没有把原有功能改坏,安全感提升了一个量级。
4.4 Agent 安全加固:工具调用白名单与权限收敛
v1.0 的安全设计基本是"裸奔"状态。Agent 可以调用的工具集合虽然不大,但缺少统一的鉴权层。任何一个工具被调用时,都没有校验"这个操作是否为当前用户所允许"。如果 Agent 的会话被注入恶意指令,理论上它可以读取任意指定范围的文件内容、发送任意消息到任意群聊。
这个风险在内部工具场景下感知不强,但一旦 Agent 要对接外部系统,或者面向更多用户开放,就是高优先级的隐患。v1.1 里做了两个方面的加固:
第一,工具调用白名单机制。每个工具声明自己的入参 schema、执行权限级别、允许被哪些角色调用、是否需要在调用前二次确认。Agent 在发起工具调用时,编排层会先校验权限,没权限的直接拦截,不会真正执行。
第二,输出内容的安全过滤。Agent 生成的日报在推送前,经过一道敏感信息检测。检测规则包括但不限于:手机号、身份证号、内部密钥格式、源代码中疑似硬编码的密码。一旦命中,把对应内容打码后再推送,防的是模型"无意间"把敏感信息暴露出去。
这两层防护加上去之后,v1.0 里好几个和"越权访问"相关的隐患就算处理干净了。安全这块投入的时间不值得省,Agent 的能力越强,权限管控就越要严格。权限一旦失控,Agent 造成的破坏比一个普通程序 Bug 大得多——它毕竟是"能调用工具的执行体"。
5. 修复过程中的实施要点与阶段复盘
5.1 每个批次的修复边界:宁可拆细,不要贪大
第一批和第四批都有 P0 的 Bug,但实际开发时我把批次切得很细,每次改动控制在 3~5 个文件以内,确保每次改动都可以独立测试。v1.0 的开发里我犯过一个错误:一口气把状态机重构、上下文隔离、日志系统升级一起改完,结果出了问题无法定位是哪个改动引入的。
v1.1 吸取教训,每个批次修完立即做一轮完整的卡片回归测试。测试通过才进入下一批次。这种"小步快跑"的方式看起来慢,实际上反而快——因为 Bug 定位的成本被控制在了最小范围。如果整个清单一次性改完再测,出了问题要在一大堆改动里找原因,那才是真正的浪费时间。
5.2 关键修复步骤的现场记录:上下文隔离改造过程
拿第四批的"上下文隔离"改造举例。这个改造涉及 Agent 运行时的核心机制,容错率很低。我采用的实施步骤是:
第一步,梳理所有上下文读写的代码路径。把代码里所有用全局变量保存状态的地方全部列出来,标记出读和写的时机。这一步是为了摸清楚改动的影响面。
第二步,设计任务级上下文的数据结构。每个任务启动时,创建一个独立的上下文对象,持有任务 ID、数据源连接、缓存句柄、日志句柄。对象创建后注入到 Agent 的执行环境里,后续所有操作都从上下文对象读取状态。
第三步,改造工具调用的参数传递机制。原来工具直接从全局变量读数据,改造后工具的入参全部显式传递,避免隐式依赖。
第四步,并发压力测试。用脚本同时触发 20 个不同用户的日报生成任务,验证上下文没有串线。
整个改造花了 12 个小时,其中一半时间花在第一步和第三步上。改造完成后,并发场景下的日报内容正确率从 80% 提升到了 100%(测试样本内)。
5.3 修复日志:手写的一段时区处理关键代码
时区问题修复时,核心是把所有时间获取收敛为一个统一函数。以下是这个函数的最终实现思路,供参考:
python复制from datetime import datetime, timezone, timedelta
# 统一使用 Asia/Shanghai 时区
LOCAL_TZ = timezone(timedelta(hours=8))
def get_now():
"""获取当前上海时间的 datetime 对象"""
return datetime.now(LOCAL_TZ)
def get_today_range():
"""获取今天的起止时间,用于日报数据源的筛选"""
now = get_now()
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
end = start + timedelta(days=1)
return start, end
def parse_user_time(user_str: str) -> datetime:
"""解析用户输入的时间字符串,缺失时区时默认使用上海时间"""
dt = datetime.fromisoformat(user_str)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=LOCAL_TZ)
return dt.astimezone(LOCAL_TZ)
这个实现的要点在于:所有时间都带时区信息,所有时间比较都在同一时区下进行,数据库存储统一用带偏移的 ISO 8601 格式。跑完单元测试后,我把常见边界场景(夏令时切换、跨日、跨月、跨年)全部验证了一遍,没有再出现时间归属错误。
5.4 阶段性复盘:哪些 Bug 是设计缺陷,哪些是执行疏漏
全部批次修完之后,我重新回看了这份清单,发现一个有意思的现象:大部分 P0 问题,根因都不是"代码写错了",而是"设计阶段就没有考虑到这个场景"。
比如状态机死锁,不是 if-else 逻辑写错了,而是设计时根本没有规划"用户主动取消任务"这条路径;上下文串线,不是全局变量操作出了错,而是设计时没有考虑多任务并发;时区问题,不是时间函数写错了,而是设计时没有统一时间基准。
这说明什么问题?说明 Agent 类项目的设计阶段,不能只围绕"正常路径"做设计,要把异常路径和边界场景当成一等公民去对待。每一段自动化流程,都要问自己三个问题:用户主动中断了会怎样?数据源返回了不符合预期的数据会怎样?两个任务同时执行会怎样?这三个问题在设计阶段想清楚,后续的 Bug 修复成本能省下一大半。
6. 遗留问题与经验总结
6.1 还没解决的事项:低频问题的追踪策略
v1.1 修复清单覆盖了大部分已知问题,但有几个低频问题仍然没有根治。坦白讲,这几个问题在现阶段很难处理,因为触发条件太苛刻,复现成本太高。
第一个是特定网络环境下,大模型 API 的连接池被耗尽,导致所有任务排队等待。这个问题的根因在第三方服务的行为上,应用层能做的不多,只能靠监控告警尽快发现。
第二个是极少数情况下,Git 数据源的分页游标失效,导致某些代码提交记录被重复拉取,日报里出现重复的工作内容。这个问题已经有 workaround——对工作项按 commit hash 去重——但没有从根上解决游标失效的问题。
第三个是被我归入"行为异常"范畴的问题:大模型偶尔会在没有任何前置指令的情况下,在日报的末尾附加一段与工作无关的建议。这个问题有安全风险,但目前只是记录在案,等模型服务平台更新后再观察。
这些遗留问题我建了一个单独的低频问题追踪表,记录触发条件和观测方法。每两周检查一次统计趋势,如果某个问题在三个月内都没有复现,就降级为"关闭观察"。
6.2 写给自己和同行的几条真心建议
这个项目从 v0.1 到 v1.1 折腾了差不多四个月,踩过的坑太多,挑几条最值得说的写在这里:
第一,先做状态管理设计,再写功能逻辑。Agent 的运行天然是有状态的,状态怎么流转、状态之间怎么迁移、异常时状态怎么收敛,这些要在动手写代码之前就想清楚。状态机的设计图不值得省,后面每一个诡异的 Bug,追根溯源大概率都能回到状态管理上。
第二,不要把大模型的输出当 JSON 来信任。任何基于大模型输出的解析,后面都要跟一个防御性的清洗和验证层。大模型的输出是"文本",不是"数据",只有经过严格验证,文本才能变成可信的数据。
第三,并发问题不要靠运气解决。"我们团队人少,同时用的人不多,应该不会触发并发问题"——这个想法我在 v1.0 也有,现实给了我一记响亮的耳光。并发安全不是可选项,在 Agent 架构设计的第一天就要考虑进去。
第四,日志和可观测性是 Agent 类项目的生命线。传统后端的 Bug 可以通过本地调试快速定位,Agent 项目由于链路长、依赖外部模型服务,很多时候只能靠日志和追踪数据来推断问题。可观测性建设不是锦上添花,而是能不能把项目持续维护下去的决定性因素。
6.3 后续迭代方向
v1.1 修复完成后,项目算是进入了一个相对稳定的阶段。接下来的迭代方向我倾向于做两件事:一是把工作流编排从自研状态机迁移到更成熟的 Agent 编排框架上,减少自研组件的维护成本;二是引入更完善的模型评测体系,对不同基础模型的输出质量做抽样对比,为模型选型提供数据依据。
打卡/日报 Agent 听起来是个小工具,但真正做深了,涉及的工程问题一点都不少。希望这篇实践笔记能帮到正在做类似项目的朋友,哪怕只是让你少踩一个我已经踩过的坑,也值了。
