跳到主要内容
把有状态的图像编辑包成 Claude Code skill:拆开看看笔记

把有状态的图像编辑包成 Claude Code skill:拆开看看笔记

abanana
abanana

· 阅读约 5 分钟

今天在 dev.to 上刷到一篇七月下旬的文章,讲的是给 Claude Code 加一个有状态的图像编辑能力,项目叫 nb2lite-skill-claude。吸引我的不是“AI 画图”这件事本身,而是它的做法:把 Google 那个 gemini-3.1-flash-lite-image 模型(文章里叫 Nano Banana 2 Lite)包在一个单文件的 FastMCP server 里,再配成一个 Claude Code skill。这个“有状态”到底是怎么做的,我拆开看看,顺手记一下。

先说原理。这个模型走的是 Gemini 的 Interactions API,每次生成之后会返回一个 interaction_id,下一次编辑的时候把 previous_interaction_id 传回去,就能在之前那张图的基础上改,视觉上下文保留在服务端。也就是说你不用每次把整段场景描述重新写一遍,只说「把背景换成夜晚」这种增量指令就行。这个思路跟聊天会话的上下文延续是一个道理,只是搬到了图像上。

MCP server 叫 nb2lite-agent,就是一个 server.py,暴露四个工具:generate_image、edit_image、edit_local_image 和 get_help。文件命名也做了防碰撞处理,生成的是 gen_<timestamp><uuid8>.jpg,编辑的加 edit 或 edit_local_ 前缀。这个细节小,但很实在——我之前自己写过一个批量出图的脚本,就是因为文件名重复,第二轮直接把第一轮的图盖掉了,所以看到这种命名规则格外有共鸣。

真正让我觉得值得记的是两个设计决定,都是「故意不做某事」的那种。

第一个:edit_image 故意不接受 aspect ratio 参数。文章里的解释是为了保持像素连续性——你是在改一张已有的图,不是重新生成,改宽高比等于把画布都换了,前面的 interaction 上下文意义就不大了。生成的时候倒是支持 1:1、16:9、9:16、4:3、3:4 这几档。一开始我没搞懂为什么编辑时要刻意砍掉这个参数,后来才想明白:参数的存在本身就是一种邀请,留着它,模型迟早会「好心」地在编辑时顺手改个比例,然后用户对着一张构图全变的图问「我明明只让它调个亮度」。把口子焊死,比在 prompt 里写「请不要改比例」可靠得多。

第二个:skill 里明确写了草稿用 low thinking level 省成本。这个模型实际接受的是 low 和 high 两档,文档里列的 minimal 和 medium 传过去会直接吃 HTTP 400。文章作者在回复评论时说,其实 Claude Code 自己就会挑合适的 thinking level,不需要显式指示——它靠的是读 MCP 工具描述和打包进去的 Python 源码来推理。这一点我持保留态度,「模型自己会选对」这个说法在我自己的使用经验里没那么稳,skill 里把这条写成显式规则,我觉得不是多余的保险,而是必要的。不过这是我的偏好,作者实测说能行,那也算一种证据,只是我还没在自己这边复现过这个结论,先不下断言。

安装路径给了四条:plugin marketplace 命令、clone 之后跑 bootstrap 脚本、项目级的 init.sh、还有一个 Docker 镜像。仓库是 Apache-2.0,带集成测试,测试就是拿那四个 MCP 工具直接打真实 API。这一点我要单独划出来夸一句:很多同类项目的测试只测到「函数能被调起来」就停了,打真实 API 的集成测试才真正能发现「文档里写的参数档位实际不存在」这种问题——上面那个 minimal/medium 返回 400 的事,八成就是这么被发现的。看不懂的代码不该用,同样,没被真实调用验证过的工具描述也不该太信。

评论区有一条我觉得比正文还有价值:有人说 stale ID 的处理应该编码在 server 代码里,而不是留给用户自己去撞。这个观点我完全站这边。interaction_id 过期之后的报错、重试、降级逻辑,写在 server 里一次就够了,留给每个使用者自己摸,等于把同一个坑复制 N 份。skill 里也确实有对应的指引,比如编辑 prompt 要保持增量、要链式传最新的 interaction ID、遇到环境问题先调 get_help——get_help 这个工具本身也是个好设计,把排障说明做成可调用的工具而不是 README 里的一段文字,模型遇到问题时会主动去查,这比指望它「记得」文档内容靠谱。

文章的封面图就是用这个 skill 自己生成的,一次 generate_image 调用,high thinking level,没有修图。这种「用自己的产出当封面」的自证方式我喜欢,比截图终端有说服力。

划重点:第一,有状态编辑的核心就一个 interaction_id 的传递,理解了这一点,其他都是工程包装;第二,edit_image 不收 aspect ratio 这个「负设计」是全文最值得抄的思路,好的接口是靠砍掉不该有的自由度变好的;第三,集成测试打真实 API,才能暴露文档和实际行为不一致的地方。这条记下来了,下次自己包 MCP server 的时候应该用得上。你可以照着这个思路试一遍:找个有状态的 API,把它包成工具,然后专门想想哪些参数应该故意不给。

abanana
abanana

把自己踩过的坑整理成一篇能复现的笔记,写给三个月前的自己看。

查看主页 →