Appearance
第一次调用 API
大模型 API 真正跑起来,脚本这边其实就几步:读环境变量,准备请求,发给接口,打印返回。
第一次写这类脚本卡住的地方通常不是代码本身——SDK 装到哪个项目里了、API Key 是不是在当前终端里、模型名有没有写错、网络能不能连到外面,这几个有一个没对上,报错看起来都像”代码坏了”。
这里先拿待办清单当材料。材料越简单,越容易判断问题出在 API 调用本身,还是出在输入内容和提示词上。
一、项目目录
新建一个干净目录,专门放这组 API 练习代码。
Windows PowerShell:
powershell
uv init ai-api-demo
Set-Location ai-api-demo
uv add openai
New-Item -ItemType Directory -Force scriptsLinux / macOS:
bash
uv init ai-api-demo
cd ai-api-demo
uv add openai
mkdir -p scriptsopenai 是官方 Python SDK。它帮脚本处理认证、请求发送、响应对象这些事情,不用自己手写 requests.post() 拼 HTTP 请求。
执行完以后,目录大概是这样:
text
ai-api-demo/
├── pyproject.toml
├── uv.lock
└── scripts/pyproject.toml 记录项目依赖,uv.lock 锁住具体版本。换机器跑时,用 uv sync 能装回同一批依赖。目录里如果多了 .venv/ 也正常,那是 uv 给当前项目准备的虚拟环境。
如果 uv add openai 下载超时,本机又需要走代理,可以先在当前终端临时设置代理,再重新执行安装命令:
powershell
$env:all_proxy = "http://127.0.0.1:7890"
uv add openai二、环境变量
API Key 不要写进 .py 文件。代码一旦传到仓库、截图、日志里,key 就泄露了。
PowerShell 里临时设置:
powershell
$env:OPENAI_API_KEY = "sk-..."Linux / macOS:
bash
export OPENAI_API_KEY="sk-..."再放一个模型名。<model-name> 是占位符,实际运行时换成账号里能用的模型名,尖括号也删掉。模型名写错时,接口一般会返回 400 类错误。
PowerShell:
powershell
$env:OPENAI_MODEL = "<model-name>"Linux / macOS:
bash
export OPENAI_MODEL="<model-name>"检查当前终端能不能读到这两个值:
bash
uv run python -c "import os; print('KEY=OK' if os.getenv('OPENAI_API_KEY') else 'KEY=MISSING'); print('MODEL=' + os.getenv('OPENAI_MODEL', 'MISSING'))"输出类似这样才继续:
text
KEY=OK
MODEL=<model-name>这里的检查只说明当前终端有变量。换一个终端、定时任务、systemd 服务或 CI 任务里跑,环境变量要重新配置。很多认证失败其实不是 key 真错了,而是脚本启动时根本没拿到这个变量。
三、基础脚本
新增文件:scripts/hello_ai.py
python
#!/usr/bin/env python3
"""调用一次大模型 API,把返回文本打印出来。"""
import os
import sys
from openai import OpenAI
def main():
# API Key 和模型名都从环境变量读,避免写死在代码里。
api_key = os.getenv("OPENAI_API_KEY")
model = os.getenv("OPENAI_MODEL")
if not api_key:
print("缺少环境变量: OPENAI_API_KEY", file=sys.stderr)
return 2
if not model:
print("缺少环境变量: OPENAI_MODEL", file=sys.stderr)
return 2
client = OpenAI(api_key=api_key, timeout=60.0)
response = client.responses.create(
model=model,
instructions="你负责整理文本。只输出待办清单,每行一条,不要解释。",
input="今晚买菜,写周报,回复邮件。",
max_output_tokens=200,
)
print(response.output_text.strip())
return 0
if __name__ == "__main__":
raise SystemExit(main())运行脚本。
PowerShell:
powershell
uv run python scripts/hello_ai.py
$LASTEXITCODELinux / macOS:
bash
uv run python scripts/hello_ai.py
echo $?输出可能是项目符号,也可能是编号,意思接近就行:
text
- 买菜
- 写周报
- 回复邮件OpenAI(api_key=api_key, timeout=60.0) 里有两个点。api_key 从环境变量来,不进代码;timeout=60.0 是请求超时,避免网络卡住时脚本一直挂着。AI 接口生成内容需要时间,超时太短容易误伤,完全不设超时又不好排查。
max_output_tokens=200 是限制输出长度。这里本来只想要三条待办,不需要让模型自由发挥一大段。
response.output_text 是响应对象里整理好的文本。响应里还有 id、usage、output 等字段,第一次调用先盯住这一项就够了。
四、拆分请求
刚才那段代码能跑,但 input 里把要求和材料揉在了一起。小例子没问题,脚本一旦开始读文件、读表单、读接口返回,这种写法就容易乱。
更清楚的写法是:instructions 放稳定要求,input 放这次要处理的材料。
把 scripts/hello_ai.py 里调用 API 的部分改成这样:
python
response = client.responses.create(
model=model,
instructions="你负责整理文本。只输出待办清单,每行一条,不要解释。",
input="今晚买菜,写周报,回复邮件。",
max_output_tokens=200,
)现在材料如果来自文件,就只换 input 这一块。
新增文件:todo.txt
text
今晚买菜
写周报
回复邮件再给 scripts/hello_ai.py 增加一行导入:
python
from pathlib import Path然后把请求前后的几行改成这样:
python
text = Path("todo.txt").read_text(encoding="utf-8")
response = client.responses.create(
model=model,
instructions="你负责整理文本。只输出待办清单,每行一条,不要解释。",
input=text,
max_output_tokens=200,
)这个变化不大,但很关键。模型不会自己去读本地文件,Python 先把 todo.txt 读出来,再把内容塞进请求里,模型才能看到。
五、错误处理
第一次调 API,出错经常集中在这几类:
| 现象 | 更像是哪一层的问题 |
|---|---|
AuthenticationError | API Key 没配、写错、被撤销 |
BadRequestError | 模型名不对、参数缺失、输入格式不对 |
RateLimitError | 请求太快、额度不足、触发限流 |
APITimeoutError | 请求等太久,模型生成慢或网络卡住 |
APIConnectionError | 代理、证书、防火墙、网络出口问题 |
如果这些错误全部包成一句“请求失败”,认证失败、网络失败、限流和模型名写错就混在一起了。分开打印,排查时至少知道从哪一层开始看。
把 scripts/hello_ai.py 改成带错误处理的版本:
python
#!/usr/bin/env python3
"""调用一次大模型 API,带基础错误处理。"""
import os
import sys
from pathlib import Path
from openai import (
APIConnectionError,
APIStatusError,
APITimeoutError,
AuthenticationError,
BadRequestError,
OpenAI,
RateLimitError,
)
def main():
api_key = os.getenv("OPENAI_API_KEY")
model = os.getenv("OPENAI_MODEL")
if not api_key:
print("缺少环境变量: OPENAI_API_KEY", file=sys.stderr)
return 2
if not model:
print("缺少环境变量: OPENAI_MODEL", file=sys.stderr)
return 2
text = Path("todo.txt").read_text(encoding="utf-8")
client = OpenAI(api_key=api_key, timeout=60.0)
try:
response = client.responses.create(
model=model,
instructions="你负责整理文本。只输出待办清单,每行一条,不要解释。",
input=text,
max_output_tokens=200,
)
except AuthenticationError as exc:
print(f"认证失败: {exc}", file=sys.stderr)
return 2
except BadRequestError as exc:
print(f"请求参数有问题: status={exc.status_code} {exc}", file=sys.stderr)
return 2
except RateLimitError as exc:
print(f"触发限流或额度不足: {exc}", file=sys.stderr)
return 3
except APITimeoutError as exc:
print(f"请求超时: {exc}", file=sys.stderr)
return 4
except APIConnectionError as exc:
print(f"网络连接失败: {exc}", file=sys.stderr)
return 4
except APIStatusError as exc:
# 其他非 2xx 状态码,例如服务端临时错误。
print(f"API 返回错误: status={exc.status_code} {exc}", file=sys.stderr)
return 5
print(response.output_text.strip())
return 0
if __name__ == "__main__":
raise SystemExit(main())退出码也顺手分开:
| 退出码 | 含义 |
|---|---|
0 | 调用成功 |
2 | 本地配置、认证或请求参数问题 |
3 | 限流或额度问题 |
4 | 网络或超时问题 |
5 | API 返回了其他错误状态 |
这个习惯和普通运维脚本一样。屏幕上打印了“失败”,但退出码还是 0,定时任务、CI 或监控就会把失败当成成功。
六、保存结果
请求通了以后,可以把输入和输出落到文件里。改提示词、换模型、换材料时,至少能回头看当时到底发了什么、收到了什么。
新增文件:scripts/save_result.py
python
#!/usr/bin/env python3
"""调用模型,并把输入和输出保存成 JSON 文件。"""
import json
import os
import sys
from datetime import datetime
from pathlib import Path
from openai import OpenAI
def main():
api_key = os.getenv("OPENAI_API_KEY")
model = os.getenv("OPENAI_MODEL")
if not api_key:
print("缺少环境变量: OPENAI_API_KEY", file=sys.stderr)
return 2
if not model:
print("缺少环境变量: OPENAI_MODEL", file=sys.stderr)
return 2
text = Path("todo.txt").read_text(encoding="utf-8")
client = OpenAI(api_key=api_key, timeout=60.0)
response = client.responses.create(
model=model,
instructions="你负责整理文本。只输出待办清单,每行一条,不要解释。",
input=text,
max_output_tokens=200,
)
output_dir = Path("outputs")
output_dir.mkdir(exist_ok=True)
result = {
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"model": model,
"input": text,
"output": response.output_text.strip(),
}
output_path = output_dir / "first-response.json"
output_path.write_text(
json.dumps(result, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(response.output_text.strip())
print(f"结果已保存: {output_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())运行:
bash
uv run python scripts/save_result.py生成的文件在这里:
text
outputs/first-response.jsonensure_ascii=False 是为了让中文正常写进 JSON,不变成一串 \u4e2d\u6587。这里保存的是整理后的文本,不是完整响应对象。完整响应字段很多,结果如果要继续交给程序处理,再专门拆字段。
七、代理网络
本地访问国外 API 时,如果网络要走代理,PowerShell 里可以临时设置:
powershell
$env:all_proxy = "http://127.0.0.1:7890"
uv run python scripts/hello_ai.pyLinux / macOS:
bash
export all_proxy="http://127.0.0.1:7890"
uv run python scripts/hello_ai.py代理只影响当前终端。换一个终端、定时任务、systemd 服务或 CI 任务里跑,代理变量也要重新配置。
如果脚本报 APIConnectionError,代码未必是问题。最直接的检查是看同一个终端里 uv add openai 能不能访问 PyPI,再看浏览器或命令行能不能访问 API 域名。包能下载但 API 不能访问,多半是代理规则、公司网络出口或证书链的问题;包也下载不了,就先处理本机网络和代理。