教程
如何通过 HiAPI API 使用 gpt-6.1-terra:curl、Python 与一个可用的请求
一次 gpt-6.1-terra 请求真正需要什么
在 HiAPI 上对 gpt-6.1-terra 的每次调用都归结为四件事:一个 URL、一个认证头、模型标识符和请求体。把这些做对,本文剩下的部分就只是管道接法。
端点
所有模型调用都通过 HiAPI 上的同一个基础 URL 发起。你不需要为每个模型分别切换主机——请求体中的模型名称才是路由请求的依据。将你的客户端指向 HiAPI 基础 URL,并把它保存在变量中,这样你只需在一处修改。
认证头
认证方式是在 Authorization 头中传递的 bearer token。该 token 是在你的 HiAPI 账户中签发的 API 密钥。切勿将其硬编码在源文件中;从环境变量(例如 HIAPI_API_KEY)读取它,这样同一份代码就能在本地、CI 和生产环境中通用。
模型标识符
请求体必须显式指定模型:"model": "gpt-6.1-terra"。该字符串区分大小写,必须完全匹配。模型名称错误或被截断是导致请求被拒绝的最常见原因之一,因此应将其视为代码中的常量,而不是随手写的字符串。
请求体字段
一次请求至少携带模型和输入内容——即你希望模型处理的提示词或消息。在这个核心之上,你可以添加模型支持的任意可选参数(输出格式提示、长度控制等),但请从能产生有效响应的最小请求体开始。最小化的请求体比全副武装的请求体更容易调试,并且能帮你判断失败是出在你的请求体上还是管道接法上。
有一个结构性要点值得尽早内化:有些响应是内联返回的,有些则作为你需要轮询的任务返回。我们会在后文介绍如何区分二者,但现在就应设计好你的请求层,使响应处理能在两种方式之间切换而无需重写。
一个可用的 curl 请求,逐行解析
以下是一个完整的调用示例,导出密钥后即可粘贴到终端中执行。它使用 HiAPI 基础 URL 和一个最小的 JSON 请求体。
curl -sS -X POST "$HIAPI_BASE_URL/v1/chat/completions" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6.1-terra",
"messages": [
{"role": "user", "content": "用三句话总结水循环。"}
]
}'
每个标志的作用
-X POST 设置 HTTP 方法。模型调用是 POST,因为它们携带请求体。
-sS 让 curl 隐藏进度信息,但仍会显示真实错误。没有 -S,一个失败的请求可能看起来只是一片沉默。
-H "Authorization: Bearer $HIAPI_API_KEY" 附加你的凭据。Bearer 前缀及其后的单个空格是认证方案的一部分;省略任何一个都会导致认证失败,而且看起来与请求头无关。
-H "Content-Type: application/json" 告诉服务器请求体是 JSON。有些网关能容忍缺失的内容类型,但不要依赖这一点。
-d '...' 是请求体。单引号可防止 shell 对 JSON 进行插值。如果你想改用文件,请将 -d '...' 替换为 -d @payload.json,并把 JSON 放在一个可以 lint 和 diff 的文件中。
两个能省时间的习惯
首先,当出现失败时,加上 -i 以打印响应状态行和响应头。一个返回 200 但响应体异常的响应,与 401 或 422 的读法截然不同,你会希望立即看到这种区别。其次,将输出通过 JSON 格式化器处理(curl ... | python -m json.tool),以便嵌套的响应字段可读。这两个标志都无副作用,在调整调用时你会经常用到它们。
将调用移植到 Python
Python 版本应保留 curl 调用的精确语义:相同的 URL、相同的请求头、相同的请求体、相同的解析方式。使用标准库可以将依赖保持在零,并使移植过程一目了然。
import json
import os
import urllib.request
HIAPI_BASE_URL = os.environ["HIAPI_BASE_URL"].rstrip("/")
HIAPI_API_KEY = os.environ["HIAPI_API_KEY"]
def call_terra(prompt: str, model: str = "gpt-6.1-terra") -> dict:
body = {
"model": model,
"messages": [{"role": "user", "content": prompt}],
}
req = urllib.request.Request(
f"{HIAPI_BASE_URL}/v1/chat/completions",
data=json.dumps(body).encode("utf-8"),
headers={
"Authorization": f"Bearer {HIAPI_API_KEY}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(req, timeout=120) as resp:
return json.loads(resp.read().decode("utf-8"))
result = call_terra("用三句话总结水循环。")
print(json.dumps(result, indent=2))
关于移植的说明
请求头在请求对象内部一次性构建,对应两个 -H 标志。请求体是一个 Python dict,用 json.dumps 序列化,这在程序上等价于单引号包裹的 curl 请求体——并且它消除了 Windows shell 上的引号问题。
该函数将模型作为带默认值的参数,而不是在各处重复字面量。这样模型名称只有单一事实来源,同时允许你刻意覆盖它。
这里刻意省略了错误处理,以保持正常路径可读。在实际代码中,应包裹 urlopen 调用并捕获 HTTP 错误异常,从错误对象读取 .read() 以查看服务器消息。超时被显式设置;无限阻塞最终会挂起一个 worker。
如果你更偏好 httpx 或 requests,映射是一对一的:requests.post(url, headers=..., json=body, timeout=120)。json= 参数会为你序列化 dict。语义没有任何变化,这正是重点——curl 调用是契约,Python 只是它的第二种呈现形式。
在单次调用与异步任务之间做决定
并非每个请求都适合等待。决策取决于工作耗时长短,以及调用方能否承受阻塞。
何时适合单次调用
简短的交互式请求——摘要、分类、小幅改写——应该是普通的同步调用。响应内联返回,你解析它,然后返回。这是最简单的代码路径,也最容易在测试中推理。如果用户正在等待 UI,同步就是预期的形态。
何时应视为任务
当工作长时间运行或产生大量产物、且连接中断意味着重做昂贵工作时,应将调用视为异步。在这些情况下,你提交请求,收到任务标识符,然后轮询直到它达到终态。提交本身很快返回,因此你的 worker 或 HTTP 处理器不会在整个过程中被占用。
一条实用规则:如果你发现自己为了“让调用成功”而设置异常长的客户端超时,这就是切换到任务的信号。超时不是调度机制。
让切换代价低廉
不要将代码库分裂为两个互不相关的客户端。将两条路径封装在一个函数后面,该函数返回即时结果或任务句柄,并让调用方请求它想要的模式。这样,请求构造——模型、认证、请求体——保持共享,只有等待策略不同。这也意味着调试是一致的:当请求体较小时,你可以同步复现异步失败。
要对自己的工作负载诚实,而不是猜测平台行为。如果你的请求通常几秒内完成,就保持同步并跳过复杂性。异步路径的价值体现在真正缓慢的请求上,而不是所有请求。
轮询完成状态而不猛击 API
轮询循环有三项职责:等待足够久以示礼貌、最终停止、清晰呈现终态。以下是一个紧凑版本。
import time
def poll_until_done(fetch_status, job_id, interval=3.0, timeout=600.0):
deadline = time.monotonic() + timeout
while True:
status = fetch_status(job_id)
state = status.get("status")
if state in ("succeeded", "failed", "cancelled"):
return status
if time.monotonic() > deadline:
raise TimeoutError(f"job {job_id} not finished after {timeout}s")
time.sleep(interval)
为什么采用这种结构
fetch_status 是注入的,因此无论你是通过 HTTP 轮询还是针对内部队列轮询,同一个循环都能工作。这也使得用一个先返回两次 running 再返回 succeeded 的假对象来测试变得轻而易举。
状态检查是对终态的显式白名单,而不是检查单个 "done" 字符串。终态各不相同——成功、失败、取消——你希望其中任何一个出现时都停止,而不是因为只查找了一种状态而空转到超时。
截止时间使用 time.monotonic(),这样墙钟调整不会意外延长或缩短等待时间。
选择轮询间隔
从几秒开始,抵制紧凑循环轮询的冲动。亚秒级间隔会消耗请求预算并增加负载,却不会让任务更快完成。如果你预期任务较长,可以考虑温和退避——例如,每次检查后增加间隔直到上限。保持上限适度;目标是减少请求,而不是制造数分钟的盲区。
超时不是可选项
始终设置超时,相对于工作要宽松但有限。触发时,不要静默返回空结果。抛出异常、记录任务标识符,并让调用方决定是否重试——轮询超时的任务可能仍在运行,盲目重新提交可能导致工作重复。
下载并存储输出
如果响应包含产物引用——URL、文件标识符或存储键——请将它们视为瞬时的。它们可能过期,可能限定在你的账户范围内,并且可能无法在重试后存活。持久的副本是你自己保存的那一份。
获取并持久化模式
当任务完成时,从终态中读取产物引用,获取一次,并写入你控制的目的地:脚本使用本地路径,任何接近生产用途的场景使用对象存储桶。将任务标识符与存储的文件一同记录,以便你能将产物追溯到产生它的请求。
def save_output(fetch_bytes, artifact_url, dest_path):
data = fetch_bytes(artifact_url)
with open(dest_path, "wb") as f:
f.write(data)
return dest_path
防止静默数据丢失的做法
写入临时路径并在成功后重命名,这样失败的下载永远不会留下一个看起来有效但只写了一半的文件。如果响应暴露了字节数或校验和,请进行验证。对于大文件,流式写入磁盘,而不是将整个负载加载到内存。
同时保留最终状态调用的原始响应。JSON 存储成本低廉,当有人问为什么文件看起来不对时,带有模型名称、时间戳和任务 id 的状态文档是重建事件经过的最快方式。
最后,将检索与处理分离。先下载、存储,再转换。如果你一边下载一边解析,一次短暂的网络错误看起来就像格式化 bug,你会花一下午解决错误的问题。
调试常见故障
大多数故障归入四类。每一类都有一个快速隔离测试。
认证错误
症状:在你请求体的任何内容变得重要之前就出现 401 或 403。检查密钥是否确实已加载——打印环境变量是否设置,而不是它的值。验证 Bearer 前缀及其后的空格。确认请求头名称拼写正确,且没有代理将其剥离。如果同一个密钥在 curl 中可用但在你的应用中不可用,说明你的应用构造请求头的方式不同;将字面请求头 dict 与 curl 标志进行对比。
请求体格式错误
症状:400 或 422,并带有关于请求体的消息。发送前验证你的 JSON——json.dumps 几乎能序列化任何东西,包括你无意构建的 dict。检查从配置文件粘贴来的尾随逗号、本应是字符串却传成列表的内容,以及缺失的必填字段。缩减到第一节中的最小请求体,然后逐个加回字段,直到它失败。
模型名称错误
症状:看起来像错误请求或模型未找到消息的拒绝。模型字符串必须精确为 gpt-6.1-terra。常见原因:辅助函数中过时的默认值、环境变量持有不同的值,或者因重新输入而非复制粘贴导致的拼写错误。将其保持为模块常量,并 grep 查找重复项。
轮询超时
症状:你的循环以超时退出,而任务仍然存活。首先,确认你轮询的任务标识符就是你提交的那个——在提交时同时打印两者是最简单的检查。然后查看你的终态列表:一个你未预料到的状态会导致循环永远运行。最后,检查你的间隔和截止时间与实际任务时长是否匹配;为短请求调校的截止时间在长请求上总会触发。超时时记录最后的状态负载,这样你就知道放弃时任务处于什么状态。
按顺序处理这些——认证、请求体、模型、轮询——因为每一层都会掩盖其下的层。一个损坏的请求头会让每个请求体看起来都是错的。