我们用 API 流水线自动化了团队周报,踩了三个月的坑才摸出这套打法
本文最后更新于 2026-05-17,文章内容可能已经过时。
我们用 API 流水线自动化了团队周报,踩了三个月的坑才摸出这套打法
周五下午三点,我们的产品经理小陈在飞书群里翻了将近 40 分钟。
她只是想找到上周三那个需求变更是谁说的、改了什么——就这么一件事。群里有 237 条消息,夹杂着"今天谁请客""会议室订好了吗""这个 bug 先不管"……最后她放弃了,在周报里写了一行"需求细节待确认"。
这个场景在我们团队每周都在上演。
---
一、这件事到底有多痛
我们是一个 10 人的产研团队,每周五下午固定有一个"写周报"的非正式惯例。说是惯例,其实是全员停工两小时,各自翻聊天记录、整理任务进度、拼凑一份给主管看的汇报文档。
算一笔账:
| 指标 | 数字 | | 团队人数 | 10 人 | | 每人每周花在周报上的时间 | 约 2 小时 | | 全年累计消耗 | 10 × 2 × 52 = 1040 小时 | | 折算成工作日 | 约 130 个工作日 |130 个工作日,相当于半个人力,全用来整理信息、复述已经发生的事情。
更让人崩溃的是:这份周报的质量还不稳定。有人写得很详细,有人就贴几个任务标题。主管每次还得逐条核对,问"这个进度是指完成了还是进行中"。
我们决定动手做自动化,但没想到这件事比想象中难得多。
---
二、先看整体架构,再聊哪里卡壳
在讲踩坑之前,先给你一张全局地图,不然后面的细节会让人迷失。
整个流水线分五个节点:
graph LR
A[飞书消息采集] --> B[数据清洗过滤]
B --> C[结构化分类]
C --> D[LLM 润色生成]
D --> E[自动推送飞书/邮件]
每个节点用了什么:
- 飞书消息采集:飞书开放平台 Webhook + Bot API,定时拉取指定群聊的消息记录
- 数据清洗过滤:Python 脚本 + LLM Prompt,过滤噪音消息
- 结构化分类:LLM 按项目/优先级/负责人三个维度打标签,输出 JSON
- LLM 润色生成:把结构化数据转化为自然语言周报段落
- 自动推送:通过飞书 Bot 发送到指定群,同时抄送邮件
这套流水线的设计原则是每个节点都可以独立替换。你用钉钉不用飞书?换掉第一个节点就行。不想用 LLM 生成?第四步改成模板填充也能跑。这不是一个黑盒系统,而是一条可拆卸的流水线。
调度用的是 GitHub Actions,每周五下午两点自动触发,全程无人值守。
---
三、哪个节点卡了最久
这是全文最重要的部分。我们在三个节点上踩了坑,每个都让我们停下来重新设计。
节点 1:飞书消息拉取——权限比你想的复杂
失败方案: 最开始我们以为,在飞书开放平台申请一个 Bot,给它加进群,就能直接读消息了。现实是:飞书的消息读取权限分得很细。im:message:readonly 这个权限需要企业管理员审批,而且只能读取 Bot 被 @ 的消息,不能主动拉取历史消息。我们第一版代码跑起来之后,每次只能拿到几条被 @ 的记录,完全没法覆盖全周的讨论。
这个方案有一个副作用:它把"信息采集"从被动变成了主动——团队成员需要有意识地把关键信息喂给系统。一开始有人不适应,后来发现这反而让大家养成了"随手记录"的习惯,周报质量反而更高了。
节点 2:噪音过滤——Prompt 工程的真实战场
失败方案: 第一版 Prompt 是这样写的:请从以下消息中,过滤掉与工作无关的内容,只保留项目进展、决策记录和问题反馈。
结果 LLM 把"下午三点开会讨论了首页改版方案"也过滤掉了,因为它判断"开会"本身不是进展。同时,"今天的 bug 修了,感觉还不错"这种模糊表达也被保留了下来,没有任何有效信息。
最终方案: 加入 few-shot 示例,明确定义"有效信息"的边界:你的任务是判断一条飞书消息是否包含"有效工作信息"。
【有效信息的定义】
- 包含具体任务的完成、推进或阻塞状态
- 包含明确的决策或需求变更
- 包含需要跟进的问题或风险
【示例 - 保留】
输入:"首页改版的设计稿已经确认,开发可以开始切图了"
输出:{"keep": true, "reason": "包含任务状态变更"}
【示例 - 过滤】
输入:"今天谁订外卖?"
输出:{"keep": false, "reason": "与工作无关"}
【示例 - 过滤】
输入:"感觉这周压力有点大"
输出:{"keep": false, "reason": "情绪表达,无具体信息"}
现在请判断以下消息:
{message}
加了 few-shot 之后,过滤准确率明显提升,误报率(把有效信息过滤掉)从体感上的两三成降到了偶发个例。
节点 3:LLM 输出格式不稳定——JSON 崩溃是真实的痛
这是让我们最头疼的问题,也是最值得展开说的部分。
失败现象: 我们要求 LLM 输出结构化 JSON,用于后续的分类和渲染。大多数时候没问题,但每隔几次就会出现这样的输出:好的,以下是整理后的结果:
json
{
"project": "首页改版",
"status": "进行中",
"owner": "小陈"
注意到问题了吗?JSON 没有闭合,而且前面多了一段自然语言解释。这种输出直接让 json.loads() 抛出异常,整条流水线中断。
更诡异的是:这种崩溃不是必现的,有时候跑三十次都没问题,然后突然在第三十一次炸掉。
最终方案: 三层兜底机制:
1. Prompt 层:明确要求"只输出 JSON,不要任何解释文字,不要 markdown 代码块标记"
2. 解析层:用正则表达式从输出中提取第一个合法 JSON 块,即使前后有噪音也能解析
3. 重试层:解析失败时自动重试最多 3 次,第 3 次失败后降级为纯文本输出并告警
这套机制上线后,格式崩溃的情况基本消失了。偶发的解析失败也能被重试机制兜住,不会中断流水线。
---
四、关键代码片段,可以直接复用
代码片段①:飞书消息接收(Python)
python
from flask import Flask, request, jsonify
import json
app = Flask(__name__)
存储收到的消息
message_buffer = []
@app.route('/webhook', methods=['POST'])
def receive_message():
data = request.json
# 飞书的验证握手
if data.get('type') == 'url_verification':
return jsonify({'challenge': data['challenge']})
# 提取消息内容
event = data.get('event', {})
if event.get('type') == 'message':
msg = {
'sender': event['sender']['sender_id']['user_id'],
'content': event['message']['content'],
'timestamp': event['message']['create_time'],
'msg_type': event['message']['msg_type']
}
message_buffer.append(msg)
print(f"收到消息: {msg['content'][:50]}...")
return jsonify({'code': 0})
if __name__ == '__main__':
app.run(port=8080)
注意:这段代码需要你在飞书开放平台配置 Webhook 回调地址,并开启"接收消息"事件订阅。
代码片段②:清洗用 Prompt 模板(含 few-shot)
python
FILTER_PROMPT = """
你的任务是判断一条飞书消息是否包含"有效工作信息"。
【有效信息的定义】
- 包含具体任务的完成、推进或阻塞状态
- 包含明确的决策或需求变更
- 包含需要跟进的问题或风险
【输出格式】
只输出 JSON,不要任何解释:
{{"keep": true/false, "category": "进展/决策/风险/其他", "summary": "一句话摘要"}}
【示例】
输入:首页改版设计稿已确认,开发可以开始切图了
输出:{{"keep": true, "category": "进展", "summary": "首页改版设计稿确认,进入开发阶段"}}
输入:今天谁订外卖
输出:{{"keep": false, "category": "其他", "summary": ""}}
输入:登录接口有个性能问题,高并发下响应超过3秒,需要排查
输出:{{"keep": true, "category": "风险", "summary": "登录接口高并发性能问题,需排查"}}
【待判断的消息】
{message}
"""
你可以直接复制这段 Prompt,
把 [项目名] 相关的上下文加在【有效信息的定义】部分,改掉就能用
代码片段③:JSON 格式校验 + 重试逻辑
python
import json
import re
from openai import OpenAI
文中的 LLM 调用部分,我们用的是 api.884819.xyz 提供的 API 接口
它兼容 OpenAI 格式,不需要改代码结构,直接换 base_url 就能接入
这对我们快速验证流水线帮助很大
client = OpenAI(
api_key="your_api_key",
base_url="https://api.884819.xyz/v1"
)
def extract_json(text: str) -> dict | None:
"""从可能包含噪音的文本中提取第一个合法 JSON"""
# 先尝试直接解析
try:
return json.loads(text.strip())
except json.JSONDecodeError:
pass
# 用正则提取 JSON 块
pattern = r'\{[^{}]*\}'
matches = re.findall(pattern, text, re.DOTALL)
for match in matches:
try:
return json.loads(match)
except json.JSONDecodeError:
continue
return None # 解析彻底失败
def call_llm_with_retry(prompt: str, max_retries: int = 3) -> dict:
"""带重试机制的 LLM 调用"""
for attempt in range(max_retries):
response = client.chat.completions.create(
model="deepseek-r1", # 或其他模型
messages=[{"role": "user", "content": prompt}],
temperature=0.1 # 低温度,输出更稳定
)
result = extract_json(response.choices[0].message.content)
if result:
return result
print(f"第 {attempt + 1} 次解析失败,重试中...")
# 三次失败后降级处理
print("⚠️ JSON 解析失败,降级为纯文本输出")
return {"keep": True, "raw_text": response.choices[0].message.content}
```
---
五、跑了 8 周之后,真实数据和没解决的问题
效果数据
| 指标 | 改造前 | 改造后(第 8 周) | | 出稿时间 | 全员停工约 2 小时 | 自动生成约 5 分钟 | | 主管核对时间 | 约 30 分钟 | 约 10 分钟(只看摘要) | | 周报覆盖率 | 依赖个人记忆,易遗漏 | 基本覆盖群内所有有效信息 | | 格式一致性 | 每人风格不同 | 统一结构,可对比历史 |出稿时间从 2 小时压缩到 5 分钟,这个数字是真实的。但需要说明的是:这 5 分钟是机器跑的时间,人工审核还需要 10 分钟左右——我们没有去掉人工审核这一步,因为 LLM 偶尔还是会把某条消息的归属搞错。
还没解决的问题(诚实说)
1. 跨项目消息归因仍靠人工当一条消息同时涉及两个项目时(比如"A 项目的接口改动会影响 B 项目"),LLM 只会归到一个项目下,另一个项目的负责人看不到。这个问题我们目前还是靠人工审核时手动调整。
2. 突发性故障偶发飞书 Webhook 偶尔会有延迟,导致某些消息在采集窗口结束后才到达,被漏掉。我们加了一个 30 分钟的缓冲窗口,但还不是完美方案。
3. 历史对比功能缺失主管有时候想看"这个问题上周提过,这周有没有推进",但我们的系统目前没有做跨周关联,每周的周报是独立的。这是下一步想做的功能。
---
自动化不是终点,它只是把你从重复劳动里解放出来,去做真正需要判断力的事。
周报自动化跑起来之后,我们节省了时间,但也发现了一个更深的问题:生成的内容"读起来没问题,用起来没用"——数据准确,格式规整,但主管看完还是不知道下周该重点关注什么。
这让我们开始思考一个新问题:怎么让 LLM 不只是"整理信息",而是真正"提炼判断"?
下一篇,我们会拆解在 Prompt 层做的三次迭代,以及一个让我们挺意外的反直觉结论。如果你也在做类似的事,或者对这个方向感兴趣,关注我们,不定期更新。
---
附:本文流水线用到的工具清单- LLM 调用:[api.884819.xyz](https://api.884819.xyz)(兼容 OpenAI SDK,国内可直连,新用户注册即送体验 token)
- 消息拉取:飞书开放平台 Webhook + Bot API
- 调度:GitHub Actions(免费额度对这个场景完全够用)
- 格式校验:Pydantic(比手写正则更健壮)
- 部署:任意能跑 Python 的服务器,或者直接用 Serverless
---
本文由8848AI原创,转载请注明出处。关注8848AI,带你从零开始学AI。#AI自动化 #飞书 #周报自动化 #LLM应用 #Python #Prompt工程 #8848AI #效率工具