关于 minimax-hailuo-video
用于准备和管理通过 MiniMax Hailuo API 发起的付费视频生成请求,强调按区域路由、遵循模型与模式限制、事前费用审批,以及谨慎处理结果不明的任务。
主要功能
- 01按服务区域选择请求入口
- 02选择受支持的视频模式与模型
- 03建立可审计的请求记录
- 04创建任务前审批费用
- 05处理任务轮询及结果不确定情形
适用场景
- 01准备经费用审批的文生视频请求
- 02用图生视频模式让静态图动起来
- 03重试前核对超时的视频任务
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ calesthio/ generative-media-skills --skill minimax-hailuo-video安装后在 Claude Code 中输入/minimax-hailuo-video确认已加载。
附带文件EVAL.md
详细介绍
MiniMax Hailuo 视频
构建经过审核的请求,获取精确的费用批准,最多创建一个付费任务,然后保存结果及其证据。将文档中的空白视为未知项,而不是默认值。
从明确边界开始
选择一个服务区域,并将其账号、密钥、计费、主机和条款一并关联:
| 区域 | 控制台/文档 | API 主机 | 计费货币 |
|---|---|---|---|
| 全球 | platform.minimax.io | https://api.minimax.io | USD |
| 中国大陆 | platform.minimaxi.com | https://api.minimaxi.com | CNY |
这两个平台属于不同的服务区域。不得假定在一个平台购买的密钥或套餐可在另一个平台使用。401 可能是区域不匹配的表现。以下示例使用按量付费 API 密钥;Token Plan/Subscription Key 有独立的配额和模型访问权限。
请勿在此工作流中使用以下产品:
hailuoai.video是面向消费者的 Hailuo 产品,其条款、隐私处理方式、功能和商业用途影响均与此处不同。- Video Agent/模板生成使用不同的模板 API。其已弃用的查询端点以及九小时链接时限说明,不适用于通用视频生成。
- 图像、语音、音乐和文本端点属于不同模态。通用 Hailuo 视频生成文档未说明可输入音频、使用音频提示词或控制生成音频。
- 网关和转售商有各自的架构、数据保留政策、定价和支持。不要将它们称为“MiniMax API”。
先按模式选择路由,再选择模型
目前,一方提供的通用视频生成使用 POST /v1/video_generation,支持四种模式。API 概览重点介绍 Hailuo 2.3 系列和 Hailuo-02;详细端点页面还列出了较旧的模型。
| 模式 | 必需的媒体字段 | 文档列出的模型 | 文档列出的输出选项 |
|---|---|---|---|
| 文本转视频(T2V) | 无;必须提供 prompt | MiniMax-Hailuo-2.3、MiniMax-Hailuo-02;T2V 端点还列出 T2V-01-Director、T2V-01 | 2.3/02:768P,6 或 10 秒;1080P,6 秒;24 fps |
| 图像转视频(I2V) | first_frame_image | MiniMax-Hailuo-2.3、MiniMax-Hailuo-2.3-Fast、MiniMax-Hailuo-02;端点还列出 I2V-01-Director、I2V-01-live、I2V-01 | 2.3/Fast/02:768P,6 或 10 秒;1080P,6 秒。02 还支持:512P,6 或 10 秒;24 fps |
| 首尾帧 | first_frame_image、last_frame_image | MiniMax-Hailuo-02 | 768P,6 或 10 秒;1080P,6 秒;24 fps。端点明确说明不支持 512P |
| 主体参考 | subject_reference 数组 | S2V-01 | **未知:**其端点未列出时长、分辨率和当前价格;但通用指南中的示例仍发送 6 秒和 1080P |
MiniMax-Hailuo-2.3-Fast 仅支持 I2V。不要将其用于 T2V。详细页面将较旧的 *-01* 模型描述为 720P/6 秒,但当前按量付费价格表中没有这些模型;付费使用前,请在正确的控制台确认其可用性和价格。对 S2V-01 也应如此处理,不要套用其他模型的价格。
当前按量付费价格,核对日期:2026-07-10:
| 组合 | 全球 | 中国大陆 |
|---|---|---|
| 2.3-Fast,768P,6 秒 | $0.19 | Â¥1.35 |
| 2.3-Fast,768P,10 秒 | $0.32 | Â¥2.25 |
| 2.3-Fast,1080P,6 秒 | $0.33 | Â¥2.31 |
| 2.3 或 02,768P,6 秒 | $0.28 | Â¥2.00 |
| 2.3 或 02,768P,10 秒 | $0.56 | Â¥4.00 |
| 2.3 或 02,1080P,6 秒 | $0.49 | Â¥3.50 |
| 02 I2V,512P,6 秒 | $0.10 | Â¥0.60 |
| 02 I2V,512P,10 秒 | $0.15 | Â¥1.00 |
这些是每个生成视频的价格,并非永久不变的固定值。批准前,请立即重新核对该区域的官方价格页面。套餐按单位扣费并提高 RPM;Token Plan 使用套餐配额。切勿将套餐的折算成本或其他区域的货币换算成按量付费报价。
构建请求档案
调用 create 前,记录:
- 区域、控制台/账户标识符、密钥类型、模型、模式、分辨率、时长、变体数量、货币、单价和定价页面的获取时间;
- 完整提示词、
prompt_optimizer、fast_pretreatment和每个非默认参数; - 源文件的 SHA-256、MIME 类型、字节长度、尺寸、宽高比、来源、许可/同意情况,以及是否包含人脸/身份信息;
- 目标受众、司法管辖区、AI 披露/水印计划、保留类别和审核者;
- 在 POST 前于本地生成的尝试 ID。
对于首帧/尾帧和 I2V 输入,官方限制为 JPG/JPEG、PNG 或 WebP;小于 20 MB;短边大于 300 px;宽高比介于 2:5 到 5:2 之间;使用公开 URL 或 Base64 数据 URL。首张图像决定输出宽高比/分辨率。在首帧/尾帧模式下,尺寸不同的尾帧会被裁切以匹配首帧。请预先对齐构图、主体比例、光照和拓扑结构,以避免出现不自然的过渡。
S2V 参考文档规定了不同的精确格式:"subject_reference": [{"type": "character", "image": ["https://authorized.example/reference.jpg"]}]。type 为 character;image 是一个包含获准使用的人脸参考 URL 的数组。不要混用 I2V 字段,也不要在 S2V 页面未说明时套用 I2V 的媒体传输规则。
公开源 URL 会让 MiniMax 获取该资源,并可能通过 URL 日志或访问控制暴露该 URL。仅在策略允许时,使用权限范围适当的 URL 或有大小限制的 Base64。未经 SSRF 防护,绝不要将任意用户 URL 交给自己的下载器。对原始资源计算哈希;不要记录 Base64 请求体或签名下载 URL。
为此运动系统编写提示词
提示词上限为 2,000 个字符。Hailuo 在有文档说明的 2.3、02 和 Director 工作流中支持明确的摄像机指令:
[Truck left]、[Truck right]、[Pan left]、[Pan right]、[Push in]、[Pull out]、[Pedestal up]、[Pedestal down]、[Tilt up]、[Tilt down]、[Zoom in]、[Zoom out]、[Shake]、[Tracking shot]、[Static shot]。
同一对方括号中的指令会同时执行;官方建议组合的指令最多不超过三个。依次放置的指令表示一个序列。它们是方向控制,而非精确到帧的时间码。
使用以下精简的提示词顺序:
- 确定主体和初始构图。
- 描述一个符合物理规律、连贯的动作过程。
- 指明主要摄像机指令或简单序列。
- 指定清晰可见的结束状态,以及必须保持稳定的细节。
对于 I2V,应描述相对于所提供画面的变化,而不是与其静态外观矛盾。对于首尾帧生成,应描述两个已知状态之间合理的衔接。对于经过精心设计的镜头,设置 prompt_optimizer: false;否则,优化器可能会改写措辞或摄像机意图。prompt_optimizer 默认为 true。fast_pretreatment 默认为 false,需要启用优化器,并且文档说明该参数适用于当前的 Hailuo I2V 模型。
以下是创作经验建议,并非 API 保证:与复杂密集的编排相比,一个主要动作和一个主导摄像机运动通常更适合 6~10 秒的片段;明确描述结束状态有助于审阅;仍应逐帧检查生成内容中的物理效果、人脸、文字、手部、遮挡和身份一致性。MiniMax 关于运动、表情、真实感和遵循指令能力提升的说法属于提供方声明,并非独立基准测试结果。
通用端点文档未说明 seed、负面提示词、任意宽高比参数、音频输入、音频提示词或音频输出开关。请勿自行编造。在检查实际文件之前,应将输出音轨是否存在及其性质视为未知;如有需要,可在后续流程中添加单独授权的音效。
在各模式专属参考文档中,model 为必填项;T2V 要求提供 prompt,而 I2V 和首尾帧模式将文本提示词列为可选项;提示词长度不得超过 2,000 个字符。first_frame_image、last_frame_image 和 subject_reference 仅在各自对应的模式中为必填项。callback_url 为可选项。duration 和 resolution 必须遵循上文的模型/模式矩阵,而非通用 CLI 默认值。当前中国大陆版 T2V 页面还列出了布尔参数 aigc_watermark,默认值为 false;当前全球版 T2V 页面则未列出该参数。
明确付费边界
默认采用 dry-run。显示脱敏后的请求、单个视频的确切费用、变体倍数、货币、权威定价时间戳/证据摘要、源哈希、本地任务键、输出目标,以及精确主机下载策略。对完整信封进行规范化哈希。要求人工批准该摘要,以及以同一货币计价的有限正数上限;仅批准金额并不足够,因为这无法绑定模型、提示词、来源、区域或保管策略。如果该组合没有当前已发布的价格,则停止并标记为 UNKNOWN,引导操作人员前往正确的控制台或联系 MiniMax 支持。
POST 之前,使用独占创建语义获取任务账本,并持久化一条 intent-recorded 记录。收到响应后,仅持久化允许列表中的结果摘要——任务 ID、提供商代码、状态消息/正文摘要和时间戳——不要保存原始提示词、Base64、错误正文或签名 URL。MiniMax 文档中没有视频创建的幂等键。因此:
- 使用有界退避重试经过身份验证的 GET/轮询/下载操作。
- 不要自动重试创建 POST。
- 如果创建请求超时,或在捕获到
task_id之前连接中断,则将其标记为create-outcome-unknown。在授权替代任务之前,先核对账单、控制台或联系支持。 - 对 5xx、提供商超时/内部错误代码、格式错误的信封,或成功响应中包含不可用任务 ID 的情况,同样视为结果不明确,并在停止前持久化该状态。
- 除非能够确证先前的创建操作并未发生,否则重放就意味着创建一个可能会产生费用的新视频。
HTTP 成功本身并不代表提供商处理成功。必须满足 base_resp.status_code == 0 且 task_id 非空。保留 base_resp.status_code 和 status_msg 以便诊断。常见代码包括:1001 超时、1002 速率限制、1004 身份验证、1008 余额不足、1026 输入敏感、1027 输出敏感、2013 参数无效、2049 密钥无效,以及 2056 用量限制。绝不要规避内容安全响应。
目前已发布的全球默认视频限制为 Hailuo 2.3 和 02 系列合计每分钟 5 个请求;套餐公布的限制更高。将此视为特定账户/套餐的限制,并重新查看控制台。轮询应单独控制频率;指南建议间隔 10 秒。
发生故障时,请查看 https://status.minimax.io/,但如果视频状态没有单独列出,不要仅凭其他已列出组件的状态推断视频生成正常。
完整的先试运行客户端
这个 Python 3.11+ 客户端涵盖两个区域中目前有定价的 T2V、I2V 以及首尾帧组合。它不会执行 S2V-01 或未定价的旧版模型。它绝不重试创建请求,会在创建前获取排他的尝试台账,恢复已知的任务 ID,检查 MiniMax 的响应封装,每 10 秒轮询一次,按照精确主机名/公共 DNS 策略下载且不携带 bearer token,并可在崩溃后恢复,通过 ffprobe 验证 MP4,再使用 ffmpeg 完整解码。将其保存为技能目录之外的 hailuo_generate.py。需要 Python 3.11+、ffprobe 和 ffmpeg。
#!/usr/bin/env python3
import argparse, base64, hashlib, http.client, ipaddress, json, os, pathlib, random, socket, ssl, subprocess, sys, tempfile, threading, time, urllib.error, urllib.parse, urllib.request
from datetime import datetime, timezone
from decimal import Decimal, InvalidOperation
HOSTS = {"global": "https://api.minimax.io", "cn": "https://api.minimaxi.com"}
PRICES = {
"global": {"currency": "USD", "table": {
("MiniMax-Hailuo-2.3-Fast", "768P", 6): "0.19", ("MiniMax-Hailuo-2.3-Fast", "768P", 10): "0.32",
("MiniMax-Hailuo-2.3-Fast", "1080P", 6): "0.33", ("MiniMax-Hailuo-2.3", "768P", 6): "0.28",
("MiniMax-Hailuo-2.3", "768P", 10): "0.56", ("MiniMax-Hailuo-2.3", "1080P", 6): "0.49",
("MiniMax-Hailuo-02", "512P", 6): "0.10", ("MiniMax-Hailuo-02", "512P", 10): "0.15",
("MiniMax-Hailuo-02", "768P", 6): "0.28", ("MiniMax-Hailuo-02", "768P", 10): "0.56",
("MiniMax-Hailuo-02", "1080P", 6): "0.49"}},
"cn": {"currency": "CNY", "table": {
("MiniMax-Hailuo-2.3-Fast", "768P", 6): "1.35", ("MiniMax-Hailuo-2.3-Fast", "768P", 10): "2.25",
("MiniMax-Hailuo-2.3-Fast", "1080P", 6): "2.31", ("MiniMax-Hailuo-2.3", "768P", 6): "2.00",
("MiniMax-Hailuo-2.3", "768P", 10): "4.00", ("MiniMax-Hailuo-2.3", "1080P", 6): "3.50",
("MiniMax-Hailuo-02", "512P", 6): "0.60", ("MiniMax-Hailuo-02", "512P", 10): "1.00",
("MiniMax-Hailuo-02", "768P", 6): "2.00", ("MiniMax-Hailuo-02", "768P", 10): "4.00",
("MiniMax-Hailuo-02", "1080P", 6): "3.50"}}
}
MODELS = {
"text": {"MiniMax-Hailuo-2.3", "MiniMax-Hailuo-02"},
"image": {"MiniMax-Hailuo-2.3", "MiniMax-Hailuo-2.3-Fast", "MiniMax-Hailuo-02"},
"first-last": {"MiniMax-Hailuo-02"},
}
MIME = {".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp"}
MAX_JSON = 256 * 1024
MAX_VIDEO = 250 * 1024 * 1024
MAX_TOOL_OUTPUT = 512 * 1024
NONTERMINAL = {"Preparing", "Queueing", "Processing"}
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl): return None
NO_REDIRECT = urllib.request.build_opener(NoRedirect)
class APIProblem(Exception):
def __init__(self, kind, http_status=None, body_sha256=None):
super().__init__(kind); self.kind = kind; self.http_status = http_status; self.body_sha256 = body_sha256
def canonical(value):
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
def sha256_bytes(value): return hashlib.sha256(value).hexdigest()
def utc_now(): return datetime.now(timezone.utc).isoformat()
def fsync_directory(path):
if os.name != "nt":
fd = os.open(path, os.O_RDONLY)
try: os.fsync(fd)
finally: os.close(fd)
def image_dimensions(data, suffix):
if suffix == ".png" and data[:8] == b"\x89PNG\r\n\x1a\n" and data[12:16] == b"IHDR":
return int.from_bytes(data[16:20], "big"), int.from_bytes(data[20:24], "big")
if suffix in {".jpg", ".jpeg"} and data[:2] == b"\xff\xd8":
i = 2
while i + 9 < len(data):
if data[i] != 0xff: i += 1; continue
marker = data[i + 1]; i += 2
if marker in {0xd8, 0xd9} or 0xd0 <= marker <= 0xd7: continue
if i + 2 > len(data): break
length = int.from_bytes(data[i:i + 2], "big")
if marker in {0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf}:
return int.from_bytes(data[i + 5:i + 7], "big"), int.from_bytes(data[i + 3:i + 5], "big")
if length < 2: break
i += length
if suffix == ".webp" and data[:4] == b"RIFF" and data[8:12] == b"WEBP":
kind = data[12:16]
if kind == b"VP8X" and len(data) >= 30:
return 1 + int.from_bytes(data[24:27], "little"), 1 + int.from_bytes(data[27:30], "little")
if kind == b"VP8L" and len(data) >= 25 and data[20] == 0x2f:
b0, b1, b2, b3 = data[21:25]
return 1 + b0 + ((b1 & 0x3f) << 8), 1 + (b1 >> 6) + (b2 << 2) + ((b3 & 0x0f) << 10)
if kind == b"VP8 " and len(data) >= 30 and data[23:26] == b"\x9d\x01\x2a":
return int.from_bytes(data[26:28], "little") & 0x3fff, int.from_bytes(data[28:30], "little") & 0x3fff
raise SystemExit("Image signature or dimensions are invalid/unsupported")
def atomic_json(path, value):
path = pathlib.Path(path); path.parent.mkdir(parents=True, exist_ok=True)
fd, name = tempfile.mkstemp(prefix=".hailuo-", suffix=".json", dir=path.parent)
try:
os.fchmod(fd, 0o600)
with os.fdopen(fd, "wb") as out:
out.write(canonical(value) + b"\n"); out.flush(); os.fsync(out.fileno())
os.replace(name, path); fsync_directory(path.parent)
finally:
if os.path.exists(name): os.unlink(name)
def exclusive_json(path, value):
path = pathlib.Path(path); path.parent.mkdir(parents=True, exist_ok=True)
fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
with os.fdopen(fd, "wb") as out:
out.write(canonical(value) + b"\n"); out.flush(); os.fsync(out.fileno())
fsync_directory(path.parent)
def read_json(path):
raw = pathlib.Path(path).read_bytes()
if len(raw) > MAX_JSON: raise SystemExit("Ledger exceeds JSON cap")
value = json.loads(raw)
if not isinstance(value, dict): raise SystemExit("Ledger is not an object")
return value
def bounded_process(argv, timeout, cap=MAX_TOOL_OUTPUT):
process = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
chunks, total, overflow, lock = [], [0], threading.Event(), threading.Lock()
def drain(stream):
while True:
data = stream.read(65536)
if not data: return
with lock:
total[0] += len(data)
if total[0] > cap:
overflow.set(); process.kill(); return
chunks.append(data)
threads = [threading.Thread(target=drain, args=(stream,), daemon=True) for stream in (process.stdout, process.stderr)]
for thread in threads: thread.start()
try: code = process.wait(timeout=timeout)
except subprocess.TimeoutExpired:
process.kill(); process.wait(); raise RuntimeError(f"tool timeout: {pathlib.Path(argv[0]).name}")
for thread in threads: thread.join(timeout=2)
output = b"".join(chunks)
if overflow.is_set(): raise RuntimeError(f"tool output cap exceeded: {pathlib.Path(argv[0]).name}")
if code: raise RuntimeError(f"tool failed: {pathlib.Path(argv[0]).name}; diagnostics_sha256={sha256_bytes(output)}")
return output
def media(path_text):
path = pathlib.Path(path_text)
if not path.is_file() or path.suffix.lower() not in MIME or path.stat().st_size >= 20 * 1024 * 1024:
raise SystemExit("Image must be JPG/JPEG, PNG, or WebP and strictly under 20 MB")
data = path.read_bytes()
width, height = image_dimensions(data, path.suffix.lower())
ratio = width / height
if min(width, height) <= 300 or not 0.4 <= ratio <= 2.5:
raise SystemExit("Image short edge must exceed 300 px and aspect ratio must be within 2:5–5:2")
bounded_process(["ffmpeg", "-nostdin", "-v", "error", "-i", str(path), "-f", "null", "-"], 60)
return {
"wire": f"data:{MIME[path.suffix.lower()]};base64,{base64.b64encode(data).decode('ascii')}",
"evidence": {"path": str(path.resolve()), "sha256": hashlib.sha256(data).hexdigest(),
"bytes": len(data), "mime": MIME[path.suffix.lower()],
"width": width, "height": height, "aspect_ratio": ratio}
}
def api_json(method, url, key, body=None, retries=0):
encoded = None if body is None else canonical(body)
for attempt in range(retries + 1):
req = urllib.request.Request(url, data=encoded, method=method,
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json", "Accept": "application/json"})
try:
with NO_REDIRECT.open(req, timeout=40) as response:
raw = response.read(MAX_JSON + 1)
if len(raw) > MAX_JSON: raise APIProblem("response-too-large")
try: result = json.loads(raw)
except json.JSONDecodeError: raise APIProblem("invalid-json", response.status, sha256_bytes(raw))
if not isinstance(result, dict): raise APIProblem("non-object-json", response.status, sha256_bytes(raw))
return result
except urllib.error.HTTPError as exc:
raw = exc.read(MAX_JSON + 1)
problem = APIProblem("http-error", exc.code, sha256_bytes(raw))
if attempt == retries or exc.code not in {408, 429, 500, 502, 503, 504}: raise problem
except APIProblem: raise
except (urllib.error.URLError, TimeoutError, OSError) as exc:
if attempt == retries: raise APIProblem("transport-" + type(exc).__name__)
time.sleep(min(8, 2 ** attempt) * (1 + random.random() * 0.25))
raise AssertionError("unreachable")
def provider_result(result):
base = result.get("base_resp")
if not isinstance(base, dict) or not isinstance(base.get("status_code"), int):
raise APIProblem("missing-provider-envelope", body_sha256=sha256_bytes(canonical(result)))
return base["status_code"], sha256_bytes(str(base.get("status_msg", "")).encode("utf-8"))
def exact_hosts(raw):
hosts = []
for item in raw.split(","):
host = item.strip().rstrip(".").lower()
if not host: continue
if "*" in host or "/" in host or ":" in host: raise SystemExit("Output hosts must be exact DNS names")
try: ipaddress.ip_address(host)
except ValueError: pass
else: raise SystemExit("Output host policy rejects IP literals")
hosts.append(host)
if not hosts: raise SystemExit("Provide at least one reviewed exact --output-host")
return tuple(sorted(set(hosts)))
def validate_output_url(url, allowed_hosts):
parsed = urllib.parse.urlsplit(url)
if parsed.scheme != "https" or not parsed.hostname or parsed.username or parsed.password or parsed.fragment:
raise RuntimeError("Output URL must be HTTPS with hostname, no userinfo, and no fragment")
if parsed.port not in (None, 443): raise RuntimeError("Output URL must use default HTTPS port")
host = parsed.hostname.rstrip(".").lower()
try: ipaddress.ip_address(host)
except ValueError: pass
else: raise RuntimeError("Output URL rejects IP literals")
if host not in allowed_hosts: raise RuntimeError("Output host is outside the approved exact-host policy")
addresses = []
for item in socket.getaddrinfo(host, 443, type=socket.SOCK_STREAM):
address = ipaddress.ip_address(item[4][0])
if not address.is_global: raise RuntimeError("Output host resolves to a non-public address")
addresses.append(str(address))
if not addresses: raise RuntimeError("Output host did not resolve")
target = parsed.path or "/"
if parsed.query: target += "?" + parsed.query
return host, target, sorted(set(addresses))
class PinnedHTTPSConnection(http.client.HTTPSConnection):
def __init__(self, host, address, timeout):
super().__init__(host, 443, timeout=timeout, context=ssl.create_default_context()); self.address = address
def connect(self):
raw = socket.create_connection((self.address, 443), self.timeout)
try: self.sock = self._context.wrap_socket(raw, server_hostname=self.host)
except Exception: raw.close(); raise
def verify_mp4(path):
path = pathlib.Path(path); size = path.stat().st_size
if size <= 0 or size > MAX_VIDEO: raise RuntimeError("Artifact size is outside policy")
digest = hashlib.sha256()
with path.open("rb") as stream:
header = stream.read(32)
if len(header) < 12 or header[4:8] != b"ftyp": raise RuntimeError("Artifact lacks ISO-BMFF ftyp signature")
stream.seek(0)
for chunk in iter(lambda: stream.read(1024 * 1024), b""): digest.update(chunk)
raw = bounded_process(["ffprobe", "-v", "error", "-show_streams", "-show_format", "-of", "json", str(path)], 30, MAX_JSON)
metadata = json.loads(raw); streams = metadata.get("streams", [])
video = next((item for item in streams if item.get("codec_type") == "video"), None)
if not video: raise RuntimeError("Artifact has no video stream")
bounded_process(["ffmpeg", "-nostdin", "-v", "error", "-i", str(path), "-f", "null", "-"], 300)
return {"bytes": size, "sha256": digest.hexdigest(), "qa": {
"container": metadata.get("format", {}).get("format_name"), "video_codec": video.get("codec_name"),
"width": video.get("width"), "height": video.get("height"), "fps": video.get("avg_frame_rate"),
"duration": metadata.get("format", {}).get("duration"),
"audio_present": any(item.get("codec_type") == "audio" for item in streams), "full_decode": "passed"}}
def safe_download(url, destination, allowed_hosts, ledger_path, record):
destination = pathlib.Path(destination); destination.parent.mkdir(parents=True, exist_ok=True)
stage = destination.with_name("." + destination.name + ".hailuo-stage")
expected = record.get("artifact_staged") or record.get("artifact")
if destination.exists():
if not isinstance(expected, dict): raise RuntimeError("Existing artifact has no durable expected digest")
artifact = verify_mp4(destination)
if (artifact["sha256"], artifact["bytes"]) != (expected.get("sha256"), expected.get("bytes")):
raise RuntimeError("Existing artifact does not match durable evidence")
return artifact
if stage.exists() and isinstance(expected, dict):
artifact = verify_mp4(stage)
if (artifact["sha256"], artifact["bytes"]) != (expected.get("sha256"), expected.get("bytes")):
raise RuntimeError("Staged artifact does not match durable evidence")
os.replace(stage, destination); fsync_directory(destination.parent); return artifact
host, target, addresses = validate_output_url(url, allowed_hosts)
url_hash = sha256_bytes(url.encode("utf-8"))
if record.get("download_url_sha256") not in (None, url_hash): raise RuntimeError("Download URL changed during an incomplete transfer")
record.update(state="downloading", download_url_sha256=url_hash, download_host=host, download_started_at=record.get("download_started_at") or utc_now())
atomic_json(ledger_path, record)
if stage.exists(): stage.unlink()
connection = PinnedHTTPSConnection(host, addresses[0], timeout=90)
try:
connection.request("GET", target, headers={"Host": host, "Accept": "video/mp4", "User-Agent": "hailuo-artifact-client/2"})
response = connection.getresponse()
if response.status != 200: raise RuntimeError(f"Output returned HTTP {response.status}")
media_type = response.getheader("Content-Type", "").split(";", 1)[0].lower()
if media_type not in {"video/mp4", "application/octet-stream"}: raise RuntimeError("Unexpected output media type")
announced = response.getheader("Content-Length")
if announced is not None and (not announced.isdigit() or int(announced) > MAX_VIDEO): raise RuntimeError("Invalid or excessive Content-Length")
fd = os.open(stage, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600); total = 0
try:
with os.fdopen(fd, "wb") as out:
while True:
chunk = response.read(1024 * 1024)
if not chunk: break
total += len(chunk)
if total > MAX_VIDEO: raise RuntimeError("Download exceeded byte cap")
out.write(chunk)
out.flush(); os.fsync(out.fileno())
except BaseException:
stage.unlink(missing_ok=True); raise
finally: connection.close()
artifact = verify_mp4(stage)
record.update(state="artifact-staged", artifact_staged=artifact, artifact_validated_at=utc_now())
atomic_json(ledger_path, record)
os.replace(stage, destination); fsync_directory(destination.parent)
return artifact
def main():
p = argparse.ArgumentParser()
p.add_argument("--region", choices=HOSTS, required=True)
p.add_argument("--mode", choices=MODELS, required=True)
p.add_argument("--model", required=True); p.add_argument("--prompt", required=True)
p.add_argument("--resolution", choices=["512P", "768P", "1080P"], required=True)
p.add_argument("--duration", type=int, choices=[6, 10], required=True)
p.add_argument("--first-frame"); p.add_argument("--last-frame")
p.add_argument("--optimizer", action=argparse.BooleanOptionalAction, default=False)
p.add_argument("--fast-pretreatment", action="store_true")
p.add_argument("--execute", action="store_true")
p.add_argument("--job-key", required=True)
p.add_argument("--output-host", required=True, help="Comma-separated reviewed exact DNS hostnames")
p.add_argument("--pricing-checked-at", required=True, help="Timezone-aware ISO-8601 time of the live regional price check")
p.add_argument("--pricing-evidence-sha256", required=True)
p.add_argument("--rights-sha256", required=True); p.add_argument("--moderation-sha256", required=True)
p.add_argument("--qa-plan-sha256", required=True)
p.add_argument("--approval-sha256")
p.add_argument("--max-cost"); p.add_argument("--max-cost-currency", choices=["USD", "CNY"])
p.add_argument("--show-full-plan", action="store_true")
p.add_argument("--ledger", default="hailuo-attempt.json")
p.add_argument("--output", default="hailuo-output.mp4")
a = p.parse_args()
if not a.job_key or any(not (character.isalnum() or character in "-_") for character in a.job_key):
raise SystemExit("job-key accepts letters, digits, hyphen, and underscore")
if a.model not in MODELS[a.mode]: raise SystemExit("Model is not valid for the selected mode")
if len(a.prompt) > 2000: raise SystemExit("Prompt exceeds 2,000 characters")
if a.fast_pretreatment and not a.optimizer: raise SystemExit("fast_pretreatment requires optimizer")
if a.mode in {"image", "first-last"} and not a.first_frame: raise SystemExit("First frame required")
if a.mode == "first-last" and not a.last_frame: raise SystemExit("Last frame required")
key = (a.model, a.resolution, a.duration)
unit = PRICES[a.region]["table"].get(key)
if unit is None: raise SystemExit("UNKNOWN current price/combination; verify in the correct MiniMax console")
if a.model == "MiniMax-Hailuo-02" and a.mode == "text" and a.resolution == "512P":
raise SystemExit("512P pricing/availability is documented for Hailuo-02 I2V, not T2V")
if a.mode == "first-last" and a.resolution == "512P":
raise SystemExit("The first/last-frame endpoint explicitly does not support 512P")
request = {"model": a.model, "prompt": a.prompt, "resolution": a.resolution,
"duration": a.duration, "prompt_optimizer": a.optimizer}
evidence = {}
if a.mode in {"image", "first-last"}:
item = media(a.first_frame); request["first_frame_image"] = item["wire"]; evidence["first_frame"] = item["evidence"]
if a.mode == "first-last":
item = media(a.last_frame); request["last_frame_image"] = item["wire"]; evidence["last_frame"] = item["evidence"]
if a.fast_pretreatment: request["fast_pretreatment"] = True
currency = PRICES[a.region]["currency"]
output_hosts = exact_hosts(a.output_host)
for name, value in (("pricing evidence", a.pricing_evidence_sha256), ("rights", a.rights_sha256),
("moderation", a.moderation_sha256), ("QA plan", a.qa_plan_sha256)):
if len(value) != 64 or any(character not in "0123456789abcdefABCDEF" for character in value):
raise SystemExit(f"{name} must be a SHA-256 digest")
redacted = dict(request)
for field in ("first_frame_image", "last_frame_image"):
if field in redacted: redacted[field] = f"<base64 redacted; sha256={evidence[field.split('_image')[0]].get('sha256', 'see evidence')}>"
redacted["prompt"] = "sha256:" + sha256_bytes(a.prompt.encode("utf-8"))
request_hash = sha256_bytes(canonical(request))
output_policy = {"exact_hosts": list(output_hosts), "https": True, "redirects": "reject",
"reject_ip_literals": True, "public_dns": True,
"dns_pinning": "connect-IP-with-TLS-SNI-and-host-certificate",
"max_bytes": MAX_VIDEO, "container": "mp4", "full_decode": True}
envelope = {"schema_version": 1, "job_key": a.job_key, "region": a.region, "api_origin": HOSTS[a.region],
"endpoint": "/v1/video_generation", "mode": a.mode, "model": a.model,
"request_sha256": request_hash, "redacted_request": redacted, "source_evidence": evidence,
"output": str(pathlib.Path(a.output).resolve()), "output_policy": output_policy,
"price": {"amount": unit, "currency": currency, "checked_at": a.pricing_checked_at,
"evidence_sha256": a.pricing_evidence_sha256.lower()},
"governance": {"rights_sha256": a.rights_sha256.lower(),
"moderation_sha256": a.moderation_sha256.lower(),
"qa_plan_sha256": a.qa_plan_sha256.lower()}}
approval = sha256_bytes(canonical(envelope))
print(json.dumps({"approval_sha256": approval, "plan": envelope}, indent=2, ensure_ascii=False))
if a.show_full_plan: print(json.dumps({"protected_payload": request}, indent=2, ensure_ascii=False))
print(f"DRY RUN: one task, exact maximum {currency} {unit}; no network request sent", file=sys.stderr)
if not a.execute: return
if a.approval_sha256 != approval: raise SystemExit("Exact approval digest mismatch")
try: maximum = Decimal(a.max_cost or "")
except InvalidOperation: raise SystemExit("max-cost must be a decimal")
if not maximum.is_finite() or maximum <= 0 or maximum < Decimal(unit):
raise SystemExit("max-cost must be finite, positive, and at least the quoted amount")
if a.max_cost_currency != currency: raise SystemExit("max-cost currency does not match the selected region")
try: checked_at = datetime.fromisoformat(a.pricing_checked_at.replace("Z", "+00:00"))
except ValueError: raise SystemExit("pricing-checked-at must be ISO-8601")
if checked_at.tzinfo is None: raise SystemExit("pricing-checked-at must include a timezone")
age = datetime.now(timezone.utc) - checked_at.astimezone(timezone.utc)
if age.total_seconds() < -300 or age.total_seconds() > 86400:
raise SystemExit("Live regional pricing evidence must be no more than 24 hours old")
api_key = os.environ.get("MINIMAX_API_KEY")
if not api_key: raise SystemExit("Set MINIMAX_API_KEY only in the environment")
ledger_path = pathlib.Path(a.ledger)
if ledger_path.exists():
intent = read_json(ledger_path)
if intent.get("request_sha256") != request_hash or intent.get("approval_sha256") != approval:
raise SystemExit("Existing job ledger belongs to a different approved request")
task_id = intent.get("task_id")
if not task_id: raise SystemExit(f"Ledger state {intent.get('state')!r} has no task ID; never replay this job key")
else:
intent = {"schema_version": 1, "state": "intent-recorded", "created_at": utc_now(),
"job_key": a.job_key, "request_sha256": request_hash, "approval_sha256": approval,
"region": a.region, "host": HOSTS[a.region], "mode": a.mode, "model": a.model,
"redacted_request": redacted, "source_evidence": evidence, "output_policy": output_policy,
"price": envelope["price"], "governance": envelope["governance"]}
try: exclusive_json(ledger_path, intent)
except FileExistsError: raise SystemExit("Another process acquired this job ledger")
intent.update(state="create-started", create_started_at=utc_now()); atomic_json(ledger_path, intent)
try: created = api_json("POST", HOSTS[a.region] + "/v1/video_generation", api_key, request, retries=0)
except APIProblem as exc:
intent.update(state="create-outcome-unknown", create_outcome_at=utc_now(), error_kind=exc.kind,
http_status=exc.http_status, error_body_sha256=exc.body_sha256, replay_allowed=False)
atomic_json(ledger_path, intent)
raise SystemExit("Create outcome unknown; do not replay until billing/console/support is reconciled")
try: provider_code, provider_message_sha256 = provider_result(created)
except APIProblem as exc:
intent.update(state="create-outcome-unknown", create_outcome_at=utc_now(), error_kind=exc.kind,
response_sha256=exc.body_sha256, replay_allowed=False); atomic_json(ledger_path, intent)
raise SystemExit("Create success envelope is unusable; do not replay")
if provider_code != 0:
uncertain = provider_code in {1001, 1024, 1033}
intent.update(state="create-outcome-unknown" if uncertain else "create-rejected",
create_outcome_at=utc_now(), provider_status_code=provider_code,
provider_status_message_sha256=provider_message_sha256, replay_allowed=False)
atomic_json(ledger_path, intent)
raise SystemExit("MiniMax rejected or could not confirm create; this job key cannot be replayed")
task_id = created.get("task_id")
if not isinstance(task_id, (str, int)) or not str(task_id).isdigit():
intent.update(state="create-outcome-unknown", create_outcome_at=utc_now(),
error_kind="unusable-success-task-id", response_sha256=sha256_bytes(canonical(created)),
replay_allowed=False); atomic_json(ledger_path, intent)
raise SystemExit("Create returned an unusable task ID; do not replay")
task_id = str(task_id)
intent.update(state="task-created", task_id=task_id, submitted_at=utc_now(),
provider_status_code=0, provider_status_message_sha256=provider_message_sha256)
atomic_json(ledger_path, intent)
deadline = time.monotonic() + 30 * 60
while time.monotonic() < deadline:
query = HOSTS[a.region] + "/v1/query/video_generation?" + urllib.parse.urlencode({"task_id": task_id})
try: status = api_json("GET", query, api_key, retries=3)
except APIProblem as exc:
intent.update(poll_error_kind=exc.kind, poll_http_status=exc.http_status,
poll_error_body_sha256=exc.body_sha256); atomic_json(ledger_path, intent); raise SystemExit("Polling failed; resume this task ID later")
provider_code, provider_message_sha256 = provider_result(status)
if provider_code != 0:
intent.update(poll_provider_status_code=provider_code,
poll_provider_status_message_sha256=provider_message_sha256); atomic_json(ledger_path, intent)
raise SystemExit("MiniMax query returned a provider error; resume after diagnosis")
if status.get("task_id") not in (None, task_id, int(task_id)):
raise SystemExit("Query task ID mismatch")
name = status.get("status")
intent.update(state="polling", last_status=name, last_polled_at=utc_now()); atomic_json(ledger_path, intent)
if name == "Success":
file_id = status.get("file_id")
if not isinstance(file_id, (str, int)) or not str(file_id).isdigit(): raise RuntimeError("Success returned invalid file_id")
file_id = str(file_id)
retrieve = HOSTS[a.region] + "/v1/files/retrieve?" + urllib.parse.urlencode({"file_id": file_id})
retrieved = api_json("GET", retrieve, api_key, retries=3)
retrieve_code, retrieve_message_sha256 = provider_result(retrieved)
if retrieve_code != 0: raise RuntimeError("MiniMax retrieve returned a provider error")
file_record = retrieved.get("file") or {}
if str(file_record.get("file_id")) not in {"None", file_id}: raise RuntimeError("Retrieve file ID mismatch")
url = file_record.get("download_url") or retrieved.get("download_url")
if not url: raise RuntimeError("Retrieve returned no download_url")
artifact = safe_download(url, a.output, set(output_hosts), ledger_path, intent)
expected_dimensions = (status.get("video_width"), status.get("video_height"))
actual_dimensions = (artifact["qa"].get("width"), artifact["qa"].get("height"))
try: actual_duration = Decimal(str(artifact["qa"].get("duration")))
except InvalidOperation: actual_duration = Decimal("NaN")
qa_state = "automated-passed"
if expected_dimensions != actual_dimensions or not actual_duration.is_finite() or abs(actual_duration - Decimal(a.duration)) > Decimal("1"):
qa_state = "quarantined-metadata-mismatch"
intent.update(state="artifact-downloaded" if qa_state == "automated-passed" else "artifact-quarantined",
file_id=file_id, retrieve_provider_message_sha256=retrieve_message_sha256,
artifact=dict(artifact, path=str(pathlib.Path(a.output).resolve())),
expected_dimensions={"width": expected_dimensions[0], "height": expected_dimensions[1]},
qa_state=qa_state, human_review="pending", stored_at=utc_now())
atomic_json(ledger_path, intent); print(json.dumps({"artifact": intent["artifact"], "qa_state": qa_state}, indent=2)); return
if name == "Fail":
intent.update(state="generation-failed", terminal_at=utc_now(),
failure_sha256=sha256_bytes(str(status.get("error_message", "")).encode("utf-8")))
atomic_json(ledger_path, intent); raise SystemExit("MiniMax generation failed")
if name not in NONTERMINAL: raise RuntimeError("Unknown task status; fail closed")
time.sleep(10)
intent.update(state="poll-deadline-reached", deadline_at=utc_now()); atomic_json(ledger_path, intent)
raise SystemExit("Polling deadline reached; task may still be running—resume query, do not recreate")
if __name__ == "__main__": main()
在不使用密钥或进行网络调用的情况下,试运行一个全球版 6 秒 768P T2V 请求。首先重新打开全球按量付费页面,记录当前带时区的检查时间,对受保护的证据副本进行哈希,并提供受保护的权利/审核/QA 证据摘要。输出主机必须是从非敏感暂存工作流或服务商确认中获知的、经过审核的精确 CDN 主机名;不要使用通配符:
python hailuo_generate.py --region global --mode text --model MiniMax-Hailuo-2.3 --resolution 768P --duration 6 --job-key kite-v1 --output-host '<reviewed-exact-download-host>' --pricing-checked-at '2026-07-10T12:00:00Z' --pricing-evidence-sha256 '<64-hex-digest>' --rights-sha256 '<64-hex-digest>' --moderation-sha256 '<64-hex-digest>' --qa-plan-sha256 '<64-hex-digest>' --prompt "A red paper kite rises above a quiet salt flat. [Tracking shot] The camera follows low and steady; the kite settles centered against the horizon."
检查受保护的载荷和未更改的材料后,使用相同参数再次执行,并添加打印出的摘要、币种匹配的上限以及明确的执行门控:
export MINIMAX_API_KEY='replace-in-your-shell-only'
python hailuo_generate.py <the-exact-unchanged-dry-run-arguments> --execute --approval-sha256 '<printed-digest>' --max-cost 0.28 --max-cost-currency USD
对于 PowerShell,使用 $env:MINIMAX_API_KEY = '...' 设置密钥。不要将真实密钥粘贴到脚本、笔记本、日志、聊天、浏览器代码或版本控制中。客户端刻意不添加中国区的 aigc_watermark 字段:当前中国区 T2V 文档中提供了该字段,而当前全球版文档中没有。如果在中国区路由中使用该字段,仅在核实当前中国区对应模式的确切参考文档后再添加;它不能替代特定司法管辖区要求的公开披露。
完成异步生命周期
轮询状态区分大小写:Preparing、Queueing、Processing、Success、Fail。成功后,获取 file_id,调用 GET /v1/files/retrieve?file_id=...,并立即下载。当前通用文件文档未说明下载 URL 的有效期或生成文件的保留期限。历史页面和单独的模板 API 提到了九小时;这并不构成当前通用视频的保证。
将经过身份验证的 retrieve 响应中的签名 URL 视为不可信。下载时不要携带 bearer token;必须使用 HTTPS、不得包含 userinfo/片段、使用默认端口、通过审核的精确主机名、DNS 解析结果全部为公共地址、拒绝 IP 字面量、使用 DNS 固定连接并针对已批准主机名进行 TLS SNI/证书验证、不跟随重定向,并设置严格的类型/字节数/时间上限。先可靠地暂存,再计算哈希、运行 ffprobe,并使用限制了 stdout/stderr 的 ffmpeg 完整解码,最后通过原子操作晋升文件。重启时,只有当现有最终文件/暂存文件的大小和摘要与持久化台账证据相符,才可采用该文件。
检查整个片段的安全性、身份漂移、物理表现和验收标准。记录容器/编解码器、时长、尺寸、帧率、音频流、字节长度、SHA-256、提示词哈希或受保护的提示词、请求/批准摘要、任务/文件 ID、模型、源文件哈希和权利证据、价格证据、自动与人工审核结果,以及下游血缘信息。将签名 URL 和密钥存储在普通日志之外;参考客户端仅记录 URL 哈希和精确主机名。
回调是可选的。MiniMax 会通过 POST challenge 验证回调,端点必须在三秒内原样返回该值。更新载荷使用小写状态 processing、success、failed,与轮询响应不同。目前没有文档说明回调签名或共享密钥机制。使用 TLS、难以猜测的路径、请求体/速率限制,以及预期任务 ID 允许列表;仅将回调视为唤醒信号,并在执行终态操作或下载前,重新查询经过身份验证的状态端点。
应用权利、肖像、披露和数据控制
仅使用你获准提交的提示词和媒体。对于可识别身份的人,应记录其知情同意,明确同意的用途、地区、受众、期限、编辑处理和合成转换;并取得适用的公开形象权、表演者、劳动者或未成年人/监护人批准。绝不暗示对方为内容背书、冒充他人实施欺诈、未经同意制作私密影像,或绕过安全过滤器。即使 API 仅将 S2V-01 使用的人脸照片标记为参考图像,该照片仍属于与生物识别相关的敏感材料。
MiniMax 当前的 API 条款要求遵守法律、公开标记深度合成内容,并使用适用的技术标识符/备案信息。添加醒目的披露,并保留溯源信息,即使某个调用方式提供 aigc_watermark 标记也是如此。发布前,应重新核查当地的选举、广告、儿童安全、隐私、生物识别和 AI 标记规则。
不要承诺“不会用于训练”或固定的删除期限。当前全球 API 条款允许为提供、维护、开发、改进、保障服务安全及执行服务规定而处理输入/输出;隐私政策说明了基于用途的数据保留方式及美国数据中心,但未为一般视频输入规定固定的删除期限。服务提供方可能会删除已存储/生成的信息。尽量减少个人数据,制定自己的保留/删除计划,限制对台账/成品的访问权限,并在地区、相关人员或法律要求时执行数据传输评估。进行敏感内容制作前,应重新阅读相应地区的现行条款和隐私声明。
审慎处理事实与冲突
将每条重要陈述归类为以下一种:
- **FACT:**当前的一手参考资料、定价、条款或状态文档直接说明了该内容。
- **PROVIDER CLAIM:**MiniMax 对模型质量或能力的描述未经独立验证。
- **HEURISTIC:**根据经验和系统限制推断出的操作或创作实践。
- **UNKNOWN:**当前一手资料未提及、存在冲突,或未给出该组合的定价。
截至 2026-07-10,已知的文档冲突/局限:
- S2V 参考资料未说明时长/分辨率,而指南示例给出了 6 s/1080P;价格缺失。
- 概览页面重点介绍 2.3/Fast/02,而端点参考资料仍保留旧版模型名称,且未提供当前按量付费价格。
- 中国 T2V 参考资料公开了
aigc_watermark;全球 T2V 参考资料则没有。 - 通用文件文档未说明 URL 有效期;不要套用历史版本或已弃用模板端点所述的 9 小时有效期。
- 轮询状态使用首字母大写的形式;回调状态使用小写形式。
如果冲突涉及费用、权利、兼容性或数据保留,应暂停并在正确的控制台中核实,或联系 MiniMax 支持。记录答复内容及检索时间,不要默默选择更方便的解释。
第一方来源索引
有关实现的信息仅使用对应区域当前的第一方页面:
- 全球文档索引:https://platform.minimax.io/docs/llms.txt
- 全球视频指南和 API 参考:https://platform.minimax.io/docs/guides/video-generation, https://platform.minimax.io/docs/api-reference/video-generation-t2v, https://platform.minimax.io/docs/api-reference/video-generation-i2v, https://platform.minimax.io/docs/api-reference/video-generation-fl2v, https://platform.minimax.io/docs/api-reference/video-generation-s2v, https://platform.minimax.io/docs/api-reference/video-generation-query, https://platform.minimax.io/docs/api-reference/video-generation-download
- 全球定价、限制、错误和发行说明:https://platform.minimax.io/docs/guides/pricing-paygo, https://platform.minimax.io/docs/guides/pricing-video, https://platform.minimax.io/docs/guides/rate-limits, https://platform.minimax.io/docs/api-reference/errorcode, https://platform.minimax.io/docs/release-notes/apis, https://platform.minimax.io/docs/release-notes/models
- 全球 API 条款和隐私政策:https://platform.minimax.io/protocol/terms-of-service, https://platform.minimax.io/protocol/privacy-policy
- 中国大陆文档索引和按量付费定价:https://platform.minimaxi.com/docs/llms.txt, https://platform.minimaxi.com/docs/guides/pricing-paygo
- 区域与密钥的区别:https://platform.minimax.io/docs/token-plan/minimax-cli
- 官方服务状态:https://status.minimax.io/
- 独立的消费者条款和隐私政策:https://hailuoai.video/doc/terms-of-service.html, https://hailuoai.video/doc/privacy-policy.html
当前文档索引中没有专门介绍 Hailuo 2.3 的技术模型卡或安全卡。除非 MiniMax 发布第一方材料,否则架构、训练语料、基准测试方法、系统性安全评估和不同人群的性能均视为未知。