作者注:拆解定时运行的监控摘要 Agent 的 4 个组成部分,讲清模型调用层如何借助 OpenAI 兼容接口与具体 SDK 解耦,并给出可运行的 Python 示例。
OpenRouter 最近发布了一篇教程,用 @openrouter/agent 0.11.0 在 TypeScript 里搭了一个定时运行的常驻 AI Agent,自动监控 Hacker News 并生成摘要。这个场景很有代表性,很多团队都想要类似的东西:每天早上自动汇总行业资讯、竞品动态或技术社区热帖。本文以它为引子,拆出一套 不绑定特定 SDK 的监控摘要 Agent 通用架构。
这不是那篇教程的复述。原教程只公开了摘要信息,我们不推测它的具体实现。本文关注更通用的问题:一个监控摘要 Agent 由哪几部分组成,模型调用层怎样设计才能随时切换模型和服务商。
核心价值:读完本文,你会掌握「定时触发、数据获取、模型摘要、结果输出」四段式结构,并拿到一份基于 OpenAI 兼容接口、可直接改造的 Python 实现。

监控摘要 Agent 核心要点
先用一张表概括整篇文章的结论,后续章节逐项展开。
| 要点 | 说明 | 价值 |
|---|---|---|
| 四段式结构 | 定时触发 → 数据获取 → 模型摘要 → 结果输出 | 每一段都可以独立替换和测试 |
| 状态管理 | 记录已处理条目,避免重复摘要 | 节省调用量,输出更干净 |
| 模型调用层解耦 | 业务代码只依赖一个摘要接口,不直接依赖某家 SDK | 换模型、换服务商只改配置 |
| OpenAI 兼容协议 | 用统一的 Chat Completions 格式作为调用层底座 | 生态成熟,可接入多家模型 |
| 模型按任务选 | 摘要任务看上下文长度、指令遵循、稳定性和成本 | 避免为简单任务付出高价 |
监控摘要 Agent 的四个组成部分
所谓「常驻 Agent」,本质上是一个被定时器反复唤醒的流水线。它不需要复杂的多步推理,但对稳定性要求很高,因为没人会盯着它跑。把它拆成四个职责清晰的组件,是让它长期稳定运行的前提。
| 组成 | 职责 | 常见实现 | 设计要点 |
|---|---|---|---|
| 定时触发器 | 按固定节奏唤醒任务 | cron、systemd timer、云函数定时器、CI 定时工作流、进程内调度库 | 触发与业务逻辑分离,失败可重跑 |
| 数据获取器 | 从信息源拉取新内容 | 公开 API、RSS、网页抓取 | 设置超时、限制条数、记录已见 ID |
| 摘要器 | 调用大模型生成摘要 | Chat Completions 接口 | 输入裁剪、提示词固定、输出格式约束 |
| 输出器 | 把结果送到人能看到的地方 | 邮件、IM 机器人 Webhook、Markdown 文件、数据库 | 推送失败要有降级,例如落盘保存 |
这四个部分之间只通过简单的数据结构交互:获取器输出「条目列表」,摘要器输出「摘要文本」,输出器负责消费文本。这样设计的好处是任何一环出问题都能单独定位,比如摘要质量差就只调摘要器,推送失败就只查输出器。
很多人第一版会把所有逻辑塞进一个脚本,能跑但难维护。一旦想把 Hacker News 换成 GitHub Trending,或者把模型换成另一家,就得改动整段代码。按组件拆开之后,扩展信息源只是新增一个获取器,模型层更是只改配置。

监控摘要 Agent 的定时触发方式选择
定时触发是「常驻」二字的来源。OpenRouter 的教程把它做成了常驻进程,但在实际部署中你有多种选择,关键看你已有的基础设施。
| 触发方式 | 运行形态 | 优点 | 需要注意 |
|---|---|---|---|
| 系统 cron | 每次启动新进程,跑完退出 | 简单可靠,进程不常驻,内存不泄漏 | 状态必须落盘,环境变量需单独配置 |
| 进程内调度库 | 常驻进程,内部定时执行 | 便于共享连接和缓存 | 进程崩溃需要守护(如 systemd、容器重启策略) |
| 云函数定时器 | 无服务器按时触发 | 免运维,按调用计费 | 有执行时长上限,状态要存外部 |
| CI 定时工作流 | 在 CI 平台上按计划运行 | 零服务器成本,日志可追溯 | 调度时间可能有延迟,不适合高频 |
对于每小时或每天跑一次的摘要任务,系统 cron 加一个「跑完即退出」的脚本通常是性价比最高的方案。它天然避免了常驻进程的内存增长问题,失败后下一轮会自动重试。只有当你需要分钟级的高频监控,或者要在多轮之间共享较重的资源时,才值得上常驻进程。
监控摘要 Agent 的模型调用层如何解耦
这是本文的重点。OpenRouter 的教程使用自家的 @openrouter/agent SDK,这对快速起步很方便。但 SDK 是否支持自定义 OpenAI 兼容端点,目前公开的摘要信息里没有说明,我们也没有核实,因此本文不讨论把该 SDK 接到其他平台的做法。
我们换一个思路:不让业务代码直接依赖任何一家的 Agent SDK,而是在中间加一层很薄的「摘要接口」。业务代码只调用 summarize(items),至于背后用哪家模型、哪个端点,全部由这一层根据配置决定。
监控摘要 Agent 的调用层接口设计
调用层的设计原则可以归纳为三条,都不复杂,但能省下以后大量返工:
- 只暴露业务语义:对外提供
summarize(items) -> str,不把messages、temperature等底层参数泄漏给业务代码。 - 底层统一协议:内部使用 OpenAI 兼容的 Chat Completions 格式,
base_url、api_key、model全部从环境变量读取。 - 失败可降级:调用失败时重试有限次数,仍失败则输出原始标题列表,保证当天至少有一份可读结果。
选择 OpenAI 兼容协议作为底座,是因为它已经成为事实上的通用格式,官方 SDK 和大量第三方工具都支持修改 base_url。只要服务端兼容这套格式,切换模型就退化成改一行配置。
| 接口选择 | 切换模型的成本 | 生态与工具 | 适合场景 |
|---|---|---|---|
| 绑定某家专用 Agent SDK | 换服务商通常要改代码 | 该 SDK 内置的能力开箱即用 | 已确定长期使用该平台、看重内置功能 |
| 直接用 OpenAI 官方 SDK + 兼容端点 | 改 base_url 和 model 即可 |
生态最成熟,示例最多 | 大多数监控摘要类任务 |
| 自封装 HTTP 调用 | 改配置即可,但维护成本自担 | 依赖最少 | 对依赖体积极其敏感的环境 |

按这种设计,API易 这类聚合平台就可以作为模型调用层的一个选项。API易 兼容 OpenAI API 格式,支持 OpenAI 官方 SDK 接入,切换模型只需修改模型名称。对监控摘要 Agent 来说,这意味着你可以在同一个 Key 下对比不同模型的摘要效果,而不用为每家模型各写一套接入代码。
为监控摘要任务选择模型要考虑什么
摘要任务看起来简单,但不同信息源对模型的要求差别很大。我们目前没有针对这类任务的系统测试数据,因此不给出「哪个模型最好」的结论,只列出决策时应该权衡的维度。
| 考量因素 | 为什么重要 | 实践建议 |
|---|---|---|
| 上下文长度 | 一次要喂入几十条标题甚至正文 | 先裁剪输入,再按裁剪后的长度选模型 |
| 指令遵循 | 摘要需要固定格式,便于推送和解析 | 用固定提示词跑几轮,看格式是否稳定 |
| 语言能力 | 英文信息源要输出中文摘要 | 重点检查专业术语翻译是否准确 |
| 调用成本 | 定时任务长期运行,费用会累积 | 每轮调用一次,控制输入条数和输出长度 |
| 服务稳定性 | 无人值守,失败往往要到第二天才发现 | 配好重试与降级,并保留备用模型 |
💡 选择建议:摘要模型没有标准答案,最可靠的办法是用你真实的信息源跑几天对比。建议通过 API易 apiyi.com 用同一套代码切换几个候选模型,比较格式稳定性和中文表达,再决定长期使用哪一个。
监控摘要 Agent 快速上手
下面用 Python 实现一个最小可用版本。信息源选用 Hacker News 公开 API(hacker-news.firebaseio.com/v0),与 OpenRouter 教程的场景保持一致,方便对照理解。
极简示例:抓取并摘要
以下代码拉取 Hacker News 热门前 10 条标题,交给模型生成中文摘要,用来验证整条链路是否跑通:
import requests
import openai
client = openai.OpenAI(
api_key="YOUR_API_KEY",
base_url="https://api.apiyi.com/v1" # OpenAI 兼容端点
)
HN = "https://hacker-news.firebaseio.com/v0"
ids = requests.get(f"{HN}/topstories.json", timeout=10).json()[:10]
titles = [requests.get(f"{HN}/item/{i}.json", timeout=10).json().get("title", "") for i in ids]
resp = client.chat.completions.create(
model="gpt-4o-mini", # 示例模型名,以平台模型列表为准
messages=[{"role": "user", "content": "用中文分 3 点总结这些热帖的主题:\n" + "\n".join(titles)}]
)
print(resp.choices[0].message.content)
这段代码能跑通,但还不能「常驻」:它没有去重、没有失败处理、模型和端点也是写死的。下面的完整版把四个组件拆开,并加上状态管理。
查看完整实现代码(四组件 + 去重 + 降级)
import json
import os
import time
from pathlib import Path
import openai
import requests
HN = "https://hacker-news.firebaseio.com/v0"
STATE_FILE = Path(os.getenv("STATE_FILE", "seen_ids.json"))
TOP_N = int(os.getenv("TOP_N", "30"))
# ---------- 数据获取器 ----------
def fetch_new_stories(seen: set) -> list:
ids = requests.get(f"{HN}/topstories.json", timeout=10).json()[:TOP_N]
items = []
for i in ids:
if i in seen:
continue
item = requests.get(f"{HN}/item/{i}.json", timeout=10).json() or {}
if item.get("title"):
items.append({"id": i, "title": item["title"],
"url": item.get("url", ""), "score": item.get("score", 0)})
return items
# ---------- 状态管理 ----------
def load_seen() -> set:
return set(json.loads(STATE_FILE.read_text())) if STATE_FILE.exists() else set()
def save_seen(seen: set) -> None:
STATE_FILE.write_text(json.dumps(sorted(seen)[-2000:])) # 只保留最近的 ID
# ---------- 摘要器(模型调用层) ----------
class Summarizer:
def __init__(self):
self.client = openai.OpenAI(
api_key=os.environ["LLM_API_KEY"],
base_url=os.getenv("LLM_BASE_URL", "https://api.apiyi.com/v1"),
)
self.model = os.getenv("SUMMARY_MODEL", "gpt-4o-mini")
def summarize(self, items: list, retries: int = 2) -> str:
lines = "\n".join(f"- [{x['score']}] {x['title']}" for x in items)
prompt = ("你是技术资讯编辑。请用中文把下面的 Hacker News 新热帖归纳为 3-5 个主题,"
"每个主题一句话,并列出最值得关注的 3 条标题。\n" + lines)
for attempt in range(retries + 1):
try:
resp = self.client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
max_tokens=800,
)
return resp.choices[0].message.content
except Exception as e:
print(f"摘要失败(第 {attempt + 1} 次):{e}")
time.sleep(2 ** attempt)
return "【模型暂不可用,以下为原始标题】\n" + lines # 降级输出
# ---------- 输出器 ----------
def deliver(text: str) -> None:
webhook = os.getenv("WEBHOOK_URL")
if webhook:
try:
requests.post(webhook, json={"text": text}, timeout=10).raise_for_status()
return
except Exception as e:
print(f"推送失败,改为落盘:{e}")
Path(f"digest-{time.strftime('%Y%m%d-%H%M')}.md").write_text(text, encoding="utf-8")
# ---------- 一次运行(由定时器触发) ----------
def run_once() -> None:
seen = load_seen()
items = fetch_new_stories(seen)
if not items:
print("没有新条目,本轮跳过")
return
deliver(Summarizer().summarize(items))
save_seen(seen | {x["id"] for x in items})
if __name__ == "__main__":
run_once()
完整版有几个关键设计值得说明。首先,Summarizer 只对外暴露 summarize,模型名、端点、Key 全部来自环境变量,这正是前文说的调用层解耦。其次,状态文件只保留最近的一批 ID,避免长期运行后文件无限增长。最后,模型失败时输出原始标题,推送失败时落盘保存,保证每一轮都有可追溯的产出。
监控摘要 Agent 的定时部署
脚本设计成「跑一次就退出」,因此直接交给系统 cron 即可。下面的配置表示每天 8 点和 20 点各运行一次:
# crontab -e
0 8,20 * * * cd /opt/hn-digest && LLM_API_KEY=xxx SUMMARY_MODEL=gpt-4o-mini /usr/bin/python3 agent.py >> run.log 2>&1
如果想对比另一个模型,只需把 SUMMARY_MODEL 改成其他模型名,代码一行不动。生产环境建议把密钥放进 .env 文件或密钥管理服务,不要直接写在 crontab 里。
🚀 快速上手建议:先用极简示例确认链路通畅,再切换到完整版接入 cron。API易 apiyi.com 为新用户提供免费测试额度,一个 Key 即可在 OpenAI、Claude、Gemini 等模型之间切换,适合在原型阶段快速比较摘要效果。

常见问题 FAQ
Q1:什么是定时运行的监控摘要 Agent?
它是一个被定时器周期性唤醒的自动化程序:按计划抓取信息源的新内容,交给大模型归纳成摘要,再推送到邮件、IM 或文件中。和对话式 Agent 不同,它无人值守、长期运行,所以稳定性和去重比复杂推理更重要。
Q2:用专用 Agent SDK 和用 OpenAI 兼容接口有什么区别?
专用 SDK 往往封装了工具调用、调度等能力,起步更快,但业务代码会和该平台绑定。OpenAI 兼容接口更底层,需要自己补齐调度和去重,换来的是切换模型和服务商只改配置。对于逻辑简单的摘要任务,后者的灵活性通常更有价值。
Q3:OpenRouter 的 @openrouter/agent 教程能直接改用其他平台吗?
目前公开的摘要信息只说明该教程用 `@openrouter/agent` 0.11.0 在 TypeScript 中实现,没有说明 SDK 是否支持自定义端点,我们也未核实。如果你希望保留切换空间,更稳妥的做法是参考其场景思路,用本文这种 OpenAI 兼容的调用层自行实现。
Q4:监控摘要 Agent 适合哪些具体场景?
– **技术社区热帖日报**:如 Hacker News、技术论坛每日主题归纳
– **竞品更新追踪**:监控竞品博客或更新日志,生成变更摘要
– **开源项目动态**:汇总关注仓库的新版本和重要 Issue
– **行业资讯晨报**:聚合多个 RSS 源,每天早上推送一份中文简报
Q5:如何通过 API 快速调用模型来实现摘要?
推荐使用支持 OpenAI 兼容格式的聚合平台:
1. 访问 API易 apiyi.com 注册账号,获取 API Key 和免费测试额度
2. 把本文代码的 `base_url` 设为 `https://api.apiyi.com/v1`
3. 通过 `SUMMARY_MODEL` 环境变量切换不同模型,对比摘要效果
Q6:怎样控制长期运行的调用成本?
核心思路是只处理增量、一轮只调一次模型。用状态文件过滤已见条目,限制每轮输入条数和 `max_tokens`,并根据实际需求设定运行频率。不同模型价格差异较大,可以在 API易 apiyi.com 的模型价格页面对比后,为摘要这类轻量任务选择更经济的模型。
Q7:监控摘要 Agent 有哪些已知限制?
– **只看标题信息有限**:仅凭标题摘要可能误判内容,需要时可抓取正文,但要注意输入长度和抓取合规
– **模型可能编造细节**:提示词应要求只基于给定内容归纳,关键结论保留原始链接
– **信息源会变化**:接口格式或限流策略调整会导致抓取失败,需要监控运行日志
监控摘要 Agent 核心要点 Key Takeaways
- 四段式结构:定时触发、数据获取、模型摘要、结果输出各自独立,便于替换和排错
- 跑完即退出更稳:低频摘要任务用 cron 加一次性脚本,比常驻进程更省心
- 状态决定质量:记录已见 ID,每轮只摘要增量,避免重复和浪费
- 调用层要解耦:业务只依赖
summarize接口,模型、端点、Key 全部来自配置 - 兼容协议做底座:OpenAI 兼容格式让换模型变成改一行环境变量
- 模型靠实测选:按上下文、指令遵循、语言、成本、稳定性对比,可在 API易 apiyi.com 用同一套代码切换验证
- 失败要有降级:模型失败输出原始标题,推送失败落盘保存
总结
搭建一个定时运行的监控摘要 Agent,关键不在于用多么复杂的框架,而在于把结构拆清楚:
- 组件拆分:触发、获取、摘要、输出四个组件通过简单数据结构衔接,每一环都能单独测试
- 接口解耦:用一层薄薄的摘要接口隔离具体 SDK,底层采用 OpenAI 兼容协议
- 稳定优先:去重、重试、降级是无人值守任务的必备设计
OpenRouter 的 Hacker News 教程提供了一个很好的场景样板,而本文的通用架构让你可以把同样的思路迁移到任何信息源和任何模型上。下一步,建议先用极简示例跑通链路,再接入 cron 连续运行几天,观察摘要质量后再决定长期使用的模型。
推荐通过 API易 apiyi.com 快速验证效果,一个 API Key 即可在多个主流模型之间切换,新用户还有免费测试额度可用于原型阶段的对比测试。
📚 参考资料
- OpenRouter 教程摘要:用 Agent SDK 搭建常驻 AI Agent 监控 Hacker News
- 链接:
aihot.news/items/upl3hjjdokqe13w3ieo9opr0v -
说明:本文的引子,介绍了用
@openrouter/agent0.11.0 在 TypeScript 中搭建定时 Agent 的场景 -
Hacker News 官方 API:公开的只读数据接口
- 链接:
github.com/HackerNews/API -
说明:包含 topstories、item 等接口的字段说明,是本文示例数据源的文档
-
OpenAI Python SDK:官方客户端库
- 链接:
github.com/openai/openai-python - 说明:介绍如何通过
base_url参数接入兼容端点
作者:APIYI 技术团队
技术交流:欢迎在 API易 网站首页联系我们,更多接入资料可访问 docs.apiyi.com 文档中心
