这篇能做出什么
做完之后,你会有一个能在本地跑的脚本:周五下班前跑一次,它自动读取你这周的 Git 提交记录和你每天随手写的流水账,交给大模型整理成一份结构化的中文周报,输出成 Markdown 文件。内容包含"本周进展 / 遇到的问题 / 下周计划"三段,进展按项目分组,每条以动词开头。月报则在周报基础上再汇总一次,不用你重新回忆一个月干了什么。
整个方案只有一个脚本文件加一个提示词,依赖极少,换成任何提供 OpenAI 兼容接口的模型服务都能用。
前置条件清单
在动手之前,确认这几件事:
1. Python 环境。Python 3 即可,安装方式和当前版本以 Python 官方文档为准。不需要装第三方库,全部用标准库实现。
2. 一个模型 API。任何提供 chat/completions 风格接口的服务都可以,需要拿到 API Key、接口地址(Base URL)和模型名称。具体型号和计费方式以服务方官方页面为准。
3. 工作记录的两类来源。一类是能自动抓的,比如 Git 仓库;另一类是手写的,比如每天的待办笔记。只有前者的话,模型只能看到"改了哪个文件",写不出"为什么改",所以手写记录这一步不建议省。
4. 基本的命令行能力。会 cd、会执行 python xxx.py 就够了。
整体思路
流程拆成三段,每段职责单一,出问题好定位:
- 采集:把分散的来源(Git、笔记)拼成一份
raw.md原始素材。 - 生成:把
raw.md塞进提示词模板,调用模型,得到周报正文。 - 落地:输出到文件,必要时再人工过一遍。
分开的好处是,当周报质量不理想时,你能立刻判断是"素材不够"还是"提示词不好",而不是面对一团黑箱。
第一步:约定一个手写记录的格式
自动化能覆盖的部分有限,日常的沟通、评审、临时支援这些事,Git 里看不到。建议每天花一分钟写一行流水账,放在一个目录里,文件名用日期,内容用列表:
```markdown
2025-01-06
- 把登录接口的超时改成可配置,线上偶发的 504 明显少了
- 和产品确认导出功能的字段范围,先做 5 列
- 帮运维排查了测试环境的磁盘告警,是日志没轮转
2025-01-07
- 导出功能接口写完,本地自测通过
- 上午开会讨论下季度排期,我们组先做数据看板
```
文件名 2025-01-06.md,放在 notes/ 目录下。格式越稳定,后面越好处理。不需要写得多好看,"做了什么 + 结果如何"就够。
如果所在环境不方便用 Markdown,用纯文本、每行一条也行,脚本里改一下读取逻辑即可。
第二步:写采集脚本
新建 collect.py:
```python
#!/usr/bin/env python3
"""采集最近 N 天的工作素材,输出 raw.md"""
import subprocess
import datetime
import pathlib
REPO = pathlib.Path(".") # 改成你的 Git 仓库路径
NOTES = pathlib.Path("notes") # 流水账目录
DAYS = 7 # 采集窗口,周报用 7,月报用 31
def git_log(days):
since = (datetime.date.today() - datetime.timedelta(days=days)).isoformat()
cmd = [
"git", "log",
"--since=" + since,
"--no-merges",
"--date=short",
"--pretty=format:%ad %s",
]
try:
r = subprocess.run(cmd, cwd=REPO, capture_output=True, text=True, timeout=60)
except Exception as exc:
return "(读取 git 记录失败:%s)" % exc
if r.returncode != 0:
return "(git 命令返回错误:%s)" % r.stderr.strip()
return r.stdout.strip() or "(窗口内没有提交记录)"
def notes_text(days):
cutoff = datetime.date.today() - datetime.timedelta(days=days)
blocks = []
for p in sorted(NOTES.glob("*.md")):
try:
day = datetime.date.fromisoformat(p.stem)
except ValueError:
continue # 文件名不是日期的文件直接跳过
if day >= cutoff:
body = p.read_text(encoding="utf-8").strip()
blocks.append("### " + p.stem + "\n" + body)
return "\n\n".join(blocks) or "(没有找到手写记录)"
def main():
text = "\n\n".join([
"# 原始素材(最近 %d 天)" % DAYS,
"## Git 提交",
git_log(DAYS),
"## 手写工作记录",
notes_text(DAYS),
])
pathlib.Path("raw.md").write_text(text, encoding="utf-8")
print(text)
if __name__ == "__main__":
main()
```
几个细节说明:
--since按提交日期过滤,配合--no-merges可以少掉合并提交带来的噪音。%s是提交标题,也可以换成%s%n%b把正文一并带出来,但素材会变长。- 如果只统计自己的提交,加一个
--author=你的名字或邮箱。 - 日期解析失败的文件名会被静默跳过,避免混进无关文件。
先手动跑一次,看 raw.md 内容对不对,再往下走。
第三步:写生成脚本
新建 generate.py,用标准库发请求,不装额外依赖:
```python
#!/usr/bin/env python3
"""读取 raw.md,调用模型生成周报"""
import os
import sys
import json
import time
import pathlib
import urllib.request
import urllib.error
def load_env(path=".env"):
"""从 .env 加载配置,适合 cron 这种拿不到 shell 变量的场景"""
p = pathlib.Path(path)
if not p.exists():
return
for line in p.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))
load_env()
API_BASE = os.environ.get("LLM_BASE_URL", "").rstrip("/")
API_KEY = os.environ.get("LLM_API_KEY", "")
MODEL = os.environ.get("LLM_MODEL", "")
if not (API_BASE and API_KEY and MODEL):
sys.exit("请先在 .env 里配置 LLM_BASE_URL / LLM_API_KEY / LLM_MODEL")
SYSTEM = "你是一名严谨的中文工作助理,只根据给定素材写周报,不补充素材之外的事实。"
PROMPT = """下面是我最近一段时间的工作素材,包含 Git 提交和手写记录。
请整理成一份中文周报,要求:
1. 分为三段:本周进展、遇到的问题、下周计划。
2. "本周进展"按项目或模块分组,每条以动词开头,写清"做了什么、带来什么结果"。
3. 只使用素材中出现的事实,不要编造数字、人名、客户名、日期。
4. 素材里信息不足以判断结果的条目,在该条末尾标注"(待补充)"。
5. 语言平实,不用"赋能""闭环""抓手"这类空话。
6. 直接输出 Markdown 正文,不要写"以下是为您整理的周报"之类的开场白。
素材如下:
---
%s
---
"""
def call_llm(prompt, retries=4):
body = json.dumps({
"model": MODEL,
"messages": [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": prompt},
],
"temperature": 0.3,
}).encode("utf-8")
req = urllib.request.Request(
API_BASE + "/chat/completions",
data=body,
headers={
"Content-Type": "application/json",
"Authorization": "Bearer " + API_KEY,
},
)
for i in range(retries):
try:
with urllib.request.urlopen(req, timeout=180) as resp:
data = json.loads(resp.read().decode("utf-8"))
return data["choices"][0]["message"]["content"].strip()
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", "ignore")[:500]
if exc.code in (429, 500, 502, 503) and i < retries - 1:
time.sleep(2 ** i * 2) # 简单的指数退避
continue
raise SystemExit("接口返回 %s:%s" % (exc.code, detail))
except urllib.error.URLError as exc:
if i < retries - 1:
time.sleep(2 ** i * 2)
continue
raise SystemExit("网络错误:%s" % exc)
def main():
raw = pathlib.Path("raw.md").read_text(encoding="utf-8")
report = call_llm(PROMPT % raw)
pathlib.Path("weekly.md").write_text(report, encoding="utf-8")
print(report)
if __name__ == "__main__":
main()
```
同目录新建 .env(记得加进 .gitignore,不要提交):
```ini
LLM_BASE_URL=https://你的服务地址/v1
LLM_API_KEY=你的密钥
LLM_MODEL=你的模型名
```
然后依次执行:
```bash
python collect.py
python generate.py
```
打开 weekly.md 看结果。第一版通常会有几个常见问题,见后面的排错部分。
第四步:做成一条命令
把两步串起来,写个 run.sh:
```bash
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"
python3 collect.py > /dev/null
python3 generate.py
```
在 Linux 或 macOS 上,用 cron 每周五下午六点自动跑:
```cron
0 18 * * 5 /bin/bash /home/me/weekly/run.sh >> /home/me/weekly/cron.log 2>&1
```
Windows 上可以用任务计划程序,或者命令行注册:
```bat
schtasks /create /tn "WeeklyReport" /tr "python C:\weekly\generate.py" /sc weekly /d FRI /st 18:00
```
macOS 也可以考虑 launchd,配置方式以 Apple 官方文档为准。
第五步:做月报
月报不需要另写一套采集逻辑,把窗口拉长、素材换成四份周报即可。最省事的做法是把本月生成的 weekly.md 按月归档到一个目录,月报脚本读这个目录,提示词改成"把这些周报归纳成月报,按主题合并同类项,突出阶段性成果与遗留问题"。主题合并是月报相对周报的主要增量:周报里分散在三周的"登录优化""超时改造""告警清理",在月报里应该合成一条"服务稳定性改善"。
常见坑与排错
接口返回 401 或 403。 多半是密钥没读到。脚本从 .env 读取就是为这个场景准备的;如果坚持用环境变量,注意 cron 不会继承你登录 shell 里的变量,需要在 crontab 里显式声明,或者干脆写进 .env。另外确认 Base URL 结尾有没有多余的斜杠,脚本里已经做了 rstrip("/"),但路径前缀要看服务方的接口文档。
接口返回 429。 触发了频率限制。脚本里已经有指数退避重试,如果仍然频繁遇到,把采集窗口调小、素材压缩,或者把任务挪到低峰时段。
周报内容空洞。 通常是素材问题,不是提示词问题。打开 raw.md 看一眼,如果 Git 提交全是"fix""update""修改",手写记录又是空白,模型只能照抄这些词。解决办法是提高素材质量:提交信息用"动作 + 对象 + 结果"的写法,手写记录里补上会议结论和沟通结果。
出现了素材里没有的数字或人名。 这是模型自由发挥。除了提示词里明确禁止,更可靠的做法是在生成后人工核对一遍所有数字和专有名词。周报是要发给同事和上级看的,事实错误比语言平淡严重得多。
时间窗口错位。 --since 基于提交时间,如果你做过 rebase 或者 cherry-pick,旧提交可能以新时间出现,导致上周的事重复进本周周报。遇到这种情况,用 --author-date-order 或者改用手写记录作为主素材,Git 记录作为参考。
素材太长超出上下文。 一个月几百条提交拼接后可能很长。可以在 collect.py 里按目录或按项目分组,每组只保留前若干条;或者先用一次模型调用对素材做压缩,再做正式生成。
输出带了开场白。 比如"好的,以下是您的周报"。提示词里已经要求直接输出正文,如果模型仍不听话,加两行代码:找到第一个 ## 的位置,截取之后的内容。
敏感信息外泄。 提交信息里可能带内网地址、客户名、密钥片段。生成前加一层替换,把关键词对照表里的词替换成代号;或者在 .env 里配置一个黑名单,脚本读到就整行剔除。对外发送前自己通读一遍,这一步不要省。
下一步建议
加一个"上周计划"的闭环。 把上一份周报里"下周计划"那一段自动提取出来,作为本周素材的一部分喂进去,让模型对照检查哪些做完了、哪些没动。这样周报就从"流水账"变成了"有交代的进展汇报"。
沉淀自己的提示词。 连续跑几周,记录每次生成后你手动改了多少。改得多的部分,说明提示词里缺少对应的约束,把那条约束补进去。这比一次性写好提示词有效。
接上分发渠道。 生成后自动发到团队群或者邮件。这一步涉及各平台的接口权限,按对应平台的官方文档来配置。
处理更敏感的内容时换本地模型。 如果素材涉及不方便外发的信息,可以在本机跑一个开源模型,把 LLM_BASE_URL 指向本地服务,脚本其余部分不用改。
把月报做成季度总结的上游。 同理,三个月的月报可以喂进去生成季度总结,链条越长,单次投入越省。
