三步:写、查、发。第二步最容易被省掉。 查的是机械项:doctype、title、charset、viewport、有没有外链脚本。 发是写操作,同一个名字只发一次;要覆盖得显式传 force。
第 10 章三个案例是「把外面的东西搬进来或搬出去」,这一章四个是「产出一样新东西」。 第一件产出物是一个网页。 它把「写文件」「检查」「发布」拆成三个工具,拆得越细,出错时越好定位。 教材这一节是在平台上配好部署工具,然后让它一条条跑。界面操作照原样说明。
一、搭建:三步,第三步最容易被忽略
用户说的是「做个活动页,发我个链接」。这句话里其实有三件事。 写:生成一个 html 文件,落盘。查:检查标题、编码、视口、外链这些机械项。发:传上去,换回一个链接。 三步里第二步最容易省。省了的后果很具体:一个白页被当成成品发出去,而且是你自己发的。
所以顺序不能变。先写、再查、最后发,查不过就不发。 这三步对应三个工具,也可以做成一个工具的三种 mode。拆成三个的好处是每一步都能单独重跑。
二、写:工具只负责落盘,顺手报一个字节数
演示 ② 里写入 index.html,返回 340 字节。这个字节数是这条工具唯一的产出。 看着没用,其实它是最快的「写成功了吗」的判据——零字节就说明写空了。 为什么会出现零字节?模型给的字符串里有非法字符、路径不存在、权限不够,都会写成一个空文件。 而这三种情况都不会抛异常。
所以写文件这类工具,返回值里务必带上字节数或者行数。能数的东西就别只说 ok。 这条规矩在第 10 章也出现过:expense.add 返回 total,就是为了让人能对账。
三、查:五条规则,全是能自动判的
演示 ③ 那五条:有 doctype、有 title、有 charset、有 viewport、没有外链脚本。 五条都能用一行代码判真假,所以放在工具里,不放在提示词里。写进提示词的规则,模型可能忘; 写成代码的规则,每次都会跑。 再看那个缺东西的版本:只过了一项,另外四项列在「没过」里。 知道差哪几项,才知道改哪里。只说「不合格」等于没说。
为什么是这五条,不是别的? 因为它们各自对应一个真实的翻车现场:没 doctype 浏览器进怪异模式、没 charset 中文乱码、 没 viewport 手机上缩成一小块。 判不了的(内容对不对、排版好不好)留给人。 工具负责机械检查,人负责审美。这条分工在办公类智能体里几乎通用。
四、发:同一个名字只发一次
演示 ④ 三次发布:第一次成,第二次被拒,带 force 又成。 第 2 次被拒是故意的。发布是写操作,重复发会让链接和版本对不上——你以为是新版,点开还是旧的。 这就是第 3 章那道闸落到具体工具上的样子:写操作要幂等,或者至少要有防重。
注意 force 这个设计:要覆盖得说一声。默认拒、显式传参才盖, 这样误覆盖就变成了一次需要人点头的动作。 和第 10 章那个 push.send 的指纹是一回事,只是判重的东西不同:那边比标题哈希,这边比名字。
五、三个坑:文件名、缓存、回滚
第一个:文件名。用户说「做一个活动页」,工具得决定叫 index.html 还是别的。 定不下来就问,别猜。猜错的名字以后要改,链接就变了。 第二个:缓存。发了新版,用户看到的还是旧的。 工具返回里带上版本号或时间戳,让人能判断「这是不是新的」。 第三个:回滚。发错了要能退回上一版。部署工具最好留一个 history,哪怕只是把旧文件复制成 .bak。
三条都不是协议的事,是「把一个网站当正经东西维护」的事。 智能体把发布变得太容易,回滚就得跟着变容易。
六、动手 Demo:网页生成:写、查、发三步
造一个部署形状的 Server:写、查、发三个工具。 先写一个正常的页面,五项检查全过,然后发布。 再发一次同一个名字,看它怎么拒;带 force 再试一次。
site = Server('web-mcp')
site.add(Tool('site.write', '把 html 写到站点目录,返回字节数', SCHEMA, w))
site.add(Tool('site.check', '检查标题、charset、viewport、外链', SCHEMA, c))
site.add(Tool('site.deploy', '发布一个文件,返回链接;重名要 force', SCHEMA, d))
print(call(site, 'site.write', {'name': 'index.html', 'html': html}))
print(call(site, 'site.check', {'name': 'index.html'}))
print(call(site, 'site.deploy', {'name': 'index.html'})) # 第 1 次
print(call(site, 'site.deploy', {'name': 'index.html'})) # 第 2 次被拒
print(call(site, 'site.deploy', {'name': 'index.html', 'force': True}))
实跑输出:
--------------------------------------------------------------------
网页生成:写、查、发
--------------------------------------------------------------------
① 三步,第三步最容易被忽略
--------------------------------------------------------------------
| 步 | 做什么 | 谁来做 | 错了会怎样 |
|---|---|---|---|
| 1 写 | 生成一个 html 文件 | 模型写内容,工具落盘 | 文件没写成功,后面全白搭 |
| 2 查 | 检查有没有标题、有没有外链 | 工具查 | 发出去才发现空白页 |
| 3 发 | 传上去,拿一个链接 | 工具发 | 重复发,链接满天飞 |
三步里第 2 步最容易省。【省了的后果是:一个 500 错误页被当成成品发出去。】
② 写:工具只负责落盘和报字节数
--------------------------------------------------------------------
写入 index.html,340 字节
【字节数是这条工具唯一的产出】。
看着没用,其实它是最快的「写成功了吗」的判据——零字节就说明写空了。
③ 查:五条规则,跑一遍
--------------------------------------------------------------------
| 检查项 | 结果 |
|---|---|
| 有 doctype | 过 |
| 有 title | 过 |
| 有 charset | 过 |
| 有 viewport | 过 |
| 没有外链脚本 | 过 |
五条全过,340 字节。
再拿一个缺东西的版本试一遍:
通过 1 项,没过:有 doctype、有 title、有 charset、有 viewport
五条规则都是【能自动判的】,所以放在工具里。判不了的(内容对不对、排版好不好)才留给人。
【工具负责机械检查,人负责审美】——这条分工在办公类智能体里几乎通用。
④ 发:三个字段,一个坑
--------------------------------------------------------------------
第 1 次:ok=True,链接 https://pages.example/index
第 2 次:ok=False,这个名字发过了,要覆盖请传 force
带 force:ok=True,链接 https://pages.example/index
第 2 次被拒是【故意的】。发布是写操作,重复发会让链接和版本对不上。
【写操作要幂等,或者至少要有防重】——第 3 章那道闸落实到具体工具上,就是这几行。
⑤ 三个坑
--------------------------------------------------------------------
第一个:文件名和路径。用户说「做一个活动页」,工具得决定叫 index.html 还是别的。
【定不下来就问,别猜。】
第二个:缓存。发了新版,用户看到的还是旧的。
工具返回里带上版本号或时间戳,让人能判断「是不是新的」。
第三个:回滚。发错了要能退回上一版。【部署工具最好留一个 history,】哪怕只是把旧文件复制到 .bak。
三条都不是协议的事,是「把一个网站当正经东西维护」的事。
①第一张表四列,第三列写「谁来做」。写这一步是模型加工具,查和发全是工具。 ②第二段只有一个数字:340 字节。它是「写成功了吗」最快的判据。 ③第三张表五项全过。换个缺东西的版本,只过一项,另外四项会逐个列出来。 ④第四段三次发布:成、被拒、带 force 又成。第 2 次那个 False 是设计出来的,不是 bug。 ⑤最后三个坑,第三个「回滚」演示里没做,但它是发布类工具迟早要补的一格。
30 秒实验
- 把 title 删掉,重跑检查,看它报哪一项、剩下的还过不过。
- 在 html 里加一个外链 script 标签,看第五项能不能挡住。
- 把文件名改成 index2.html 再发一次,看是不是绕过了防重。
常见误区
- 「写完直接发就行,检查是多余的。」省掉查,发出去的就是白页,而且是你自己发的。
- 「发布重复了再盖一次就好。」默认拒是故意的,误覆盖比重复贵得多。
- 「写文件返回 ok 就够了。」返回字节数。零字节也返回 ok,那是最坑的一种成功。
自测三问(点开对答案)
1. 三步里哪一步最容易被省,为什么不能省? —— 查。省了会把白页当成成品发出去,而且发的人是你自己。 2. 为什么检查规则要写进工具,不写进提示词? —— 能自动判的规则写成代码每次都跑;写进提示词模型可能忘。 3. 发布为什么要默认拒重名? —— 重复发会让链接和版本对不上。默认拒、要覆盖显式传 force,误覆盖就多了一道人工确认。网页是一个文件。下一节的产出物是一张表,难点从「发出去」挪到了「先定结构」。
- ☐ (本篇完,下一篇 11.2 数据表格生成智能体)