准备密钥与运行环境

在 注册 或 登录 后,前往 密钥管理 创建适合接口的分组密钥。将密钥放在运行脚本的环境变量中;不要写进代码、浏览器或提交记录。运行脚本会产生请求与潜在费用。

Python 3 安装 python3 -m pip install openai anthropic;Node.js 使用支持当前 SDK 的 LTS 版本,安装 npm install openai @anthropic-ai/sdk。请使用官方 SDK 软件包。

bash
export MONKEYAI_API_KEY="YOUR_API_KEY"
powershell
$env:MONKEYAI_API_KEY="YOUR_API_KEY"

协议与分组选择

协议稳定路径本文模型
ResponsesPOST /v1/responsesgpt-6.1-sol
Chat CompletionsPOST /v1/chat/completionsgpt-6.1-sol
MessagesPOST /v1/messagesclaude-opus-4-8

OpenAI SDK 的 base 是 https://monkeytheme.com/v1;Anthropic SDK 的 base 是 https://monkeytheme.com,由 SDK 拼接 /v1/messages。不要重复拼接 /v1。

模型列表列有上述模型。外接脚本选择 Codex Pro(外接版)、GPT 官方直连或允许外接的 Claude 分组;“仅限Codex”“仅限CC”分组不能按通用脚本使用。模型出现在广场不等于任意密钥都可访问,也不能据此认定所有视觉、工具或协议特性均已验证。Gemini 原生路由为 /v1beta/,不能仅靠更换模型名把原生请求改成这三种格式。

Python:三种协议的文本、视觉与流式

保存为 api_demo.py。三个参数依次选择协议、输入和传输方式,例如 python3 api_demo.py responses text plain。把 text 改为 vision 时,先在当前目录放置自己的静态 input.png;把 plain 改为 stream 使用流式处理。三个协议均有文本和视觉分支。它们展示协议结构,所选分组的视觉兼容性仍需实际验证。

python
import base64
import os
import sys
from pathlib import Path
from openai import OpenAI, APIError
from anthropic import Anthropic, APIError as AnthropicError

# python api_demo.py responses|chat|messages text|vision plain|stream
protocol, mode, delivery = sys.argv[1:]
if protocol not in {"responses", "chat", "messages"}:
    raise ValueError("Unknown protocol")
if mode not in {"text", "vision"} or delivery not in {"plain", "stream"}:
    raise ValueError("Invalid mode or delivery")
streaming = delivery == "stream"
key = os.environ["MONKEYAI_API_KEY"]
image = None
if mode == "vision":
    image = base64.b64encode(Path("input.png").read_bytes()).decode("ascii")
try:
    if protocol == "messages":
        client = Anthropic(api_key=key, base_url="https://monkeytheme.com",
                           timeout=120.0, max_retries=0)
        content = [{"type": "text", "text": "Describe the visible evidence."}]
        if image:
            content.append({"type": "image", "source": {"type": "base64",
                "media_type": "image/png", "data": image}})
        args = dict(model="claude-opus-4-8", max_tokens=600,
                    messages=[{"role": "user", "content": content}])
        if streaming:
            with client.messages.stream(**args) as events:
                for text in events.text_stream:
                    print(text, end="", flush=True)
                final = events.get_final_message()
                print("\nusage:", final.usage)
        else:
            result = client.messages.create(**args)
            for block in result.content:
                if block.type == "text":
                    print(block.text)
            print("usage:", result.usage)
    else:
        client = OpenAI(api_key=key, base_url="https://monkeytheme.com/v1",
                        timeout=120.0, max_retries=0)
        if protocol == "responses":
            content = [{"type": "input_text", "text": "Describe the visible evidence."}]
            if image:
                content.append({"type": "input_image",
                    "image_url": "data:image/png;base64," + image})
            result = client.responses.create(model="gpt-6.1-sol",
                input=[{"role": "user", "content": content}], stream=streaming)
            if streaming:
                with result as events:
                    for event in events:
                        if event.type == "response.output_text.delta":
                            print(event.delta, end="", flush=True)
                        elif event.type in {"error", "response.failed", "response.incomplete"}:
                            raise RuntimeError(str(event))
                        elif event.type == "response.completed":
                            print("\nusage:", event.response.usage)
            else:
                print(result.output_text)
                print("usage:", result.usage)
        else:
            content = [{"type": "text", "text": "Describe the visible evidence."}]
            if image:
                content.append({"type": "image_url", "image_url": {
                    "url": "data:image/png;base64," + image}})
            result = client.chat.completions.create(model="gpt-6.1-sol",
                messages=[{"role": "user", "content": content}], stream=streaming)
            if streaming:
                with result as chunks:
                    for chunk in chunks:
                        if chunk.choices:
                            print(chunk.choices[0].delta.content or "", end="", flush=True)
            else:
                print(result.choices[0].message.content)
                print("usage:", result.usage)
except (APIError, AnthropicError, OSError, RuntimeError) as error:
    print("Request failed:", error, file=sys.stderr)
    sys.exit(1)

Node.js:同样的六类输入与两种传输

保存为 api_demo.mjs,运行方式如 node api_demo.mjs messages vision stream。不要将 SSE 当成一个完整 JSON 文档解析。SDK 负责事件边界,应用只拼接文本增量。

javascript
import fs from "node:fs/promises";
import OpenAI from "openai";
import Anthropic from "@anthropic-ai/sdk";

// node api_demo.mjs responses|chat|messages text|vision plain|stream
try {
  const [protocol, mode, delivery] = process.argv.slice(2);
  if (!["responses", "chat", "messages"].includes(protocol) ||
      !["text", "vision"].includes(mode) || !["plain", "stream"].includes(delivery)) {
    throw new Error("Invalid protocol, mode or delivery");
  }
  const apiKey = process.env.MONKEYAI_API_KEY;
  if (!apiKey) throw new Error("Set MONKEYAI_API_KEY");
  const stream = delivery === "stream";
  const image = mode === "vision" ? (await fs.readFile("input.png")).toString("base64") : null;
  if (protocol === "messages") {
    const client = new Anthropic({apiKey, baseURL: "https://monkeytheme.com",
      timeout: 120000, maxRetries: 0});
    const content = [{type: "text", text: "Describe the visible evidence."}];
    if (image) content.push({type: "image", source: {
      type: "base64", media_type: "image/png", data: image}});
    const args = {model: "claude-opus-4-8", max_tokens: 600,
      messages: [{role: "user", content}]};
    if (stream) {
      const events = client.messages.stream(args);
      for await (const event of events) {
        if (event.type === "content_block_delta" && event.delta.type === "text_delta")
          process.stdout.write(event.delta.text);
      }
      console.log("\nusage:", (await events.finalMessage()).usage);
    } else {
      const result = await client.messages.create(args);
      for (const block of result.content) if (block.type === "text") console.log(block.text);
      console.log("usage:", result.usage);
    }
  } else {
    const client = new OpenAI({apiKey, baseURL: "https://monkeytheme.com/v1",
      timeout: 120000, maxRetries: 0});
    if (protocol === "responses") {
      const content = [{type: "input_text", text: "Describe the visible evidence."}];
      if (image) content.push({type: "input_image", image_url: `data:image/png;base64,${image}`});
      const result = await client.responses.create({model: "gpt-6.1-sol",
        input: [{role: "user", content}], stream});
      if (stream) {
        for await (const event of result) {
          if (event.type === "response.output_text.delta") process.stdout.write(event.delta);
          if (["error", "response.failed", "response.incomplete"].includes(event.type))
            throw new Error(JSON.stringify(event));
          if (event.type === "response.completed") console.log("\nusage:", event.response.usage);
        }
      } else { console.log(result.output_text); console.log("usage:", result.usage); }
    } else {
      const content = [{type: "text", text: "Describe the visible evidence."}];
      if (image) content.push({type: "image_url", image_url: {url: `data:image/png;base64,${image}`}});
      const result = await client.chat.completions.create({model: "gpt-6.1-sol",
        messages: [{role: "user", content}], stream});
      if (stream) {
        for await (const chunk of result) process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
      } else { console.log(result.choices[0].message.content); console.log("usage:", result.usage); }
    }
  }
} catch (error) {
  console.error("Request failed:", error.status ?? "", error.message);
  process.exitCode = 1;
}

流式完成与用量

Responses 只读取 response.output_text.delta 文本;完成事件可提供 usage,失败或不完整事件必须保留为失败状态。Messages 只拼接文本块,结束后读取最终消息。Chat 的首个块或 usage 块可能没有 choices,delta 也可能为空;上例已跳过空值。Chat 流不保证提供 usage;确认分组支持后可请求 stream_options: {include_usage: true} 并读取最终 usage 块,否则以后台用量记录对账。

连接中断后的部分文本不能当作完整成功结果。生产程序应记录完成状态、停止原因和可用请求 ID;需要提前停止时关闭流。示例没有调用工具;若启用工具调用,还需累积并校验工具参数,不能把它们当作普通文本。不要对已经生成部分内容的请求盲目重试,重复调用可能重复收费。

图片生成与编辑

模型列表列有 gpt-image-2,Codex Pro(外接版)与 ChatGPT 生图分组包含它;Codex Lite 不在该生图列表中。下面分别使用稳定路径 /v1/images/generations 和 /v1/images/edits,编辑由 SDK 发送 multipart 文件。模型列名不是编辑、遮罩、尺寸或返回格式已在当前渠道验证的证据;这些兼容性待验证。示例要求 Base64 返回,遇到仅 URL 返回会明确报错,不自动下载未知地址。

Python 保存为 image_demo.py,Node.js 保存为 image_demo.mjs,参数为 generate 或 edit。编辑需自备 input.png。输出会覆盖当前目录的 output.png,请先保留已有文件。这里没有视频示例:当前证据不能保证 MonkeyAI 视频生成可用。

python
import base64
import os
import sys
from pathlib import Path
from openai import OpenAI, APIError

# python image_demo.py generate|edit
try:
    client = OpenAI(api_key=os.environ["MONKEYAI_API_KEY"],
        base_url="https://monkeytheme.com/v1", timeout=180.0, max_retries=0)
    mode = sys.argv[1]
    if mode == "generate":
        result = client.images.generate(model="gpt-image-2",
            prompt="A paper sculpture of a mountain, plain background", n=1)
    elif mode == "edit":
        with open("input.png", "rb") as image:
            result = client.images.edit(model="gpt-image-2", image=image,
                prompt="Change the background to pale blue; preserve the subject", n=1)
    else:
        raise ValueError("Use generate or edit")
    if not result.data or not result.data[0].b64_json:
        raise RuntimeError("No Base64 image returned; inspect the response format safely")
    Path("output.png").write_bytes(base64.b64decode(result.data[0].b64_json, validate=True))
    print("Saved output.png")
except (APIError, OSError, ValueError, RuntimeError) as error:
    print("Image request failed:", error, file=sys.stderr)
    sys.exit(1)
javascript
import fs from "node:fs";
import OpenAI from "openai";

try {
  if (!process.env.MONKEYAI_API_KEY) throw new Error("Set MONKEYAI_API_KEY");
  const client = new OpenAI({apiKey: process.env.MONKEYAI_API_KEY,
    baseURL: "https://monkeytheme.com/v1", timeout: 180000, maxRetries: 0});
  const mode = process.argv[2];
  let result;
  if (mode === "generate") {
    result = await client.images.generate({model: "gpt-image-2",
      prompt: "A paper sculpture of a mountain, plain background", n: 1});
  } else if (mode === "edit") {
    result = await client.images.edit({model: "gpt-image-2", image: fs.createReadStream("input.png"),
      prompt: "Change the background to pale blue; preserve the subject", n: 1});
  } else throw new Error("Use generate or edit");
  if (!result.data?.[0]?.b64_json) throw new Error("No Base64 image returned; inspect response format safely");
  fs.writeFileSync("output.png", Buffer.from(result.data[0].b64_json, "base64"));
  console.log("Saved output.png");
} catch (error) {
  console.error("Image request failed:", error.status ?? "", error.message);
  process.exitCode = 1;
}

大文件输入

模型不会自动读取本地路径。先在本地提取 PDF/办公文件中的文字;扫描件需要 OCR。Python 用 Path("document.txt").read_text(encoding="utf-8"),Node.js 用 await fs.readFile("document.txt", "utf8") 取得正文,再替换示例文本块中的 text。不要把整个二进制文件放进 text。文件上传、file ID、远程 URL 自动抓取与 PDF 原生输入在这些分组上尚未验证,不能假定 /v1/files 可用。按段落拆分超长输入,保留段落 ID,再汇总分段结果;图片 Base64 增大会同时影响请求体与输入预算。本站确切文件、图片和上下文限制需按模型/渠道确认。

费用与排错

以 实时价格 和账户用量为准。文本输入、输出、缓存及图片的计费方式可能不同,分组倍率和长上下文规则也可能影响费用。不要将每图价格当成文本单价,或把无 usage 的流式响应当成免费。

现象处理
401 / 403检查环境变量、密钥有效性、余额及分组客户端限制;不要打印 Authorization
404 / model_not_found核对完整模型 ID、协议路径和密钥分组;不要随意追加 /v1
400 / 422检查协议专用内容块、图片 MIME 类型及可选参数;逐项减少不兼容选项
413 / 上下文超限缩小图片、提取文本、分段;字符数不是 Token 数
429 / 503降低并发,尊重 Retry-After,设置有上限的指数退避
超时 / 中断检查代理与网络,标记部分结果;确认计费状态再决定是否重试
图片输出为空核对生图分组、返回格式和权限;不要伪造图片或成功结果

错误可能包含用户输入,记录前应脱敏。示例禁用 SDK 自动重试,避免教程隐式多次请求;HTTP 错误、网络异常及流内错误都需要处理。确切 SDK 版本兼容、实时通道和端到端视觉/编辑效果需按所选渠道确认。