知识点思维导图
29 个知识节点
参考资料
Python(23) - 调用大模型 API
读完后,你应能完成以下任务:
- 绘制“Python(23) - 调用大模型 API / 零、本篇在阶段五里的位置”的关键对象与数据流,解释“把本篇当成"最小可用形态":能稳定发请求、能收流式输出,后面四篇全建在它上面。”,并用源码位置、日志或 Trace 标注证据。
- 为“Python(23) - 调用大模型 API / 先建立前端锚点:SDK ≈ 封装好的 axios”设计正常与异常输入,验证“默认是同步阻塞的。上面 client.chat.completions.create(...) 没有 await——它是一行卡住的同步调用,直到模型回完才返回。前端 fetch 永远返回 Promise,Python 这里默认不是。想要 await 风格得用 AsyncOpenAI(见第五节)。 -> 没有"自动 await 顶层"那回事。”,输出首个偏差位置与回归测试结果。
- 实现“Python(23) - 调用大模型 API / 安装与 API Key:别把密钥写进代码”的最小代码或配置,检验“API Key 是付费凭证,绝对不要硬编码进代码、更不要提交到 git(这点和前端把密钥写进前端代码一样是大忌,只是后果更直接——会被刷爆账单)。”,输出命令、结果与 Diff,并说明不适用边界。
你在前端调后端接口,无非就是
fetch/axios发个请求、拿 JSON、渲染。调大模型 API 本质上完全一样——只不过官方给你封了一个 SDK(≈ 一个 npm 包),让你不用手写fetch拼 headers。本篇解决三件事:怎么用 OpenAI / Claude 的 Python SDK 发一次对话请求;流式输出(打字机效果)在 Python 里怎么写、和前端的ReadableStream/EventSource有什么对应关系;以及新手最容易踩的几个坑(API Key、消息格式、同步 vs 异步)。
一、零、本篇在阶段五里的位置
阶段五(AI 编程)的依赖链是这样的,本篇是起点:
| 篇 | 主题 | 一句话 |
|---|---|---|
| 23(本篇) | 调用大模型 API | 学会发一次请求、收一次回复(含流式) |
| 24 | function calling | 给模型注册一组可调用函数(≈ 函数注册表) |
| 25 | embedding | 把文字压成一串坐标,语义相近的点挨得近 |
| 26 | RAG | 检索 + 拼接 + 生成 |
| 27 | Agent | 带记忆的 while 循环:思考 → 调工具 → 再思考 |
把本篇当成"最小可用形态":能稳定发请求、能收流式输出,后面四篇全建在它上面。
二、先建立前端锚点:SDK ≈ 封装好的 axios
你在前端调第三方服务,从来不会裸写 fetch,而是装个官方 SDK:
// JavaScript:装个 SDK,本质是帮你封好了 fetch / headers / 鉴权
import OpenAI from "openai"
const client = new OpenAI() // 自动读环境变量 OPENAI_API_KEY
const resp = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "你好" }],
})
console.log(resp.choices[0].message.content)
Python 这边几乎一一对应,只是语法换成 Python:
核心切入点:调大模型 = 发一个 HTTP POST 请求,SDK 帮你把鉴权、序列化、重试都封好了。心智模型和你调任何一个 RESTful 接口没区别。
2.1 边界:哪里和前端不一样
- 默认是同步阻塞的。上面
client.chat.completions.create(...)没有await——它是一行卡住的同步调用,直到模型回完才返回。前端fetch永远返回 Promise,Python 这里默认不是。想要await风格得用AsyncOpenAI(见第五节)。 - 没有"自动 await 顶层"那回事。脚本里直接调就行,不需要包
async function main()。
三、安装与 API Key:别把密钥写进代码
pip install openai # OpenAI 官方 SDK
pip install anthropic # Claude(Anthropic)官方 SDK
API Key 是付费凭证,绝对不要硬编码进代码、更不要提交到 git(这点和前端把密钥写进前端代码一样是大忌,只是后果更直接——会被刷爆账单)。标准做法是放环境变量:
# 终端里设置(或写进 .env,再用 python-dotenv 加载)
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
对照前端:这就是你 Node 项目里
import 'dotenv/config'那一套,思路完全一致。记得把.env加进.gitignore。
四、messages:对话就是一个"角色数组"
messages 是调大模型最核心的概念。它是一个数组,每个元素是 {role, content},描述一轮对话:
| role | 作用 | 类比 |
|---|---|---|
system |
设定模型人设/规则,整段对话的"全局配置" | 像组件的 props 默认值 / 全局 config |
user |
用户说的话 | 用户输入 |
assistant |
模型之前说过的话 | 模型的历史回复 |
关键认知(容易踩坑):大模型 API 是无状态的,类似一个纯函数。它不像聊天 App 那样记得你。所谓"多轮对话",是你自己在客户端维护那个 messages 数组,每次请求都把全部历史一起发过去。第 27 篇 Agent 的"记忆"本质就是在管理这个数组。
五、流式输出:打字机效果 ≈ 前端的 ReadableStream
非流式:等模型全部生成完,一次性返回整段——前端体验是"转圈几秒,然后唰一下全出来"。 流式:模型边生成边吐字,一个 token 一个 token 推给你——就是 ChatGPT 那种"打字机"效果。
前端你见过这个:fetch 返回的 response.body 是 ReadableStream,你用 for await...of 一块块读:
// JavaScript:流式,开启 stream:true 后返回一个异步可迭代对象
const stream = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "讲个一句话笑话" }],
stream: true,
})
// for await...of:一块块拿增量内容
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content || ""
process.stdout.write(delta) // 实时打印,不换行
}
Python 几乎是镜像——区别只是同步版用普通 for:
5.1 边界:和前端流式的差异
| 前端 fetch 流式 | Python openai 流式 | |
|---|---|---|
| 迭代语法 | 永远 for await...of(异步) |
同步客户端用 for,异步客户端才用 async for |
| 拿到的块 | 原始字节,常要自己解析 SSE | SDK 已帮你解析成对象,直接取 .delta.content |
| 增量字段 | 自己拆 | 现成的 chunk.choices[0].delta.content |
一句话:SDK 已经把 SSE(Server-Sent Events)解析这层脏活干完了,你只管循环取增量。
flush=True是关键,否则 Python 会攒着一起输出,看不到逐字效果。
六、异步版:要 await 风格就用 AsyncOpenAI
如果你在 FastAPI(详见第 14 篇)里调大模型,路由是 async def,那就该用异步客户端,避免阻塞事件循环(async 心智模型详见第 17 篇,和 JS 单线程事件循环高度一致):
记忆口诀:同步客户端配普通 for,异步客户端配 async for。混用会直接报错。
七、Claude(Anthropic)SDK:思路一样,两处不同
Claude 的 SDK 整体思路和 OpenAI 一致,但有两个必须注意的差异:
流式 Claude 提供了更顺手的写法——用 with 上下文管理器(详见第 9 篇)配 text_stream:
OpenAI 与 Claude 速查对照:
| OpenAI | Claude (Anthropic) | |
|---|---|---|
| 入口方法 | client.chat.completions.create |
client.messages.create |
| system 提示 | 放进 messages 数组 |
顶层独立 system 参数 |
max_tokens |
可选 | 必填 |
| 取文本 | resp.choices[0].message.content |
message.content[0].text |
| 流式纯文本迭代 | 自己取 chunk.choices[0].delta.content |
stream.text_stream 直接给文字 |
八、错误处理:网络会抖、key 会错、额度会爆
调外部 API 一定要处理异常(异常机制详见第 7 篇 Java 对照 / 第 9 篇)。常见错误类型 SDK 都给了类,建议至少兜住鉴权错误和限流错误:
对照前端:等价于
try { await axios(...) } catch (e) { if (e.response?.status === 401) ... }。区别是 Python SDK 把状态码包成了具体的异常类,except分支比判断status更语义化。
九、总结
- 零、本篇在阶段五里的位置:把本篇当成"最小可用形态":能稳定发请求、能收流式输出,后面四篇全建在它上面。
- 先建立前端锚点:SDK ≈ 封装好的 axios:默认是同步阻塞的。上面 client.chat.completions.create(...) 没有 await——它是一行卡住的同步调用,直到模型回完才返回。前端 fetch 永远返回 Promise,Python 这里默认不是。想要 await 风格得用 AsyncOpenAI(见第五节)。 -> 没有"自动 await 顶层"那回事。
- 安装与 API Key:别把密钥写进代码:API Key 是付费凭证,绝对不要硬编码进代码、更不要提交到 git(这点和前端把密钥写进前端代码一样是大忌,只是后果更直接——会被刷爆账单)。
- messages:对话就是一个"角色数组":messages 是调大模型最核心的概念。
- 流式输出:打字机效果 ≈ 前端的 ReadableStream:非流式:等模型全部生成完,一次性返回整段——前端体验是"转圈几秒,然后唰一下全出来"。
- 错误处理:网络会抖、key 会错、额度会爆:区别是 Python SDK 把状态码包成了具体的异常类,except 分支比判断 status 更语义化。
学完自测
选择所有正确答案;提交后逐项核对判断依据。