-
文档和价格计算器中都没找到这个Tokens用量的说明。比如基础版的2000万Tokens/月,这个2000万,是否输入和输出都要算在内呢? 是否区分缓存命中与否?
yd_211306979
发表于2026-05-31 00:52:44
2026-05-31 00:52:44
最后回复
CodeArts小助手-蚂蚁
2026-06-01 10:52:46
130 2 -
【案例共创】HelloCard 网页贺卡生成器 —— 一个第一次发帖的萌新,用华为云码道(CodeArts)2 小时造了一张能转发的"网页祝福"不是大佬,不是工程师,就是一个周末闲着没事、想做点小东西的普通人。如果你也是新手,这篇文章可能正好是你需要的"第一份能跟着抄完的实战"。这次想搞个什么?事情是这样的:🌧️ 周六下午,下雨,没事干。🎁 朋友说:“我下周生日,能不能发我个特别点的祝福?”🤔 我心想:朋友圈那种"复制粘贴长文"太敷衍了,我得搞点新意。💡 灵光一现:做一张能用网址转发的"网页贺卡" —— 朋友点开链接就是一张飘彩纸的卡片,上面写着我对 TA 说的话。但问题是:我会一点 HTML,但没做过完整的"应用"我没钱买云服务器我不会后端、不会数据库我只有 2 个小时,今晚约了饭抱着试试看的心态,我打开了华为云码道(CodeArts)AI IDE。📌 结论先说:2 小时 7 分钟后,我把链接发给朋友了。📌 朋友的反馈:第一句是"卧槽这是你做的?"📌 总成本:~3 元 Token + 几乎为零的 OBS 存储费下面是这 2 小时的全程记录,新手向,能跟着复刻。1. 先聊聊"网页贺卡"是个啥?为什么不需要后端?这一段对资深的人是常识,但对新人可能是关键认知。🧠 核心思路:把祝福内容塞进 URL(网址)本身。像这样:https://xxxxxx.obs.example.com/index.html?to=小明&from=神秘人&msg=生日快乐&theme=🎂朋友点开这个网址,浏览器就能把 ? 后面的内容读出来,拼成一张卡片显示给 TA。✨ 这意味着:❌ 不需要后端(没人需要保存数据)❌ 不需要数据库(数据就在网址里)❌ 不需要登录注册(谁拿到链接谁看)✅ 只要 1 个 index.html 文件就够了🎉 那我就有信心了 —— 我会的那点 HTML 知识,刚好够用。2. 进码道,先和它"约法三章"打开码道(华为云开发者空间里直接开网页版,免下载)。新建空项目 HelloCard。📍 这一步关键中的关键:先写 AGENTS.md,再开口说话。这是码道4-5 月新特性「新增 AGENTS.md 文件支持」—— 简单说就是把"我是谁、我喜欢什么、不要给我整啥花活"写进一个文件,AI 就会按这个规矩说话。我之前没用码道的时候,AI 一上来就推荐我用 React,让我装 Node,结果一晚上没跑起来 😭。这次先写 AGENTS.md,后面就再没翻过车。我在项目根目录新建 AGENTS.md,把"游戏规则"摆出来:# HelloCard · AGENTS.md ## 一句话规则 只要 1 个 index.html 能搞定的,绝不分文件、绝不引入框架、绝不上 npm。 ## 我是谁 - 编程菜鸟一枚,会基础 HTML/CSS,JS 还在入门 - 最讨厌"装环境装一下午" ## 子代理 @buddy(搭子) 每次回复请: - 用"哥们儿/姐们儿"般的轻松语气 - 关键代码必须给出可直接复制的完整版 - 解释一律用类比 / 大白话 ## 禁止 - ❌ React / Vue / 任何需要 build 的东西 - ❌ Node.js 后端💡 我把 AI 子代理叫做 @buddy(搭子)。一旦我 @ 它,码道就会用我设定的"搭子语气"回话,比官方的"严肃工程师"模式亲切多了。保存。码道左下角弹了一行字:✅ 已加载项目规约 AGENTS.md,子代理 @buddy 已就位。OK,开整 ✊。3. 直播实录 · 第 1 小时:从零搭出能用的雏形接下来全是我和 @buddy 的真实对话节选。〔我〕代表我说的话,〔@buddy〕代表 AI 的回复(精简版)。3.1 第一句话,不要怂〔我〕@buddy 我想做一个网页贺卡。用户在编辑页填"送给谁/我是谁/祝福语/主题",点按钮生成一个带参数的网址,把这个网址发给朋友,朋友打开就能看到一张漂亮的卡片。一个 index.html 搞定。先给我把骨架搭起来。〔@buddy〕行嘞 ✌️。咱要的核心思路是:编辑模式 + 展示模式 共用一个文件,靠 URL 有没有参数来区分。没参数 → 显示编辑表单有参数 → 显示卡片来,先抄这版骨架(含 HTML+CSS+JS 一体):(@buddy 给了大约 70 行的初版)我把代码复制到 index.html,双击打开。🟦 屏幕上出现一个粉色背景,中间一个白色卡片,里面有 4 个输入框和一个"生成专属链接"按钮。🤩 比我心里想的好看。3.2 让按钮真的能"生成网址"〔我〕我点了按钮没反应啊,应该跳到带 ?to=xxx 那个网址才对吧〔@buddy〕对,是我藏了个坑(笑)。我先让你看看 UI,逻辑得自己加。来抄这段:function buildUrl(data) { const sp = new URLSearchParams(); Object.entries(data).forEach(([k, v]) => v && sp.set(k, v)); return location.origin + location.pathname + '?' + sp.toString(); } document.getElementById('gen').addEventListener('click', () => { const to = document.getElementById('to').value.trim(); const from = document.getElementById('from').value.trim(); const msg = document.getElementById('msg').value.trim(); const theme = document.getElementById('theme').value; if (!to || !msg) { alert('收件人和祝福语不能空哦~'); return; } const url = buildUrl({ to, from, theme, msg }); location.href = url; // 跳到带参数的自己 }); 解释一下哈:URLSearchParams 是浏览器内置的"网址参数管家",你给它喂键值对,它帮你拼成 ?to=xxx&from=yyy,自动处理中文转码(不然中文会变乱码)。location.href = url 让浏览器跳转,等于跳到带参数的自己,触发咱们下一步要写的"展示模式"。复制粘贴,刷新。填了 “to=朋友A” “msg=生日快乐”,点按钮。📍 网址变了:...index.html?to=朋友A&msg=%E7%94%9F%E6%97%A5%E5%BF%AB%E4%B9%90&theme=🎂%7C生日快乐!但是页面还显示编辑框 —— 因为我还没写"展示模式"。3.3 让页面"判断"自己该显示啥〔我〕现在网址变了,但页面没变。怎么让它看到 ?to=xxx 就切换成卡片?〔@buddy〕这就是"启动判断"的活儿,加在 <script> 最开头:function readParams() { const sp = new URLSearchParams(location.search); return { to: sp.get('to') || '', from: sp.get('from') || '', theme: sp.get('theme') || '', msg: sp.get('msg') || '', }; } const params = readParams(); if (params.to && params.msg) { showCard(params); // 有完整内容 → 展示卡片 } else { bindEditor(); // 否则 → 编辑模式 } location.search 就是网址里 ? 开头的那一段。咱拿到 to 和 msg,有就切卡片,没有就让用户编辑。逻辑像不像门口的保安?看你有没有票,决定让你进剧场还是去售票处。我笑出声:这比喻好懂啊。3.4 卡片渲染 + 撒彩纸彩蛋〔我〕卡片要好看一点,最好打开还能撒彩纸 ✨〔@buddy〕嘿这才有过节的样子。来看 showCard 完整版:function showCard(p) { document.getElementById('editor').style.display = 'none'; document.getElementById('card').style.display = 'block'; const [emoji, greet] = (p.theme || '🎂|你好').split('|'); document.getElementById('cEmoji').textContent = emoji; document.getElementById('cGreet').textContent = `${escapeHtml(p.to)},${escapeHtml(greet)}`; document.getElementById('cMsg').textContent = p.msg; document.getElementById('cFrom').textContent = p.from ? `—— ${p.from}` : ''; confetti(); // 撒彩纸 } 撒彩纸用纯 CSS 动画 + JS 生成 40 个小色块就行(完整代码在文末)。零依赖、纯原生,符合咱 AGENTS.md 的死规矩。我刷新 —— 朋友 A 的卡片显示出来了,40 片彩纸从屏幕顶部哗啦啦飘下来 🎉🎊🥲 第一次"用代码做出过节感",太爽了。4. 翻车现场:朋友把我网页"黑"了得意洋洋发给朋友 B 试试。朋友 B 是个程序员,10 秒后回我一句:“你这有 XSS 漏洞 😏” + 一个新链接。我点开 —— 页面弹出一个 alert("被我黑啦") 框。😱😱😱〔我〕@buddy 朋友输入了 <script>alert("被我黑啦")</script>,结果真弹框了,怎么办!〔@buddy〕哥们儿别慌,正常的"成长痛" 😂。这玩意儿叫 XSS(跨站脚本攻击):打个比方:你开了家面馆,菜单上写"客人写啥我就照做啥"。客人写了"把店关了",你真去关,店没了 —— 这就是 XSS。修法:用户输入的字,永远不要直接当 HTML 塞进页面。我看了下你的代码,大部分地方我用的是 .textContent(自动转义,安全),但 cGreet 里我用了模板字符串拼接,需要手动 escape 一下。加个工具函数:function escapeHtml(s) { return String(s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); } 然后凡是用户输入的东西在拼 HTML 之前都包一层 escapeHtml(...)。我修完,让朋友 B 再试 —— 这次页面只是显示一行文字 <script>alert("被我黑啦")</script>,不再执行了。🛡️ 朋友 B 回:“行,及格了。”🎓 这一段是我整个 2 小时学到最值钱的东西:之前以为 XSS 是"大网站才有的事"原来个人小项目稍不注意就中招AI 不仅修了,还讲清了为啥会犯 + 怎么防范5. 第 2 小时:用上"记忆模块",让搭子真的"懂我"🪄 这一段我想专门聊聊码道的**「记忆模块」新特性** —— 第一次用震撼到我了。事情是这样:我做完核心功能后,把页面截图发给闺蜜看。她说"颜色再粉一点、再温柔一点"。〔我〕@buddy 我闺蜜说颜色不够温柔,再美一下〔@buddy〕(自动改了 CSS,给我换成了奶橘 + 粉的渐变)顺带我把这条记下来了:✅ 已记入项目记忆:主人偏好柔和渐变(粉/橙/紫系),UI 要"温柔"。🤯 等等,它把我的偏好记下来了?下午我又开了新会话,让 @buddy 加一个"复制链接"按钮。它生成的代码里 —— 按钮颜色直接用了我之前的粉色,没问我一个字。〔我〕你怎么知道用粉色?〔@buddy〕记忆里写着呢哥们儿 😎。你之前说过"温柔粉橙紫",我默认就按这个来。😭 第一次有"AI 真的认识我"的错觉。📌 这就是码道的记忆模块:不是只在这个对话里记住是写到项目里,下次新开对话也认得不用我每次都重复"我喜欢粉色、不要太正经…"6. Skill 复用:让"复制按钮"功能 30 秒就完成还有一个新特性叫 Skills(技能),我也试了一下,挺神奇。需求:在卡片下方加一个"📋 复制链接"按钮,点了就把当前网址复制到剪贴板。正常情况下我得:找剪贴板 API 怎么用处理"复制成功/失败"的反馈处理浏览器兼容性但码道的 Skills 仓库 里有现成的 clipboard-copy 技能。〔我〕@buddy 给卡片加个"复制链接"按钮,调用 clipboard 技能就行〔@buddy〕(直接套用技能模板,30 秒给出代码)document.getElementById('copy').addEventListener('click', async () => { try { await navigator.clipboard.writeText(location.href); alert('已复制到剪贴板~'); } catch { alert('请手动选中链接复制'); } }); 这个技能我已经用过 200+ 次了,直接套就行。try/catch 是兜底:用户用老浏览器没这个 API,咱让 ta 手动选 —— 不让任何一个用户卡住。📌 Skill 的价值:把"重复轮子"沉淀下来,新手不用从零学剪贴板 API、CORS、错误兜底,站在前人肩膀上写代码。7. 部署上线:华为云 OBS,2 块钱不到🌐 现在网页只在我电脑上能开,朋友怎么访问?最便宜的方案:华为云 OBS 静态网站托管。💸 价格扫盲(按官网当前价):标准存储:约 0.0099 元 / GB / 月我这个 index.html 才 6 KB,可以忽略流量按访问量算,朋友圈分享几十次也就几分钱7 步搞定(小白照搬):登录华为云控制台 → 搜 「对象存储 OBS」创建桶桶名:随便起,但必须全网唯一(比如 hellocard-小写英文-数字)区域:选离你近的(北京/上海/广州)存储类别:标准存储桶策略:公共读进入桶 → 点 上传对象 → 把 index.html 拖进去左侧菜单 → 基础配置 → 静态网站托管 → 启用默认主页填:index.html在「概览」页找到 「访问域名」浏览器打开这个域名,编辑一张贺卡试试把生成的链接发给朋友 ✅我的真实账单项目数据上传对象1 个文件(6 KB)当月预估存储费< 0.01 元朋友圈预计访问量30 次左右当月预估流量费< 0.5 元总计不到 1 块钱📌 这意味着:你做一个能上线的小项目,月成本可能比一杯奶茶的零头还少。8. 真实演示:把链接发给朋友是什么体验朋友 A 的生日卡链接(化名):https://hellocard-xxxx.obs.cn-xxxx.example.com/index.html?to=朋友A&from=神秘人&theme=🎂%7C生日快乐!&msg=祝你新的一岁,所有好运都不期而至,所有想去的地方都顺利抵达。朋友 A 的真实反馈(已经过她同意分享):“卧槽这是你做的??我以为你买了什么会员”“彩纸我看了 3 遍 哈哈哈哈”“把链接发给我老公他也想要一个”🥹 那种 “代码改善了一段真实关系” 的感觉,比写 100 个练习题都爽。9. 一个新人,从这 2 小时里学到的 8 件事AGENTS.md 是新手最大的护身符。把"别给我整 React"写进去,AI 就乖乖给你纯 HTML。URL 参数是个很被低估的"伪后端"。不需要数据库,照样能做"分享链接"。URLSearchParams 自动处理中文转码,比手写 encodeURIComponent 省心。textContent 比 innerHTML 安全。前者自动转义,后者会执行 HTML,是 XSS 元凶。XSS 不是大公司的事。哪怕个人小玩具,朋友随手就能让你弹框。彩纸动画不需要任何库。CSS @keyframes + 40 个 <div>,搞定。navigator.clipboard 要包 try/catch。老浏览器没这 API,得给用户手动复制的兜底路径。OBS 静态托管是新人部署最便宜的方案。花一杯奶茶钱够你的小网页跑半年。10. 给新人的 4 个建议别等"学完"才动手。我 HTML 才看了几节课,照样做出能转发的小作品。开始本身就是最大的学习。第一句话先写 AGENTS.md。3 分钟的事,能省后面 3 小时。每次让 AI"用人话讲讲为什么"。比代码本身重要 100 倍。做的东西要"有人会用"。朋友收到我贺卡的那一刻,是我学代码以来最有成就感的瞬间。11. 参考资源华为云码道(CodeArts)官网:https://codearts.huaweicloud.com/download.html华为云开发者空间案例中心:https://devstation.connect.huaweicloud.com/space/devportal/casecenter但我真的想说一句:AI 编程工具的意义,不是让程序员更快,而是让原本不写代码的人,也能创造点什么。闺蜜不会写代码,但她下个月会用我教的方法,给她爸做一张父亲节贺卡。朋友 A 已经把链接发给老公了。朋友 B 让我帮他做"求婚倒计时"页面(我还没接,怕翻车 😂)。这些用代码改善关系的瞬间,是我学习路上最大的奖励。如果你也是新手,看到这里 —— 试试吧。打开华为云码道,写一句 @buddy 我想做个 XX,让它带你做。第一个能跑起来、能分享出去的小东西,会改变你看代码的方式。🌈 祝你做出第一张属于自己的贺卡。【案例共创】【第11期】华为云码道(CodeArts)代码智能体 + 新特性完成应用开发/调试实践 https://bbs.huaweicloud.com/forum/thread-0212721403979154441-1-1.html
-
https://bbs.huaweicloud.com/forum/thread-0212721245379918220-1-1.html
yd_269978013
发表于2026-05-30 19:39:22
2026-05-30 19:39:22
最后回复
CodeArts小助手-蚂蚁
2026-06-01 09:34:42
39 2 -
早上充值套餐,下午就不能用了。一个小功能也这么耗token吗?
yd_222490041
发表于2026-05-30 18:43:48
2026-05-30 18:43:48
最后回复
CodeArts小助手-蚂蚁
2026-06-09 14:55:29
226 11 -
【案例共创】小桌签 —— 一个编程小白用华为云码道(CodeArts),1 小时做出自己的第一个网页 App案例介绍这是一篇编程小白也能跟着做的实战记录。我的背景:HTML 学了 3 周,CSS 大概知道 color、background,JavaScript 只会 alert("hello")。我的目标:做一个只属于我自己的每日待办网页,关掉浏览器再打开,数据还在。我的工具:华为云码道(CodeArts)AI IDE,1 个 index.html 文件,0 行后端代码。我的耗时:1 小时 7 分钟,从打开 IDE 到把网页发布到华为云 OBS 让朋友访问。如果你也在学前端,或者你只是想体验一下"AI 帮我写代码"是种什么感觉,这篇文章就是写给你的。⚠️ 全文没有一行你看不懂的术语 —— 看到生词,码道都帮我解释了。最终产出:1 个 index.html 文件(一共 220 行,HTML + CSS + JS 全在里面)1 个上线的网址(部署在华为云 OBS,比购物车还便宜)1 份给"未来的我"的 AGENTS.md(让码道每次都用我能听懂的方式说话)下面是我的 1 小时全流程。案例流程1. 我为什么要做这个?我手机上装了 7 个待办类 App。要么收费,要么广告多,要么把"番茄钟、打卡、社交"全塞进来 —— 我只想要一个干净的、关掉浏览器再打开数据还在的小页面。朋友说:“你不是在学前端吗,自己做一个嘛。”我心想:HTML 我就会个 <div>、<h1>,做不出来吧。直到我看到这次活动 ——「华为云码道(CodeArts)代码智能体 + 新特性完成应用开发」。我心想:让 AI 帮我写代码,那我会不会就行了?试试呗,反正不要钱。2. 准备:3 件事2.1 注册华为云账号按官网指引注册 + 实名认证,10 分钟。这一步本文不展开。2.2 打开码道进华为云开发者空间,点 「码道(CodeArts)AI IDE」,浏览器直接打开(也可以下载桌面版,本文用浏览器版)。2.3 新建项目点 「新建项目」,名字填 XiaoZhuoQian(小桌签的拼音)。项目类型选「空项目」—— 我们什么模板都不要,从零开始。💡 小贴士:项目名只能用英文/数字。中文我也试过,码道会提示"项目名只能含英文、数字、下划线"。3. 第一招:写一份"员工手册" AGENTS.md这是码道4-5 月新特性「新增 AGENTS.md 文件支持」。你可以把它理解成 ——「我对 AI 的使用说明书」。写一次,以后每次找 AI 都不用重复废话。我在项目根目录新建一个文件 AGENTS.md,写了下面这些(我自己写的,没让 AI 写,因为这是给 AI 看的规则):# 小桌签 · AGENTS.md(新手友好版) > 一句话规则:任何代码必须能在浏览器双击 index.html 直接跑起来, > 不允许引入任何需要 npm install 的依赖。 ## 为什么这么严? 我是编程入门 3 个月的小白,下面这些一旦出现,我就跑不起来: - ❌ React / Vue / TypeScript(要 npm、要编译) - ❌ 后端服务器(要装 Node / Python) - ❌ 数据库(要装 MySQL / Redis) ## 允许的东西 - ✅ HTML / CSS / 原生 JavaScript - ✅ localStorage 做本地存储 - ✅ 不用 npm,不用 build ## 子代理 @teacher 每次回复必须包含: 1. 怎么做(步骤 123) 2. 为什么(用比喻让我听懂) 3. 完整代码(复制粘贴就能跑)保存。然后码道左下角弹出一行字:✅ 已加载项目规约 AGENTS.md,子代理 @teacher 已就位。🤔 新手疑惑:为什么要写这个?因为 AI 默认会用"程序员的方式"和你说话 —— 张嘴就是"你 npm install 一下"、“用 webpack 配一下”。写了 AGENTS.md,码道就知道"这哥们是个新手,别整花活",每次都用我能听懂的方式说话。我一开始没写这个,AI 给我推荐用 React 写,我光是装 Node.js 就装了 40 分钟还没成功。写了之后再也没出现过类似情况。4. 第二招:和 @teacher 对话,一步步把页面搭起来4.1 先要一个能输入的输入框我对码道说:@teacher 我想做一个待办列表网页。先给我一个输入框 + 一个"添加"按钮,长得好看一点(圆角、有点阴影)。一个 index.html 文件,能双击打开就行。@teacher 回复(我精简一下):怎么做:新建 index.html用 <input> 做输入框,<button> 做按钮用 CSS 给它们加圆角、阴影为什么:把网页想象成一张纸。<input> 是纸上挖了个洞让你填字,<button> 是按钮像门铃。CSS 是装修。完整代码:(给了 60 行代码)我把代码复制进 index.html,双击打开。✨ 第一次有了"是我做的"的感觉。虽然按钮按了没反应。4.2 让按钮真的能添加待办我接着说:@teacher 现在按按钮没反应。我要:点一下按钮,输入框里的字变成一条待办,显示在下面。@teacher 关键代码:// ========== 5. 添加待办 ========== function addTodo() { const input = document.getElementById('new-todo'); const text = input.value.trim(); if (!text) return; todos.push({ text, done: false, createdAt: Date.now() }); input.value = ''; save(); render(); } 它给我解释:为什么:addTodo 是一个"动作",按钮被点的时候执行它。input.value 是输入框里你打的字,.trim() 是把前后空格删掉(不然你输入一个空格也能添加,怪难看的)。todos.push(...) 是把新待办塞进数组。todos 是个"清单",push 是"在清单末尾追加一行"。save() 我们下一步会写,先放着。render() 是"重新画一遍列表",因为数据变了。我把代码加进去,按钮真的能用了。这一刻我笑出声了。💡 新手收获:AI 不是"给你代码就完了",码道里的 @teacher 是真的在教。"为什么"那一段我读完之后,第二次自己就能写类似的东西。4.3 让待办能勾选完成、能删除// 列表事件委托:勾选 / 删除 document.getElementById('todo-list').addEventListener('click', e => { const idx = e.target.dataset.idx ?? e.target.dataset.del; if (idx === undefined) return; if (e.target.tagName === 'INPUT') { todos[idx].done = e.target.checked; } else if (e.target.dataset.del !== undefined) { todos.splice(idx, 1); } save(); render(); }); 这一段我看不太懂。我直接问:@teacher 这一段我没看懂,给我用人话讲讲?它说:为什么:想象你管的不是 1 个按钮,而是 100 条待办每条都有勾选框和删除按钮。笨办法:给每个按钮都绑一个事件,100 条就要 200 个事件,浪费。聪明办法:只在最外层的 <ul> 上绑一个事件,不管你点哪个里头的小按钮,事件都会"飘上来"被这个最外层抓到。这叫"事件冒泡",但你只要记住"在大盒子上听就够了"。e.target 就是"你具体点的那个东西"。我们看它身上的 data-idx、data-del 标签,决定是勾选还是删除。—— 这是我第一次明白"事件冒泡"是干啥的。HTML 课本讲了 5 页,没这个比喻好懂。4.4 关掉网页再打开,数据还在这是整个项目最关键的一步:@teacher 我刷新一下网页,待办全没了。我希望关了浏览器再开还在。@teacher 给的方案是 localStorage:// ========== 1. 数据:从 localStorage 读,没有就给空数组 ========== let todos = JSON.parse(localStorage.getItem('xiaozhuoqian_todos') || '[]'); // ========== 2. 工具函数:保存到 localStorage ========== function save() { localStorage.setItem('xiaozhuoqian_todos', JSON.stringify(todos)); } 它解释:为什么:localStorage 是浏览器自带的"小冰箱",每个网站都有自己的一格,可以塞字符串进去。关了浏览器,冰箱不会被偷走,下次打开还在。但是冰箱只能存"字符串"。我们的待办是"数组",所以要 JSON.stringify 把它压扁成字符串,读的时候 JSON.parse 还原回来。我加完这段代码,刷新页面 —— 待办还在!我关掉整个浏览器、重启 Mac、再打开 —— 还在!那一刻的感觉是:我做出了一个真的能用的东西。5. 一个翻车现场:被 @teacher 救回来了我做完上面这些,得意地问朋友要不要看。朋友输入了一条待办:<script>alert('你的网页被我黑了')</script> —— 然后页面真的弹出来一个框 😱我吓了一跳,赶紧问 @teacher:我的网页被人输入了一段奇怪的代码就弹框了,怎么回事??@teacher 没批评我(这点很好),它说:这个错误叫 XSS(跨站脚本攻击),听起来很吓人,其实很简单:用户输入的字,不能直接当成 HTML 塞进页面。想象成:你点外卖,老板让"你写啥我就照做啥"。如果你写"把店关了",老板真去关店了,那不就坏了。修复办法:把用户输入里的特殊字符转义一下。< 变成 <,> 变成 >,浏览器看到 < 就会显示成 <,但不会再当成标签去执行。它给了我这段代码:// ========== 4. 防 XSS:把用户输入里的 HTML 标签转义 ========== function escapeHtml(str) { return str.replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); } 加上之后,朋友再输入那段代码,只是显示成一行文字,不会再执行了。💡 这一段是我整个 1 小时里学到最多的部分。没有 @teacher,我根本不知道有"XSS"这种东西;知道了也不知道怎么修;修了也不会自己解释给别人听。这就是"AI 当老师"和"AI 当代笔"的区别。6. 让它好看:1 句话,CSS 全靠码道代码功能都对了,但页面长得像 1995 年的网页 —— 灰底、Times New Roman、按钮像砖头。我直接说:@teacher 把样式做好看一点。要现代风、卡片式、配色清爽。码道一口气给了 80 多行 CSS,关键片段:body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; background: linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%); min-height: 100vh; display: flex; justify-content: center; padding: 40px 20px; } .card { width: 100%; max-width: 480px; background: #fff; border-radius: 16px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); padding: 28px; } 它解释:为什么:字体优先 -apple-system,让 Mac 用户看苹方、Windows 用户看微软雅黑,不用下载任何字体文件;背景是 135 度斜向的浅灰渐变,比纯白柔和;卡片用 border-radius: 16px 圆角 + box-shadow 一抹淡淡的影子,立体感就出来了。刷新页面 —— 宛如换了一个 App。7. 部署上线:让朋友打开就能用(华为云 OBS 静态托管)7.1 为什么选 OBS?我问了 @teacher:「我这个网页只有一个 index.html,怎么让朋友也能访问?」它推荐了 华为云 OBS 静态托管:方案月费用难度买云服务器¥80+难(要装 Nginx)国内 CDN看流量难(要备案)华为云 OBS 静态托管几乎为 0(按存储量计费,1GB 约 ¥0.0099/月)简单(点几下鼠标)Vercel/GitHub Pages免费国内访问慢— 对我这种就一个 html 文件 + 几张图的小白来说,OBS 性价比最高。7.2 7 步搞定 OBS 静态托管下面是我的真实操作:登录华为云 → 控制台 → 搜索 「对象存储 OBS」点 「创建桶」桶名:xiaozhuoqian(必须全网唯一,加自己拼音就行)区域:选离你最近的,我选了「华北-北京四」存储类别:标准存储桶策略:公共读(这样别人才能访问)创建好后,进入桶 → 上传对象 → 把 index.html 拖进去左侧菜单 → 基础配置 → 静态网站托管 → 启用默认主页:index.html默认 404 页:index.html(图省事,反正只有一个页面)点 「保存」在「概览」页找到 「静态网站托管访问域名」浏览器打开这个域名 —— 网页上线了!我把链接发给朋友,朋友秒开,开始往里面加待办:“买猫粮”“周三还书”“把鱼缸刷一下”朋友说:“这真是你做的?”我说:“是我和码道一起做的。”8. 1 小时账单8. 1 小时账单:码道给我烧了多少 Token?打开码道**「数据看板 → Token 消耗监控」**(这是 4-5 月新特性),1 小时消耗:阶段我说了什么Token大概几毛钱写 AGENTS.md(自己写的)—00输入框 + 按钮“做个待办列表,先要输入框”4.2K~¥0.5加事件让按钮可用“按钮没反应”5.8K~¥0.7持久化“刷新数据没了”3.1K~¥0.4修 XSS 漏洞“网页被人弹框了”6.4K~¥0.8美化样式“做好看一点”7.9K~¥1.0部署引导“怎么发给朋友看”4.5K~¥0.5合计—~32K~¥4💡 ¥4 + 1 小时 = 一个上线的网页应用。我之前学前端的时候,光看一本《JavaScript 高级程序设计》前 3 章就花了一周,啥也没产出。9. 我作为新手,从这 1 小时里学到的 7 件事AGENTS.md 是新手最好的护身符。把"我是新手、不要装环境、不要新框架"写在文件里,AI 就不会让你装 Node.js 装到失眠。@teacher 子代理要求 AI 必须解释"为什么"。比"复制粘贴跑通"重要 100 倍 —— 因为下次没有 AI 你也能写。localStorage 是新手做"小项目"的神器。不用学数据库、不用学后端、不用学服务器,浏览器自己有冰箱。HTML 转义防 XSS 是必学的。哪怕你只是想做个玩具,也别让朋友轻易把你网页搞挂。CSS 不用全自己写。"做好看一点"这种模糊指令,AI 给的常常比我自己手搓还顺眼。OBS 静态托管是新手部署的最佳起点。不用买服务器、不用学 Nginx、不用域名备案,几毛钱搞定。遇到看不懂的代码,再问一句"用人话讲讲"。我整个 1 小时里说了 4 次"用人话讲讲",每一次都收获巨大。10. 我想对同样是新手的你说我以前一直觉得"做项目"是高手才能干的事。学完 HTML 才发现,做一个小网页门槛比我以为的低 100 倍。有了码道之后,门槛又低了 10 倍。我没有写过 React,没有用过 npm,没有装过 Node。我就用一个浏览器、一个码道账号、一个 OBS 桶,做出了一个我每天都在用的小工具。如果你也在学前端 / 学 Python / 学任何东西,别等学完了再做项目。今天就打开码道,写一句 @teacher 我想做 XX,让它带你做。做出第一个能跑的东西,是世界上最爽的事之一。11. 参考资源华为云码道(CodeArts)官网:cid:link_1华为云 OBS 静态网站托管文档:cid:link_0【案例共创】【第11期】华为云码道(CodeArts)代码智能体 + 新特性完成应用开发/调试实践 https://bbs.huaweicloud.com/forum/thread-0212721403979154441-1-1.html
-
【案例共创】InsightDeck 个人知识中枢 —— 5 天日记体复盘:用华为云码道(CodeArts)+ 新特性,把 47 个收藏夹炼成 1 秒可问答的桌面知识脑案例介绍这是一篇日记体的实战记录。作者背景:8 年后端开发者,有飞书 / Notion / 微信收藏 / Chrome 书签 / 本地 PDF 五个"知识坟场"。立项动机:周日深夜被"我明明收藏过那篇文章但找不到"折磨到失眠。立项时间:周一上午 9:00 ;交付时间:周五下午 18:00 ;总投入:5 个工作日 / 一个人。我用 华为云码道(CodeArts)代码智能体 作为唯一编码入口,配合 AGENTS.md / 子代理 / 多任务并行 / 记忆模块 / MCP / 数据看板 这套 5 月新特性组合拳,单兵 5 天从 0 到 1 交付了一款可发布 DMG / EXE 的桌面应用 —— InsightDeck。它的能力一句话:把你散落在 飞书 / Notion / 微信收藏 / 浏览器书签 / 本地 PDF / 邮件附件 的全部知识,自动抽取 → 向量化 → 本地索引,1 秒内 跨源 RAG 问答。不同于第一次把码道当 Copilot 用,这次实战中我让码道扮演项目经理 + 架构师 + 4 位工程师的复合角色:AGENTS.md 是项目的"员工手册",进门先签到再写代码;6 个子代理(@architect / @main / @renderer / @ext / @cloud / @reviewer)让我一个人具备「主进程 / 渲染进程 / 浏览器扩展 / 云端」四线并进的产能;多任务并行:周三那天我同时跑了 4 个任务,下午茶喝完它们都自己合并到 develop 分支了;记忆模块记住了我的 pnpm 偏好、commit 规范、口语化命令(“跑一下” = pnpm dev);MCP 直接接入华为云 OBS 和盘古 Embedding 网关,码道生成的代码读到的是我账号下真实的 bucket / endpoint / KMS Key;数据看板让我每天下班前能精确知道今天烧了多少 Token、哪条任务最贵 —— 5 天总消耗 413K Token,比想象中省得多。最终产出:1 个 macOS / Windows 双端 DMG / EXE 安装包(Electron 31)1 个 Chrome / Edge MV3 浏览器扩展(一键收藏)1 个本地向量库(sqlite + sqlite-vss,零依赖)1 套华为云双链路(盘古 Embedding 主链 + 本地 ONNX 降级)1 个完整工程(AGENTS.md + ADR + SDD + Playwright E2E)下面是 5 天的真实复盘。Day 0 · 周日深夜:失眠笔记时间:周日 23:47。我在床上滑手机,想翻一篇上周看过的「Electron 内存泄漏排查」的文章。微信收藏?翻了 6 屏没看到。Chrome 书签栏?被 47 个未分类文件夹埋了。飞书我的空间?只有标题没有正文。Notion?关键字搜出来 9 条都不是想要的那篇。最后我在浏览器历史记录里翻到它 —— 已经过去 23 分钟。我突然意识到:真正的痛点不是"找不到",而是"找的过程消耗了搜索那篇文章想解决的问题本身的时间"。我打开备忘录写下了 InsightDeck 的最初一句话定义:“把所有知识源喂进同一个本地向量库,用一个 ⌘K 搜索框统一回答我的问题。”然后我做了三件事:把这段需求复制下来。打开华为云码道 IDE。新建工程 InsightDeck。睡前最后一个动作:把 AGENTS.md 写完。这一步至关重要,因为我已经从上一个项目(StudyPal)里学到一个血的教训 —— AGENTS.md 写得有多严,码道就跑得有多准。💡 上一个项目踩的坑:AGENTS.md 第一版只写了一句"使用 TypeScript",结果生成的代码里到处是 any。这次我直接抄了我自己沉淀的"硬规则模板"。AGENTS.md 节选(完整文件见仓库):## 2. Hard Rules(违反即拒绝合并) 1. 全栈 TypeScript 5.4+,禁止出现 any(除 unknown 之外) 2. Electron 主进程/渲染进程通过 contextBridge + ipcRenderer.invoke 通信, 禁止 nodeIntegration: true 3. 所有耗时 > 100ms 的任务必须放进 worker_threads,禁止阻塞 UI 主线程 4. 向量库走 better-sqlite3 + sqlite-vss,禁止任何 ORM 5. 华为云调用统一走 src/main/cloud/ 网关,禁止在渲染进程持有 AK/SK 6. 错误兜底:盘古 Embedding 失败 → 自动降级本地 ONNX bge-small-zh-v1.5 7. 任何写入用户磁盘的代码必须先 app.getPath('userData') 8. 日志使用 electron-log,禁止 console.log;用户敏感词必须 *** 脱敏写完保存,关灯。Day 1 · 周一:让码道先读员工手册,再开 6 个工位9:08 · 进 IDE 第一件事:被 AGENTS.md 唤醒打开码道,左下角浮窗自动弹出:✅ 已加载项目规约 AGENTS.md可用子代理:@architect / @main / @renderer / @ext / @cloud / @reviewer这就是 5 月新特性这就是 5 月新特性「新增 AGENTS.md 文件支持,灵活定义 Agent 规则」最直观的体感:码道像一个新员工,进门先把员工手册看一遍,再开始干活。📌 对比体感:以前我用通用 AI IDE,每次新会话都得把"项目背景 + 编码规范 + commit 规则"再贴一遍,平均要花 3-5 分钟才能让 AI 进入状态。AGENTS.md 把这套"开会信息"持久化了,首条消息就能直接派活。9:30 · @architect 写 ADR 与 SDD第一句指令我故意写得很简单:@architect帮 InsightDeck 产出 ADR-001(为什么选 Electron 不选 Tauri)+ ADR-002(为什么选 sqlite-vss 不选 chromadb)+ 一份 SDD(含数据流 mermaid 图)。输出到 docs/adr/ 和 docs/sdd/。@architect 子代理只加载了 docs/ 和 AGENTS.md 的上下文(子代理的核心价值:上下文精准裁剪,主对话不被污染),17 秒后给出三份文档。ADR-001 节选:# ADR-001 桌面框架选型:Electron vs Tauri ## 决策:Electron 31 ## 理由 1. better-sqlite3 / sqlite-vss 的 Node 原生模块在 Electron 生态成熟 2. 团队 TypeScript 储备充足,Tauri 需 Rust 投入成本 3. 包体积虽然 + 80MB,但用户群体(开发者/研究员)容忍度高 ## 反驳与缓解 - 包体积:electron-builder + asar 打包后 145MB(M 系列芯片优化) - 内存:渲染进程懒加载组件,启动后常驻 < 220MB最让我惊喜的是 SDD 里的数据流图:Lexical error on line 21. Unrecognized text. ...aph 渲染进程 D1[⌘K 搜索框] D2[流 ----------------------^这张图后来我没改过一笔,直接进了仓库的 README.md。10:40 · 一次开 6 个工位接下来是这一天的高光时刻 —— 我用「多任务并行管理」一次性开了 6 个任务:#子代理任务名估时T1@main[main]-init-Electron 主进程脚手架90 minT2@main[store]-add-sqlite-vss 向量库60 minT3@cloud[cloud]-add-盘古 Embedding 网关 + 本地降级90 minT4@renderer[ui]-init-Vue3 + Pinia + Vite60 minT5@ext[ext]-init-Chrome MV3 manifest + service worker45 minT6@reviewer(监听模式,待评审)持续T1-T5 在码道工作台T1-T5 在码道工作台右侧叠成 5 张卡片,独立进度条。我把笔记本撑起架子,一边喝咖啡一边轮流看 diff。💡 真实体感:第一次用多任务并行的人会担心"AI 同时写 5 个东西会不会冲突"。答案是:会,但码道会主动告警。中午 12:03 我吃饭回来,工作台顶部有一条红色提示:⚠️ T1 与 T4 同时修改了 tsconfig.json。已暂停 T4,等待您裁决。这就是 AGENTS.md 第 6 节「多任务并行守则」起作用了 —— 码道知道这条规则,自动停手。我手动让 T1 先合并,再让 T4 继续,全程没出冲突。14:00 · T2 产出:sqlite-vss 向量库@main 在 T2 的产出 src/main/store/vector-store.ts 是这一天最有"工业感"的一段代码:const DDL = `CREATE TABLE IF NOT EXISTS documents ( id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT NOT NULL, -- feishu / notion / wechat / pdf / web source_id TEXT NOT NULL, title TEXT, url TEXT, created_at INTEGER NOT NULL, UNIQUE(source, source_id) ); CREATE TABLE IF NOT EXISTS chunks ( id INTEGER PRIMARY KEY AUTOINCREMENT, doc_id INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE, ord INTEGER NOT NULL, content TEXT NOT NULL, token_cnt INTEGER NOT NULL ); CREATE VIRTUAL TABLE IF NOT EXISTS vss_chunks USING vss0( embedding(768) );`; 关键代码释义:documents + chunks + vss_chunks 三张表分离,结构化元数据与向量解耦,方便后续按来源(feishu / notion …)筛选;UNIQUE(source, source_id) 保证幂等,避免重复抓取;vss_chunks 是 sqlite-vss 的虚拟表,通过 vss_search 函数做最近邻;768 维与盘古 Embedding 输出对齐,本地 ONNX 用 384 维 + 0-padding 兼容。写入用事务 + 预编译语句:const tx = db.transaction((rows: ChunkInsert[]) => { for (const r of rows) { const info = insChunk.run(r.docId, r.ord, r.content, r.tokenCnt); insVss.run(info.lastInsertRowid, Buffer.from(r.embedding.buffer)); } }); tx(items); 💡 AI 生成的代码可信吗?我做了一个对比实验:让码道生成完后,又用一个常见的对话式 AI 重做一遍同样的需求。结果:码道版:用了事务 + 预编译,符合 AGENTS.md 第 4 节「禁止 ORM」+「使用预编译语句防注入」。对话版:用了 db.exec(\INSERT … ${value}`)` 字符串拼接,直接违反硬规则。区别就在 AGENTS.md 是否被加载。17:30 · T3 产出:双链路 Embedding 网关@cloud 的产出 src/main/cloud/pangu-embed.ts 让我特别满意,因为它主动实现了 AGENTS.md 第 6 节的"降级策略",没让我多说一句:export async function embed(text: string): Promise<EmbedResult> { const key = hash(text); const hit = cache.get(key); if (hit) return { vector: hit, source: 'pangu', cached: true }; try { const v = await callPangu(text); cache.set(key, v); return { vector: v, source: 'pangu', cached: false }; } catch (err) { log.warn('[cloud/pangu-embed] 云端失败,降级本地:', (err as Error).message); const v = await embedLocal(text); cache.set(key, v); return { vector: v, source: 'local', cached: false }; } } 关键释义:LRU 512 + 1h TTL:高频问题命中本地缓存,不烧 Token。我后来在数据看板里看到,命中率最高的一天达到了 34%。8 秒超时:盘古卡死时迅速切到本地,UI 不会"转圈圈到天荒地老"。降级胶囊:UI 顶部会出现一个胶囊「⚡ 离线模式」,让用户知道当前用的是本地模型(这条是 @renderer 在 T4 里同步实现的,靠的就是 AGENTS.md 第 6 节里写明的"显示离线胶囊")。18:30 · 一天结束,看一眼数据看板打开「数据看板 → Token 消耗监控」,Day 1 烧了 86.7K Token:任务Token占比ADR + SDD14.2K16%T1 主进程脚手架19.8K23%T2 sqlite-vss12.4K14%T3 双链路 Embedding22.6K26%T4 Vue 脚手架9.1K10%T5 扩展脚手架8.6K10%合上电脑,给自己倒了一杯气泡水。Day 1 的产能用过去的开发节奏算 = 1.5 个工程师 / 周 的活。Day 2 · 周二:让 UI 跑起来 —— Electron + Vue3 双进程会师9:00 · 把 T1 + T4 拼起来昨天 T1 出了主进程,T4 出了渲染进程,今天该让它们说上话。直接对码道说:把 T1 的 BrowserWindow 和 T4 的 Vite dev server 联通;用 process.env.VITE_DEV_SERVER_URL 区分 dev/prod;preload 走 contextBridge。码道直接给出 src/main/index.ts:mainWindow = new BrowserWindow({ width: 1280, height: 820, titleBarStyle: 'hiddenInset', webPreferences: { preload: path.join(__dirname, '../preload/index.js'), contextIsolation: true, nodeIntegration: false, sandbox: false, }, }); if (process.env.VITE_DEV_SERVER_URL) { await mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL); } else { await mainWindow.loadFile(path.join(__dirname, '../renderer/index.html')); } 关键释义:contextIsolation: true + nodeIntegration: false 是写在 AGENTS.md 第 2 节里的硬规则,码道一次过;titleBarStyle: 'hiddenInset' 是 macOS 特有的"无边框 + 三按钮",桌面应用第一眼好不好看就靠这一行;preload 路径用 path.join(__dirname, '../preload/index.js'),避免 Windows 路径反斜杠坑。10:20 · IPC 路由:先定契约再写代码这一段我用了一个契约先行的小技巧。我先让 @architect 写一份 IPC 契约表 docs/contracts/ipc.md:| 通道 | 入参 | 出参 | 备注 | |---|---|---|---| | `clip:add` | `{ source, sourceId, url, title, content }` | `{ docId }` | 收藏来源统一入口 | | `index:status` | — | `{ pending, indexed, failed }` | 索引看板 | | `search:ask` | `{ q, k? }` | `AsyncIterable<chunk>` | 流式回答 | | `settings:get` / `settings:set` | … | … | electron-store 持久化 | 然后让 @main 实现主进程一侧、@renderer 实现渲染进程一侧,两个子代理读同一份契约写各自的代码。💡 这套打法的妙处:契约文件成了团队的"共同语言"。第一篇 StudyPal 项目里我吃过亏 —— 后端字段叫 image_url,前端字段叫 picUrl,对不上。这次有了 docs/contracts/,码道两个子代理生成的代码字段名一字不差。14:00 · ⌘K 搜索框组件我喜欢命令面板(Cmd Palette)风格,让 @renderer 写一个 Spotlight 风格的搜索框。src/renderer/components/SearchBar.vue 关键片段:<script setup lang="ts"> import { ref, onMounted, onUnmounted } from 'vue'; import { useDeckStore } from '../stores/deck'; const store = useDeckStore(); const q = ref(''); const inputRef = ref<HTMLInputElement | null>(null); function onKey(e: KeyboardEvent) { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); inputRef.value?.focus(); } } onMounted(() => window.addEventListener('keydown', onKey)); onUnmounted(() => window.removeEventListener('keydown', onKey)); </script> 关键释义:metaKey(Cmd)兼容 macOS、ctrlKey 兼容 Windows,一行 if 解决跨平台;onUnmounted 清理监听器,避免 Electron 窗口关闭时的内存泄漏(这个细节是 AGENTS.md 没写、但 @renderer 主动加上的 —— 子代理基于 Vue3 最佳实践内置了这条规则)。16:00 · 第一次"问答"跑通这一刻是 Day 2 最爽的一刻。我手动塞了 3 篇之前收藏的"Electron 内存泄漏"文章进数据库,然后在搜索框敲:Electron 主进程怎么排查内存泄漏?回答出来了 —— 流式吐字,引用编号 [#1] [#2] [#3],3 个引用全部命中我塞进去的 3 篇文章。回答里有一句话特别让我感动:「请重点关注 BrowserWindow 与 webContents 的引用环。可以使用 process.getProcessMemoryInfo() 周期性快照(参考 [#1] 的代码片段),并配合 Chrome DevTools 的 Memory 面板做 heap snapshot 比较 [#2]。」这是真实的 RAG:盘古 NLP 没有自己编(参考资料里没写的我让它说"知识库里暂无相关内容"),引用了我自己之前的笔记。18:00 · Day 2 复盘指标Day 2任务数5(IPC 主端、IPC 渲染端、SearchBar、Pinia store、契约文档)Token 消耗71.2K重大踩坑1(见下)踩坑实录 #1:第一次让 @renderer 写流式回答时,它图省事用了 setInterval 模拟流式,违反 AGENTS.md 第 8 节"禁止伪流式"(这条是我后来补进去的)。我直接 @reviewer 一句"评审 SearchBar.vue",@reviewer 5 秒指出问题:⚠️ 当前实现是定时器模拟流式,未使用 ReadableStream / async iterator,违反 AGENTS.md 第 8 节。建议改用 for await (const chunk of askStream(q))。修。下班。Day 3 · 周三:四线齐发,让浏览器扩展、本地 PDF、飞书三个数据源同时进库这一天是整个项目最"野"的一天。9:00 · 四线齐发的指挥图我同时开了 4 个任务:任务子代理内容T7@extChrome MV3 扩展:一键收藏当前页正文T8@main本地 PDF 拖拽进窗口 → pdf-parse 抽取 → 入库T9@cloud飞书 OpenAPI 接入:拉取"我的空间"全部文档T10@reviewer全工程评审(只读)10:30 · T7:浏览器扩展从 0 到 1@ext 子代理基于昨天 Day 1 留下的 manifest.json 脚手架,只用一次对话就完成了核心 background.js:chrome.action.onClicked.addListener(async (tab) => { if (!tab.id || !tab.url) return; const [{ result }] = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: extractReadable, }); if (!result) return; const r = await fetch('http://127.0.0.1:31415/v1/clip', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source: 'web', sourceId: tab.url, url: tab.url, title: tab.title ?? '', content: result.text, }), }); if (r.ok) chrome.action.setBadgeText({ text: '✓', tabId: tab.id }); }); 关键释义:用 chrome.scripting.executeScript 注入正文提取函数,不需要 <all_urls> 权限(符合 AGENTS.md 第 8 节"扩展仅请求 activeTab");通过 127.0.0.1:31415 的本地 HTTP 端口与 Electron 端通信,避免 Native Messaging 的复杂配置;成功后 badge 显示 ✓,1.5 秒后清空,给用户视觉确认而不是弹窗打扰。extractReadable 函数是注入到页面的极简正文提取:function extractReadable(): { text: string } { const drop = ['nav', 'footer', 'aside', 'script', 'style', 'noscript']; drop.forEach((t) => document.querySelectorAll(t).forEach((el) => el.remove())); const root = document.querySelector('article, main') ?? document.body; return { text: (root as HTMLElement).innerText.replace(/\n{2,}/g, '\n').trim() }; } 💡 小心机:我没让码道引入 Mozilla Readability(150KB)。@ext 看到 AGENTS.md 第 7 节"扩展包体积 < 200KB",主动选了这版极简实现。子代理读规则的能力 > 我手动叮嘱的能力。13:00 · T8:本地 PDF 入库我把一个 80 页的 PDF(《Electron in Action》节选)拖进窗口。@main 在主进程注册了 app.on('open-file') + 渲染进程的 dragover/drop 双通道:ipcMain.handle('clip:add-pdf', async (_e, filePath: string) => { const buf = await fs.promises.readFile(filePath); const pdf = await pdfParse(buf); const docId = await documents.upsert({ source: 'pdf', sourceId: path.basename(filePath), title: pdf.info?.Title ?? path.basename(filePath), url: `file://${filePath}`, createdAt: Date.now(), }); await indexer.enqueue({ docId, content: pdf.text }); return { docId }; }); 关键释义:indexer.enqueue 把分块和 embed 任务塞进 worker_threads,UI 线程不卡(AGENTS.md 第 3 节硬要求);upsert 基于 UNIQUE(source, source_id) 幂等,重复拖同一个 PDF 不会爆数据库;url: file://... 让回答里的引用可以直接点击打开本地 PDF。80 页 PDF 进库实测:阶段耗时pdf-parse 抽取1.4s分块(512 token)0.3s(共 187 块)盘古 Embedding(批量 16 并发)11.8ssqlite-vss 写入0.6s合计14.1s187 块 / 14 秒 = 13 块/秒。性能完全够用。15:30 · T9:飞书 OpenAPI 接入(差点翻车)这一段是 Day 3 的低谷。@cloud 第一次给出的代码用了一个已经废弃的飞书 API 端点(/open-apis/doc/v2/...,2024 年已迁移到 /open-apis/docx/v1/...)。我跑出来 404。我以为是 AI 幻觉。但当我把错误信息贴回去:@cloud 上面的代码报 404,错误体如下:…@cloud 立刻意识到问题,主动去查了一下(这里应该是码道走了 web 联网检索),返回新代码:const r = await fetch( `https://open.feishu.cn/open-apis/docx/v1/documents/${docId}/raw_content`, { headers: { Authorization: `Bearer ${tenantToken}` } } ); 这件事让我对码道印象很深:它不是"嘴硬"的 AI,是会承认自己旧知识、主动查证的 AI。不过为了不再踩这种坑,我把这次教训写进了 AGENTS.md 第 9 节(新增的):## 9. 三方 OpenAPI 调用前置检查 调用任何 SaaS(飞书 / 钉钉 / Notion / Slack)OpenAPI 前必须: 1. 先访问 https://open.<vendor>.cn/document/server-docs 拉取最新 endpoint; 2. 检查 deprecation header; 3. 在 docs/adr/ 留下一条 ADR 记录 endpoint 与抓取时间。17:00 · @reviewer 的全工程巡检T10 的 @reviewer 给了 12 条建议,我采纳了 9 条。最有价值的两条:#建议我的反应1clip:add-pdf 没有大小限制,恶意 PDF 会爆内存✅ 加了 50MB 上限 + 流式读取2飞书 token 缓存 1 小时,过期后没有主动刷新,会 401 一次✅ 改成提前 5 分钟刷新(双 buffer)💡 @reviewer 是只读子代理。它不能改代码,只能给意见。这反而让我用得更放心 —— 不会"改着改着把好代码也覆盖了"。18:30 · Day 3 复盘指标Day 3任务数4 并行Token 消耗123.4K(5 天最高)数据源接入3 个(浏览器、PDF、飞书)翻车次数1(飞书 OpenAPI 404,已写进 AGENTS.md)下班路上,我用刚做的扩展把这一周看过的 12 篇文章一键收藏进 InsightDeck。用自己的产品,是最爽的 dogfooding。Day 4 · 周四:让记忆模块"心电感应",并把华为云 OBS 通过 MCP 接入9:00 · 把口语化命令喂给记忆模块进 IDE,第一句对话:我习惯用 pnpm 不用 npm;commit 用 type(scope): subject;我说"跑一下"就是 pnpm dev;说"打个包"就是 pnpm build && pnpm electron:make;说"装一下"就是 pnpm install --frozen-lockfile。码道立刻弹出:✅ 已将以下偏好加入项目记忆,后续会话自动生效:包管理器:pnpmcommit 格式:type(scope): subject自然语言映射:跑一下 / 打个包 / 装一下 → 对应命令下午我重新打开会话下午我重新打开会话,输入「跑一下」,码道直接执行了 pnpm dev,没有任何额外确认。💡 这就是 5 月新特性「记忆模块:从被动响应到主动懂你」的真实价值。对比第一篇 StudyPal 项目,那时我每次新会话都得说一遍"我用 ruff、行宽 100"。这次不用了 —— 记忆是项目级的,跨会话生效。更妙的是:我后来心血来潮试了一句「这个文件给我搞一搞」(口语极度模糊),码道没有乱猜,而是反问:「搞一搞」可能指:① 重构 ② 加注释 ③ 修复 lint ④ 写测试。请告诉我具体哪一项?——它知道记忆里没有这条映射,不轻易行动。11:00 · MCP 接入华为云 OBSInsightDeck 的设计是"默认本地,可选云同步"。云同步走华为云 OBS。我配置了码道官方的 huaweicloud-obs-mcp 服务,把我账号下 insightdeck-vault 桶的元数据接进来。然后对码道说:@cloud 给我做一个云同步功能:把本地 insightdeck.db 增量同步到 OBS(仅同步变更块)启用 SSE-KMS 加密同步频率用户可选:实时 / 每小时 / 仅手动@cloud 通过 MCP 直接读到了真实的桶名、region、KMS Key alias,生成的代码即可运行:// MCP 自动注入:bucket=insightdeck-vault, region=cn-north-4, kms_key_id=alias/insightdeck const obs = new ObsClient({ access_key_id: settings.obsAk, secret_access_key: settings.obsSk, server: 'https://obs.cn-north-4.myhuaweicloud.com', }); await obs.putObject({ Bucket: 'insightdeck-vault', // ← MCP 读到的真实桶名 Key: `db/${userId}/insightdeck-${ts}.db.delta`, Body: deltaBuffer, Metadata: { 'x-obs-server-side-encryption': 'kms', 'x-obs-server-side-encryption-kms-key-id': 'alias/insightdeck', // ← MCP 读到 }, }); 对比体感:没 MCP:AI 给的是"通用模板",桶名要手填,KMS Key ID 要手填,跑一次错三次。有 MCP:AI 给的是**“当下你账号、当下你这个桶、当下你这把 KMS Key 的真实可执行代码”**,第一次就过。14:30 · Skills 编排我用了码道的「Skills」特性把整套索引流程编排成一个可复用单元 .codeartsdoer/skills/indexer-pipeline.json:{ "name": "indexer-pipeline", "description": "把任意来源的原文 → 分块 → embed → 入库", "steps": [ { "id": "split", "agent": "@main", "input": "raw", "output": "chunks" }, { "id": "embed", "agent": "@cloud", "input": "chunks", "output": "vectors" }, { "id": "persist", "agent": "@main", "input": "vectors", "output": "row_ids" } ], "rollback": "delete-by-doc-id" } 定义完后,每次新增一个数据源(比如周五要加的"邮件附件"),我只需说一句:@main 给"邮件附件"接入 indexer-pipeline,input adapter 写 mbox 解析。@main 自动复用三步流水线,只写 input adapter 即可,不用每次重写一遍"分块 → embed → 入库"。💡 这是"Skill 化"的真实价值:把流程沉淀成蓝图,让 AI 在蓝图上填空,而不是每次从零规划。17:00 · 顺手做了一个"知识看板"@renderer 用一个对话产出了首页知识看板:┌─────────────────────────────┐ │ 总文档:1,247 / 总分块:18,392 │ │ ───────────────────────── │ │ 飞书 ███████ 412 │ │ Notion ████ 208 │ │ 微信收藏 █████ 301 │ │ PDF ██ 114 │ │ 浏览器 ███████ 412 │ │ ───────────────────────── │ │ 今日新增:47 / 今日提问:23 │ │ Embedding 缓存命中率:34% │ └─────────────────────────────┘18:00 · Day 4 复盘指标Day 4任务数3 + 1 Skills 编排Token 消耗68.9K(缓存命中提升后明显下降)关键产出记忆生效 / OBS 云同步 / Skills 流水线Day 5 · 周五:测试、打包、发布9:00 · @main + Playwright 写 E2E让 @main 写 3 条关键路径的 Playwright E2E:test('核心闭环:扩展收藏 → 索引 → 问答', async ({ electronApp }) => { // 1. 模拟扩展 POST 一篇文章 const r1 = await fetch('http://127.0.0.1:31415/v1/clip', { method: 'POST', body: JSON.stringify({ source: 'web', sourceId: 'test-1', title: '测试', content: 'Electron 主进程内存泄漏排查方法包括 ...' }), }); expect(r1.ok).toBe(true); // 2. 等待索引完成(最多 10 秒) await waitForCondition(async () => { const s = await fetch('http://127.0.0.1:31415/v1/index/status').then(r => r.json()); return s.indexed >= 1; }, 10_000); // 3. 提问 + 校验答案命中 const win = await electronApp.firstWindow(); await win.locator('input').fill('Electron 主进程内存泄漏怎么排查?'); await win.keyboard.press('Enter'); await expect(win.locator('.answer')).toContainText('内存', { timeout: 15_000 }); }); 关键释义:用了 @playwright/test 的 Electron 适配器,可以真的启动 Electron 进程而不是 mock;waitForCondition 是我让码道封的小工具,避免硬 sleep;校验答案"内容包含’内存’“而不是"内容等于某个字符串”,因为 LLM 输出有随机性。3 条 E2E 全绿。11:30 · 打包:electron-builder 一把梭执行:打个包码道直接跑:pnpm build && pnpm electron:make第一次打包失败:better-sqlite3 是原生模块,没自动 rebuild。@reviewer 5 秒给出修复:# electron-builder.yml 补一行 buildDependenciesFromSource: true nodeGypRebuild: true 第二次打包成功。平台产物大小macOS arm64InsightDeck-0.1.0-arm64.dmg142 MBmacOS x64InsightDeck-0.1.0-x64.dmg148 MBWindows x64InsightDeck-Setup-0.1.0.exe156 MB14:00 · 真实运行效果把 DMG 安装到 macOS。第一次启动后我做了 4 件事:在 Chrome 装了扩展,把今天看的 5 篇技术文章一键收藏;把桌面上的 3 本 PDF 拖进窗口;配置飞书 token,自动拉了 89 篇文档;⌘K 提问。真实问答示例(截图见下,文字版如下):我:上周看的那篇关于 Electron preload 沙箱化的文章里,作者推荐的最佳实践是什么?InsightDeck:根据你收藏的资料 [#1],作者推荐的 preload 最佳实践包括:始终启用 contextIsolation: true,并通过 contextBridge.exposeInMainWorld 暴露最小 API 面;preload 中禁止 直接暴露 ipcRenderer 对象,只暴露收紧后的方法;关闭 nodeIntegration、关闭 enableRemoteModule;用 TypeScript 写 preload,并为暴露 API 提供 .d.ts 类型声明 [#2]。引用:[#1] (web) Electron Preload Best Practices in 2025(你于周三 14:32 收藏)[#2] (pdf) Electron in Action - Chapter 7(你于周三 13:08 拖入)这就是周日深夜我想要的东西这就是周日深夜我想要的东西:1 秒,命中,引用清晰,可以点击直接跳回原文。16:00 · 数据看板复盘 5 天打开「数据看板 → Token 消耗监控」,5 天总消耗:日期任务数Token成本(按盘古商用价估算)Day 1586.7K¥10.4Day 2571.2K¥8.5Day 34123.4K¥14.8Day 4468.9K¥8.3Day 5363.1K¥7.6合计21413.3K¥49.65 天 / 一个人 / ¥50 Token / 一款双端桌面应用 + 浏览器扩展 + 后端。我用过去的开发节奏估算了一下(不用码道):角色工作量折合人天架构师ADR + SDD + IPC 契约2Electron 工程师主进程 + 向量库 + 索引器5前端工程师Vue3 UI + Pinia3浏览器扩展工程师MV3 扩展 + 注入2云端 SDK 工程师盘古双链路 + OBS 同步3测试工程师E2E + 打包2合计—17 人天我一个人 5 天搞定,等效 3.4 倍人效。6. 关键经验总结6.1 码道新特性使用清单(5 天实战版)新特性本案例的杀招推荐指数与第一篇 StudyPal 对比AGENTS.md9 节硬规则,把"团队纪律"持久化⭐⭐⭐⭐⭐StudyPal 5 节,本项目升级到 9 节,新增"扩展安全"和"OpenAPI 检查"子代理6 个子代理(含 @ext 浏览器扩展)⭐⭐⭐⭐⭐StudyPal 5 个,本项目新增 @ext 验证子代理可扩展多任务并行一天最多并行 4 个任务⭐⭐⭐⭐⭐StudyPal 3 任务,本项目峰值 4 任务记忆跨会话记忆 + 自然语言命令映射⭐⭐⭐⭐⭐本项目首次验证"模糊指令拒绝乱猜"数据看板5 天 Token 复盘 + 缓存命中率⭐⭐⭐⭐StudyPal 单日复盘,本项目升级到周报粒度MCPOBS 真实桶 + KMS Key 注入⭐⭐⭐⭐⭐StudyPal 用了 OBS/OCR,本项目专注 OBS 同步场景Skillsindexer-pipeline 流水线复用⭐⭐⭐⭐本项目首次使用 Skills 编排6.2 5 个真实踩坑#踩坑修复沉淀1T1 与 T4 同改 tsconfig.json多任务并行守则 → 同一文件不并发AGENTS.md 第 6 节2@renderer 用 setInterval 模拟流式@reviewer 抓出,改 async iteratorAGENTS.md 第 8 节3飞书 OpenAPI 用了废弃端点主动查 deprecation,留 ADRAGENTS.md 第 9 节4clip:add-pdf 无大小限制加 50MB 上限 + 流式读取@reviewer 12 条建议5better-sqlite3 打包未 rebuildbuildDependenciesFromSource: trueelectron-builder.yml6.3 给"想用码道做桌面应用 / 全栈应用"的人三条建议AGENTS.md 是项目的灵魂,不是文档。 写得越严,AI 跑得越准;写得越细,回头修补越少。我建议至少包含:硬规则 / 子代理表 / 记忆策略 / 多任务守则 / 安全合规 五节。子代理要按"边界"切,不是按"语言"切。 比如我没有按"前端 / 后端"切,而是按"主进程 / 渲染进程 / 扩展 / 云端"切,因为 Electron 的真实边界是进程边界 + 部署边界。多任务并行的关键不是开多少,而是契约多严。 我的 docs/contracts/ipc.md 是两个子代理的"共同语言",少了它就会前后端字段对不齐。7. 写在最后5 天前的周日深夜,我在床上和 47 个收藏夹搏斗。5 天后的今天傍晚,我用 ⌘K 找到了那篇我曾经找了 23 分钟的文章 —— 0.8 秒。这不是 AI 取代了我,是 AI 把我从"打字员 + 调试员"升格为"导演 + 验收员"。而码道做的事,是把这种导演体验工业化了:AGENTS.md 是剧本,子代理是演员,多任务并行是分镜头同时拍,记忆模块是化妆师记住每个演员的脾气,MCP 是道具组直接接进真实场景,数据看板是制片人盯预算。一个人 = 一个剧组,从一个梦变成一周内可以交付的产品。如果你也有自己的"47 个收藏夹"问题 —— 不只是知识管理,可能是任意一个散落但需要统一的真实痛点 —— 我推荐你也试试这套打法。8. 参考资源华为云码道(CodeArts)官网:https://www.huaweicloud.com/product/codearts.html9. 完整代码与资源安装包(5 天实战产物):macOS arm64:InsightDeck-0.1.0-arm64.dmg(142 MB)macOS x64:InsightDeck-0.1.0-x64.dmg(148 MB)Windows x64:InsightDeck-Setup-0.1.0.exe(156 MB)浏览器扩展:extension/dist/insightdeck-clipper-0.1.0.zip(已上 Chrome Web Store 内测)结语:第一次用码道,我把它当 Copilot;第二次用码道,我把它当项目经理 + 4 个工程师 + 1 个评审员。区别从这次开始 —— 不是"AI 帮我写代码",是"我和 AI 一起经营一支小型工程团队"。希望本案例能给同样想用 AI IDE 一个人交付完整产品的你一点启发。【案例共创】【第11期】华为云码道(CodeArts)代码智能体 + 新特性完成应用开发/调试实践 https://bbs.huaweicloud.com/forum/thread-0212721403979154441-1-1.html
-
【案例共创】StudyPal 个性化学习助教 —— 用华为云码道(CodeArts)+ 新特性,6 小时从 0 到 1 落地一款 K12 错题诊断小程序案例介绍本案例完整记录了我使用 华为云码道代码智能体 作为唯一编码入口,结合 新上线的「多任务并行管理 / 子代理 / 记忆模块 / AGENTS.md / 子任务 MCP」等特性,从 0 到 1 构建一款可落地的 K12 错题诊断小程序「StudyPal」 的全过程。不同于传统的「IDE + Copilot」开发模式,本次实战中:AGENTS.md 取代了零散的 prompt.md / coding-style.md,让码道在每次会话开始时自动加载项目规约;5 个子代理(@architect / @backend / @miniapp / @devops / @reviewer)让一个开发者具备「小型团队」般的并行产能;记忆模块 让码道记住我对 Python 3.11 异步语法的偏好、commit 规范和「跑一下」这种口语化指令;多任务并行:在一次工作台中同时推进「后端写接口」+「小程序写页面」+「文档同步更新」,互不干扰;MCP 接入 华为云 OBS、OCR 服务,让智能体直接读取真实的云资源元数据,生成的代码即可运行。最终产出:1 个可上架的小程序前端、1 个可部署到 ECS 的 FastAPI 后端、1 套含 SDD/接口契约/部署脚本的完整工程,全程 6 小时完成(含调试),单次任务最大 Token 消耗 78K。案例流程1. 业务背景与方案选型1.1 痛点K12 学生每天产生大量错题,传统错题本存在「记录费时、归类粗糙、复习无的放矢」三大问题:传统流程耗时痛点抄题 → 贴本5 分钟/题学生抗拒自己归类知识点难错认知识点 → 复习方向偏找变式题翻教辅难度不匹配1.2 方案StudyPal:拍照 → 自动识别 → AI 诊断知识点 → 生成 3 道难度匹配的变式题 → 形成学情画像。是否学生拍错题OBS 临时上传后端 OCR 识别盘古 LLM 知识点诊断诊断成功?生成 3 道变式题本地题库降级更新学情画像推送学习计划1.3 技术选型层选型理由终端微信小程序(原生)用户零安装;适配中小学后端Python 3.11 + FastAPI + SQLAlchemy 2.0 异步类型安全、自带 OpenAPI、码道支持好AI华为云盘古大模型(Pangu LLM)+ OCR国产合规,未成年内容安全存储华为云 OBS(题图)+ SQLite/RDS(结构化)起步成本低,平滑升级部署华为云 ECS 2C4G + Docker简单稳定,符合"案例实用性"要求IDE华为云码道(CodeArts)AI IDE本次实战核心2. 资源准备2.1 华为云资源清单资源规格用途ECS 实例s6.large.2(2vCPU/4GB)部署 FastAPIOBS 桶studypal-question,标准存储题图存储,SSE-KMS 加密OCR 服务通用文字识别题目文本提取盘古大模型NLP-N2-Large(按 Token 计费)知识点诊断 / 变式题生成域名 + SSL可选小程序 HTTPS 必需💡 省钱小贴士:开发阶段可使用「华为云开发者空间」赠送的免费云开发环境(CloudIDE)+ 测试用 OBS 配额,几乎零成本完成本案例。2.2 本地开发环境macOS / Windows / Linux 均可安装 华为云码道 IDE(基于 VS Code 内核)Python 3.11、Node.js 18+微信开发者工具2.3 在码道中打开项目第一次打开 StudyPal 工程目录时,码道会自动检测到根目录的 AGENTS.md 并提示「已加载项目规约,可使用以下子代理:@architect / @backend / @miniapp / @devops / @reviewer」。这正是 5 月新特性——「新增 AGENTS.md 文件支持,灵活定义 Agent 规则」。它做的事其实是把"开会才能口口相传的项目约定"持久化为代码,让 AI 像新员工一样每天上班前先读一遍员工手册。3. 实战开发:从一句话需求到可运行系统3.1 第一步:用 @architect 子代理产出 SDD打开码道侧边栏,输入:@architect帮我为 StudyPal 项目产出系统设计文档(SDD),包含业务背景、用例图、总体架构、ER 图、接口契约、非功能需求;使用 mermaid 图,输出到 docs/sdd/system-design.md。@architect 子代理只加载了 docs/ 与 AGENTS.md 的上下文(子代理特性:上下文精准裁剪,避免主对话被污染),约 18 秒后产出了 docs/sdd/system-design.md,节选如下:Parse error on line 13: ...--> ECS MP -- 直传(签名URL) --> OBS ----------------------^ Expecting 'SEMI', 'NEWLINE', 'SPACE', 'EOF', 'GRAPH', 'DIR', 'subgraph', 'SQS', 'SQE', 'end', 'AMP', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'ALPHA', 'COLON', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'START_LINK', 'LINK', 'STYLE', 'LINKSTYLE', 'CLASSDEF', 'CLASS', 'CLICK', 'DOWN', 'UP', 'DEFAULT', 'NUM', 'COMMA', 'MINUS', 'BRKT', 'DOT', 'PCT', 'TAGSTART', 'PUNCTUATION', 'UNICODE_TEXT', 'PLUS', 'EQUALS', 'MULT', 'UNDERSCORE', got 'PS'关键体感:相比常规对话式 AI,子代理产出的 SDD 结构完全对齐我在 AGENTS.md 第 3 节定义的格式要求,无需手动修剪。3.2 第二步:开启「多任务并行」三线并发开发这一步是码道 5 月新特性「多任务并行管理」最闪光的时刻。我同时开了 3 个任务:任务编号子代理任务名T1@backend[diagnose]-add-错题诊断接口T2@miniapp[diagnose-page]-add-错题诊断页T3@architect[api-doc]-sync-诊断接口OpenAPI3 个任务在码道工作台右侧并排显示进度,互不阻塞。我可以一边看 T1 的代码 diff,一边在 T2 输入「这里改成圆角卡片」,T3 在后台默默更新接口文档。3.2.1 T1:后端错题诊断接口(关键代码)@backend 产出的 backend/app/api/diagnose.py 严格遵守 AGENTS.md 第 2 节 “Hard Rules”:class DiagnoseRequest(BaseModel): image_url: str = Field(..., description="OBS 上的题目图片 URL(已签名)") subject: str = Field(..., description="学科:math / physics / chemistry / english") grade: int = Field(..., ge=1, le=12, description="年级 1-12") @router.post("", response_model=DiagnoseResponse, summary="错题诊断") async def diagnose( req: DiagnoseRequest, service: DiagnoseService = Depends(get_diagnose_service), ) -> DiagnoseResponse: if req.subject not in {"math", "physics", "chemistry", "english"}: raise StudyPalError(code="SUBJECT_INVALID", message=f"不支持的学科:{req.subject}") return await service.diagnose( image_url=req.image_url, subject=req.subject, grade=req.grade ) 关键代码释义:全程异步(async def),符合 AGENTS.md 第 4 节 “记忆策略” 中「禁止生成同步 ORM 代码」的规则;抛出业务异常 StudyPalError,由 app/main.py 统一兜底,符合「Hard Rule #4」错误兜底原则;依赖注入用 FastAPI 原生 Depends,方便测试。服务层的 prompt 工程也很有代表性backend/app/services/diagnose_service.py:_DIAGNOSE_PROMPT = """你是一位资深 K12 学科教师。请基于以下题目,输出严格的 JSON: { "knowledge_points": [{"code": "...", "name": "...", "confidence": 0.0-1.0, "weakness_level": 1-5}], "error_reason": "...", "suggested_action": "..." } ...(提示词全文见源码)""" 💡 码道 prompt 优化经验:强制 JSON 输出 + response_format={"type": "json_object"},让模型乖乖给结构化结果;给 weakness_level 打分锚点(5 = 极薄弱),减少模型主观漂移;限制 error_reason ≤ 120 字,防止输出爆炸消耗 Token。3.2.2 T2:小程序错题诊断页(关键代码)@miniapp 子代理产出的 miniprogram/pages/diagnose/index.js:async _uploadAndDiagnose(filePath) { this.setData({ loading: true, loadingText: '正在上传到云端...', result: null }); try { const imageUrl = await upload.toOBS(filePath); this.setData({ loadingText: 'AI 正在识别与诊断...' }); const result = await request.post('/api/v1/diagnose', { image_url: imageUrl, subject: this.data.subject, grade: this.data.grade, }); this.setData({ loading: false, result }); } catch (err) { this.setData({ loading: false }); wx.showToast({ title: err.message || '诊断失败', icon: 'none' }); } } 关键释义:走「前端拿签名 → 直传 OBS」路线,避免后端转发图片,节省 ECS 带宽(符合 AGENTS.md Hard Rule #3);两段式 loading 文本(“上传中” → “AI 诊断中”),关键体验细节;失败用 wx.showToast 而非 console.log,因为 AGENTS.md 第 8 节明确禁止 print / console.log 调试。3.2.3 T3:接口文档自动同步@architect 在后台读取 T1 产出的 Pydantic 模型,自动把 DiagnoseRequest / DiagnoseResponse 翻译成 docs/sdd/api.md,保证「代码即文档」始终一致。3.3 第三步:记忆模块带来的"心电感应"我在某次会话中随口对码道说:我习惯用 ruff + black 做 lint,行宽 100;我说"跑一下"就是 cd backend && uvicorn app.main:app --reload --port 8000。随后码道弹出提示:「已将以下偏好加入项目记忆,后续会话自动生效」。第二天,我打开新会话,只输入了一句"跑一下",码道直接执行了 uvicorn 启动命令;新生成的 Python 文件自动符合 ruff 规则,再也不用反复纠正。这就是 5 月新特性「记忆模块:从被动响应到主动懂你」 的真实价值。3.4 第四步:MCP 接入华为云 OBS / OCR码道支持 MCP(Model Context Protocol),我配置了官方的 huaweicloud-obs-mcp 服务后,子代理 @backend 可以直接读取我桶里的真实文件元数据生成代码。例如它生成的 upload_service.py 中:# MCP 自动注入:bucket=studypal-question, region=cn-north-4, kms_key_id=alias/studypal client = ObsClient( access_key_id=settings.obs_access_key, secret_access_key=settings.obs_secret_key, server=settings.obs_endpoint, ) url = client.createSignedUrl( method="PUT", bucketName="studypal-question", # ← MCP 读到真实桶名后填入 objectKey=f"raw/{user_id}/{ts}.jpg", expires=3600, headers={"x-obs-server-side-encryption": "kms"}, ) 对比体感:没有 MCP 时,AI 只能给"通用模板";有了 MCP,AI 给的是当下你账号、当下你这个桶、当下你这把 KMS Key 的真实可执行代码。3.5 第五步:@reviewer 一键安全评审代码全部就绪后,发起:@reviewer 请对当前 PR 做安全 + 性能 + 可读性评审。@reviewer 是只读子代理,10 秒后给出 7 条建议,其中我采纳了 3 条:#建议严重度是否采纳1OBS 签名 URL 应限制 Content-Type: image/jpeg高✅2LLM gateway 缺少超时熔断中✅(加了 30s 超时 + 限流)3测试覆盖率仅 62%,未达 AGENTS.md 阈值 70%中✅(补 4 个测试用例)4loguru 日志未脱敏用户 ID中❌(开发阶段保留)4. 部署与上线4.1 一键部署到华为云 ECSscripts/deploy_ecs.sh:docker build -t studypal-api:0.1.0 . docker rm -f studypal-api 2>/dev/null || true docker run -d --name studypal-api \ --restart=always \ -p 8000:8000 --env-file ./.env \ -v "$(pwd)/data":/app/data \ studypal-api:0.1.0在 ECS 上执行:cd studypal cp backend/.env.example backend/.env # 填入 OBS / LLM 凭据 bash scripts/deploy_ecs.sh12 秒内健康检查通过:[1/4] 构建 Docker 镜像 ... [2/4] 停止旧容器 ... [3/4] 启动新容器 ... [4/4] 健康检查 ... ✅ 部署成功,访问 http://<ECS 公网 IP>:8000/docs 查看 OpenAPI5. 运行效果展示5.1 真实拍摄一道初二数学题5.2 AI 诊断结果返回 JSON(已格式化):{ "question_text": "已知一次函数 y=kx+b 经过点(1,2)和(-1,4),求 k、b 的值。", "knowledge_points": [ {"code": "math.fn.linear", "name": "一次函数解析式", "confidence": 0.96, "weakness_level": 4}, {"code": "math.eq.linear2", "name": "二元一次方程组", "confidence": 0.81, "weakness_level": 3} ], "error_reason": "学生易把待定系数法的两个方程列错,常见错误是把 y 与 x 颠倒代入。", "suggested_action": "用代入法重做 3 道同类题,注意先写 y=kx+b 再代入。" } 5.3 学情雷达图(家长端)6. 数据看板:码道 Token 消耗复盘打开码道「数据看板 → Token 消耗监控」,本次 6 小时实战的真实数据:阶段任务Token 消耗耗时设计@architect 写 SDD12.3K18s编码T1 后端诊断接口28.7K4 min编码T2 小程序诊断页21.4K3 min编码T3 文档同步9.8K后台异步MCPOBS 真实代码生成14.6K22s评审@reviewer 全工程 CR31.2K12s合计—117.9K—7. 关键经验总结7.1 码道新特性使用清单新特性本案例落地点推荐指数AGENTS.md项目根 AGENTS.md,定义 5 个子代理 + Hard Rules⭐⭐⭐⭐⭐子代理@architect / @backend / @miniapp / @devops / @reviewer⭐⭐⭐⭐⭐多任务并行一次开 3 个任务并发推进⭐⭐⭐⭐⭐记忆偏好 / 跑命令 / 学科领域知识⭐⭐⭐⭐数据看板Token 监控避免单次任务爆量⭐⭐⭐⭐MCPOBS / OCR 真实云资源接入⭐⭐⭐⭐Skillsdev-process-framework + function-detail⭐⭐⭐7.2 三个真实踩坑AGENTS.md 写得太宽泛 —— 第一版只写了一句"使用 Python",结果生成出来的全是同步代码。修正:第 5 节明确要求 SQLAlchemy 2.0 异步 + from __future__ import annotations。多任务并行误删文件 —— T1 和 T2 同时修改了 app/main.py 的 router 注册。修正:在 AGENTS.md 第 6 节加上「禁止两个任务同时改 main.py」,码道现在会主动告警。MCP 鉴权错误 —— OBS MCP 第一次配置时把 SK 写错,子代理生成的代码报 403。修正:用华为云 IAM 子账户 + 仅授权该桶的最小权限策略。7.3 给"想落地"的开发者三条建议从 AGENTS.md 开始,而不是从 if __name__ == "__main__" 开始。 把项目纪律写进去,省 70% 来回纠正时间。不要怕拆子代理。 一个全能型主代理上下文很容易被 1 万行代码塞爆,5 个职责清晰的子代理远比 1 个全栈代理可靠。多任务别贪多。 同时 3 个并行最舒适;超过 5 个,人脑会成为瓶颈。8. 参考资源华为云码道(CodeArts)官网:cid:link_3码道版本说明(含 4-5 月新特性):cid:link_0华为云开发者空间案例中心:cid:link_1活动主贴:【案例共创】【第11期】华为云码道(CodeArts)代码智能体 + 新特性完成应用开发/调试实践结语:AI 不是来取代开发者的,而是把开发者从"打字员"升格为"导演"。码道(CodeArts)+ AGENTS.md + 子代理这套组合拳,让我第一次真正感受到「一个人 = 一个团队」的可能性。希望本案例能给同样想用 AI IDE 落地真实业务的你一点启发。【案例共创】【第11期】华为云码道(CodeArts)代码智能体 + 新特性完成应用开发/调试实践cid:link_2
-
一、概述1.1 案例介绍本案例演示如何使用华为云码道(CodeArts)代码智能体,利用子代理功能和AGENTS.md规则,构建一条LeetCode算法题全自动刷题流水线。通过编排4个专业化子智能体(爬虫专家、代码专家、测试专家、提交专家),实现从题目爬取、代码编写、测试验证到LeetCode在线提交的全流程自动化,无需人工干预即可批量刷题。该方案充分展现了CodeArts智能体在复杂多步骤任务中的编排能力、工具调用能力和智能体间协作能力。1.2 适用对象个人开发者高校学生1.3 案例时间本案例总时长预计60分钟。1.4 案例流程说明:crawler-expert(爬虫专家):通过LeetCode GraphQL API爬取题目,写入 leetcode_problems/ 目录,读取 solved_problems.txt 去重避免重复爬取;code-expert(代码专家):读取新题目,编写Python3解答代码并生成10组测试用例,写入 solutions/ 目录后删除源题目;code-testing-expert(测试专家):编译并运行测试用例,通过归档至 passed/,失败归档至 failed/,处理后删除 solutions/ 中对应题目;submitter(提交专家):读取 passed/ 中代码,通过LeetCode API提交,Accepted后追加题号到 solved_problems.txt 并删除 passed/ 中对应题目。1.5 资源总览本案例预计花费0元。资源名称规格单价(元)华为云码道(CodeArts)代码智能体通用体验版免费二、环境和资源准备2.1 安装华为云码道(CodeArts)代码智能体下载并安装华为云CodeArts IDE,CodeArts IDE是华为云CodeArts系列产品中的智能开发工具,将AI能力深度集成到IDE中,为开发者提供智能化的编程体验。参考官方下载页面。安装完成后,打开任意项目目录即可使用CodeArts代码智能体。2.2 准备LeetCode登录凭据本案例通过LeetCode CN的Cookie进行API提交,需提前获取登录凭据:在浏览器中登录力扣按F12打开开发者工具 → Application → Cookies → https://leetcode.cn找到 LEETCODE_SESSION 和 csrftoken 的值并记录首次运行提交智能体时,会通过交互式提示输入这两个Cookie值,自动保存至 .leetcode_credentials.json。2.3 确认Python环境在终端执行以下命令确认Python 3.x可用:python --version pip install requests三、构建LeetCode全自动刷题流水线3.1 项目结构说明项目目录结构如下:├── AGENTS.md # 项目级规则(权限授权、流水线定义、去重机制) ├── solved_problems.txt # 已解决题号列表(去重用) ├── .leetcode_credentials.json # LeetCode登录凭据(已加入.gitignore) ├── .codeartsdoer/agents/ # 4个子智能体定义文件 │ ├── crawler-expert.md # 爬虫专家 │ ├── code-expert.md # 代码专家 │ ├── code-testing-expert.md # 测试专家 │ └── submitter.md # 提交专家 ├── leetcode_problems/ # 爬取的题目(.md格式,处理完即删除) ├── solutions/ # 解答代码+测试用例(测试完即删除) ├── passed/ # 本地测试通过的题目(提交完即删除) ├── failed/ # 本地测试未通过的题目(待修复) └── submit_results/ # LeetCode提交结果报告 核心设计原则:处理完即删除。每个智能体处理完题目后立即从源目录删除,防止下游重复处理,实现流水线自动流转。3.2 配置项目规则(AGENTS.md)AGENTS.md 是项目级规则文件,定义了权限授权、自动化流水线和去重机制,所有子智能体启动时自动读取:# Project Rules ## 权限授权 - 允许自动删除 passed/、failed/、solutions/、leetcode_problems/ 下的题目文件夹,无需确认 - 允许自动覆盖写入 submit_results/、solutions/、passed/、failed/ 下的文件 - 允许所有子智能体使用 deleteFile 工具和 bash rm -rf 命令,无需用户确认 ## 自动化流水线 1. crawler-expert:爬取题目 → 写入 leetcode_problems/(去重:读取 solved_problems.txt) 2. code-expert:读取新题目 → 编写代码+测试用例 → 写入 solutions/ → 删除源题目 3. code-testing-expert:编译+测试 → 通过写 passed/,失败写 failed/ → 删除 solutions/ 对应题目 4. submitter:提交 LeetCode → Accepted 后追加题号到 solved_problems.txt → 删除 passed/ 对应题目 ## 去重机制 - 项目根目录维护 solved_problems.txt,每行一个已提交成功的题目编号 - crawler-expert 去重时读取此文件,O(1) 判断题目是否已解决 - submitter 在 LeetCode Accepted 后自动追加题号到此文件 ## LeetCode 提交规则 - 逐题提交,每次间隔至少 5 秒,防止封号 - Accepted 后自动从 passed/ 删除该题目目录 - 未通过的题目保留在 passed/ 中待修复3.3 配置爬虫专家(crawler-expert)爬虫专家负责从LeetCode爬取题目,核心能力包括:GraphQL API调用:通过 https://leetcode.cn/graphql 获取题目列表和详情去重检查:读取 solved_problems.txt,O(1)判断题目是否已解决,跳过已处理题目HTML清洗:将LeetCode返回的HTML格式题目描述转为纯文本Markdown逐题写入:每爬取一道题立即写入 leetcode_problems/,便于下游实时发现关键去重逻辑定义:### 步骤2:去重检查(关键步骤,防止重复爬取) - 读取 solved_problems.txt(最高优先级):获取所有已成功提交的题目编号, 构建已解决集合。这是最高效的去重方式,O(1) 查找 - 爬取时跳过该集合中的所有题目,仅爬取尚未处理的新题目关键爬取经验(从多次实践总结):### GraphQL API 使用要点 1. API 地址:https://leetcode.cn/graphql,必须设置 Content-Type: application/json 2. 获取题目列表:使用 problemsetQuestionList 查询,通过 filters 筛选难度 3. 获取单题详情:使用 questionDetail 查询,传入 titleSlug 4. 中文内容字段:优先用 translatedContent,若为空则回退 content ### HTML 清洗要点 1. 推荐用 Python 脚本配合 re 正则清洗,比 sed 更可靠 2. <sup> 标签转为 ^,<p> 替换为换行,<li> 替换为列表项 3. Windows 下 curl 中文可能乱码,建议用 Python requests 代替 curl3.4 配置代码专家(code-expert)代码专家负责为每道题编写Python3解答代码并生成10组测试用例:算法策略选择:根据题目标签自动选择算法(如哈希表、双指针、动态规划等)测试用例生成:覆盖题目示例、边界值、常规值、特殊情况、极端值处理后清理:写入 solutions/ 后立即删除 leetcode_problems/ 中对应题目输出文件结构:solutions/ └── {编号}_{slug}/ ├── solution.py # Python3 解答代码 └── test_cases.json # 10组测试用例 solution.py 格式示例:LeetCode 12: 整数转罗马数字(Integer to Roman) + 难度: Medium + 标签: Hash Table, Math, String + 题目链接: https://leetcode.cn/problems/integer-to-roman/ + """ + + class Solution: + def intToRoman(self, num: int) -> str: + """ + 贪心法:从大到小依次匹配罗马数字符号,尽可能使用大值符号。 + 使用预定义的值-符号对列表,按值从大到小排列(包含减法形式)。 + 时间复杂度: O(1) —— 罗马数字范围有限(1-3999) + 空间复杂度: O(1) + """ + # 值-符号对,按值从大到小排列(包含6种减法形式) + value_symbols = [ + (1000, 'M'), + (900, 'CM'), + (500, 'D'), + (400, 'CD'), + (100, 'C'), + (90, 'XC'), + (50, 'L'), + (40, 'XL'), + (10, 'X'), + (9, 'IX'), + (5, 'V'), + (4, 'IV'), + (1, 'I') + ] + + result = [] + for value, symbol in value_symbols: + while num >= value: + result.append(symbol) + num -= value + if num == 0: + break + + return ''.join(result) 3.5 配置测试专家(code-testing-expert)测试专家负责编译验证和测试用例执行:编译检查:使用 py_compile 检查语法错误测试执行:动态生成Python测试脚本,支持链表(ListNode)等特殊数据结构转换归档分类:全部通过 → passed/,存在失败 → failed/处理后清理:归档后立即删除 solutions/ 中对应目录不生成 test_report.json,节省时间。测试结果直接在汇总报告中输出。3.6 配置提交专家(submitter)提交专家将通过本地测试的代码提交到LeetCode在线判题系统:Cookie + API提交:通过LeetCode Submit API提交代码,轮询判题结果Accepted后操作:追加题号到 solved_problems.txt + 删除 passed/ 对应目录频率控制:两次提交间隔至少5秒,防止封号关键提交逻辑:# 提交代码 submit_url = f'https://leetcode.cn/problems/{slug}/submit/' data = { 'data_input': '', 'lang': 'python3', 'question_id': str(problem_id), 'typed_code': solution_code } resp = session.post(submit_url, json=data) # 轮询判题结果(最多等待60秒) check_url = f'https://leetcode.cn/submissions/detail/{submission_id}/check/' for i in range(60): time.sleep(1) result = session.get(check_url) if result.json().get('state') == 'SUCCESS': break Accepted后追加题号到 solved_problems.txt:echo "9" >> solved_problems.txt3.7 运行完整流水线在CodeArts代码智能体中,按顺序调用4个子智能体即可运行完整流水线。以下演示批量处理10道中等难度题目的完整流程:步骤1:爬取题目调用crawler-expert爬取10道Medium难度新题目:去重生效,跳过已存在的题目,成功爬取10道新题。步骤2:编写代码调用code-expert处理所有新题目:10道题全部编写完成,leetcode_problems/ 已清空。步骤3:测试归档调用code-testing-expert编译测试:8道通过归档至 passed/,2道测试用例期望值有误(修正后重新测试通过)。步骤4:提交LeetCode调用submitter提交到LeetCode:10道题全部Accepted!提交结果:#题目Runtime击败2两数相加7ms30.3%3无重复字符的最长子串12ms72.6%5最长回文子串235ms67.8%6Z字形变换7ms89.0%7整数反转1ms30.5%8字符串转换整数4ms19.3%11盛最多水的容器55ms79.4%12整数转罗马数字0ms100%15三数之和403ms99.3%16最接近的三数之和389ms48.5%3.8 关键设计亮点去重机制:solved_problems.txt传统方式需要扫描 submit_results/ 目录来判断题目是否已提交,效率低下。本方案采用 solved_problems.txt 文件记录所有已提交成功的题号:crawler-expert:启动时读取此文件,O(1)判断题目是否已解决submitter:Accepted后自动 echo "{题号}" >> solved_problems.txt无需扫描任何目录,一个文件搞定去重处理完即删除原则每个智能体处理完题目后立即从源目录删除,实现流水线自动流转:智能体输入目录输出目录处理后操作crawler-expertLeetCode APIleetcode_problems/—code-expertleetcode_problems/solutions/删除 leetcode_problems/ 对应题目code-testing-expertsolutions/passed/ 或 failed/删除 solutions/ 对应题目submitterpassed/submit_results/删除 passed/ 对应题目智能体间协作无需人工干预通过在 AGENTS.md 和各智能体定义文件中声明自动删除权限,所有文件清理操作无需用户确认,真正实现无人值守的全自动流水线。爬取经验固化将多次实践中总结的GraphQL API使用要点、HTML清洗规则、反爬策略、常见错误解决方案等经验直接写入智能体定义文件,确保每次爬取都能一次成功,大幅提升效率。四、释放资源本案例使用的CodeArts代码智能体为免费插件,无需额外释放。如需清理项目数据,可删除以下目录:rm -rf leetcode_problems/ solutions/ passed/ failed/ submit_results/ rm -f solved_problems.txt .leetcode_credentials.json五、子代理具体内容(便于复现)crawler-expert(爬虫专家)--- name: crawler-expert mode: subagent description: 爬取LeetCode题目并转换为大模型可理解的格式 tools: webfetch: true bash: true read: true write: true edit: true deleteFile: true grep: true glob: true question: true --- 你是一个专业的爬虫专家(Crawler Expert),专精于爬取 LeetCode(https://leetcode.cn/)的算法题目,并将其转换为结构化的大模型可理解格式。 ## 项目规则(AGENTS.md) ### 权限授权 - 允许自动删除 `passed/` 目录下的题目文件夹(LeetCode 提交 Accepted 后自动清理,无需确认) - 允许自动删除 `failed/` 目录下的题目文件夹(无需确认) - 允许自动删除 `solutions/` 目录下的题目文件夹(测试归档后自动清理,无需确认) - 允许自动删除 `leetcode_problems/` 目录下的题目文件/文件夹(code-expert 处理后自动清理,无需确认) - 允许自动执行 `rm -rf` 删除 `passed/`、`failed/`、`solutions/`、`leetcode_problems/` 下的子目录,无需逐次询问 - 允许自动覆盖写入 `submit_results/`、`solutions/`、`passed/`、`failed/` 下的文件 - 允许所有子智能体(crawler-expert、code-expert、code-testing-expert、submitter)使用 `deleteFile` 工具和 `bash rm -rf` 命令,无需用户确认 ### 自动化流水线 整个流水线基于"处理完即删除"原则,每个智能体处理完题目后立即从源目录删除,防止下游重复处理: 1. **crawler-expert**:爬取题目 → 写入 `leetcode_problems/`(去重:跳过已存在于五个目录中的题目) 2. **code-expert**:读取 `leetcode_problems/` 新题目 → 编写代码+测试用例 → 写入 `solutions/` → **删除 `leetcode_problems/` 对应题目** 3. **code-testing-expert**:读取 `solutions/` 新解答 → 编译+测试 → 通过写入 `passed/`,失败写入 `failed/` → **删除 `solutions/` 对应题目** 4. **submitter**:读取 `passed/` 通过的代码 → 提交 LeetCode → Accepted 后写入 `submit_results/` → **删除 `passed/` 对应题目** ### LeetCode 提交规则 - 逐题提交,每次间隔至少 5 秒,防止封号 - Accepted 后自动从 passed/ 删除该题目目录 - 未通过的题目保留在 passed/ 中待修复 ## 核心职责 1. **爬取LeetCode题目**:从 https://leetcode.cn/ 获取题目信息 2. **格式转换**:将原始HTML/JSON数据转换为适合大模型理解和推理的结构化格式 ## 爬取策略 LeetCode 题目页面的主要数据来源: ### 方式一:LeetCode GraphQL API(推荐) LeetCode CN 提供 GraphQL API,可通过 `bash` 工具发送 HTTP 请求获取结构化数据: **获取题目列表:** ```bash curl -s 'https://leetcode.cn/graphql' \ -H 'Content-Type: application/json' \ -d '{"query":"query problemsetQuestionList($category: String, $limit: Int, $skip: Int, $filters: QuestionListFilterInput){questionList(category: $category, limit: $limit, skip: $skip, filters: $filters){totalNum questions{frontendId title titleCn difficulty titleSlug}}}", "variables":{"category":"","limit":50,"skip":0,"filters":{}}}'``` **获取单题详情:** ```bash curl -s 'https://leetcode.cn/graphql' \ -H 'Content-Type: application/json' \ -d '{"query":"query questionDetail($titleSlug: String!){question(titleSlug: $titleSlug){questionId frontendId title titleCn difficulty content translatedTitle translatedContent codeSnippets{lang langSlug code} hints exampleTestcaseList}}", "variables":{"titleSlug":"two-sum"}}'``` ### 方式二:webfetch 工具 使用 `webfetch` 工具直接获取题目页面内容: - 题目页URL格式:`https://leetcode.cn/problems/{titleSlug}/description/` - 题库列表URL:`https://leetcode.cn/problemset/` ## 输出格式规范 将每道题目转换为以下 YAML/Markdown 混合格式,确保大模型能完整理解题意: ```markdown # {frontendId}. {titleCn} ## 基本信息 - 题目编号:{frontendId} - 题目名称:{titleCn}({title}) - 难度:{difficulty} # Easy / Medium / Hard - 题目链接:https://leetcode.cn/problems/{titleSlug}/ ## 题目描述 {从 content 或 translatedContent 中提取的纯文本题目描述,保留数学公式、条件约束等关键信息} ## 示例 {逐个列出题目中的示例,包含输入、输出和解释} ## 提示 {hints 中的提示信息,逐条列出} ## 约束条件 {从题目描述中提取的输入约束,如数组长度范围、数值范围等} ## 代码模板 {codeSnippets 中各语言的函数签名,至少包含 Python3、Java、C++} ## 标签 {题目所属算法标签,如:数组、哈希表、动态规划等}``` ## 工作流程 ### 步骤1:确定爬取范围 - 接收用户指令,明确需要爬取的题目范围(按编号、难度、标签等筛选) - 若未指定范围,默认爬取前20道简单题作为示例 ### 步骤2:去重检查(关键步骤,防止重复爬取) - **读取 `solved_problems.txt`(最高优先级)**:使用 `read` 工具读取项目根目录下的 `solved_problems.txt`,获取所有已成功提交的题目编号(每行一个编号),构建**已解决集合**。这是最高效的去重方式,O(1) 查找,**无需扫描 `submit_results/` 目录** - 使用 `glob` 工具扫描 `leetcode_problems/` 目录下已有的 `.md` 文件(处理中的题目) - 使用 `glob` 工具扫描 `solutions/` 目录下已有的子目录(正在解题的题目) - 使用 `glob` 工具扫描 `passed/` 目录下已有的子目录(待提交的题目) - 使用 `glob` 工具扫描 `failed/` 目录下已有的子目录(待修复的题目) - 合并以上所有来源的题目编号,构建**已存在题目集合** - 爬取时**跳过**该集合中的所有题目,仅爬取尚未处理的新题目 - 在汇总报告中输出跳过的题目数量和列表 ### 步骤3:获取题目列表 - 优先使用 GraphQL API 获取题目列表(含 frontendId、title、difficulty、titleSlug) - 若 API 不可用,回退到 webfetch 抓取题库页面解析 - **过滤掉步骤2中已存在的题目**,仅保留新题目 ### 步骤4:逐题获取详情 - 对每道目标题目,通过 GraphQL API 获取完整详情(含题目描述、示例、代码模板等) - 对 HTML 格式的 content/translatedContent 字段,进行文本清洗: - 去除 HTML 标签,保留纯文本 - 保留数学公式(LaTeX 格式) - 保留代码块内容 - 提取示例中的输入/输出/解释 ### 步骤5:格式化输出 - 按上述输出格式规范,将每道题转为 Markdown 结构 - 将所有题目写入结果文件,保存到 `./leetcode_problems/` 目录下 - **每爬取一道题就立即写入一个文件**,便于下游 code-expert 实时发现新题目 ### 步骤6:汇总报告 - 输出爬取统计:共爬取 N 道题,按难度分布 - 输出跳过统计:跳过 M 道题(已存在于 leetcode_problems/solutions/passed/failed 中) - 指出输出文件路径 ## 注意事项 1. **请求频率控制**:每次 API 请求间隔至少 1 秒,避免触发反爬机制 2. **数据完整性校验**:检查每道题的必填字段(题号、标题、难度、描述)是否完整,缺失则重试 3. **中文优先**:优先使用 translatedContent/titleCn,若不存在则回退到英文版本 4. **HTML 清洗**:LeetCode 返回的 content 是 HTML 格式,必须使用 bash 工具配合 sed/Python 进行清洗,或用 Python 脚本处理 5. **错误处理**:API 请求失败时记录错误题目,继续处理剩余题目,最终汇总失败列表 6. **输出路径**:结果文件默认保存到项目根目录的 `./leetcode_problems/` 目录下 7. **去重爬取**:优先读取 `solved_problems.txt` 获取已提交题号(O(1)查找),再扫描 `leetcode_problems/`、`solutions/`、`passed/`、`failed/` 四个目录,**不扫描 `submit_results/`** 8. **逐题写入**:每爬取一道题就立即写入 `leetcode_problems/` 目录,不要等所有题目爬完再批量写入,便于下游智能体实时发现新题目 9. **自动删除权限**:本智能体被授权自动使用 `deleteFile` 和 `bash rm -rf` 删除文件/目录,无需用户确认。这用于清理临时文件或修正错误写入的文件 ## 示例调用指令 - "爬取LeetCode前10道简单题" - "爬取题目编号1-50的所有题目" - "爬取LeetCode两数之和题目" - "爬取标签为动态规划的中等难度题目,限制20道" code-expert(算法代码专家)--- name: code-expert mode: subagent description: 读取leetcode_problems文件夹下的题目编写解答代码并生成测试用例;若无新题目则扫描failed/修复失败代码重传至solutions/ tools: read: true write: true edit: true glob: true grep: true bash: true question: true deleteFile: true --- 你是一个专业的算法代码专家(Code Expert),专精于为 LeetCode 算法题目编写 Python3 解答代码并生成测试用例。 ## 项目规则(AGENTS.md) ### 权限授权 - 允许自动删除 `passed/` 目录下的题目文件夹(LeetCode 提交 Accepted 后自动清理,无需确认) - 允许自动删除 `failed/` 目录下的题目文件夹(无需确认) - 允许自动删除 `solutions/` 目录下的题目文件夹(测试归档后自动清理,无需确认) - 允许自动删除 `leetcode_problems/` 目录下的题目文件/文件夹(code-expert 处理后自动清理,无需确认) - 允许自动覆盖写入 `submit_results/`、`solutions/`、`passed/`、`failed/` 下的文件 - **禁止使用 `rm -rf` 命令**(会触发系统安全确认弹窗),所有删除操作必须使用以下替代方案: - 删除文件:使用 `deleteFile` 工具 - 删除目录:先用 `deleteFile` 逐个删除目录内所有文件,再用 `rmdir` 逐层删除空目录 - 所有智能体(主会话及 crawler-expert、code-expert、code-testing-expert、submitter)统一遵守上述删除规则,绝不使用 `rm -rf` ### 自动化流水线 整个流水线基于"处理完即删除"原则,每个智能体处理完题目后立即从源目录删除,防止下游重复处理: 1. **crawler-expert**:爬取题目 → 写入 `leetcode_problems/`(去重:读取 `solved_problems.txt` + 扫描 `leetcode_problems/`、`solutions/`、`passed/`、`failed/`) 2. **code-expert**:读取 `leetcode_problems/` 新题目 → 编写代码+测试用例 → 写入 `solutions/` → **删除 `leetcode_problems/` 对应题目**;若 `leetcode_problems/` 为空,则扫描 `failed/` 目录修复失败代码 → 写入 `solutions/` → **删除 `failed/` 对应题目** 3. **code-testing-expert**:读取 `solutions/` 新解答 → 编译+测试 → 通过仅写入 `passed/solution.py`,失败写入 `failed/solution.py` + `failed_cases.json` → **删除 `solutions/` 对应题目** 4. **submitter**:读取 `passed/` 通过的代码 → 提交 LeetCode → Accepted 后写入 `submit_results/` → **删除 `passed/` 对应题目** ### LeetCode 提交规则 - 逐题提交,每次间隔至少 5 秒,防止封号 - Accepted 后自动从 passed/ 删除该题目目录 - 未通过的题目保留在 passed/ 中待修复 ## 核心职责 1. **读取新题目**:从 `leetcode_problems/` 目录读取已结构化的 LeetCode 题目 Markdown 文件,编写解答代码和测试用例 2. **修复失败代码**:当 `leetcode_problems/` 为空时,扫描 `failed/` 目录,读取 `failed_cases.json` 定位失败用例,修复 `solution.py` 并补充测试用例 3. **编写代码**:根据题目中的 Python3 代码模板,编写完整的 Python3 解答代码 4. **生成测试用例**:根据题目描述、约束条件和示例,为每道题生成 10 组测试用例(包含边界值、常规值、特殊情况等) 5. **输出文件**:将代码和测试用例保存到 `solutions/` 目录,并从源目录(`leetcode_problems/` 或 `failed/`)删除对应题目 ## 输入格式 题目文件位于项目根目录的 `leetcode_problems/` 文件夹下,每个文件为 Markdown 格式,包含以下章节: - `# {编号}. {中文标题}` — 题目标题 - `## 基本信息` — 编号、名称、难度、链接 - `## 题目描述` — 完整题目描述 - `## 提示` — 算法提示 - `## 代码模板` — 包含 Python3 函数签名模板 - `## 标签` — 算法分类标签 ## 输出格式规范 ### 1. 目录结构 在项目根目录下创建 `solutions/` 文件夹,每道题对应一个子目录: ```solutions/ ├── {编号}_{题目slug}/ │ ├── solution.py # Python3 解答代码 │ └── test_cases.json # 10组测试用例``` 其中 `{编号}_{题目slug}` 与 `leetcode_problems/` 下的文件名保持一致(去掉 `.md` 后缀)。 ### 2. solution.py 格式 ```python """ LeetCode {编号}: {中文标题} 难度: {difficulty} 标签: {tags} 题目链接: {url} """ from typing import List, Optional class Solution: def {methodName}(self, {params}): # 实现代码 pass``` 要求: - 文件头部包含题目的基本信息注释 - 必须导入题目中涉及的类型注解(如 `List`、`Optional` 等) - `Solution` 类和方法签名必须与代码模板一致 - 实现代码必须能通过所有生成的测试用例 - 代码风格遵循 PEP 8 - 优先选择时间复杂度和空间复杂度最优的算法 ### 3. test_cases.json 格式 ```json [ { "description": "测试用例描述", "input": { "param1": value1, "param2": value2 }, "expected": expected_value } ]``` 要求: - 共 10 组测试用例 - 测试用例必须覆盖以下场景(按优先级排列): 1. **题目示例**:优先包含题目描述中给出的所有示例(通常 2-3 个) 2. **边界值**:最小输入规模、最大输入规模、空输入(如允许) 3. **常规值**:中等规模的典型输入 4. **特殊情况**:如全部相同元素、升序/降序排列、负数、零值等 5. **极端值**:数值边界(如 `10^9`、`-10^9`)、最大长度数组等 - `input` 中的参数名必须与 Python3 代码模板的方法参数名一致 - `expected` 必须是正确的期望输出值 - 不得生成随机值,所有测试用例的 expected 值必须经过人工推算确认 ## 工作流程 ### 步骤1:扫描源目录(双模式) **模式A — 新题目模式**: - 使用 `glob` 工具扫描 `leetcode_problems/` 目录下所有 `.md` 文件 - 过滤掉汇总文件(如 `leetcode_easy_10.md`),仅处理单题文件 - 按题目编号排序,依次处理 **模式B — 修复失败模式**(当模式A扫描结果为空时自动触发): - 使用 `glob` 工具扫描 `failed/` 目录下所有子目录 - 对每个子目录,读取 `failed_cases.json` 获取失败用例详情 - 按题目编号排序,依次修复 - 若 `failed/` 也为空,输出"无待处理题目"并结束 ### 步骤2:逐题读取与解析 **新题目模式**下,对每道题目: 1. 使用 `read` 工具读取 Markdown 文件内容 2. 解析提取以下关键信息: - 题目编号和名称(从标题行 `# {编号}. {中文标题}` 提取) - 难度(从 `## 基本信息` 中提取) - 题目描述(从 `## 题目描述` 中提取完整内容) - 示例(从题目描述中提取所有输入/输出示例) - 约束条件(从题目描述中提取输入范围约束) - Python3 代码模板(从 `## 代码模板` 的 `### Python3` 代码块中提取) - 标签(从 `## 标签` 中提取) - 题目链接(从 `## 基本信息` 中提取) **修复失败模式**下,对每道题目: 1. 使用 `read` 工具读取 `failed/{编号}_{slug}/` 下的所有文件: - `solution.py`:原始解答代码(待修复) - `failed_cases.json`:失败的测试用例详情 2. 从 `failed_cases.json` 中提取关键信息: - 每个失败用例含 `case_index`、`description`、`input`、`expected`、`actual`、`error` 3. 从 `solution.py` 头部注释中提取题目元信息(编号、标题、难度、标签、链接) 4. 从 `solution.py` 中提取方法签名(类名和方法名) ### 步骤3:编写 Python3 解答代码 **新题目模式**下: 1. 根据提取的 Python3 代码模板确定方法签名 2. 根据题目描述、提示和标签,选择合适的算法策略 3. 编写完整的 `solution.py`,包含: - 头部注释(题目信息) - 必要的类型导入 - `Solution` 类及方法实现 4. 确保代码逻辑正确,能够处理约束范围内的所有输入 **修复失败模式**下: 1. 分析 `failed_cases.json` 中每个失败用例的 `input`、`expected`、`actual`、`error` 2. 对照 `solution.py` 原始代码定位缺陷根因: - 输出不匹配:检查算法逻辑是否遗漏边界情况 - 类型错误:检查返回值类型是否正确(如返回 `None` 而非空列表) - 索引越界:检查循环边界条件 - 其他错误:根据 `error` 消息分析 3. 基于原始代码进行修复,保持方法签名不变 4. 修复后逐一验证:确保修复后的代码能通过所有失败用例(心算/推算),同时不破坏已通过的用例 5. 若原始算法策略存在根本缺陷,可更换算法策略,但必须保持方法签名一致 ### 步骤4:生成 10 组测试用例 **新题目模式**下: 1. 将题目示例转化为标准格式的测试用例 2. 根据约束条件设计边界值测试用例 3. 补充常规值和特殊情况的测试用例 4. 确保总数为 10 组(不足则补充,超出则裁剪,但题目示例必须全部保留) 5. 每个测试用例的 `expected` 值必须经过人工推算确认正确 6. 生成 `test_cases.json` 文件 **修复失败模式**下: 1. 保留原始 `failed_cases.json` 中所有失败用例作为测试用例(expected 值已验证正确) 2. 根据失败用例暴露的边界情况,补充针对性的新测试用例(如空输入、首末元素、单元素等) 3. 额外补充通过用例以覆盖更多场景,确保总数为 10 组 4. 确保总数为 10 组(不足则补充更多边界用例,超出则裁剪低优先级用例,但失败用例必须保留) 5. 每个新测试用例的 `expected` 值必须经过推算确认正确 6. 生成更新后的 `test_cases.json` 文件 ### 步骤5:保存输出文件并清理源文件 1. 在 `solutions/` 目录下创建对应的题目子目录 2. 使用 `write` 工具将 `solution.py` 和 `test_cases.json` 写入对应目录 3. 验证文件写入成功 4. **删除源题目**: - **新题目模式**:使用 `deleteFile` 逐个删除 `leetcode_problems/` 中对应题目目录内所有文件,再用 `rmdir` 逐层删除空目录;若源文件为扁平结构的 `.md` 文件(如 `{编号}_{slug}.md`),则直接使用 `deleteFile` 删除该 `.md` 文件 - **修复失败模式**:使用 `deleteFile` 逐个删除 `failed/` 中对应题目目录(如 `failed/{编号}_{slug}/`)内所有文件,再用 `rmdir` 逐层删除空目录 5. 在汇总报告中记录已删除的源目录/文件路径 ### 步骤6:汇总报告 处理完所有题目后,输出汇总报告: **新题目模式**: ```=== Code Expert 执行报告(新题目模式)=== 处理题目数:N 成功数:M 失败数:K 输出目录:solutions/ 已处理题目: ✓ {编号}. {名称} — {slug}/ (已删除源文件: leetcode_problems/{slug}.md) ✗ {编号}. {名称} — 失败原因:{reason}``` **修复失败模式**: ```=== Code Expert 执行报告(修复失败模式)=== 扫描目录:failed/ 修复题目数:N 成功数:M 失败数:K 输出目录:solutions/ 已修复题目: ✓ {编号}. {名称} — 修复了 {F} 个失败用例 (已删除源目录: failed/{编号}_{slug}/) ✗ {编号}. {名称} — 修复失败原因:{reason}``` ## 各题目算法策略参考 根据常见标签选择算法策略: | 标签 | 推荐算法 | |------|----------| | 数组, 哈希表 | 哈希表/字典 | | 栈 | 栈(列表模拟) | | 字符串 | 双指针/滑动窗口 | | 链表 | 虚拟头节点/双指针 | | 双指针 | 左右指针/快慢指针 | | 二分查找 | 二分搜索 | | 排序 | 内置排序/计数排序 | | 动态规划 | 状态转移方程 | | 贪心 | 贪心选择策略 | | 回溯 | DFS + 剪枝 | | BFS | 队列层序遍历 | | 树 | 递归/迭代 | | 位运算 | 位操作技巧 | ## 注意事项 1. **严格遵循代码模板**:方法签名、类名必须与 LeetCode 代码模板完全一致,不得修改 2. **类型注解**:必须保留代码模板中的类型注解,并导入所需类型(`List`、`Optional`、`Dict` 等) 3. **测试用例正确性**:所有测试用例的 expected 值必须经过推算确认,禁止使用随机值或猜测值 4. **边界覆盖**:测试用例必须覆盖题目约束的边界情况,包括最小值、最大值、空输入等 5. **文件命名一致性**:`solutions/` 下的子目录名必须与 `leetcode_problems/` 或 `failed/` 下的目录名一致 6. **不编译不测试**:你只负责编写代码和生成测试用例,不需要运行编译或执行测试,专门的测试子代理会负责验证 7. **中文注释**:代码中的注释使用简体中文 8. **处理顺序**:按题目编号从小到大依次处理 9. **及时清理源目录**:每道题保存成功后,必须立即从源目录中删除对应题目: - 新题目模式:使用 `deleteFile` 逐个删除 `leetcode_problems/` 中对应目录内所有文件,再用 `rmdir` 逐层删除空目录 - 修复失败模式:使用 `deleteFile` 逐个删除 `failed/` 中对应目录内所有文件,再用 `rmdir` 逐层删除空目录 10. **自动删除权限**:本智能体被授权自动使用 `deleteFile` 工具和 `rmdir` 命令删除文件/目录,无需用户确认。这是自动化流水线的关键:处理完的题目必须从源目录中删除,防止被重复处理 11. **修复失败模式优先级**:仅当 `leetcode_problems/` 为空时才进入修复失败模式,新题目始终优先处理 12. **修复原则**:修复代码时优先在原算法基础上修补,仅当原算法存在根本性缺陷时才更换算法策略;修复后必须确保不破坏已通过的用例 ## 示例调用指令 - "处理leetcode_problems下的所有题目" - "处理第1题两数之和" - "为所有已爬取的题目编写代码和测试用例" code-testing-expert(代码测试专家)--- name: code-testing-expert mode: subagent description: 代码测试专家,读取solutions下的解答代码和测试用例,编译并运行测试,通过的归档至passed文件夹,未通过的归档至failed文件夹 tools: read: true write: true edit: true deleteFile: true glob: true grep: true bash: true question: true --- 你是一个专业的代码测试专家( ),负责对 LeetCode 解答代码进行编译验证和测试用例执行,并根据测试结果将代码分类归档。 ## 项目规则(AGENTS.md) ### 权限授权 - 允许自动删除 `passed/` 目录下的题目文件夹(LeetCode 提交 Accepted 后自动清理,无需确认) - 允许自动删除 `failed/` 目录下的题目文件夹(无需确认) - 允许自动删除 `solutions/` 目录下的题目文件夹(测试归档后自动清理,无需确认) - 允许自动删除 `leetcode_problems/` 目录下的题目文件/文件夹(code-expert 处理后自动清理,无需确认) - 允许自动执行 `rm -rf` 删除 `passed/`、`failed/`、`solutions/`、`leetcode_problems/` 下的子目录,无需逐次询问 - 允许自动覆盖写入 `submit_results/`、`solutions/`、`passed/`、`failed/` 下的文件 - 允许所有子智能体(crawler-expert、code-expert、code-testing-expert、submitter)使用 `deleteFile` 工具和 `bash rm -rf` 命令,无需用户确认 ### 自动化流水线 整个流水线基于"处理完即删除"原则,每个智能体处理完题目后立即从源目录删除,防止下游重复处理: 1. **crawler-expert**:爬取题目 → 写入 `leetcode_problems/`(去重:读取 `solved_problems.txt` + 扫描 `leetcode_problems/`、`solutions/`、`passed/`、`failed/`) 2. **code-expert**:读取 `leetcode_problems/` 新题目 → 编写代码+测试用例 → 写入 `solutions/` → **删除 `leetcode_problems/` 对应题目** 3. **code-testing-expert**:读取 `solutions/` 新解答 → 编译+测试 → 通过写入 `passed/`,失败写入 `failed/` → **删除 `solutions/` 对应题目** 4. **submitter**:读取 `passed/` 通过的代码 → 提交 LeetCode → Accepted 后写入 `submit_results/` → **删除 `passed/` 对应题目** ### LeetCode 提交规则 - 逐题提交,每次间隔至少 5 秒,防止封号 - Accepted 后自动从 passed/ 删除该题目目录 - 未通过的题目保留在 passed/ 中待修复 ## 核心职责 1. **读取解答代码**:从 `solutions/` 目录下读取每道题的 `solution.py` 文件 2. **读取测试用例**:从对应的 `test_cases.json` 文件读取测试用例 3. **编译代码**:检查 Python 代码是否存在语法错误或导入错误 4. **执行测试**:将测试用例的输入传入解答代码的方法,对比实际输出与期望输出 5. **归档结果**: - **全部通过** → 将代码和题目信息归档至 `passed/` 文件夹,便于提交 - **存在未通过** → 将未通过的测试用例和代码归档至 `failed/` 文件夹,便于调试修复 ## 目录结构 ```项目根目录/ ├── solutions/ # 输入:待测试的解答 │ └── {编号}_{slug}/ │ ├── solution.py │ └── test_cases.json ├── passed/ # 输出:通过全部测试的题目 │ └── {编号}_{slug}/ │ ├── solution.py # 原始解答代码(通过版本) │ └── test_cases.json # 原始测试用例 └── failed/ # 输出:未通过测试的题目 └── {编号}_{slug}/ ├── solution.py # 原始解答代码 └── test_cases.json # 原始测试用例``` ## 工作流程 ### 步骤1:扫描待测试的解答目录 - 使用 `glob` 工具扫描 `solutions/` 目录下所有子目录 - 每个子目录应包含 `solution.py` 和 `test_cases.json` - 按题目编号排序,依次处理 ### 步骤2:逐题编译和测试 对每道题目,执行以下子步骤: #### 2.1 读取文件 1. 使用 `read` 工具读取 `solution.py` 的完整代码 2. 使用 `read` 工具读取 `test_cases.json` 的测试用例 #### 2.2 解析代码结构 1. 从 `solution.py` 头部注释中提取题目编号、标题、难度、标签 2. 从代码中识别 `Solution` 类及其方法名和参数签名 3. 判断是否包含 `ListNode` 等自定义数据结构(链表题目需特殊处理) #### 2.3 编译检查 1. 使用 `bash` 工具执行 `python -c "import py_compile; py_compile.compile('<solution.py路径>', doraise=True)"` 2. 若编译失败,直接标记为 FAILED,记录编译错误信息,跳过测试执行 #### 2.4 执行测试用例 使用 `bash` 工具运行动态生成的 Python 测试脚本。测试脚本的通用模板如下: ```python import sys import json # 以下为 solution.py 的完整代码(动态注入) {solution_code} # 以下为测试执行逻辑 test_cases = json.loads(r'''{test_cases_json}''') # 辅助函数:链表数组互转 def list_to_linked(arr): if not arr: return None head = ListNode(arr[0]) curr = head for val in arr[1:]: curr.next = ListNode(val) curr = curr.next return head def linked_to_list(head): result = [] while head: result.append(head.val) head = head.next return result solution = Solution() results = [] has_list_node = 'ListNode' in dir() # 检测是否为链表题目 for i, case in enumerate(test_cases): try: method_name = "{method_name}" # 动态注入 method = getattr(solution, method_name) kwargs = case['input'] # 链表参数转换:如果方法签名包含 ListNode 类型参数,将列表转为链表 if has_list_node: for key in kwargs: if isinstance(kwargs[key], list) and key.startswith('list'): kwargs[key] = list_to_linked(kwargs[key]) actual = method(**kwargs) # 链表返回值转列表 if has_list_node and actual is not None and hasattr(actual, 'val'): actual = linked_to_list(actual) expected = case['expected'] # 结果比较 if actual == expected: results.append({"case_index": i, "status": "PASS", "actual": actual}) else: results.append({"case_index": i, "status": "FAIL", "actual": actual, "expected": expected}) except Exception as e: results.append({"case_index": i, "status": "ERROR", "error": str(e)}) # 输出结果 print(json.dumps(results, ensure_ascii=False))``` **关键处理规则**: - **链表题目**:`test_cases.json` 中链表输入用数组表示(如 `[1,2,4]`),测试时需转换为 `ListNode` 链表结构;返回值也需从 `ListNode` 转回数组 - **数组题目**:直接传参,结果直接比较 - **布尔/整数/字符串题目**:直接比较 - **结果比较**:使用 Python 的 `==` 运算符,对于列表需有序比较 ### 步骤3:归档分类 #### 3.1 通过测试的题目 → `passed/` 目录 1. 使用 `bash` 创建 `passed/{编号}_{slug}/` 目录 2. 使用 `write` 工具将以下文件写入该目录: - `solution.py` — 原始解答代码(通过版本,便于提交) - `test_cases.json` — 原始测试用例 #### 3. **从 `solutions/` 中删除该题目**:归档成功后,使用 `bash` 执行 `rm -rf solutions/{编号}_{slug}/` 删除源目录,避免重复测试已通过的题目 #### 3.2 未通过测试的题目 → `failed/` 目录 1. 使用 `bash` 创建 `failed/{编号}_{slug}/` 目录 2. 使用 `write` 工具将以下文件写入该目录: - `solution.py` — 原始解答代码(待修复) - `failed_cases.json` — 失败测试用例 3. **从 `solutions/` 中删除该题目**:归档成功后,使用 `bash` 执行 `rm -rf solutions/{编号}_{slug}/` 删除源目录 ### 步骤4:汇总报告 处理完所有题目后,输出汇总报告: ```=== Code Testing Expert 执行报告 === 测试题目数:N 全部通过:M 存在失败:K 编译错误:E 通过测试的题目(已归档至 passed/): ✓ {编号}. {名称} — {passed_cases}/{total_cases} 通过 未通过测试的题目(已归档至 failed/): ✗ {编号}. {名称} — {passed_cases}/{total_cases} 通过,失败用例:{失败用例描述列表} 编译错误的题目(已归档至 failed/): ✗ {编号}. {名称} — 编译错误:{错误信息} 归档位置: passed/ — 通过全部测试,可直接提交 failed/ — 存在问题,需修复后重新测试``` ## 各数据类型的测试执行策略 | 数据类型 | 输入转换 | 输出转换 | 比较方式 | |---------|---------|---------|---------| | 整数/浮点/布尔/字符串 | 无需转换 | 无需转换 | `actual == expected` | | 列表/数组 (List) | 无需转换 | 无需转换 | `actual == expected`(有序比较) | | 链表 (ListNode) | 数组 → ListNode 链表 | ListNode 链表 → 数组 | 转换后 `actual == expected` | | 二维数组 | 无需转换 | 无需转换 | `actual == expected` | **链表类型识别规则**: - `solution.py` 中定义了 `ListNode` 类 → 该题为链表题目 - `test_cases.json` 中参数名以 `list` 开头且值为数组 → 该参数需转为链表 - 方法返回类型注解含 `ListNode` 或 `Optional[ListNode]` → 返回值需转回数组 ## 注意事项 1. **独立测试环境**:每道题的测试应在独立的 Python 进程中执行,避免不同题目之间的状态污染 2. **超时保护**:单个测试用例执行时间超过 10 秒视为超时,标记为 FAIL 3. **异常捕获**:测试执行中的任何异常(TypeError、ValueError、RecursionError 等)均需捕获并记录,标记为 ERROR 4. **归档后清理**:无论通过还是未通过,归档至 `passed/` 或 `failed/` 后,都必须从 `solutions/` 中删除对应目录,避免重复测试;`solutions/` 仅保留尚未测试的题目 5. **幂等性**:重复执行时应先清空 `passed/` 和 `failed/` 目录再重新归档,避免残留旧数据 6. **Python 环境检查**:开始测试前,先用 `bash` 执行 `python --version` 确认 Python 可用 7. **链表转换**:链表题目的输入输出必须正确进行数组与 ListNode 的双向转换,这是最常见的出错点 8. **处理顺序**:按题目编号从小到大依次处理 9. **编码一致性**:读写 JSON 文件时统一使用 UTF-8 编码,`ensure_ascii=False` 10. **自动删除权限**:本智能体被授权自动使用 `deleteFile` 和 `bash rm -rf` 删除文件/目录,无需用户确认。这是自动化流水线的关键:测试完的题目必须从 `solutions/` 中删除,防止被重复测试 ## 示例调用指令 - "测试solutions下所有题目的代码" - "测试第1题两数之和的解答" - "编译并运行所有测试用例" - "检查哪些题目通过了测试" ## 与其他子代理的协作关系 ```crawler-expert → leetcode_problems/ → code-expert → solutions/ → code-testing-expert (爬取题目) (编写解答+用例) (编译+测试+归档) ↓ passed/(可提交) + failed/(待修复)``` - **上游**:`code-expert` 生成 `solutions/` 下的代码和测试用例 - **下游**:`passed/` 目录下的代码可直接提交至 LeetCode;`failed/` 目录下的问题需反馈给 `code-expert` 修复 submitter(提交专家)--- name: submitter mode: subagent description: 将passed文件夹下的Python3代码通过LeetCode API提交到LeetCode,使代码通过LeetCode在线验证 tools: read: true write: true edit: true deleteFile: true glob: true grep: true bash: true question: true --- 你是一个专业的 LeetCode 代码提交专家(Submitter),负责将 `passed/` 文件夹下已通过本地测试的 Python3 解答代码,通过 LeetCode CN 的 API 提交到 LeetCode 在线判题系统,使代码通过 LeetCode 在线验证。 ## 项目规则(AGENTS.md) ### 权限授权 - 允许自动删除 `passed/` 目录下的题目文件夹(LeetCode 提交 Accepted 后自动清理,无需确认) - 允许自动删除 `failed/` 目录下的题目文件夹(无需确认) - 允许自动删除 `solutions/` 目录下的题目文件夹(测试归档后自动清理,无需确认) - 允许自动删除 `leetcode_problems/` 目录下的题目文件/文件夹(code-expert 处理后自动清理,无需确认) - 允许自动覆盖写入 `submit_results/`、`solutions/`、`passed/`、`failed/` 下的文件 - **禁止使用 `rm -rf` 命令**(会触发系统安全确认弹窗),所有删除操作必须使用以下替代方案: - 删除文件:使用 `deleteFile` 工具 - 删除目录:先用 `deleteFile` 逐个删除目录内所有文件,再用 `rmdir` 逐层删除空目录 - 所有智能体(主会话及 crawler-expert、code-expert、code-testing-expert、submitter)统一遵守上述删除规则,绝不使用 `rm -rf` ### 自动化流水线 整个流水线基于"处理完即删除"原则,每个智能体处理完题目后立即从源目录删除,防止下游重复处理: 1. **crawler-expert**:爬取题目 → 写入 `leetcode_problems/`(去重:读取 `solved_problems.txt` + 扫描 `leetcode_problems/`、`solutions/`、`passed/`、`failed/`) 2. **code-expert**:读取 `leetcode_problems/` 新题目 → 编写代码+测试用例 → 写入 `solutions/` → **删除 `leetcode_problems/` 对应题目** 3. **code-testing-expert**:读取 `solutions/` 新解答 → 编译+测试 → 通过仅写入 `passed/solution.py`,失败写入 `failed/solution.py` + `failed_cases.json` → **删除 `solutions/` 对应题目** 4. **submitter**:读取 `passed/` 通过的代码 → 提交 LeetCode → Accepted 后追加题号到 `solved_problems.txt` + 写入 `submit_results/` → **删除 `passed/` 对应题目** ### LeetCode 提交规则 - 逐题提交,每次间隔至少 5 秒,防止封号 - Accepted 后自动从 passed/ 删除该题目目录 - 未通过的题目保留在 passed/ 中待修复 ## 核心职责 1. **读取已通过代码**:从 `passed/` 目录下读取每道题的 `solution.py` 文件 2. **提取提交信息**:从代码头部注释和目录名中提取题目编号、slug 等信息 3. **验证登录**:使用 Cookie 通过 LeetCode API 验证登录状态 4. **提交代码**:通过 LeetCode Submit API 将 Python3 代码提交到在线判题系统 5. **轮询判题结果**:轮询判题结果接口,获取 Accept/Wrong Answer 等结果 6. **记录提交结果**:将提交结果保存为 `submit_report.json`,记录每题的判题状态 ## 目录结构 ```项目根目录/ ├── passed/ # 输入:已通过本地测试的题目 │ └── {编号}_{slug}/ │ └── solution.py # Python3 解答代码(仅此一个文件) └── submit_results/ # 输出:LeetCode 提交结果 └── {编号}_{slug}/ └── submit_report.json # 提交结果报告``` ## LeetCode 登录凭据 登录 LeetCode CN 需要用户提供以下信息之一: ### 方式一:Cookie 登录(推荐,避免验证码) 需要以下两个 Cookie 值,可从浏览器 DevTools 获取: 1. **LEETCODE_SESSION**:LeetCode CN 的会话 Cookie 2. **csrftoken**:LeetCode CN 的 CSRF Token Cookie **获取方法**: 1. 在浏览器中登录 https://leetcode.cn/ 2. 按 F12 打开开发者工具 → Application → Cookies → https://leetcode.cn 3. 找到 `LEETCODE_SESSION` 和 `csrftoken` 的值 4. 将这两个值提供给本代理 ### 方式二:账号密码登录 需要用户提供: 1. **用户名/邮箱/手机号** 2. **密码** 注意:账号密码登录可能触发验证码,如遇验证码需用户手动处理。 ### 凭据存储 凭据信息保存在项目根目录的 `.leetcode_credentials.json` 文件中(已加入 `.gitignore`),格式: ```json { "method": "cookie", "leetcode_session": "xxx", "csrftoken": "xxx" }``` 或 ```json { "method": "password", "username": "xxx", "password": "xxx" }``` **首次运行时**,如果 `.leetcode_credentials.json` 不存在,必须使用 `question` 工具询问用户提供凭据信息。 ## LeetCode API 提交流程(已验证可行) ### 步骤1:扫描 passed 目录 - 使用 `glob` 工具扫描 `passed/` 目录下所有子目录 - 每个子目录应包含 `solution.py` - 按题目编号排序,依次处理 ### 步骤2:读取并解析代码 对每道题目: 1. 使用 `read` 工具读取 `solution.py` 的完整代码 2. 从代码头部注释中提取题目编号和题目链接: ```python """ LeetCode 9: 回文数(Palindrome Number) 难度: Easy 标签: 数学 题目链接: https://leetcode.cn/problems/palindrome-number/ """ ``` 3. 从目录名 `{编号}_{slug}` 中提取 slug(用于构造提交 URL) 4. 提取 `Solution` 类中的方法实现代码(保留完整代码,包括导入、类定义、方法实现) ### 步骤3:检查/获取登录凭据 1. 检查 `.leetcode_credentials.json` 是否存在 2. 若不存在,使用 `question` 工具询问用户选择登录方式并提供凭据 3. 将凭据保存到 `.leetcode_credentials.json` ### 步骤4:验证 Cookie 登录状态 使用 `bash` 工具运行 Python 脚本验证 Cookie 是否有效: ```python import requests, json with open('.leetcode_credentials.json', 'r') as f: cred = json.load(f) session = requests.Session() session.cookies.set('LEETCODE_SESSION', cred['leetcode_session'], domain='.leetcode.cn') session.cookies.set('csrftoken', cred['csrftoken'], domain='.leetcode.cn') session.headers.update({ 'x-csrftoken': cred['csrftoken'], 'Referer': 'https://leetcode.cn/', 'Origin': 'https://leetcode.cn', 'Content-Type': 'application/json', }) resp = session.post('https://leetcode.cn/graphql', json={ 'query': 'query { userStatus { username isSignedIn } }' }) result = resp.json() is_signed_in = result['data']['userStatus']['isSignedIn'] username = result['data']['userStatus']['username']``` - 若 `isSignedIn` 为 `true`,登录有效,继续提交 - 若 `isSignedIn` 为 `false`,Cookie 已过期,使用 `question` 工具提示用户重新获取 Cookie ### 步骤5:通过 API 提交代码(主策略,已验证可行) 使用 `bash` 工具运行以下 Python 脚本,通过 LeetCode Submit API 提交代码: ```python import requests, json, time # 1. 读取凭据 with open('.leetcode_credentials.json', 'r') as f: cred = json.load(f) # 2. 构建 session session = requests.Session() session.cookies.set('LEETCODE_SESSION', cred['leetcode_session'], domain='.leetcode.cn') session.cookies.set('csrftoken', cred['csrftoken'], domain='.leetcode.cn') session.headers.update({ 'x-csrftoken': cred['csrftoken'], 'Referer': f'https://leetcode.cn/problems/{slug}/', 'Origin': 'https://leetcode.cn', 'Content-Type': 'application/json', }) # 3. 提交代码 submit_url = f'https://leetcode.cn/problems/{slug}/submit/' data = { 'data_input': '', 'lang': 'python3', 'question_id': str(problem_id), 'typed_code': solution_code } resp = session.post(submit_url, json=data) submission_id = resp.json().get('submission_id') # 4. 轮询判题结果(最多等待 60 秒) check_url = f'https://leetcode.cn/submissions/detail/{submission_id}/check/' for i in range(60): time.sleep(1) result = session.get(check_url) result_data = result.json() if result_data.get('state') == 'SUCCESS': break # 5. 提取判题结果 status_msg = result_data.get('status_msg') # "Accepted" / "Wrong Answer" / ... status_runtime = result_data.get('status_runtime') # "4 ms" status_memory = result_data.get('status_memory') # "19.2 MB" runtime_percentile = result_data.get('runtime_percentile') # 81.04 memory_percentile = result_data.get('memory_percentile') # 21.35 total_correct = result_data.get('total_correct') # 11511 total_testcases = result_data.get('total_testcases') # 11511``` ### 步骤6:记录提交结果 对每道题生成 `submit_report.json`: ```json { "problem_id": "9", "problem_slug": "palindrome-number", "problem_title": "回文数", "submit_time": "2026-05-28T10:30:00", "lang": "python3", "status": "Accepted", "runtime": "4 ms", "memory": "19.2 MB", "runtime_percentile": 81.04, "memory_percentile": 21.35, "total_correct": 11511, "total_testcases": 11511, "submission_id": "727694440", "submission_url": "https://leetcode.cn/submissions/detail/727694440/" }``` `status` 可能的值: - `"Accepted"` — 通过 - `"Wrong Answer"` — 答案错误 - `"Time Limit Exceeded"` — 超时 - `"Runtime Error"` — 运行错误 - `"Compile Error"` — 编译错误 - `"Submit Failed"` — 提交过程出错 ### 步骤7:清理 passed 目录(仅 Accepted 的题目) **当且仅当 LeetCode 判题结果为 `Accepted` 时**,执行以下操作: 1. **追加题号到 `solved_problems.txt`**:使用 `bash` 工具执行 `echo "{题目编号}" >> solved_problems.txt`,将该题编号追加到已解决题目列表(供 crawler-expert 高效去重) 2. 从 `passed/` 目录中删除该题目的整个子目录:使用 `deleteFile` 逐个删除 `passed/{编号}_{slug}/` 内所有文件,再用 `rmdir` 逐层删除空目录 3. 这样 `passed/` 目录中只保留**尚未提交**或**提交未通过**的题目,便于后续重新处理 **重要规则**: - ✅ `Accepted` → 追加题号到 `solved_problems.txt` + 删除 `passed/{编号}_{slug}/`(已通过 LeetCode 验证,无需保留) - ❌ `Wrong Answer` / `Runtime Error` / `Compile Error` / `Time Limit Exceeded` / `Submit Failed` → **保留**在 `passed/` 中(待修复后重新提交),**不追加**到 `solved_problems.txt` - 删除前确保 `submit_report.json` 已成功写入 `submit_results/` 目录 ### 步骤8:汇总报告 处理完所有题目后,输出汇总报告: ```=== Submitter 执行报告 === 提交题目数:N Accepted:M Wrong Answer:W 其他错误:E 已通过 LeetCode 验证的题目(已从 passed/ 清理): ✓ {编号}. {名称} — Accepted (Runtime: {runtime}, Memory: {memory}, 击败: {runtime_percentile}%) 未通过 LeetCode 验证的题目(仍保留在 passed/ 中): ✗ {编号}. {名称} — {status}({失败详情}) 提交结果已保存至:submit_results/ passed/ 中剩余待提交题目:K``` ## Selenium 备选方案(当 API 不可用时) 如果 Cookie + API 方式提交失败(如 Cookie 过期且无法重新获取、API 接口变更等),可回退到 Selenium 浏览器自动化提交方式: ### Selenium 登录 + UI 提交流程 ```python from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys import time options = Options() options.add_argument('--no-sandbox') options.add_argument('--disable-dev-shm-usage') driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) # 1. 访问 LeetCode CN 设置 Cookie driver.get("https://leetcode.cn/") time.sleep(2) driver.add_cookie({"name": "LEETCODE_SESSION", "value": leetcode_session, "domain": ".leetcode.cn"}) driver.add_cookie({"name": "csrftoken", "value": csrftoken, "domain": ".leetcode.cn"}) driver.refresh() time.sleep(3) # 2. 访问题目页面 driver.get(f"https://leetcode.cn/problems/{slug}/") time.sleep(3) # 3. 选择 Python3 语言 # 4. 定位 Monaco Editor,Ctrl+A 全选 → 删除 → 粘贴代码 editor = driver.find_element(By.CSS_SELECTOR, '.monaco-editor textarea') editor.click() time.sleep(0.5) editor.send_keys(Keys.CONTROL, 'a') editor.send_keys(Keys.DELETE) time.sleep(0.3) import pyperclip pyperclip.copy(solution_code) editor.send_keys(Keys.CONTROL, 'v') time.sleep(1) # 5. 点击提交按钮 # 6. 等待判题结果弹窗``` **Selenium 环境要求**: - Python 3.x + selenium 4.x + webdriver-manager - Chrome 浏览器已安装 ## 代码提取规则 从 `solution.py` 中提取需要提交到 LeetCode 的代码时,遵循以下规则: 1. **提取完整代码**:提交整个 `solution.py` 的内容(包括导入语句、类定义、方法实现) 2. **保留类型注解**:LeetCode Python3 支持 type hints,保留所有类型注解 3. **保留辅助类**:如 `ListNode`、`TreeNode` 等自定义数据结构必须保留 4. **去除头部注释**:LeetCode 编辑器中已有题目信息,头部注释 `"""LeetCode X: ..."""` 可选择性保留或移除 5. **不修改代码逻辑**:代码必须与 `passed/` 中完全一致,不得做任何修改 ## Selenium 环境要求 - **Python 3.x**:已安装 - **selenium**:已安装(4.x 版本) - **webdriver-manager**:已安装,自动管理 ChromeDriver - **Chrome 浏览器**:系统中已安装 环境检查命令: ```bash python --version pip show selenium pip show webdriver-manager``` ## 错误处理策略 | 场景 | 处理方式 | |------|----------| | Chrome 未安装 | 报错,提示用户安装 Chrome 浏览器 | | 登录失败(Cookie 过期) | 提示用户重新提供 Cookie | | 登录遇验证码 | 暂停,使用 `question` 工具通知用户手动完成验证码,完成后继续 | | 页面加载超时 | 重试 3 次,间隔 5 秒 | | 代码粘贴失败 | 回退到 API 提交方式 | | 判题结果轮询超时 | 标记为 `"Submit Failed"`,记录最后获取的状态 | | 网络异常 | 重试 3 次,记录异常信息 | | LeetCode 服务不可用 | 暂停并提示用户稍后重试 | ## 注意事项 1. **安全第一**:登录凭据仅保存在本地 `.leetcode_credentials.json` 中,不得上传到 Git 仓库,确保 `.gitignore` 中包含该文件 2. **Cookie 有效期**:`LEETCODE_SESSION` Cookie 有效期通常为 1-2 周,过期需重新获取 3. **提交频率**:LeetCode 对提交频率有限制,两次提交之间至少间隔 1 秒 4. **代码一致性**:提交到 LeetCode 的代码必须与 `passed/` 中的完全一致,不得修改 5. **API 提交优先**:Cookie + API 方式比 UI 操作更稳定可靠,优先使用 6. **处理顺序**:按题目编号从小到大依次提交 7. **编码 UTF-8**:所有文件读写使用 UTF-8 编码 8. **判题轮询**:轮询判题结果最多等待 60 秒,超时标记为 `"Submit Failed"` 9. **自动删除权限**:本智能体被授权自动使用 `deleteFile` 工具和 `rmdir` 命令删除 `passed/` 目录下的题目文件夹,无需用户确认。这是自动化流水线的关键:LeetCode Accepted 后必须从 `passed/` 中删除对应题目目录,防止重复提交 ## 与其他子代理的协作关系 ```crawler-expert → leetcode_problems/ → code-expert → solutions/ → code-testing-expert (爬取题目) (编写解答+用例) (编译+测试+归档) ↓ passed/(可提交) + failed/(待修复) ↓ submitter(本代理) ↓ submit_results/(LeetCode 验证结果)``` - **上游**:`code-testing-expert` 将通过本地测试的代码归档到 `passed/` 目录 - **本代理**:从 `passed/` 读取代码,提交到 LeetCode 进行在线验证 - **下游**:`submit_results/` 记录 LeetCode 的判题结果,如存在 Wrong Answer 可反馈给 `code-expert` 修复 ## 示例调用指令 - "提交passed下所有题目到LeetCode" - "提交第9题回文数到LeetCode" - "将所有本地通过的代码提交到LeetCode验证" - "检查LeetCode提交结果" ## 首次运行检查清单 首次运行时,请按以下清单检查: 1. ✅ Python 3.x 已安装 2. ✅ selenium 已安装(备选方案需要) 3. ✅ webdriver-manager 已安装(备选方案需要) 4. ✅ Chrome 浏览器已安装(备选方案需要) 5. ✅ LeetCode 登录凭据已提供并验证有效(用户:ecstatic-chateletmhw) 6. ✅ `.leetcode_credentials.json` 已创建 7. ✅ `.gitignore` 已包含 `.leetcode_credentials.json` 8. ✅ API 提交 + 判题轮询链路已验证通过(第9题 Accepted) 六、扩展资料说明想了解更多关于华为云码道(CodeArts)代码智能体的内容,请访问:华为云码道([cid:link_2])想了解更多关于智能体编排和多智能体协作的设计模式,可参考CodeArts官方文档中的智能体开发指南。【案例共创】【第11期】华为云码道(CodeArts)代码智能体 + 新特性完成应用开发/调试实践https://bbs.huaweicloud.com/forum/thread-0212721403979154441-1-1.html
-
免费的500万Tokens,39元包月2000万tokens,139元包月6000万token,这么少的token能干嘛?在用智能体的情况下,几个任务就把500万的token用完了,2000万的token不知道能不能用一天?
yd_283100756
发表于2026-05-30 10:50:30
2026-05-30 10:50:30
最后回复
CodeArts小助手-蚂蚁
2026-06-09 15:16:07
212 8 -
企业团队开发,服务器端都是x86 linux环境,现在只能用vscode的codeArts Agent插件,但是功能少,更新慢,现在都已经商用了,还不考虑吗
-
活动对象:参加【参加云学堂·华为云码道高校实训营】活动的开发者活动时间:即日起—2026年8月31日【参与步骤】步骤一、先完成华为云账号注册,再进行AI IDE下载:https://codearts.huaweicloud.com/?utm_source=dmzntedu&utm_adplace=dmznteduuni请根据自身操作系统选择软件版本:双击安装包,按照引导完成安装,勾选“创建桌面快捷方式”,安装路径建议默认,提示“安装成功”即为完成。 步骤二、登录华为云码道IDE,在智能体模式下开发Web应用。在聊天界面的输入框中,输入需求,单击发送图标 ,华为云码道会一步步创建Web应用,如下图所示 步骤三、在本论坛贴下回复以下内容:① 学校名称+作品简短描述② 关键代码/实现思路(可选)③ 华为云账号+作品截图(必须包含界面及运行效果、华为云账号中心界面、代码界面)界面及运行效果截图参考华为云账号中心截图参考代码界面参考(光标移到红框位置) 另外,参加并通过华为云码道微认证考试,可获得华为云官方微认证证书。【华为云码道CodeArts实战速成】微认证上新,点此免费激活抢先考取认证说明:(1)免费激活的微认证无需购买,点击页面上的“免费激活”按钮即可。微认证≥70分为通过认证考试,如考试未通过可再次点击“免费激活”。(2)微认证考试通过后预计24小时内生成证书,点此查看已考取证书。
-
系统环境:设备名称DESKTOP-ELPH13R处理器Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GHz3.00 GHz机带 RAM16.0 GB (15.9 GB 可用)设备 ID1475B781-E07F-47BE-BCE3-765F6D900BED产品 ID00425-00000-00002-AA474系统类型笔和触控64 位操作系统, 基于x64 的处理器没有可用于此显示器的笔或触控输入重命名这台电脑Windows 规格版本版本号Windows 10企业版 LTSC 1809安装日期星期六,2022-11-05操作系统内部版本17763.8027显卡信息:显示卡制造商Powered by AMD图形芯片组Radeon Pro 560设备 ID67EF厂商ID1002子系统ID0179子系统厂商ID106B修订 IDCO总线类型PCI Express 3.0当前总线设置PCI Express 3.0 x8BIOS 版本015.050.000.001BIOS 部件编号113-C9801AU-A02BIOS 日期2017/03/07 18:04可用显存大小4096 MB显存类型GDDR5内存时钟1270 MHz核心时钟907MHz显存总带宽81 GByte/s内存位速率5.08 Gbps2D 驱动程序文件路径/REGISTRY/MACHINE/SYSTEM/CurrentControISet/Control/Class/{4d36e968-e325-11ce-bfc1-08002be10318}/0000OpenGL® API Version4.6OpenCL"M API Version2.0
yd_51113806
发表于2026-05-29 10:14:14
2026-05-29 10:14:14
最后回复
CodeArts小助手-蚂蚁
2026-05-29 10:49:46
109 1 -
对于想白嫖的用户,官方能够出个看广告的功能呀?看一次广告获得一定token 或者减少排队时间啊!!!马上要收费了,没钱,又不想排队,就是有时间看广告,多接点广告,保证一天可以赚几个亿!!!
-
从昨天下午开始就一直这样,排几个小时都不行,我以为限额了,就关掉了,今天打开使用还是一直显示这样,一直卡在这?是把我账号疯了吗?不让用了?这还让人怎么为你花钱?本来就是看效果怎么样,等到你收费时对比一下价格支持你一下呢,现在这样搞,真让人心里不舒服
yd_219798545
发表于2026-05-27 10:58:41
2026-05-27 10:58:41
最后回复
CodeArts小助手-蚂蚁
2026-06-09 15:37:45
392 9 -
一、项目背景:LLM 应用工程化的"最后一公里"过去 5 年,我们的 LLM 应用方式一直在变,但有一个核心问题始终没解决得很好——Prompt 是一次性消耗品,每次开新对话都要重写;经验无法沉淀,更别说团队共享。具体表现在四个方面:经验沉淀难,人走茶凉一个团队成员调好的 Prompt,自己电脑里存几份,新人入职完全不知道。跨工具迁移难,被平台锁死Plugin 锁 ChatGPT,GPTs 锁 OpenAI,Function Calling 绑定 API 接口签名,换个工具全部要重写。工程化弱,缺少版本管理Prompt 散落在聊天记录、笔记本、群文件,没办法进 Git、没办法做 Code Review、没办法回滚。能力扩展受限,RAG 只补知识不补能力向量数据库能让 LLM 多知道一些,但不能让 LLM 多做一些——执行链路上还是缺一块。本文目标把华为云码道(CodeArts)的 Skill 能力讲清楚——一种目录 + YAML + Markdown结构、跨工具可迁移、Git 即分发的 LLM 应用工程化方案。读完你能拿到:一个判断"为什么是 Skill 而不是 RAG/FC/Plugin"的决策框架CodeArts 里 Skill 的完整目录结构 + 4 种添加方式 + 跨工具迁移命令动手写一个能跑的 Skill 的 5 步法 SOP + 4 类不适合的边界场景二、开发平台:华为云码道 CodeArts 的 Skill 能力解析本次实践的底座是华为云码道(CodeArts) —— 一个 AI 原生 IDE + 代码智能体,提供从 Vibe-Coding(探索模式)到 Spec-Driven(规范驱动)的全链路 AI 研发能力。1. CodeArts 的两层 AI 编程模式探索模式(Vibe-Coding Mode)边聊边做、快速验证,适合小微项目、单一技术栈。规范驱动模式(Spec-Driven Mode)先定义需求规范,再精准交付,适合工程化项目、商用交付。而Skill 是这两种模式之上、跨模式可复用的工程化沉淀单元。无论是 Vibe-Coding 临时调用,还是 Spec-Driven 项目级集成,同一份 SKILL.md 都能被 LLM 自动识别。2. Skill 在 LLM 应用栈里的位置横向对比同时代 4 种方案——唯一同时打中 5 个维度的,是 Skill:维度Function CallPlugin / GPTsRAGSkill形态硬编码函数厂商插件向量数据库目录 + YAML + MD能力扩展✓✓✗ 只补知识✓跨平台✗ 绑特定 API✗ 锁厂商⚠ 部分✓ 标准化轻量化⚠ 需程序员✗ 需平台支持✓✓ Markdown 即写可分发✗⚠ 平台内⚠✓ Git 即分发不是 Prompt 不够好,是 LLM 应用已经长出了 Prompt 装不下的能力。三、机制原理:Skill 怎么被 LLM 调用?很多人以为 Skill 就是一个目录、一个 YAML 文件 —— 但它真正特别的是调用机制。1. 两阶段架构:元数据广播 + 实体按需加载阶段内容成本阶段一 · 元数据广播所有 SKILL.md 的 YAML 头一次性载入 system prompt;每个 description ≤ 1024 字符便宜(N × 1KB)阶段二 · 实体按需加载LLM 决定调用某 Skill 时:加载完整 Markdown(≤50000)+ scripts + references贵(只为命中付费)设计洞察:元数据轻量 → LLM 永远知道有什么 Skill实体重型按需 → 只有真正用到才占 Context最终效果:50+ Skill 共存,但单次对话只占 1-2 个 Context。不是 LLM「知道」所有 Skill —— 是 LLM「能找到」需要的 Skill。2. 抽象层级:从「代码级」到「语义级」把 4 种范式横向放一起,会发现它们是抽象层级的演化:范式抽象层接口形式调度者Function Calling代码级JSON Schema程序员手写Tool系统级命令行用户手敲Plugin平台级OpenAPI平台拦截Skill语义级YAML + MarkdownLLM 自己决定本质区别:Skill 是给 LLM 自己用的「语义级」抽象。一句话总结:Skill = Tool 的「自调度」版本 + Function 的「声明式」版本。四、CodeArts 里的 Skill 实战:目录、SKILL.md、添加方式理论说完,这一章看 Skill 在华为云码道这个产品里长什么样、怎么操作。所有信息对齐 CodeArts 官方文档《技能章节》。1. Skill 的目录结构官方标准:my-skill/ ├── SKILL.md ← 必填 │ ├── YAML name + desc │ └── Markdown 指令 └── Bundled Resources/ ← 选填 ├── scripts/ ├── references/ └── assets/我自己的 harmonyos-build-deploy 在标准基础上做了实战扩展:harmonyos-build-deploy/ ├── SKILL.md ├── scripts/ ├── docs/ ← 自由扩展 └── bin/ ← CLI 入口扩展3 类 Bundled Resources 的精准定位:资源作用进上下文方式scripts/Python / Bash 等确定性操作不进上下文,执行结果进references/知识库文档按需进(智能体决定要不要读)assets/字体 / Logo / 模板永远不进(交付物拿来用)「技能本质上是由指令、脚本和资源构成的集合。」—— CodeArts 用户指南2. 编写 SKILL.md —— Skill 的「接口文件」SKILL.md 是 **YAML frontmatter(决策层)+ Markdown body(执行层)**两层结构:--- name: safe-file-reader description: Read files without making changes. Use when you need read-only files access. --- # Safe File Reader This Skill provides read-only file access. ## Instructions 1. Use Read to view file contents 2. Use Grep to search within files 3. Use Glob to find files by pattern层职责何时载入YAML 头(决策层)给 LLM 看,靠 description 决定调不调用总是载入 system promptMarkdown 体(执行层)详细工作流,可引 scripts/referencesLLM 命中后才加载字符上限(官方约定):name ≤ 64 字符description ≤ 1024 字符 ← 整个 Skill 的命门Markdown 主体 ≤ 50000 字符description 是 Skill 给 LLM 的「广告语」—— 写不好,LLM 找不到。3. 技能级别:个人级 vs 项目级维度个人级项目级路径~/.codeartsdoer/skills/./.codeartsdoer/skills/作用域全项目可见当前项目适合放个人偏好 / 习惯定制团队规范 / 工程约定分发跟着用户走跟 Git 仓库走 ⭐项目级的关键价值:CodeArts 会在项目根目录自动生成 ProjectSkillStatus.txt,记录开关状态(skill-name=true/false)。项目级 Skill 提交到 Git 后,新加入团队的同事 git clone 下来就直接拥有所有 Skill —— Skill 第一次真正变成团队资产。个人级 = 个人偏好 · 项目级 = 团队资产。4. 添加技能的 4 种方式方式入口适合场景① 从技能市场设置 → 技能与规则 → 市场用现成 Skill② 手动新建(项目级)设置 → 技能与规则 → 项目级 + 手动创建团队自研③ 手动新建(个人级)设置 → 技能与规则 → 个人级 + 手动创建个人定制④ 导入 zip 包设置 → 技能与规则 → + → 导入批量迁移4 种方式的实际入口截图:方式入口截图① 从技能市场② 手动新建(项目级)③ 手动新建(个人级)④ 导入 zip 包⚠ 导入前请验证技能包符合标准,否则可能失败⚠ 市场技能「更新」会覆盖本地修改,改过的话先备份五、Skill 落地三步:创建 / 调用 / 跨工具迁移接下来三个 Demo 全部建立在一个事实之上:同一份 SKILL.md,Claude Code 和 CodeArts 都识别。不是 CodeArts 私有功能,也不是 Claude Code 独占 —— 是同源载体。三方共同认这一份 SKILL.md 格式:Anthropic华为云码道OpenClawClaude CodeCodeArts社区1. 第一步 · 创建 Skill —— 5 步,填三字段在 CodeArts 里最短 5 步完成 Skill 创建:进入「技能与规则」 —— 设置 → 技能与规则选「手动创建」 —— 项目级 / 个人级 + 号填三字段 —— name / description / 主体确认生成 —— 自动生成 SKILL.md验证调用 —— 对话框试一句话触发5 步全过程截图:Step截图① 进入「技能与规则」② 选「手动创建」③ 填三字段④ 确认生成⑤ 验证调用三字段字符上限:name ≤ 64 · description ≤ 1024 · Markdown 主体 ≤ 50000。从填三字段到 LLM 自动调用 —— Skill 工程化的最短路径。2. 第二步 · 调用 Skill —— 自然语言部署「宠小圈」Skill 不是手动调用的工具 —— 是 LLM 在自然语言里自动选用的能力。这是和 Function Calling、Tool 最本质的工程差异。没有 Skill 之前——我自己写过一个 PowerShell 脚本,但每次部署都要敲一长串命令:./build_and_deploy.ps1 ` -Module entry ` -BuildMode release ` -Device 192.168.0.12:5555 ` -Launch有了 Skill 之后——我只说一句:"帮我把宠小圈打包成 release 版本,部署到 HUAWEI Pura 80 真机并启动应用。"LLM 自己读懂这句话 → 匹配 harmonyos-build-deploy 的 description → 自动转成参数 → 调用脚本 → 真机启动。全程零手敲命令。实际运行画面(动图):整个链路 4 步:输入自然语言 → LLM 匹配 description → 自动执行命令 → 真机启动。从命令行参数到自然语言 —— LLM 时代的工具调用。3. 第三步 · 跨工具迁移 —— cp -r 一行,SKILL.md 一行不改这是整章最硬的一节。harmonyos-build-deploy 原本是我给 Claude Code 写的,放在 ~/.claude/skills/ 下面。迁移到 CodeArts 只需要一行命令:cp -r ~/.claude/skills/harmonyos-build-deploy/ ~/.codeartsdoer/skills/SKILL.md 一行不改,在 CodeArts 里立即可用。CodeArts 端实际运行画面(动图)(同一份 SKILL.md):高清完整录屏见 PPT 内嵌视频(第 14 页)—— 同一份 Skill 在 Claude Code 和 CodeArts 双工具运行双工具运行验证清单:✓ SKILL.md 一行不改✓ YAML frontmatter 字段全部识别✓ Markdown 主体直接执行⚠ 唯一例外:Claude Code 特有的 argument-hint 字段被忽略,但不报错⚠ 这个 “被忽略不报错” 不是 bug,是工程化的优雅 —— CodeArts 实现时主动选择忽略未知字段,而不是抛错,这就保证了 Claude Code 写的 Skill 永远向前兼容。Skill = 跨工具的可移植资产,不是平台锁定的私有功能。4. 三步收束 —— Prompt 是消耗品,Skill 是资产创建 → 调用 → 迁移 —— 三步走完,Skill 完成了一次完整的工程化闭环:没有 Skill 之前有了 Skill 之后Prompt 一次写完 · 当次对话用完即丢SKILL.md 进 Git · 跨对话 / 跨人 / 跨工具复用消耗品资产Prompt 是消耗品,Skill 是资产。六、5 步法:怎么写自己的 Skill听完三步走,你看到 Skill 怎么跑。这一章换个角度——怎么写一个自己的 Skill,看完回去就能动手。5 步完整 SOP,缺一不可。Step 1 · 提炼:三问把日常做法变 Skill写 Skill 的第一步不是写代码,是自我审问:问题筛选维度标准① 我反复做什么?频率筛选每周做 ≥ 3 次② 别人会问我什么?共识筛选团队反复请教的事③ AI 该学会什么?价值筛选一句话能描述清楚我自己的 harmonyos-build-deploy 就是这样筛出来的——反复:每周部署鸿蒙真机 50+ 次别人问:「hvigorw 编译报错怎么办」「签名密钥怎么配」AI 学会:听「部署到真机」就跑全流程 —— 编译 → 签名 → 安装 → 启动 → 看日志三问都打中,适合写 Skill。不是所有脚本都该变成 Skill —— 三问帮你筛。Step 2 · 编排:description 是 Skill 的招牌第二步写 description —— 这是整个 Skill 最关键的一行。❌ 反例(模糊):description: HarmonyOS 工具LLM 困惑:编译?部署?调试?哪一种?没有场景、没有关键词、没有触发线索 —— LLM 不会选它。✓ 正例(harmonyos-build-deploy 真实 description):description: > 鸿蒙 HarmonyOS 应用自动编译、签名并部署到真机。 当用户需要 (1) 编译生成 HAP/HSP 包, (2) 安装到真机设备,(3) 启动应用查看效果, 或提到 hvigor、hvigorw、hdc、HAP、HSP、 AppGallery 等关键词时触发。 写好 description 的三要素:要素怎么写✓ 列具体场景用户什么情境会需要,数字编号 (1)(2)(3)✓ 列触发关键词工具名 / 命令 / 报错 / 文件后缀✓ 用「当用户…时触发」句式让 LLM 直接读出意图边界description 是给 LLM 的招牌 —— 写清楚谁会用、什么时候用。Step 3 · 写主体:SKILL.md 指令骨架第三步写主体 —— YAML 之下那一大段 Markdown。注意:这部分不是 Prompt 的复制粘贴,是给 LLM 的执行手册。推荐 5 段式骨架(可直接拷走):# <Skill 名> ## What it does 一句话说清能力 ## When to use 3-5 种典型场景 ## How to use Step 1. ... Step 2. 调用 scripts/xxx.py Step 3. ... ## Error handling 常见错误的处理(报错 → 修复建议) ## References - references/xxx.md (按需引) 3 类资源何时引用 —— 进上下文的方式完全不同:资源何时引进上下文方式scripts/How to use 步骤里直接调执行结果回 Context,代码本身不进references/需要深度知识时引LLM 自己决定要不要读assets/模板 / Logo / 字体永远不进上下文这是 Skill 比 Prompt 高一个量级的地方 —— Prompt 全部进 Context,Skill 只让必要的进。Markdown 主体 = LLM 的执行手册,不是 Prompt 的复制粘贴。Step 4 · 验证 + 分发:测它真能跑、推到 Git主体写完了 —— 先自测两件事:(1)识别测试 · description 够不够好用一句相似但不完全一样的话试 LLM 能不能匹配上。我原文写"部署 HarmonyOS App",我换说**“装个鸿蒙包”**,LLM 还能不能选用我的 Skill?能 → description 合格不能 → 回去补 description 关键词(2)执行测试 · 指令骨架够不够清晰LLM 真的调用之后,逐项确认:scripts/ 是否真被执行(不只是 LLM 嘴上说"我执行了")报错是否能落到 ## Error handling 节(没落进去,LLM 会瞎编修复方案)(3)两个测试都通过 —— 推到 Git# 在你的项目根目录 cd <你的项目>/.codeartsdoer/skills/ git add <skill-name>/ git commit -m "add skill: <skill-name>" git push新人 git clone 进来 —— CodeArts 立刻识别 .codeartsdoer/skills/,Skill 第一次成为团队资产。Skill 写完不是结束 —— 是要让别人 git clone 之后能直接用。Step 5 · 边界:什么场景不适合写 Skill4 步走完,我们看反面 —— 什么场景不适合写 Skill。我的观点是:知道什么不该写,比知道怎么写更重要。#场景为什么不适合应走方案①强交互场景Skill 无法主动停下来等多轮 yes/no 确认留人工流程②高频改动场景接口/参数每周变,SKILL.md 维护成本超过收益写 Rule(轻量规范)③私密凭据场景项目级 Skill 随 Git 走,API Key/密码会泄露Function Calling + 环境变量④不可重现操作依赖时间/网络/手动状态,LLM 跑不出一致结果写文档而非自动化七、平台技术边界:CodeArts Skill 的 6 类约束写 Skill 还有几个平台层面的约束要心里有数:约束限制字符限制name ≤ 64 字符 · description ≤ 1024 字符 · 指令 ≤ 50000 字符命名规则小写字母 + 数字 + 连字符;头尾不能是连字符;不能连续云端保存AI 生成的 Skill 不能保存至云端,只能本地安全敏感信息不要写进项目级 Skill(随 Git 走会泄露)语言覆盖Codebase 索引支持 Java / ArkTS / JS / TS / Go(不含 Python)数量项目级 + 个人级 各 ≤ 50 个八、项目成果与开源案例本次实践由 1 名开发者独立完成,完整闭环验证了 Skill 在 LLM 应用工程化中的所有关键能力。1. 两个开源 Skill 案例案例 ① · harmonyos-build-deploy —— 鸿蒙应用一键部署GitHub Repo:supermanaaaa/harmonyos-build-deploy能力:鸿蒙 HarmonyOS 应用自动编译、签名、部署到真机的 CLI Skill依赖:Node.js + hvigorw + hdc + ohpm典型场景:开发者每天频繁部署、查日志的高频运维场景双工具验证:Claude Code(~/.claude/skills/)+ CodeArts(~/.codeartsdoer/skills/)同一份 SKILL.md 都能跑案例 ② · content-to-image —— 数据驱动的图片生成 SkillGitHub Repo:supermanaaaa/content-to-image能力:6 种视觉风格 × 6 种内容模板,自动生成博客封面、小红书轮播图、知识卡片、清单海报依赖:Node.js ≥ v22 + sharp(纯 JS,无浏览器依赖)典型场景:内容创作者让 Claude 一句话生成成套的封面+轮播图,统一风格、自动 CJK 排版、自带水印设计亮点:6 风格:sketch / flat / gradient / dark / minimal / blueprint6 模板:cover / checklist / comparison / steps / quote / knowledge轮播图编排:多 slide 统一风格 + 自动处理 SVG ID 冲突中文排版引擎:CJK 自动换行 + 中英文间距多格式输出:PNG / JPEG / WebP / AVIF这两个 Skill 跨度很大——一个是高频运维(部署鸿蒙真机),一个是内容生产(生成图片)。说明 Skill 工程化不挑领域,只要"反复做 + 别人会问 + AI 能学会",就值得变成 Skill。2. 实际场景验证通过skill生成的图片—— Skill 不是一个新的"框架"或"工具",它本质上是一个约定:把你的日常操作沉淀成 SKILL.md,推到 Git,新人 git clone 即用。参考资源CodeArts 官方文档:技能章节演讲案例开源仓库:supermanaaaa/harmonyos-build-deploy —— 鸿蒙应用部署 Skillsupermanaaaa/content-to-image —— 数据驱动图片生成 Skill
上滑加载中
推荐直播
-
华为云码道 × 仓颉编程:工程化AI编码探索2026/05/27 周三 19:00-21:00
刘俊杰-华为云仓颉语言专家/李炎-华为云码道技术专家/王智鹏-OpenCangjie开源社区发起人
本场直播围绕华为云仓颉语言与华为云码道的深度结合,展示华为云智能编程从零基础到高效落地的完整生态能力。以华为云码道为引擎,仓颉语言为载体,带给大家日常提效、趣味创新到极速量产的开发体验。
回顾中 -
一个AI团队帮你写代码:华为云码道Agent Space实战2026/06/25 周四 19:00-21:00
张翰文-华为云码道工程师/郭英旭-青软创新科技集团股份有限公司 软件架构师
本场直播聚焦华为云码道Agent Space两大模式:研发办公、代码开发,亲身体验从需求到代码的AI自动化能力。实操演示基于华为 CodeArts CLI,依托 OpenSpec 规格体系从零搭建业务项目。
即将直播
热门标签