准备密钥与运行环境
在 注册 或 登录 后,前往 密钥管理 创建适合接口的分组密钥。将密钥放在运行脚本的环境变量中;不要写进代码、浏览器或提交记录。运行脚本会产生请求与潜在费用。
Python 3 安装 python3 -m pip install openai anthropic;Node.js 使用支持当前 SDK 的 LTS 版本,安装 npm install openai @anthropic-ai/sdk。请使用官方 SDK 软件包。
export MONKEYAI_API_KEY="YOUR_API_KEY"$env:MONKEYAI_API_KEY="YOUR_API_KEY"协议与分组选择
| 协议 | 稳定路径 | 本文模型 |
|---|---|---|
| Responses | POST /v1/responses | gpt-6.1-sol |
| Chat Completions | POST /v1/chat/completions | gpt-6.1-sol |
| Messages | POST /v1/messages | claude-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 使用流式处理。三个协议均有文本和视觉分支。它们展示协议结构,所选分组的视觉兼容性仍需实际验证。
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 负责事件边界,应用只拼接文本增量。
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 视频生成可用。
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)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 版本兼容、实时通道和端到端视觉/编辑效果需按所选渠道确认。