入口、密钥与当前版本
TypeSafe 面向规则判断、分类和评分,使用 POST https://monkeytheme.com/v1/systemone,返回完整 JSON,不使用聊天消息协议或流式输出。在 注册 或 登录 后,到 密钥管理 选择 TypeSafe 分组密钥。
当前后端仅接受 jev-latest。 模型广场虽列出 jev-1.13.0、jev-1.14.0,目前不承诺固定版本可调用;不要把广场展示当成可调用证据。本文所有请求使用 jev-latest,不保证它当前映射到某个固定版本。需要复现时保存请求、时间和实际返回的 model,并先确认版本支持状态。
示例没有发送真实请求,没有虚构返回值。运行会发送你的数据并可能计费。密钥仅从环境读取,不能放入网页前端或 Git。
export TYPESAFE_API_KEY="YOUR_API_KEY"$env:TYPESAFE_API_KEY="YOUR_API_KEY"使用 Bash/WSL 的最小 cURL 请求(需要已设置环境变量;运行将发送请求):
curl --fail-with-body --connect-timeout 15 --max-time 120 "https://monkeytheme.com/v1/systemone" -H "Authorization: Bearer $TYPESAFE_API_KEY" -H "Content-Type: application/json" --data-binary '{"model":"jev-latest","state":"Please replace my damaged parcel.","questions":{"replacement":{"type":"noul","instructions":"Is a replacement requested?"}}}'state、questions 与答案结构
请求包含 model、state 和非空对象 questions。state 可为字符串、对象或数组,保存待评估材料;questions 的键是你定义的结果 ID,每题用 instructions 写清要判断什么。返回 answers 使用相同 ID。不要发送 messages、prompt 或 stream=true。图片资产使用顶层 assets。
| type | criteria | 如何读取 |
|---|---|---|
noul | 可选的 true / false 描述对象 | answers.ID.noul 为 0–1 概率,不是布尔值;业务自行设置阈值 |
choice | 选项 ID 到描述的对象,参考协议为 1–255 项 | answers.ID.choice 是选项 ID;同时检查 confidence 与 probabilities |
score | 按低到高排列的描述数组,参考协议为 1–10 级,从 0 编号 | answers.ID.score 是概率加权分数,可能是小数;参考 legend、confidence、probabilities |
box | 用 instructions 描述一个具体目标 | 图片定位实验题型;MonkeyAI 当前可用性未验证,不能当成已支持 |
同一请求内各题独立评估,共享 state;不能要求第二题读取第一题尚未返回的答案。依赖前一步判断的流程应由应用拆成多次调用。confidence 与概率含义不同,0.8 等阈值需要用自己的验证集校准,不能把概率直接当成真假或准确率保证。
Python:判断、分类、评分与文件模式
Python 3 只需标准库。保存为 typesafe_demo.py,文本示例运行 python3 typesafe_demo.py text;文件模式运行 python3 typesafe_demo.py file。图片模式为待验证示例,确认渠道能力后再使用。示例实际打印响应,不预设任何成功答案。
import base64
import json
import os
import sys
import urllib.error
import urllib.request
import uuid
from pathlib import Path
# python typesafe_demo.py text|file|image
def evaluate(body):
request = urllib.request.Request(
"https://monkeytheme.com/v1/systemone",
data=json.dumps(body, ensure_ascii=False).encode("utf-8"),
headers={"Authorization": "Bearer " + os.environ["TYPESAFE_API_KEY"],
"Content-Type": "application/json"}, method="POST")
try:
with urllib.request.urlopen(request, timeout=120) as response:
result = json.load(response)
except urllib.error.HTTPError as error:
# Do not log credentials or private request contents.
raise RuntimeError(f"HTTP {error.code}; inspect a redacted error body locally") from error
if not isinstance(result.get("answers"), dict):
raise ValueError("Response has no answers object")
return result
try:
mode = sys.argv[1]
body = {"model": "jev-latest", "state": {"note":
"The delivery arrived damaged. Can someone arrange a replacement this week?"},
"questions": {
"replacement": {"type": "noul", "instructions": "Is a replacement requested?",
"criteria": {"true": "A replacement is requested.",
"false": "No replacement is requested."}},
"route": {"type": "choice", "instructions": "Select the responsible team.",
"criteria": {"delivery": "Damaged or missing deliveries.",
"account": "Account access.", "other": "Other issues."}},
"priority": {"type": "score", "instructions": "Assess the stated time pressure.",
"criteria": ["No deadline.", "Within a week.", "Immediate action required."]}}}
if mode == "file":
body["state"] = Path("document.txt").read_text(encoding="utf-8")
body["questions"] = {"renewal": {"type": "noul",
"instructions": "Does the document require an explicit renewal decision?"}}
elif mode == "image":
# Unverified on MonkeyAI: confirm image capability before using this mode.
ref = str(uuid.uuid4())
body["state"] = {"photo": ref}
body["assets"] = {ref: {"type": "image", "format": "png",
"data_base64": base64.b64encode(Path("input.png").read_bytes()).decode("ascii")}}
body["questions"] = {"label_visible": {"type": "noul",
"instructions": "Is a shipping label visible in the image?"}}
elif mode != "text":
raise ValueError("Use text, file or image")
result = evaluate(body)
print(json.dumps(result, ensure_ascii=False, indent=2))
if mode == "text":
probability = result["answers"]["replacement"]["noul"]
if isinstance(probability, bool) or not isinstance(probability, (int, float)) or not 0 <= probability <= 1:
raise ValueError("Invalid noul probability")
# 0.8 is an illustrative policy threshold, not a service guarantee.
print("Review for replacement:", probability >= 0.8)
print("usage:", result.get("usage"))
except (OSError, ValueError, KeyError, TypeError, RuntimeError) as error:
print("TypeSafe request failed:", error, file=sys.stderr)
sys.exit(1)Node.js:相同任务,无额外依赖
使用支持 fetch 与 AbortSignal.timeout 的 Node.js 18+(建议维护中的 LTS)。保存为 typesafe_demo.mjs,运行 node typesafe_demo.mjs text、file 或确认能力后的 image。HTTP 错误、JSON 错误、文件读取错误和超时都会走错误分支;不会自动重试产生额外请求。
import fs from "node:fs/promises";
import {randomUUID} from "node:crypto";
// node typesafe_demo.mjs text|file|image
async function evaluate(body) {
const key = process.env.TYPESAFE_API_KEY;
if (!key) throw new Error("Set TYPESAFE_API_KEY");
const response = await fetch("https://monkeytheme.com/v1/systemone", {
method: "POST", headers: {Authorization: `Bearer ${key}`, "Content-Type": "application/json"},
body: JSON.stringify(body), signal: AbortSignal.timeout(120000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}; inspect a redacted error body locally`);
const result = await response.json();
if (!result.answers || typeof result.answers !== "object" || Array.isArray(result.answers))
throw new Error("Response has no answers object");
return result;
}
try {
const mode = process.argv[2];
const body = {model: "jev-latest", state: {note:
"The delivery arrived damaged. Can someone arrange a replacement this week?"},
questions: {
replacement: {type: "noul", instructions: "Is a replacement requested?",
criteria: {true: "A replacement is requested.", false: "No replacement is requested."}},
route: {type: "choice", instructions: "Select the responsible team.",
criteria: {delivery: "Damaged or missing deliveries.", account: "Account access.", other: "Other issues."}},
priority: {type: "score", instructions: "Assess the stated time pressure.",
criteria: ["No deadline.", "Within a week.", "Immediate action required."]}
}};
if (mode === "file") {
body.state = await fs.readFile("document.txt", "utf8");
body.questions = {renewal: {type: "noul",
instructions: "Does the document require an explicit renewal decision?"}};
} else if (mode === "image") {
// Unverified on MonkeyAI: confirm image capability before using this mode.
const ref = randomUUID();
body.state = {photo: ref};
body.assets = {[ref]: {type: "image", format: "png",
data_base64: (await fs.readFile("input.png")).toString("base64")}};
body.questions = {label_visible: {type: "noul",
instructions: "Is a shipping label visible in the image?"}};
} else if (mode !== "text") throw new Error("Use text, file or image");
const result = await evaluate(body);
console.log(JSON.stringify(result, null, 2));
if (mode === "text") {
const probability = result.answers.replacement.noul;
if (typeof probability !== "number" || probability < 0 || probability > 1)
throw new Error("Invalid noul probability");
console.log("Review for replacement:", probability >= 0.8);
}
console.log("usage:", result.usage);
} catch (error) {
console.error("TypeSafe request failed:", error.message);
process.exitCode = 1;
}图片输入与 Box:未验证能力
图片模式展示参考协议的 JSON + Base64 结构:为图片创建 UUID,在 assets 中放 type: image、format: png 和不带 data URL 前缀、不换行的 data_base64,再用 state.photo 引用该 UUID。JPEG 要同步修改文件名和 format 为 jpg 或 jpeg。重复引用可能增加展开后的输入;共用图片尽量只在 state 引用一次。图片 URL 和本地路径不能替代文件字节,也不能假定服务自动下载它们。multipart、SDK 自动上传及压缩请求体在本站未验证。
参考资料描述静态 PNG/JPEG、每边 280–1568 像素、单图 16 MiB、请求体 32 MiB;这些不是已验证的 MonkeyAI 限制或容量承诺,本站及当前别名实际支持的格式、尺寸和上限仍待确认。先校正方向,必要时缩放或补边;Base64 通常增加约三分之一体积。不要据此承诺动画或视频输入。
Box 同样未验证;不能因广场列出 1.14.0 就认定可调用或可定位。待确认后,题目结构为下方示意片段(不是独立请求,也不应直接加入生产示例):
{"target": {"type": "box", "instructions": "Locate the shipping label in the referenced image."}}参考坐标为归一化的 left/top/right/bottom,原点为左上角。对发送图片宽 W、高 H,像素框为 (left×W, top×H, right×W, bottom×H);取整并限制边界,缩放或裁剪后需换算回原图。没有目标时也可能返回框,宜先用 noul 验证目标存在,再决定是否采纳坐标。confidence 可能为 null,不应解释成 0。多个目标拆成明确的单目标问题。上述说明用于理解协议,不代表当前服务已能返回 Box。
长文件与上下文边界
在当前目录准备 UTF-8 的 document.txt,两种语言的 file 模式都会读取内容放入 state,检查文档是否要求明确续约决定。文件名、网址本身不会被读取;PDF、Office 和扫描件须先本地提取文本或 OCR,不要直接把二进制解码成 UTF-8。
参考资料提到整次请求与 state 加单题的 262K Token 上限;需确认 MonkeyAI 当前 jev-latest 的上下文上限、入口大小或长上下文计费,不能将参考协议上限视为本站容量保证。输入预算包括 state、问题及图片,字符数不是 Token 数。逐段处理超大文件,给每段稳定 ID,在应用中汇总;注意跨段依赖可能影响判断。不要将某段未提到条款误判为整份文档不存在条款。
用量与计费
读取响应中的 usage.input_tokens、usage.output_tokens,并到 实时价格 与账户用量记录核对。清单中 TypeSafe 采用 Token 计费且输出标价为零;实际有效费率仍取决于当前分组规则。若规则确认为仅输入计费,估算为输入 Token ÷ 1,000,000 × 每百万输入有效价格。不要用四舍五入的显示价精确对账,也不要未经确认宣称图片无附加费或长上下文费率不变。图像会影响输入用量;失败或重试的计费应查记录,不凭空推断。
排错与上线前检查
| 现象 | 检查与处理 |
|---|---|
| 401 / 403 | 环境变量、完整密钥、有效期、余额和 TypeSafe 分组;不要打印密钥 |
| 404 / 协议错误 | 固定使用 /v1/systemone;Chat、Responses、Messages 都不能代替它 |
| model_not_found / 固定版本被拒 | 当前仅使用 jev-latest;不保证固定版本可用,不要循环重试同一版本 |
| 400 / 422 | 检查非空 questions、state、type、instructions、criteria;不发送 stream;图片还需核对引用与编码 |
| 413 | 减小文件或图片,考虑 Base64 膨胀及网关限制,拆分请求 |
| max_tokens_exceeded | 同时缩减 state、问题与图片,避免重复引用;确认实际上下文上限 |
| 429 / 503 | 降低并发,按 Retry-After(如有)做有上限退避;持续失败时保留脱敏请求 ID 给支持人员 |
| 超时 / 非 JSON / answers 缺失 | 标记失败,保留脱敏状态与内容类型;核查网关响应,不构造假的答案 |
使用前请确认别名映射、响应字段、图片和 Box 能力、输入限制、运行时兼容性与实际计费;文本请求成功不代表所有能力均可用。