本文记录了在国内阿里云服务器上,基于 OpenClaw Agent 框架,为飞书机器人接入 Gemini 画图能力(Nano Banana)的完整实战过程。包含 5 个踩坑点、3 次失败尝试、以及最终成功的方案。适合正在折腾 AI Agent 基础设施的开发者参考。


一、背景

1.1 我的 Agent 技术栈

我基于 OpenClaw(一个开源 AI Agent 框架)搭建了一套飞书机器人系统。整体架构如下:

  • 宿主环境:阿里云 ECS(中国大陆)
  • Agent 框架:OpenClaw 2026.3.8
  • 交互入口:飞书(Feishu)群机器人
  • Agent 团队:1 个主 Agent + 4 个子 Agent(研究员、程序员、写手、交易员),各司其职

之前的模型配置是:

功能 模型 来源
日常对话 Kimi K2.5 阿里云 DashScope
写文章 Claude Sonnet 4.6 第三方中转站 A
编程 GLM-5 阿里云 DashScope
交易分析 Qwen3-Max 阿里云 DashScope

系统运行良好,唯独画图功能一直是个空白——OpenClaw 内置的 openai-image-gen Skill 需要调用 api.openai.com,而国内服务器根本访问不到。

1.2 本次目标

  1. 接入 Gemini 图像生成模型(Nano Banana),实现飞书端的 AI 画图
  2. 由于 Google API 在国内不可直连,必须通过第三方中转站访问
  3. 实现多模型协作:日常对话用国内模型(省钱快速),画图时才调用 Gemini(按需付费)

二、中转站选型

2.1 为什么需要中转站

国内阿里云 ECS 无法直连 Google API:

curl -s --max-time 5 <https://generativelanguage.googleapis.com/>
# 结果:连接超时,完全不通

同样 api.openai.com 也不通。所以必须找一个支持 OpenAI 兼容格式的第三方中转站来转发 Gemini 的请求。

2.2 选型踩坑

我先后试了两家中转站:

第一家(中转站 A)

  • 优点:OpenAI 兼容格式,支持支付宝
  • 坑点:模型名需要加 [L] 前缀!直接写 gemini-2.5-pro 会报 model_not_found,必须写 [L]gemini-2.5-pro
  • 结局:充值 500 额度,画图测试时频繁遇到 429 限流(上游负载饱和),余额很快用完后返回 403 quota 不足,且不会自动降级

第二家(中转站 B)

  • 优点:46 个 Gemini 模型可用,不需要加前缀,按次计费更可控
  • 坑点:API Key 有分组机制,不同分组对应不同模型通道。初始分组不包含画图模型,需要在后台调整分组
  • 结局:调整分组后,gemini-2.5-flash-image 模型可用,画图成功

经验教训:接入新中转站时,第一步永远是 curl /v1/models 查看实际可用的模型列表和真实模型名。不同中转站的命名规则可能完全不同。


三、OpenClaw 多模型配置

3.1 Provider 配置

OpenClaw 通过 openclaw.json 管理多个模型 Provider。添加新中转站只需在 models.providers 下新增一个条目:

{
  "models": {
    "providers": {
      "dashscope": { "...": "阿里云 DashScope,国内模型" },
      "my-claude-proxy": { "...": "Claude 中转站" },
      "gemini-proxy": {
        "baseUrl": "<https://your-proxy.example.com/v1>",
        "apiKey": "sk-xxxx****",
        "api": "openai-completions",
        "models": [
          {
            "id": "gemini-2.5-flash-image",
            "name": "Nano Banana 2",
            "input": ["text", "image"],
            "contextWindow": 1048576,
            "maxTokens": 8192
          }
        ]
      }
    }
  }
}

3.2 子 Agent 独立模型

OpenClaw 支持在 agents.list 中为每个子 Agent 指定不同的模型:

{
  "agents": {
    "defaults": {
      "model": "dashscope/kimi-k2.5"
    },
    "list": [
      { "id": "main" },
      { "id": "writer", "model": "my-claude-proxy/claude-sonnet-4-6" },
      { "id": "coder", "model": "dashscope/glm-5" },
      { "id": "researcher", "model": "my-claude-proxy/claude-opus-4-6" },
      { "id": "trader", "model": "dashscope/qwen3-max" }
    ]
  }
}

这样就实现了:

  • 日常对话(main)→ Kimi K2.5(便宜、快速、国内直连)
  • 写文章(writer)→ Claude Sonnet 4.6(文笔好)
  • 画图 → Gemini Nano Banana(通过自定义 Skill 脚本调用)

3.3 降级链配置

agents.defaults.models 支持模型降级。当主力模型不可用时,自动尝试下一个:

对话:Kimi K2.5 → Claude → Qwen → MiniMax → GLM
画图:gemini-2.5-flash-image → gemini-3-pro-image-preview → Qwen3-VL

注意:降级链主要处理 5xx、超时等错误。403(余额不足)不一定触发降级——OpenClaw 可能将其视为配置错误而非临时故障。这个坑后面会讲。


四、Nano Banana 画图接入(核心,含 3 次失败)

这是本文的重头戏。看起来简单的一件事——「让飞书机器人画图」——我经历了 3 次失败 才成功。

4.1 第一次尝试:创建自定义 Skill 覆盖(❌ 失败)

OpenClaw 的 Skill 系统允许用户在 ~/.openclaw/skills/ 目录下创建同名 Skill 来覆盖内置版本。我的想法是:

  1. ~/.openclaw/skills/nano-banana-pro/ 创建自定义 Skill
  2. ~/.openclaw/skills/openai-image-gen/SKILL.md 创建覆盖文件,标记为 DISABLED
  3. 重启 Gateway,模型应该会用新 Skill 画图

结果:飞书发「画一只猫」→ 依然报 Network is unreachable

查看日志后发现惊人的真相

Gemini 模型根本没有调用任何 Skill 文件。
它通过 exec 工具,自己生成了一个完整的 200+ 行 Python 脚本,
内联调用 <https://api.openai.com/v1/images/generations>

日志中清晰可见 Gemini 生成的完整 Python 代码:import argparse, import base64, ... 一直到 urllib.request.Request("<https://api.openai.com/v1/images/generations>")

根因:Gemini 等强模型使用 exec 工具时,不是「调用 Skill 文件」,而是读取 Skill 描述后自己写代码执行。它从训练知识中知道 OpenAI Images API 的用法,于是自己生成了代码。

4.2 第二次尝试:在 SOUL.md 中添加指令(❌ 失败)

OpenClaw 的 SOUL.md 文件定义了 Agent 的行为规则。我在其中添加了:

### 画图功能(Nano Banana)
- 使用 nano-banana-pro Skill,严禁使用 openai-image-gen
- 命令:python3 {baseDir}/scripts/generate_image.py --prompt "描述"

结果:依然失败。模型完全忽略了这条指令。

根因{baseDir} 是 Skill 系统内部的模板变量,只在 Skill 执行上下文中才会被解析。写在 SOUL.md 里,模型看到的就是字面量 {baseDir},无法理解这是什么路径。

4.3 第三次尝试:绝对路径 + 禁用内置 Skill + 新会话(✅ 成功!)

三管齐下

第一刀:SOUL.md 用绝对路径

### 画图功能(Nano Banana)
- 命令:exec timeout=300 python3 /home/user/.openclaw/skills/nano-banana-pro/scripts/generate_image.py --prompt "用户描述" --filename "/home/user/.openclaw/workspace/tmp/输出.png"
- 严禁自己编写画图代码,严禁调用 api.openai.com
- 严禁使用 openai-image-gen Skill

第二刀:备份禁用内置 openai-image-gen

# 备份而非删除(随时可恢复)
mv /usr/lib/node_modules/openclaw/skills/openai-image-gen/scripts/gen.py \\
   /usr/lib/node_modules/openclaw/skills/openai-image-gen/scripts/gen.py.bak

将内置 SKILL.md 替换为 DEPRECATED 提示,同时设置不可能满足的环境变量要求,确保 Skill 永远不会被加载。

第三刀:飞书开新会话

在飞书里发 /new 命令开启全新会话。这一步至关重要——旧会话的上下文缓存了之前的 Skill 信息,即使文件改了,旧会话仍然使用旧的系统提示。

结果:成功!飞书发「画一辆蓝色的跑车在城市道路上」→ 生成了一张高质量图片


五、自定义画图脚本设计

5.1 设计原则

服务器环境受限(国内 ECS、无法安装 uvpip 不稳定),所以脚本必须:

  1. 零依赖:只用 Python 标准库(urllib.request
  2. 自动读取配置:从 openclaw.json 获取 API Key,无需手动传参
  3. 内置降级:多个模型按优先级尝试,一个失败自动换下一个
  4. 输出 MEDIA 标记:让 OpenClaw 自动将图片发送到飞书

5.2 核心代码结构

#!/usr/bin/env python3
"""Nano Banana - via proxy. No pip dependencies."""

import urllib.request, json, base64, os
from pathlib import Path

# 模型降级列表
MODELS = [
    "gemini-2.5-flash-image",
    "gemini-2.5-flash-image-preview",
    "gemini-3.1-flash-image-preview",
    "gemini-3-pro-image-preview",
]

def get_config():
    """从 openclaw.json 自动读取 API Key 和 baseUrl"""
    cfg = json.load(open(os.path.expanduser(
        "~/.openclaw/openclaw.json")))
    provider = cfg["models"]["providers"]["gemini-proxy"]
    return provider["apiKey"], provider["baseUrl"]

def generate_image(prompt, api_key, base_url, model):
    """调用 OpenAI 兼容格式的图像生成 API"""
    payload = {
        "model": model,
        "messages": [{"role": "user", "content": prompt}],
        "max_tokens": 4096
    }
    req = urllib.request.Request(
        f"{base_url}/chat/completions",
        data=json.dumps(payload).encode(),
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
    )
    resp = urllib.request.urlopen(req, timeout=300)
    return json.loads(resp.read())

def main():
    # ... 参数解析、配置读取 ...
    for model in MODELS:
        try:
            result = generate_image(prompt, key, url, model)
            # 提取 base64 图片并保存
            # ...
            print(f"MEDIA:{filepath}")  # OpenClaw 识别此标记
            return 0
        except Exception as e:
            print(f"Warning: {model} failed: {e}")
    print("Error: All models failed.")
    return 1

关键是最后的 MEDIA: 输出——OpenClaw 会识别这个标记,自动将文件作为图片附件发送到飞书。


六、踩坑总结(共 5 个坑)

坑 1:强模型用 exec 自己写代码,无视 Skill 文件

项目 详情
严重程度 🔴 高
现象 创建了自定义 Skill,但模型不调用,自己写 200 行代码调 OpenAI
根因 Gemini 等强模型通过 exec 工具执行任意代码,Skill 文件只是参考不是约束
解决 SOUL.md 中用绝对路径强制指定命令 + 禁用旧 Skill + 开新会话

坑 2:SOUL.md{baseDir} 模板变量不解析

项目 详情
严重程度 🟡 中
现象 SOUL.md 里写了 {baseDir}/scripts/xxx.py,模型看到的是字面量
根因 {baseDir} 是 Skill 内部模板变量,只在 Skill 执行上下文中解析
解决 改用绝对路径 /home/user/.openclaw/skills/xxx/scripts/xxx.py

坑 3:中转站模型名有特殊前缀

项目 详情
严重程度 🟡 中
现象 gemini-2.5-promodel_not_found
根因 该中转站要求模型名加 [L] 前缀
解决 先用 /v1/models 接口查询真实模型名

坑 4:OpenClaw 不支持 visionModel 字段

项目 详情
严重程度 🔴 高(Gateway 无法启动)
现象 添加 agents.defaults.visionModel 后 Gateway 报 Unrecognized key 拒绝启动
根因 OpenClaw 3.8 不支持该字段,视觉能力通过 Provider 模型的 input 字段声明
解决 删除该字段

坑 5:中转站 API Key 分组不含画图模型通道

项目 详情
严重程度 🟡 中
现象 gemini-3.1-flash-image-preview 返回 503 "No available channel under group VIP6"
根因 API Key 绑定的分组没有画图模型的通道权限
解决 在中转站后台调整 Key 的分组,或者测试其他模型名找到可用的

七、最终架构

graph TB
    User["用户(飞书)"] --> Main["主 Agent(Kimi K2.5)"]
    Main --> |"日常对话"| Kimi["阿里云 DashScope<br>Kimi K2.5"]
    Main --> |"写文章"| Writer["子 Agent: Writer<br>Claude Sonnet 4.6"]
    Main --> |"编程"| Coder["子 Agent: Coder<br>GLM-5"]
    Main --> |"股票分析"| Trader["子 Agent: Trader<br>Qwen3-Max"]
    Main --> |"画图"| Script["nano-banana-pro<br>Python 脚本"]
    Script --> Proxy["第三方中转站"]
    Proxy --> Gemini["Google Gemini<br>Nano Banana"]
    Writer --> Claude["Claude 中转站"]
    
    style Main fill:#4CAF50,color:white
    style Script fill:#FF9800,color:white
    style Gemini fill:#2196F3,color:white

费用优化效果

模型 用途 费用来源 调用频率
Kimi K2.5 日常对话 阿里云(极低)
GLM-5 编程 阿里云(极低)
Qwen3-Max 交易分析 阿里云(低)
Claude Sonnet 4.6 写文章 第三方中转(中)
Gemini Nano Banana 画图 第三方中转(按次) 极低

日常 90% 的请求走国内模型(几乎免费),只有写文章和画图才调用海外模型,整体成本极低


八、经验总结

8.1 关于强模型的 exec 行为

Gemini/Claude 等强模型不是你想象中的「工具调用者」,它们是「代码生成者」。

当你给它一个画图任务,它不会老老实实调用你准备好的脚本。它会自己写一个完整的 Python 程序,用它从训练数据中学到的 API 知识来执行任务。

要控制这种行为,指令必须极其明确——包括完整的绝对路径、明确的禁止条款、以及物理上移除干扰项。

8.2 关于修改配置后的生效

飞书 Agent 的会话有上下文缓存。改了配置文件不等于立即生效,必须:

  1. 重启 Gateway
  2. 在飞书里开新会话/new/reset

8.3 关于第三方中转站

  • 先查 /v1/models,不要假设模型名
  • 注意分组/通道机制,不是所有模型对所有 Key 都可用
  • 图像模型容易 429 限流,降级链很重要
  • 按次计费比按 token 计费更适合画图场景

8.4 关于零依赖开发

国内 ECS 环境经常缺少 pipuv 等包管理工具,安装过程本身就可能失败。用纯标准库开发脚本urllib.request 替代 requests)是最稳妥的选择。


九、写在最后

从「画图功能不可用」到「多模型协作 + 飞书出图」,这个过程花了大约一天时间。最大的收获不是画图本身,而是理解了 AI Agent 框架中模型行为的不可预测性——你以为它会按照你的配置做事,但强模型有自己的「想法」。

如果你也在国内服务器上折腾 AI Agent,希望这篇实战记录能帮你少走一些弯路。


本文基于 OpenClaw 2026.3.8 版本,所有 API Key 和服务器地址已脱敏处理。

作者使用的飞书机器人名为「钱多多CEO」,采用 1 主 4 副的 Agent 团队架构。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐