Hermes 工程化实战专栏:第一个真实任务——「各家 AI 动态日报」端到端跑通

Hermes 工程化实战专栏:第一个真实任务——「各家 AI 动态日报」端到端跑通 钥匙到手第一件事不是让它陪聊是让它干活。我给 Hermes 派的第一个真实任务叫「各家 AI 动态日报」每天盯着六家 AI 公司的动静国内国外都算上把发布整理成一张能直接看的网页。这篇就是把这张网页从零跑出来的全程实录——收集、汇总、出网页三步端到端中间踩了三个真坑。就这三步一条线串到底。这一路会踩三个真坑两个出在数据源RSS 是全量存档OpenAI 一个源抓到 1156 条智谱和豆包是 JS 渲染的 SPA静态抓取拿不到条目。第三个出在 Hermes 自己身上——等它第一次开口汇报你自己看。载体项目各家 AI 动态日报先交代这个项目为什么值得当第一个任务。需求就三句话每天 6:00 收集国内外 AI 动态——智谱、Kimi、字节跳动三家国内Anthropic、OpenAI、Google 三家国外汇总去重同一件事只讲一遍生成一个网页本地双击就能看。本期先手动跑一轮调度每天 6:00 自动跑留到 H7跨天去重昨天报过的今天不重复留到 下一篇现在接着往下看项目 README 里端到端就是三条命令python collect.py# 多源抓取 → items.json标题/链接/来源/日期python curate.py# 调 Hermes 汇总 → digest.md今日概览 国内/国外分组python render.py# 渲染 → report.html本地浏览器打开即可查看选它当第一个任务是因为它把 Hermes 系列后面要碰的能力全串上了多源收集、汇总、记忆去重、技能沉淀、定时调度、多渠道推送。这一篇先立骨架后面每篇都是往骨架上装一块。第一步 收集先摸清数据源在哪写收集器之前先搞清楚一件事这六家 AI 公司的动态到底从哪拿动手前先探测一遍每个源发个 HTTP 请求看状态码。这是本机的探测实录200 https://www.anthropic.com/news 404 https://www.anthropic.com/feed.xml 403 https://openai.com/news/ # HTML 页被反爬但 RSS 可用 200 https://openai.com/news/rss.xml 200 https://blog.google/technology/ai/rss/ 200 https://www.kimi.com/blog/ # 月之暗面博客moonshot.cn/news 跳转至此 200 https://www.zhipuai.cn/rss.xml # 假 RSS返回 Next.js HTML SPA 200 https://www.doubao.com/news # JS 渲染静态抓取无条目探测结果跟直觉差得远四类情况OpenAI / Google有 RSS。openai.com/news这个 HTML 页 403 被反爬但openai.com/news/rss.xml是 200RSS 反而畅通。Google 同理。这是最省事的源Anthropic / Kimi只有 HTML 锚文本。Anthropic 的feed.xml404没有 RSS/news/页面的链接锚文本里带着分类日期标题Kimi 的博客也是锚文本就是标题智谱假 RSS。zhipuai.cn/rss.xml返回 200但内容是一坨 Next.js 的 HTML SPA——名字叫 rss.xml实际是个空壳页面解析不出任何条目豆包JS 渲染。doubao.com/news是 200可静态抓取拿不到条目页面内容全是浏览器里跑 JS 渲染出来的。所以能用的可靠源一共 4 个两个 RSS 两个 HTML 锚文本。智谱和豆包这两个 JS 源静态抓取这条路走不通。其实**数据源是现实世界不是文档。**文档会告诉你「该有 RSS」现实是有的源没有、有的源伪装成有、有的源要浏览器渲染。写爬虫的人一半的活是跟「源不如预期」周旋。遇到 JS 源怎么办我的做法是优雅降级先不硬刚跳过它在输出里记一笔「这个源暂时抓不到」等后面有浏览器适配器再补。任务不因一个源卡死——宁可少两个源也不要整条管线死在半路。这个结构叫「源适配器」每个源一个适配器差异收在适配器里抓取主流程不用改。JS 源在架构里也占一个位置只是本期适配器还没写——位置先留着后面补浏览器适配器时直接填进去。写收集器 collect.py把「抓得到」变成「抓得对」收集器collect.py纯标准库urllib 抓页面、xml 解析 RSS、re 提链接装了 Python 就能跑不引第三方库。核心是四组_adapter_*函数每组对应一个源_adapter_openai/_adapter_google解析 RSS 里的item_adapter_anthropic正则提/news/链接从锚文本里拆分类日期标题_adapter_kimi提kimi.com/blog/文章链接锚文本即标题。每个源抓完都产出同样形状的记录title、url、date、summary。形状统一后面汇总、渲染才好处理。title从这里就开始进文档——这是渲染器能生成标题链接的源头。源清单在SOURCES里SOURCES[{key:openai,name:OpenAI,cat:国外,type:rss,url:https://openai.com/news/rss.xml},{key:google,name:Google AI,cat:国外,type:rss,url:https://blog.google/technology/ai/rss/},{key:anthropic,name:Anthropic,cat:国外,type:html_links,url:https://www.anthropic.com/news},{key:kimi,name:Kimi(月之暗面),cat:国内,type:html_links,url:https://www.kimi.com/blog/},# 智谱 / 字节豆包 为 JS 渲染 SPA静态抓取拿不到条目列表本期跳过待浏览器适配器]每行一个源type决定走哪类适配器cat是国内还是国外url是抓哪。最后两行注释掉的智谱和豆包不是忘了——是本期适配器还没写占着位置等浏览器适配器。初版我栽了个坑就在「抓多少」这件事上。第一版写得很天真把每个源抓到的条目全部收进来。一跑OpenAI 抓到 1156 条。RSS 是全量存档不是当天的新闻流——openai.com/news/rss.xml把这几年发的所有新闻全吐出来了。要是全收今天日报里一半是三个月前的旧闻。修法是两个约束写在一个函数里RECENT_DAYS2# RSS 源只保留最近 N 天内的条目MAX_PER_SOURCE8# 每个源最多取前 N 条保证日报可读def_slice_source_items(raw:list,src_type:str)-list:每源剪裁RSS 按最近 N 天过滤HTML 源取最新 N 条列表即最新。ifsrc_typerss:cutoffdatetime.date.today()-datetime.timedelta(daysRECENT_DAYS)recent[itforitinrawifit[date]andit[date]cutoff.isoformat()]recent.sort(keylambdax:x[date],reverseTrue)returnrecent[:MAX_PER_SOURCE]returnraw[:MAX_PER_SOURCE]RSS 源按日期切最近 2 天内的才要再按日期倒序取前 8 条HTML 源列表本身就是最新在前直接取前 8 条。RECENT_DAYS 2、MAX_PER_SOURCE 8两个常量写在文件顶部一眼能调。去重放在collect()里按 URL 去重——同一条新闻多个源都报时保留先抓到的# 按 URL 去重同一条新闻多个源都报时保留先抓到的seen_url,dedupedset(),[]foritinall_items:ifit[url]inseen_url:continueseen_url.add(it[url])deduped.append(it)跑一遍命令与输出逐字取自本机取证cd/d/CF-AICoding/hermes-lab/daily-reportPYTHONIOENCODINGutf-8 .venv/Scripts/python.exe collect.py收集完成共 24 条写入 items.json - OpenAI: 抓到 1156 条取最近 7 条 - Google AI: 抓到 20 条取最近 1 条 - Anthropic: 抓到 11 条取最近 8 条 - Kimi(月之暗面): 抓到 9 条取最近 8 条 [2026-08-28] OpenAISupporting Thailands next generation of AI startu...24 条四个源按各自的规则收敛OpenAI 1156 条里留 7 条、Google 20 留 1、Anthropic 11 留 8、Kimi 9 留 8。Google 只留 1 条因为最近两天它就发了一条——过滤不是「每源灌满」是「每源只留最近」。「抓到 1156 条取最近 7 条」这句话本身就是 RSS 存档坑的体检报告。写到这第一步收工。items.json里 24 条每条带着 title/url/date/summary/source/category标题已经进文档。下一步把这些原料交给 Hermes 整理成日报正文。第二步 汇总Hermes 当汇总官第二步是把 24 条原料变成一篇人能读的日报。这步我用 Hermes。对应的是curate.py核心动作就一个调hermes chat非交互地把 24 条塞进提示词让它整理成日报正文。命令在main()里cmd[HERMES,chat,-q,prompt,--provider,PROVIDER,-m,MODEL,-Q]rsubprocess.run(cmd,capture_outputTrue,encodingutf-8,errorsreplace)-q给问题、--provider deepseek走 H1 配的 provider、-m deepseek-v4-flash指定模型、-Q安静模式——不带交互提示符适合程序化调用。这就是「脚本/定时任务调 Hermes 的标准姿势」一次性、非交互、把结果拿回来。提示词由build_prompt拼出来先给要求再给条目defbuild_prompt(data:dict)-str:lines[f今天是{data[generated_at]}。下面是我用脚本抓到的各家 AI 动态共{data[count]}条请整理成一份日报正文,要求① 按【国内】/【国外】分组② 每组里每条给一句话要点基于摘要摘要为空的按标题转述不要编造,③ 开头给一句今日概览④ 结尾给一条『值得关注』⑤ 中文输出不客套不加标题以外的废话。,条目列表,]fori,itinenumerate(data[items],1):summary(it.get(summary)or).strip()ssummaryiflen(summary)60elsesummary[:57]…lines.append(f{i}. [{it.get(source)}] ({it.get(date)or近期}){it.get(title)}——{s})return\n.join(lines)要求五条分国内/国外、每条一句话要点、开头今日概览、结尾值得关注、中文不客套。条目列表带上来源、日期、标题和摘要——摘要超过 60 字截断提示词别太长。跑一遍PYTHONIOENCODINGutf-8 .venv/Scripts/python.exe curate.py原始输出 21143 字符 → 截出正文 1108 字符返回码 0 今日概览Anthropic 集中发布 Claude Opus 5、Sonnet 5 并披露多项治理与研究计划月之暗面批量更新 K3、K2.6 等模型与评测基准OpenAI 则继续在教育、国际扩张与安全议题上推进。 【国内】- 月之暗面发布新一代模型 Kimi K3。... 【国外】- OpenAI与泰国 MHESI 联合推出为期八周的加速器。...这里先按下不表**Hermes 第一次汇总出了个意外。**21143 字符里正文只占 1108剩下全是它「想」出来的东西。怎么想的、怎么把正文捞出来下一节讲。推理模型的第一个坑输出混着 reasoning_content问题出在deepseek-v4-flash身上——它是推理模型思考模式默认开。什么是思考模式模型在给出正式答案前先自己过一遍脑子。这条链路里它的思考不是悄悄进行的——hermes chat把思考过程整个打印出来了混在正式输出里。而且它不止想了一版正文 108 行、142 行、257 行、294 行各起草一次多版草稿都留在输出里真正的日报正文藏在最后一次「今日概览」和session_id:之间。一句话21143 字符原始输出 整段英文思考 四版草稿 最后的正文。它把草稿纸也一起交上来了。这是推理模型接 harness 的第一个真实问题——也是系列第 2 篇埋的伏笔。那篇探测 DeepSeek 时响应里有个reasoning_content字段正文content反而是空的当时留了句「这个字段后面是整篇的伏笔」。现在兑现当 harness 把模型输出原样打回时推理模型会把思考过程一并吐出来程序要自己把正文从思考里捞出来。curate.py的解法在extract_report里defextract_report(out:str)-str:从 Hermes 原始输出里截出日报正文。 推理模型deepseek-v4-flash 思考模式默认开会把整段英文思考/多版草稿 一起吐出来正文在最后一段「今日概览」与 session_id 之间。 这是推理模型接 harness 的第一个真实坑输出混着 reasoning_content。 endout.find(session_id:)ifend-1:endlen(out)startout.rfind(今日概览)ifstart-1:startout.rfind(每日概览)bodyout[start:end].strip()ifstart!-1elseout[:end].strip()# 去掉草稿里穿插的英文残留如 ...one sentence...bodyre.sub(r今日概览[:]\s*\.\.\.one sentence\.\.\.,,body)returnbody两个定位点一前一后把正文夹出来rfind(今日概览)从后往前找「今日概览」拿到的是最后一次出现的位置——正文的开头find(session_id:)从前往后找session_id:那是 Hermes 会话编号——正文的结尾两者之间就是正文。为什么用rfind不用find因为「今日概览」在草稿里也出现过find会拿到第一版草稿的概览rfind才拿得到真正正文那版。多版草稿这个坑用「取最后一次」绕过去。除了截取还要剥装饰。Hermes 输出带着 ANSI 转义和┌─ Reasoning ─┐装饰框strip_ansi把它们去掉defstrip_ansi(text:str)-str:去掉 ANSI 转义与 Reasoning 装饰框只留纯文本内容。textre.sub(r\x1b\[[0-9;]*m,,text)lines[lnforlnintext.splitlines()ifnotre.match(r^[\s]*[┌└│┐┘─],ln)]return\n.join(lines).strip()截取 剥装饰21143 字符压成 1108 字符干净正文。流程画出来这步的价值不在正则写得多妙在定位了「推理模型接 harness」这个坑本身模型给的不只是答案还有它的思考过程程序要的只是答案就得学会把两者分开。后面 H3/H4/H5 只要还调推理模型这个截取逻辑都得跟着走。第三步 出网页render.py 生成静态 HTML原料齐了items.json24 条加digest.mdHermes 汇总的日报正文。第三步把它们渲染成一个网页。render.py纯标准库生成静态 HTML。两份输入一个输出items.jsondigest.md→report.html。做日报时我定了一条硬规矩**标题必须进文档。**日报不能只是「机器能读的 JSON」得是「人打开就能看」的网页——浏览器标签页要有标题、页面顶部要有可见大标题、每条新闻的标题要作为链接写进正文。这条规矩落下来就是item_links函数defitem_links(items:list)-str:按国内/国外分组渲染条目标题链接标题写进文档。groups{国内:[],国外:[]}foritinitems:groups.setdefault(it.get(category,其他),[]).append(it)blocks[]forcatin(国内,国外,其他):arrgroups.get(cat)ifnotarr:continuerows[]foritinarr:titlehtml_mod.escape(it.get(title,(无标题)))urlit.get(url,)dateit.get(date)or近期linkfa href{html_mod.escape(url)}{title}/aifurlelsetitle rows.append(flispan classdate{date}/span{link}f span classsrc{html_mod.escape(it.get(source,))}/span/li)blocks.append(fh2{cat}·{len(arr)}条/h2ul classitems{.join(rows)}/ul)return\n.join(blocks)24 条按category分成国内/国外两组每条渲染成li日期 标题链接 来源。标题从items.json的title字段来——收集器那步把title写进items.json这里把它写进 HTML。源头在这里闭合。页面结构在main()里节选html_docf!DOCTYPE htmlhtml langzh-CNheadmeta charsetutf-8meta nameviewportcontentwidthdevice-width, initial-scale1title{title}·{today}/titlestyle...bodyh1{title}/h1divclassmeta报告日期{today} 抓取时间{data.get(generated_at,)} 共{data.get(count,0)}条 来源{, .join(n.split()[0]fornindata.get(notes,[]))}/divh2今日概览Hermes 汇总/h2divclassdigest{md_to_html(digest)}/divh2全部条目/h2{item_links(data.get(items,[]))}title{title} · {today}/title是浏览器标签h1{title}/h1是页面顶部可见大标题正文先放 Hermes 汇总的今日概览再放全部条目链接。标题进文档的三个落点浏览器标签、可见 h1、每条标题链接。md_to_html是一个极简 Markdown→HTML 转换覆盖日报会用的#、**、-列表不引第三方模板。跑一遍PYTHONIOENCODINGutf-8 .venv/Scripts/python.exe render.py已生成 report.html7321 字符本地浏览器打开即可查看校验结果title各家 AI 动态日报 · 2026-08-28/title、页面顶部h1各家 AI 动态日报/h1、24 条标题链接、国内/国外分组 h2HTML 标签闭合校验无错误。7321 字符一个静态页双击report.html就能看。端到端复盘Hermes 的定位与边界三条命令串起来就是开头那张全景图collect.py → items.json → curate.py(Hermes) → digest.md → render.py → report.html本地打开就是一张日报顶部是 Hermes 写的今日概览往下是国内/国外两组 24 条标题链接。到这里先把 Hermes 的定位说清楚。这一期里它扮演的角色是汇总官——纯对话把 24 条原料整理成正文。它没有调工具抓取是 collect.py 干的渲染是 render.py 干的Hermes 只在中间做「理解并整理」这一步。结论骨架立起来了回到开头那句话第一个真实任务不是让 Hermes 陪聊是让它干活。现在它干完了一轮——24 条原料进来一张网页出去中间 Hermes 当汇总官把 24 条整理成 1108 字符的日报正文。真装真跑的价值就藏在这三个坑里。文档不会告诉你 RSS 是全量存档、不会告诉你有些源伪装成 RSS、更不会告诉你推理模型会把草稿纸一起交上来——这些只有跑一遍才知道。但这才是个开始。这张日报今天记得住明天就忘了——昨天报过的 OpenAI 那条今天可能又报一遍。这是 H3 要解决的事让 Hermes 记住昨天报过什么跨天去重。骨架立起来了后面 H3 记忆、H4 学习闭环、H5 技能固化、H6 多渠道推送、H7 每天 6:00 无人值守都是往这根骨架上装肉。你的 agent 第一个真实任务是什么卡在哪一步是跟我一样卡在「源比文档刁钻」还是卡在把模型输出整理成程序能用的样子还是压根没想好让 agent 干什么评论区聊聊~下一篇会接着聊让 agent 记住昨天的事。想看跨天去重怎么做的关注别走丢。