EasyCLIProxyAPI 本地网关架构与多智能体客户端适配¶
本文目标:全面梳理本地大模型网关从非官方分叉(ZCode-Antigravity)向官方稳定核心(EasyCLIProxyAPI 7.2.149+)迁移的演进历程;详解 Hermes Agent 与 ZCode 双端接入使用 Gemini 3.8/3.7 Flash 对话推理、多模态文生图 Skill(
gemini-3.1-flash-image/ Nano Banana 2)的完整配置步骤、关键配置文件、避坑指南与全流程故障速查表。实测环境:Windows 11 / EasyCLIProxyAPI 0.2.71 (Core 7.2.149) / Hermes Agent / ZCode 客户端 / Google AI Pro 个人订阅。
一、 本地模型网关的架构演进¶
在日常使用多种 AI 编程助手(如 Hermes Agent、ZCode、Claude Code、Codex)时,很多开发者选择在本地搭建网关以统一承接 Google Antigravity、Kimi、Claude 等模型渠道。
┌───────────────────────┐
│ Hermes Agent │ (OpenAI 兼容协议 / 18080)
└───────────┬───────────┘
│
▼
┌───────────────────────┐
┌─────────────►│ EasyCLIProxyAPI │◄────────────┐
│ │ (官方核心 v7.2.149) │ │
│ └───────────┬───────────┘ │
│ │ │
│ (Anthropic 协议 / 18080) │ │
│ ▼ │
┌─────────────┐ ┌───────────────────┐ ┌─────────────┴──────────┐
│ ZCode │ │ 本地代理 127.0.0.1 │ │ 其它 CLI 智能体 (Codex) │
└─────────────┘ └─────────┬─────────┘ └────────────────────────┘
│
▼
┌─────────────────────────┐
│ Google Antigravity 服务 │
└─────────────────────────┘
1. 早期分叉分支的局限与风险¶
早期社区存在基于旧版本修改的中间派生版本(例如 ZCode-Antigravity 7.2.132-zcode)。在实际生产使用中,此类版本暴露出以下问题:
- 凭据丢失陷阱:官方二进制在发布期通过 -X ldflags 注入了官方发布的 OAuth Client ID 与 Secret;若在本地直接裸 go build 编译,源码中的变量为空,会导致运行时抛出 500 OAuth client is not configured,随后标记为 503 auth_unavailable,造成大面积鉴权不可用;
- 私有接口依赖:派生版本常会添加非官方标准接口(如 /v0/management/api-call),导致外部插件产生私有依赖,一旦版本升级就会彻底失效。
2. 迁移至官方核心的优势¶
随着上游官方核心演进至 7.2.149(EasyCLIProxyAPI 桌面控制台标配):
- 原生多模型支持:上游已原生集成 gemini-3.8-flash、gemini-3.8-flash-high、gemini-3.7-flash 等全系模型及思考推理链(Thinking Variant);
- 官方内嵌安全凭据:官方发行版自带合法 OAuth 认证身份,规避本地编译导致凭据丢失的风险;
- 双协议原生互通:同时支持标准 OpenAI 格式(/v1/chat/completions)与 Anthropic Messages 格式(/v1/messages),Hermes Agent 与 ZCode 可共用同一个 127.0.0.1:18080 端口无缝并发调用。
二、 Windows 客户端探查陷阱与目录联接(Junction)¶
在 EasyCLIProxyAPI 桌面控制台的「智能体配置」中,有时会遇到一个典型问题:本地明明确认已安装了 ZCode 等客户端,但界面却弹出黄色警告“只检测到配置文件,未检测到客户端”,且右下角启动按钮显示“无法启动”被禁用。
1. 探查机制排查¶
反编译与特征码扫描显示,控制台在 Windows 上采用固定硬编码的规范路径来探测客户端可执行文件:
- %LOCALAPPDATA%\Programs\<Agent>\<Agent>.exe(用户级安装规范路径)
- %ProgramFiles%\<Agent>\<Agent>.exe(系统级 64 位标准安装路径)
若用户将客户端安装在非系统盘(例如 D:\zcode\ZCode.exe),控制台仅能在用户目录(~/.zcode)找到配置,却无法在默认路径找到程序实体,因而禁用启动逻辑。
2. 优雅解决方案:NTFS 目录联接(Junction)¶
无需搬迁文件或重装软件,只需以管理员权限在命令行中建立 NTFS 目录联接即可:
:: 映射到用户本地应用规范路径
mklink /J "%LOCALAPPDATA%\Programs\ZCode" "D:\zcode"
:: 映射到系统 Program Files 规范路径
mklink /J "%ProgramFiles%\ZCode" "D:\zcode"
原理解析:
mklink /J 是目录级重解析点(reparse point),效果上类似路径别名——与文件级硬链接是不同机制
- 建立联接后,控制台在标准路径即可瞬间探测到 ZCode.exe,版本信息立刻正常显示,“无法启动”按钮随之恢复为正常启动控制。
三、 Hermes Agent 接入使用 Gemini 配置实战¶
Hermes Agent 底层采用标准 OpenAI 兼容格式对接本地网关,主要通过配置文件 ~/.hermes/config.yaml 管理。
1. 主模型与辅助模型配置¶
在 config.yaml 中配置默认主力模型与提供商:
model:
default: gemini-3.8-flash
provider: cpa-gui
base_url: http://127.0.0.1:18080/v1
auxiliary:
vision:
provider: cpa-gui
model: gemini-3.8-flash
agent:
reasoning_effort: ultra # 开启 Gemini 3.8 Flash Ultra 思考链
2. 自定义提供商注册 (custom_providers)¶
确保在 custom_providers 列表内注册统一且唯一的 cpa-gui 项:
custom_providers:
- name: cpa-gui
base_url: http://127.0.0.1:18080/v1
api_key: <YOUR_GATEWAY_KEY> # 取自 EasyCLIProxyAPI 的 api-keys
api_mode: chat_completions
model: gemini-3.8-flash
models_discovered: true
models:
gemini-3.8-flash: {}
gemini-3.7-flash: {}
gemini-3.6-flash: {}
gemini-3.1-pro-low: {}
gemini-web-search: {}
claude-sonnet-4-6: {}
claude-opus-4-6-thinking: {}
gpt-oss-120b-medium: {}
3. 注意点与防坑准则¶
- 严禁重复定义提供商:旧版配置常遗留
Local (127.0.0.1:18080)。若与cpa-gui同时存在,二者打向同一端口,会导致桌面 GUI 的模型下拉框内出现两套完全重合的模型列表。必须清理掉冗余项; - 凭据池同步清理:编辑
config.yaml去除重复项后,需同步检查~/.hermes/auth.json中的credential_pool,删除废弃条目; - 浏览器沙箱强制隔离:配置
browser.use_real_profile: false,避免 Agent 浏览器操作污染甚至清空日常 Edge/Chrome 的扩展注册表。
[!WARNING] 历史存档(2026-09-12) 第四、五节以 ZCode 客户端为配置载体。ZCode 已于 2026-09-09 弃用并卸载,
~/.zcode相关路径已不存在;但网关侧的双协议适配(/v1/messagesAnthropic 格式转换)仍然有效,适用于任何 Anthropic 协议客户端。以下按写作时点存档。
四、 ZCode 客户端接入使用 Gemini 配置实战¶
ZCode 客户端与 OpenAI 格式不同,其底层采用的是 Anthropic Messages 协议(/v1/messages)。EasyCLIProxyAPI 官方核心原生支持此格式转换。
1. 配置文件双层定位¶
ZCode 的配置分为两层,建议同步配置:
1. 全局默认配置:C:\Users\<用户名>\.zcode\v2\config.json
2. 工作区定制配置:<项目根目录>\.zcode\v2\config.json(若存在)
2. 提供商注入 (zcode-antigravity-local)¶
在 config.json 的 provider 字典中注入 Google 本地网关节点:
{
"provider": {
"zcode-antigravity-local": {
"name": "Google",
"kind": "anthropic",
"options": {
"apiKey": "<YOUR_GATEWAY_KEY>",
"baseURL": "http://127.0.0.1:18080",
"apiKeyRequired": true
},
"enabled": true,
"source": "custom",
"x-zcode-antigravity-managed": 1,
"models": {
"gemini-3.8-flash": {
"name": "Gemini 3.8 Flash",
"limit": { "context": 1048576 },
"modalities": {
"input": ["text", "image", "audio", "video"],
"output": ["text"]
},
"reasoning": {
"enabled": true,
"variants": ["low", "medium", "high"],
"defaultVariant": "high"
},
"zcode": { "priority": 200 }
},
"gemini-3.7-flash": {
"name": "Gemini 3.7 Flash",
"limit": { "context": 1048576 },
"modalities": {
"input": ["text", "image", "audio", "video"],
"output": ["text"]
},
"reasoning": {
"enabled": true,
"variants": ["low", "medium", "high"],
"defaultVariant": "high"
},
"zcode": { "priority": 201 }
},
"gemini-3.1-pro-low": {
"name": "Gemini 3.1 Pro (Low)",
"limit": { "context": 1048576 },
"modalities": {
"input": ["text", "image", "audio", "video"],
"output": ["text"]
},
"reasoning": { "enabled": true, "variants": ["low", "medium", "high"], "defaultVariant": "low" },
"zcode": { "priority": 203 }
},
"gemini-web-search": {
"name": "Gemini Web Search (Google)",
"limit": { "context": 1048576 },
"modalities": {
"input": ["text", "image", "audio", "video"],
"output": ["text"]
},
"zcode": { "priority": 204 }
}
}
}
}
}
3. 模型列表置顶展示¶
在 ~/.zcode/v2/model-provider-display-order.json 中,将 \"zcode-antigravity-local\" 放置在 providerIds 数组的首位:
这样启动 ZCode 后,顶部模型下拉框首项即为 Google 官方 Gemini 全系模型。
五、 多模态扩展:ZCode 接入 Gemini 原生生图 Skill (gemini-3.1-flash-image / Nano Banana 2)¶
很多用户在配置了 Gemini 模型后,让智能体画图却发现智能体只输出文字描述,甚至产生幻觉。这是由于 Tool-Calling 机制与模型多模态能力脱节导致的。
[用户发出画图指令]
│
▼
[ZCode Agent] ──(自动触发)──> [Skill: gemini-image-gen]
│
▼ (执行 Python 脚本)
[generate_image.py]
│
▼ (POST /v1/chat/completions)
[EasyCLIProxyAPI 网关] (http://127.0.0.1:18080)
│
▼ (调用 Google 远端接口)
[gemini-3.1-flash-image]
│
▼ (返回包含 data:image/jpeg;base64 的 JSON)
[Python 脚本解码并保存]
│
▼
[本地文件: ./generated_images/xxx.jpg]
│
▼
[ZCode 渲染展示 Markdown 预览]
1. 主控模型与生图后端解耦¶
- 主控对话模型 (Chat Controller):负责理解自然语言、优化扩展 Prompt,触发工具调用(如
gemini-3.8-flash/gemini-3.7-flash); - 生图后端 (Image Generation Backend):负责计算并生成图片 Base64 数据(
gemini-3.1-flash-image,即 Nano Banana 2)。 - 无论主控模型使用的是哪个模型,只要挂载了生图 Skill,都能自由调用生图后端。
2. 为什么走 /v1/chat/completions 而不是 /v1/images/generations?¶
Google 的 gemini-3.1-flash-image 在 Antigravity 中是作为多模态补全模型注册的。它不支持标准 OpenAI 的 /v1/images/generations 端点(调用会报 400: Model is not supported on /v1/images/generations),必须向 /v1/chat/completions 发起对话补全请求,并从返回结果的 choices[0].message.images 数组中提取 data:image/jpeg;base64 编码。
3. 创建 gemini-image-gen Skill¶
在 ZCode 用户全局技能目录 ~/.zcode/skills/gemini-image-gen/(或项目级目录)下创建两份文件:
(1) SKILL.md(向 Agent 注册调用规范)¶
---
name: gemini-image-gen
description: Use this skill whenever the user asks to generate, draw, paint, or render an image, illustration, anime art, or photo using Google Gemini / Imagen 3 backend.
---
# Gemini Image Generation Skill
Use this skill to generate high quality images using Google's Imagen 3 / Gemini Image API and save them directly to the local workspace.
## How to execute
Run the generation script via Bash tool:
```bash
python "C:/Users/<用户名>/.zcode/skills/gemini-image-gen/generate_image.py" --prompt "YOUR_DETAILED_PROMPT" --output-dir "generated_images" --output-name "custom_name"
```
### Parameters
- `--prompt` (必填): 详细的英文提示词,包含主体、画风、材质、光影及构图。
- `--output-dir`: 保存目标文件夹(默认 `./generated_images`)。
- `--output-name`: 自定义保存文件名(不含扩展名)。
- `--model`: 默认为 `gemini-3.1-flash-image`。
### When Executing
1. 调用系统命令执行上述脚本。
2. 读取脚本输出的 JSON 结果。
3. 成功后以 Markdown 图片/链接格式返回给用户展示:``。
(2) generate_image.py(请求网关并保存图片)¶
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Gemini / Nano Banana 2 Image Generation Script for EasyCLIProxyAPI Bridge
"""
import os
import sys
import json
import base64
import argparse
from datetime import datetime
import requests
LOCAL_BRIDGE_URL = "http://127.0.0.1:18080"
LOCAL_BRIDGE_KEY = "<YOUR_GATEWAY_KEY>"
def generate_via_antigravity(prompt, base_url, api_key, model="gemini-3.1-flash-image", timeout=60):
url = f"{base_url}/v1/chat/completions"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": [{"role": "user", "content": prompt}]
}
response = requests.post(url, json=payload, headers=headers, timeout=timeout)
if response.status_code != 200:
raise Exception(f"Antigravity Bridge Error ({response.status_code}): {response.text}")
data = response.json()
choices = data.get("choices", [])
if not choices:
raise Exception("No choices returned by Antigravity Bridge")
msg = choices[0].get("message", {})
images = []
# 提取 choices[0].message.images 中的 Base64
if "images" in msg and msg["images"]:
for img_obj in msg["images"]:
url_val = img_obj.get("image_url", {}).get("url", "")
if url_val.startswith("data:image"):
header, b64_data = url_val.split(",", 1)
mime = "image/jpeg" if "jpeg" in header or "jpg" in header else "image/png"
images.append((base64.b64decode(b64_data), mime))
elif url_val.startswith("http"):
r = requests.get(url_val, timeout=30)
images.append((r.content, "image/jpeg"))
if not images:
raise Exception(f"No image was generated. Model reply: {msg.get('content')}")
return images
def main():
parser = argparse.ArgumentParser(description="Generate images via Gemini / Antigravity Bridge")
parser.add_argument("--prompt", "-p", required=True, help="Image generation prompt")
parser.add_argument("--model", "-m", default="gemini-3.1-flash-image", help="Model name")
parser.add_argument("--output-dir", "-o", default="generated_images", help="Output directory")
parser.add_argument("--output-name", "-n", default=None, help="Output file name")
parser.add_argument("--base-url", default=LOCAL_BRIDGE_URL, help="Antigravity bridge base URL")
parser.add_argument("--api-key", "-k", default=LOCAL_BRIDGE_KEY, help="Antigravity bridge API key")
args = parser.parse_args()
os.makedirs(args.output_dir, exist_ok=True)
try:
images = generate_via_antigravity(args.prompt, args.base_url, args.api_key, model=args.model)
saved_paths = []
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
for i, (img_bytes, mime) in enumerate(images):
ext = "png" if "png" in mime else "jpg"
filename = f"{args.output_name}.{ext}" if args.output_name else f"gemini_image_{timestamp}_{i+1}.{ext}"
filepath = os.path.abspath(os.path.join(args.output_dir, filename))
with open(filepath, "wb") as f:
f.write(img_bytes)
saved_paths.append(filepath)
print(json.dumps({
"status": "success",
"model": args.model,
"prompt": args.prompt,
"images": saved_paths
}, ensure_ascii=False, indent=2))
except Exception as e:
print(json.dumps({
"status": "error",
"error_type": "GENERATION_FAILED",
"message": str(e)
}, ensure_ascii=False, indent=2))
sys.exit(1)
if __name__ == "__main__":
main()
六、 全流程避坑与常见故障速查表(血泪经验汇编)¶
| 故障现象 | 触发时机 / 原因 | 避坑方案与解决对策 |
|---|---|---|
HTTP 500 后变 503 auth_unavailable |
本地直接裸 go build 编译 CLIProxyAPI 二进制,丢失了官方发布期通过 -X ldflags 注入的 OAuth Client 凭据。 |
严禁用本地裸构建覆盖官方核心。直接使用 EasyCLIProxyAPI 官方预编译的 cpa-core\cli-proxy-api.exe(7.2.149+)。 |
| “只检测到配置文件,未检测到客户端” | EasyCLIProxyAPI 控制台硬编码探查系统盘规范路径,而 ZCode 安装在 D:\zcode。 |
在 %LOCALAPPDATA%\Programs\ZCode 与 %ProgramFiles%\ZCode 建立 NTFS 目录联接(mklink /J)。 |
| Hermes 模型下拉列表重复翻倍 | config.yaml 中同时保留了旧网关名称(Local (127.0.0.1:18080))与新网关名称(cpa-gui)。 |
清理 config.yaml 与 auth.json,统一规范化为单实例 cpa-gui。 |
| 日常浏览器扩展和脚本全清空 | Hermes 开启 browser.use_real_profile: true(历史版本会把无扩展内存状态写回日常配置)。⚠️ 勘误(2026-09-12 对照 v0.21.1 源码):现行实现为快照副本隔离(~/.hermes/browser-profile/),写回路径已与日常配置隔离,故障链不成立 |
建议保持 use_real_profile: false(纵深防御) |
| 两端查看的 Google 配额完全不一致 | 通用 Google 生产端点 cloudcode-pa 与 Antigravity 专有端点 daily-cloudcode-pa 属于云端解耦配额池。 |
查询 Antigravity 实际调用消耗时,必须指定 daily-cloudcode-pa.googleapis.com 端点。 |
| HTTP 403: IP banned due to too many failed attempts | 前端微件使用普通 API Key 频繁轮询 /v0/management/ 高权限管理接口,触发了防爆破 30 分钟 IP 熔断。 |
数据面与管理面隔离;获取配额改走本地轻量 Python 微服务,绝不高频撞击管理接口。 |
| 刷新配额点击无反应 / 误以为卡死 | 内存防抖缓存瞬间命中,且界面缺乏加载动画与完成时间戳。 | 后端增加 ?force=1 穿透参数;前端配套 SVG 旋转 Spinner、✓ 已刷新 徽章变形与 Toast 弹窗反馈。 |
400: Model is not supported on /v1/images/generations |
Antigravity 桥接中的 gemini-3.1-flash-image 是对话多模态格式,不支持标准 OpenAI 生图端点。 |
将请求端点由 /v1/images/generations 改为 /v1/chat/completions,并在返回的 choices[0].message.images 中提取 Base64。 |
400: User location is not supported for the API use |
直连 Google AI Studio 时,国内出口代理 IP 处于未获支持的地区。 | 通过本地 EasyCLIProxyAPI 网关桥接服务中转,自动规避原生地域检测。 |
401: Invalid API key |
生图脚本中的 API Key 与网关 config.toml 或 config.yaml 的 api-keys 不匹配。 |
统一提取网关中配置的明文密钥(如 <YOUR_GATEWAY_KEY>)。 |