代码语言

知识点思维导图

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 边界:哪里和前端不一样

  1. 默认是同步阻塞的。上面 client.chat.completions.create(...) 没有 await——它是一行卡住的同步调用,直到模型回完才返回。前端 fetch 永远返回 Promise,Python 这里默认不是。想要 await 风格得用 AsyncOpenAI(见第五节)。
  2. 没有"自动 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.bodyReadableStream,你用 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 更语义化。

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“调用大模型 API”中,需要同时满足“零、本篇在阶段五里的位置”与“先建立前端锚点:SDK ≈ 封装好的 axios”。给定正文约束“阶段五(AI 编程)的依赖链是这样的,本篇是起点。”,哪些判断保持了原有处理机制?多选
2“调用大模型 API”出现偏差:“在“调用大模型 API / 边界:哪里和前端不一样”中,即使不满足“前端 fetch 永远返回 Promise,Python 这里默认不是”,结果与副作用仍会保持不变。”已成为实际行为。围绕“边界:哪里和前端不一样”与“安装与 API Key:别把密钥写进代码”,哪些判断能定位被改变的职责或边界?多选
3评审“调用大模型 API”方案时,验收条件包含“它是一个数组,每个元素是 {role, content},描述一轮对话。”。关于“messages:对话就是一个"角色数组"”与“流式输出:打字机效果 ≈ 前端的 ReadableStream”的哪些决策符合正文机制?多选