
每周五下午团队群里开始催周报的时候我基本都会对着屏幕发呆几分钟。写代码、开会、修bug忙了一周真要回忆这周干了啥脑子里那些片段全混在一起。翻Git记录、翻聊天记录、翻任务面板最后憋出一段自己都不想看的流水账。后来我花几个晚上搭了一套基于AI Agent的周报自动化工作流把收集、清洗、生成、推送全部串起来现在周五只需要花几分钟审一遍生成结果剩下的时间都省出来了。这篇博文就是我当时完整的搭建实录代码全部贴出来。我先把结论放在前面周报这份工作本质上是数据整理不是写作。一旦你接受这个定位就会发现它非常适合用AI Agent来跑。这篇文章适合被周报折腾的人、想统一团队周报格式的leader、以及想在真实业务场景里落地AI Agent但不想一上来就套重型框架的开发同学。下面是我从零开始的完整过程。1. 周报自动化的真正痛点不是缺文采而是缺数据整理1.1 周五下午三点的“黑暗时刻”说句实话写周报这件事本身没什么技术含量但每个周五下午它总能准时让人烦躁。我观察过一圈身边的同事大家周五的流程惊人的一致先翻一遍Git提交记录再去聊天软件里搜“这周干了啥”运气好能翻到自己前两天留下的零散笔记运气不好就全靠硬想。整个过程通常要花30到40分钟写出来的内容不是流水账就是空泛的自我表扬更难受的是你辛辛苦苦写完了leader可能只花30秒扫一眼里面的重点还得他费劲帮你找。后来我琢磨了一下周报本质上是把一个周期内散落在不同地方的信息提炼成几个有逻辑的模块。现实中这些输入源非常多样Git提交信息、任务平台的状态、会议纪要、临时记录的Notes甚至还有聊天记录里的关键结论。如果靠人工回忆漏项是必然的如果靠纯脚本把数据拼成一段话又没有任何可读性。我当时的判断是这件事最好由AI Agent来做——它既能自动化收集数据又能用大模型把数据组织成结构清晰、读起来像正常人写的周报。1.2 为什么这件事要交给AI Agent而不是模板可能有人会问市面上的周报模板工具已经很多了为什么非要扯上AI Agent。我的回答有两个。第一周报的输入源是分散且不规则的。Git提交信息里中英文混杂任务平台的状态不一定和实际同步Notes里可能只有一两句含糊的感想。模板只能让你填一个又一个空但没有办法替你去理解、归类、提炼这些非标准化数据。AI Agent能做这件事因为它背后有大模型的理解能力能够根据上下文把零散信息组织成“本周完成、进行中、风险问题、下周计划”这类结构。第二AI Agent的价值是跑完一个闭环而不仅仅是提供一个聪明的Prompt。打个比方Prompt像点菜Agent是后厨帮你买菜、洗菜、切菜、炒菜。你只需要周五设置好定时任务它自己去收集数据、生成周报、推送到群你要做的只是最后看一眼。这也是我把方案从“一段Prompt”升级成“一个工作流”的根本原因。2. 整体工作流设计先画流程再写代码2.1 拆解周报工作流的四个环节动手写代码之前我习惯先用纸把流程画出来。这个习惯是以前做数据平台时学到的任何自动化任务先别急着写逻辑把数据从哪里来、到哪里去、中间经过哪些处理画清楚后面能少踩一半坑。我的周报自动化工作流拆成四个环节数据收集从Git提交记录、Jira或Trello等任务平台、零散笔记里抓取一周的信息。数据清洗过滤掉merge commit、无意义的chore、不属于自己的提交把原始数据整理成统一格式。AI生成将清洗后的数据组装成上下文交给大模型生成一份结构化的周报。推送与归档把最终结果推到飞书、钉钉或邮件顺手存一份到本地目录或知识库。为什么要按这四个环节拆核心原因是“可替换”。今天你用的是Git提交数据明天想把“本周上线清单”也加进来只需要在数据收集环节增加一个函数今天推送飞书明天公司换了钉钉只需要改最后一步。每个环节之间的接口是明确的——收集环节输出JSON清洗环节输出干净的JSON生成环节输出Markdown文本推送环节拿到Markdown去发送。模块化带来的好处在维护阶段会体现得特别明显因为它降低了“改一处崩全局”的概率。2.2 自研脚本还是可视化平台一次真实的技术选型对比画完流程之后我面临一个很实际的问题这套流程用Python脚本实现还是用Dify、Coze扣子这类可视化工作流平台或者用n8n这种集成工具我把三种方案摆在一起做了个对比。方案适合人群优点缺点Python LLM SDK有编程基础的开发者灵活、可控、能处理脏数据需要自己写采集和清洗逻辑维护成本高Dify / Coze 工作流产品经理、运营、非程序员可视化配置、上手快、内置常用节点复杂清洗逻辑受限数据源集成要另外配n8n 自定义节点偏后端、已有系统多集成范围广适合跨系统串联对数据清洗这类“重逻辑”场景可视化拖拽反而别扭我当时的选择是主体用Python LLM SDK自研同时用Dify搭了一个轻量版给不写代码的同事用。为什么这么选因为Git提交记录是典型的“脏数据”重灾区——同一个人换邮箱会被识别成两个人Chore提交混在功能提交里还有大量依赖升级的噪音。这种清洗逻辑用可视化节点做起来非常别扭但在Python里就是几行正则和函数的事。不过后来我也发现如果数据源本身就比较规整比如团队统一用Jira并且状态更新及时那确实没必要上代码Dify一个LLM节点加几个前置节点就够了。所以“哪个方案最好”的判断标准其实是“你手里的数据干不干净”。数据越规范越适合可视化平台数据越乱越需要代码来做深度清洗。2.3 为什么是Agent而不是一个Prompt模板再说说“Agent”这个定语。我见过很多号称“AI写周报”的方案本质上就是一个Prompt模板加一段输入文本让模型总结一下。这种方案不是不能用但它有两个致命问题。第一它不会替你去取数据。你得自己把Commit记录和任务列表复制粘贴进去一旦数据源多了操作反而更繁琐。第二它对脏数据没有免疫力。输入里如果混了同事的名字、无意义的merge提交输出必定跟着乱而且你很难控制模型生成的边界。而我说的Agent是一个能“自动执行流程”的程序按计划收集数据、过滤数据、理解数据、生成文本、推送结果。每一个环节都在代码里显式定义出现问题时可以单独定位。严格讲我这版并不是带自主规划循环的强Agent更像一个轻量级Agent框架但它的结构已经是Agent的雏形——有感知数据采集、有决策Prompt中的判断规则、有行动推送和存档。这个定位挺重要的。因为很多人一听到Agent就想到ReAct循环、工具调用、记忆机制。如果你做周报这种规则明确的任务完全不需要绕一圈。先跑通一个轻量闭环比设计一个所谓的“华丽智能体”要实用得多。3. 完整代码五分钟看懂核心实现3.1 项目目录和核心配置我建议你在自己的机器上建一个独立目录把下面的代码放进去跑。我实际使用的项目结构很简单四个文件足以跑通整个流程weekly_report_agent/ ├── config.yaml # 个人配置仓库路径、通知地址等 ├── main.py # 主程序按流程依次执行 ├── requirements.txt # openai, requests, pyyaml └── output/ └── 2025-W02.md # 自动生成的周报markdown存档依赖只有三个库openai、requests、pyyaml。环境变量里需要配好你的LLM API Key具体变量名取决于你用的是什么服务。如果你接的是兼容OpenAI协议的服务改一下配置里的base_url和model就行。下面是config.yaml的一个示例。这个文件把所有需要频繁改动的内容都收拢在一起避免频繁改代码。llm: provider: openai model: gpt-4o-mini base_url: null # 如果你接的是兼容OpenAI协议的服务在这里填地址 temperature: 0.3 max_tokens: 2000 collectors: git: repo_path: /path/to/your/project since_days: 7 my_emails: - yournameexample.com task: enabled: false source: jira url: https://your-jira.example.com project_key: YOUR_PROJECT notifier: channel: feishu_webhook webhook_url: https://open.feishu.cn/open-apis/bot/v2/hook/your-bot-token output_dir: ./output提示所有密钥都通过环境变量或配置文件传入代码里不要硬编码任何API Key和token。一旦你把脚本交到别人手上硬编码的密钥就是安全隐患。3.2 数据收集从Git拉取本周提交写数据收集逻辑时我没有用GitPython而是直接调用Git命令。原因很简单这个脚本目标是轻量、零重依赖Git命令本身已经足够稳定和通用没必要为一个周报脚本引入额外依赖。实际项目里如果你已经在用GitPython换掉也完全没问题这里只是给一个最省心的路径。下面是收集Git提交记录的完整函数。我会把提交哈希、作者名、作者邮箱、提交日期、提交信息全部取出来后面清洗时用得上。import subprocess from datetime import datetime, timedelta def collect_git_commits(repo_path: str, since_days: int) - list[dict]: since (datetime.now() - timedelta(dayssince_days)).strftime(%Y-%m-%d) cmd [ git, -C, repo_path, log, --since, since, --prettyformat:%H|%an|%ae|%ad|%s, --dateformat:%Y-%m-%d %H:%M ] result subprocess.run(cmd, capture_outputTrue, textTrue, checkTrue) commits [] for line in result.stdout.strip().splitlines(): if not line: continue hash_, author_name, author_email, date, message line.split(|, 4) commits.append({ hash: hash_[:8], author_name: author_name, author_email: author_email, date: date, message: message }) return commits注意我在--pretty里额外加了%ae这个字段也就是提交者的邮箱。很多人在这一步不提取邮箱后面清洗时就会遇到“同一个人的不同邮箱被当成两个人”的问题。这个坑在第五部分我会详细讲。3.3 数据清洗把脏数据变成干净JSON收集到的原始数据不能直接丢给大模型否则模型会一本正经地把你同事的提交、依赖升级的commit、以及merge信息全部写进你的周报。清洗这一步我做了三件事只保留自己的提交过滤明显的噪音提交对过长的英文message做截断处理。NOISE_KEYWORDS [merge, merge branch, chore(release), bump version, update lockfile, generated by] def clean_commits(commits: list[dict], my_emails: list[str]) - list[dict]: emails set(my_emails) filtered [] for c in commits: if c[author_email] not in emails: continue lower_msg c[message].lower() if any(k in lower_msg for k in NOISE_KEYWORDS): continue if len(c[message]) 80: c[message] c[message][:80] ... filtered.append(c) return sorted(filtered, keylambda x: x[date])这版clean_commits虽然不复杂但解决了80%的脏数据问题。如果你还想更精细可以再按任务维度分组、给commit打标签但那已经属于锦上添花了先把基本盘稳住。这里我要多说一句过滤merge提交时不要只匹配小写merge因为很多提交信息是“Merge branch master into dev”这种大小写混合的我上面用的是in lower_msg直接对整句话做小写判断更稳。3.4 上下文组装给Agent一份“看得懂”的输入清洗完的数据现在可以组装上下文了。组装的关键是“结构化”——不要把所有提交信息拼成一大段人话而是以JSON格式交给模型。大模型收到数组和字段名的时候对数据的理解远比你把它翻译成一段散文要准。我在组装上下文之前还会做一次脱敏把疑似IP和长token串替换成占位符目的是防止内部信息被带出去。有人可能觉得多此一举但周报是会被转发到各种群里的小心驶得万年船。import re def redact(text: str) - str: text re.sub(r\b(?:\d{1,3}\.){3}\d{1,3}\b, [IP], text) text re.sub(r[A-Za-z0-9_\-]{24,}, [TOKEN], text) return text def build_context(commits: list[dict], tasks_done: list[str], tasks_todo: list[str], notes: list[str]) - str: ctx { commits: [redact(c[message]) for c in commits], tasks_done: tasks_done, tasks_todo: tasks_todo, notes: [redact(n) for n in notes], } return json.dumps(ctx, ensure_asciiFalse, indent2)这条redact函数是我后期才加上的。第一次跑通时没做脱敏生成结果里差点把内网IP发进群后怕了很久。如果你的工程里涉及数据库连接串、内部域名建议把这套脱敏规则扩展得更严一点。3.5 核心生成逻辑调用LLM并约束输出格式到核心步骤了。我直接调用LLM接口传入一个经过三轮迭代才稳定下来的系统提示词。这个提示词很关键它决定了生成内容的质量边界。SYSTEM_PROMPT 你是一名严谨的软件工程师正在编写个人周报。 要求 1. 周报分四段本周完成、进行中、风险与问题、下周计划。 2. 每个任务用一句话概括务必写出进展或结果。 3. 只能使用用户提供的上下文信息不得编造数据或任务。 4. 语气简洁、书面避免“非常努力”“积极推动”“赋能”等空话套话。 5. 如果没有执行中的任务可以省略“进行中”段落。 输出为Markdown格式。 def generate_report(context: str, llm_config: dict) - str: client OpenAI() resp client.chat.completions.create( modelllm_config.get(model, gpt-4o-mini), temperaturellm_config.get(temperature, 0.3), max_tokensllm_config.get(max_tokens, 2000), messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: f本周上下文数据如下\n{context}\n\n请生成周报。} ] ) return resp.choices[0].message.content如果你接的是兼容OpenAI协议的服务只需要在创建OpenAI客户端时传base_url参数。其余接口结构都一样不需要绑定某一家。“能跑通”比“选型完美”重要这是我个人的体会。3.6 推送与存档发飞书Webhook并保存Markdown周报生成后如果只是打印到终端那就没达到“自动闭环”的目的。我自己用的是飞书自定义机器人Webhook简单直接普通文本消息就够了。钉钉、企业微信的Webhook格式大同小异照官方文档改一下payload字段即可。def send_feishu_webhook(url: str, content: str) - None: payload { msg_type: text, content: {text: content} } resp requests.post(url, jsonpayload, timeout10) resp.raise_for_status() def save_report(report: str, output_dir: str, filename: str) - str: from pathlib import Path Path(output_dir).mkdir(parentsTrue, exist_okTrue) path Path(output_dir) / filename path.write_text(report, encodingutf-8) return str(path)主程序再把这几步串起来整体形式就是一个典型的流程编排def main(): cfg load_config(config.yaml) commits collect_git_commits( cfg[collectors][git][repo_path], cfg[collectors][git].get(since_days, 7), ) commits clean_commits(commits, cfg[collectors][git][my_emails]) context build_context(commits, tasks_done[], tasks_todo[], notes[]) report generate_report(context, cfg[llm]) save_report(report, cfg[output_dir], f{datetime.now():%Y-W%W}.md) send_feishu_webhook(cfg[notifier][webhook_url], report)tasks_done和tasks_todo在示例里是空的。如果你的任务平台有API就自己去写一个collect_tasks()函数把任务状态转成列表传进来。整体结构是开放的不限定数据源。4. 关键参数与Prompt工程让输出变成“像人写的”4.1 我的三版Prompt迭代过程直接给一版能用的Prompt不算本事我更想把踩坑过程写出来。你会发现看起来差不多的Prompt输出质量能差出一条街。第一版我写的是“请根据以下数据生成周报”。结果输出非常空洞模型把每条commit差不多复述了一遍最后加一句“本周工作较为饱满下周继续努力”AI味重到根本不敢发出去。第二版我加上了“分四段本周完成、进行中、风险与问题、下周计划”的结构要求。效果好了很多至少能看了但还是偶尔出现“存在一定风险需要持续关注”这种正确的废话。第三版我加入了“只能使用用户提供的上下文信息不得编造”和“避免空话套话”同时明确“每个任务用一句话概括写出进展或结果”。这一版基本就稳定了输出风格很像一个正常工程师写的周报。总的来说Prompt迭代的顺序应该是先定结构再控事实最后磨语气。你要是反着来先追求语言优美结果结构一团糟最后又要推倒重来。4.2 temperature、max_tokens到底怎么调这两个参数是影响输出的最直接因素。temperature控制随机性。周报是事实型文本我建议设置在0.2到0.4之间。我实测0.3最适合既不会像机器一样干巴巴也不会放飞自我。如果你设置在0.7以上很容易出现“把并发压测写成了性能优化方案”这种夸大其词的句子因为模型在创作而不是陈述。max_tokens要估算周报长度。一般周报800到1500字就够用2000个token已经覆盖英文和中文混合场景。设太大会浪费等待时间设太小会截断Markdown表格两头不讨好。如果你每周任务特别多再适当加。还有一个容易被忽略的参数是frequency_penalty。如果你发现自己生成的周报每段开头总是“本周”“该项目”之类的词重复可以适当调高frequency_penalty比如0.3到0.5让输出用语更多样一点。4.3 上下文太长时的截断策略如果你一周的提交特别多上下文会越攒越长token消费和响应速度都跟着上来。大模型对长上下文虽然能处理但周报不需要模型读6000个词的commit日志。我的策略很简单清洗完数据后对列表做“按时间倒序截断”优先保留最近的、最关键的提交。你也可以增加filter_by_path之类的过滤函数只保留src目录下的改动把文档、配置类的噪音挡在外面。def truncate_messages(messages: list[str], max_len: int 3000) - list[str]: total 0 result [] for m in reversed(messages): total len(m) if total max_len: break result.append(m) return list(reversed(result))这个截断逻辑虽然粗暴但非常实用。只要你能接受“更早的提交细节可能丢失”这个策略就能帮你稳定控制成本。5. 实战问题与排查技巧跑了半年的经验和坑5.1 同一人多个Git邮箱周报被撕成两半第一次跑通全流程后我得意地把周报发给leader结果发现“自己”的提交只显示了一半。排查后才发现我的Git全局配置和项目级配置用了不同的邮箱Git把它们记成了两个作者。更要命的是有一个同事的提交也混进来了因为他在某些自动化任务里用了另一个邮箱后缀。解决方案就是我在清洗函数里已经做的事维护一个my_emails列表把所有属于你的邮箱都列进去清洗时按邮箱过滤不要按作者名过滤。作者名是可以改的但邮箱在绝大多数情况下是稳定的用邮箱做过滤更可靠。5.2 周报里出现了我没做过的事模型的“善解人意”有一次生成的周报里写着“完成支付模块重构性能提升30%”我看了一愣我一周做的明明只是修了一个支付回调的bug哪来的重构后来把上下文拉出来查发现那个commit信息写得太像一次重构模型顺着就展开了想象。这个问题的根源不在模型而在输入数据的表述。我的解决办法是双管齐下在Prompt里强调“不得编造数据”同时在数据清洗阶段增加过滤把“refactor”“optimize”这类容易被模型放大的提交信息原样保留不去做过多的解释。其实最有效的是“人工审核兜底”每周推送前我会花一分钟扫一遍发现问题立刻改。Agent能帮你省时间但不能完全替你做判断。5.3 定时任务跑挂了但是没人知道给脚本加crontab之后我一度非常放心直到第二周才发现webhook推送失败了两天。原因是crontab的运行环境是一个最小PATHpython3和git都可能找不到而且脚本遇到异常会直接退出不给我留任何日志。解决方式用绝对路径指定可执行文件例如/usr/bin/python3在脚本入口包一层 try/except把异常写入日志文件crontab里重定向标准输出和标准错误下面是我实际在用的定时任务配置0 18 * * 5 /usr/bin/python3 /home/yourname/weekly_report_agent/main.py /var/log/weekly_report_agent.log 21还有一个容易被忽略的细节不要把脚本放在会被Git管理的目录下面否则脚本自己的输出文件每次都会成为commit对象下次数据清洗时还得想方设法把自己排除掉。我在第二周就踩了这个坑后来立刻把脚本和项目仓库分开了。5.4 脱敏不彻底内部信息差点被发进群周报送进群之前我把脱敏函数加进了上下文组装环节。但第一版脱敏只过滤了IPv4地址没过滤内网域名和长token串。有一次生成结果里出现了一个疑似数据库连接串长度很长、包含下划线和等号差点被推送到群里。后来我把脱敏规则扩展成三段IP地址、长度超过24位的连续字符、以及形如http://内网主机名/...的URL。虽然这会误伤一些正常的内部项目名但周报这种场景牺牲一点可读性换安全是值得的。尤其是团队群里有外部协作者时这类问题一旦发生就是事故。6. 方案扩展与我的真实体会6.1 从代码方案迁移到Dify、Coze的轻量版我前面提到给不写代码的同事搭了一个Dify版。如果你也不想维护代码可以参考这个思路在Dify里创建一个“文本生成”类型的工作流输入节点接收原始数据中间接几个清洗节点最后用LLM节点按我上面的系统提示词生成周报。Coze扣子也类似把数据源节点和LLM节点连线就行。推荐做法是代码方案作为自己的主力跑因为数据清洗灵活团队成员就用低代码版因为大家要的是“把内容贴进去就出结果”不在乎过程。如果你连低代码平台都不想配还有一个更轻的方案把数据导成CSV直接丢给我上面这个Python脚本一样能生成。6.2 我为什么不建议完全去掉人工审核看到这里你可能觉得既然Agent已经能把周报生成得这么流畅为什么还要人工审核我的回答是周报是为了给决策者看不是写给档案馆存档的。自动生成的内容可以做到结构正确、语言通顺但Agent不知道leader最近在关心什么。有些本周踩到的坑、下周的潜在风险、某个客户的关键反馈藏在commit信息和任务状态之外Agent看不到。所以我一直把这套方案定位成“半自动”Agent负责把80%的重复劳动做掉我只需要花几分钟补齐那20%的“人味”。这也是我认为最健康的人机协作姿势——不是让机器完全替代人而是让它把人从低价值重复劳动里解放出来让你有精力去做只有人能做的判断。6.3 跑顺之后可以继续扩展的方向这套流程真正跑顺之后往后面加东西是比较容易的因为核心的四个环节都解耦了。比如增加知识库沉淀每次生成的周报自动归档到Notion或本地数据库按月搜索方便复盘。再比如增加趋势分析让Agent对比最近四周的周报自动统计任务完成率、延期项为排期提供参考。还可以接入更多数据源从飞书云文档、多维表格、语雀文档里读取任务信息动态构建上下文。只要数据收集、清洗、生成、推送这四个环节的接口保持不变你想加多少扩展都很容易。最后再分享一个小技巧这套流程跑顺之后你会在周五之前主动把数据源维护好因为你知道提前维护能生成一份更准确的周报。周报自动化这件事表面上是在减少写周报的时间实际上是在倒逼你把工作数据沉淀成结构化的东西。对我个人来说这个收益远大于那每周省下来的半小时。