
🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统与网络知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》 《MySQL数据库学习》 《Python知识内容》 《cpolar知识学习》
✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介:

本文帮助你把语言模型集成到Python应用程序中.我们先用Playground 理解提示词与模型输出,再建立可复用的Python客户端,学习请求参数、返回结果和函数调用,最后处理费用、隐私、嵌入与内容审核.GPT-4和ChatGPT是历史起点.实际编程时,应区分 ChatGPT 产品、提供能力的具体模型和调用它的 API.本文使用现行 Python 客户端的 Chat Completions 接口延续原书的消息列表教学;新项目也可以选择Responses API,二者有不同的输入与输出结构.

目录
- 1.基本概念
- [2.OpenAI API 提供的可用模型](#2.OpenAI API 提供的可用模型)
- [3.在OpenAI Playground 中使用GPT模型](#3.在OpenAI Playground 中使用GPT模型)
- [4.开始使用OpenAI Python库](#4.开始使用OpenAI Python库)
-
- 4.1OpenAI访问权限和API密钥
- [4.2Hello World示例程序](#4.2Hello World示例程序)
- 5.使用GPT-4和ChatGPT
- 6.使用其他文本补全模型
- 7.考虑因素
- [8.其他OpenAI API和功能](#8.其他OpenAI API和功能)
- 9.小结
1.基本概念
OpenAI 通过 API 将模型作为远程服务提供。开发者组织输入,向服务发送请求,再读取模型生成的内容。Python SDK 包装了认证、HTTP 通信和数据对象;安装 SDK 并不会把模型权重下载到本机。
上一篇文章介绍的提示词和标记(token)在这里仍是基础。提示词用于表达任务、提供材料和约定输出形式。在聊天接口里,这些信息放进消息列表;列表中的角色帮助区分应用指令、用户输入、助手回答和工具结果。提示词也可以包含图像等内容,具体输入模态取决于模型与接口。
token 是分词器处理文本后的单位,可能是一个词、词的一部分、标点或其他片段。原书所说"100 个 token 大约对应 75 个英语单词"可用来建立直觉,但中文、代码及不同分词器不能直接套用这个比例。计费和上下文控制应以模型对应的 token 统计为准。
一次典型调用有三个要点:选择能完成任务的模型,给出足够且相关的输入,正确解释返回结果。 输入越长、要求生成的内容越多,通常会增加费用和等待时间;具体关系还受模型、缓存与服务负载影响。


以"把这段文字压缩成三句话"为例:应用在请求里提供模型 ID、原文和输出要求;服务端完成推理;响应包含生成内容以及可用的用量信息。拿到响应后,应用仍需检查是否被截断、是否有可显示文本,以及回答是否满足业务要求。返回成功状态并不自动证明回答正确。
2.OpenAI API 提供的可用模型
模型选择首先是任务选择。一个写作助手、一个商品检索助手和一个语音转录工具,需要的输入输出能力并不相同。先明确任务是否需要文本、图像、音频、工具调用或嵌入,再选择对应模型及接口。
模型发展线索。 2023 年前后的模型阶段:InstructGPT 系列用指令来驱动单轮文本生成;GPT-3.5 Turbo 和 GPT-4 进一步推动了基于消息列表的对话接口。text-ada-001、text-babbage-001、text-curie-001、text-davinci-003、gpt-3.5-turbo-0613 等名称在本章中只用于理解这段历史。这些历史名称、价格和窗口大小不作为本版示例的运行配置。
从这条发展线索中可以保留两条实用认识。第一,聊天模型也能执行摘要、翻译、改写和续写等单轮任务,消息列表并不强制要求多轮聊天。第二,模型的"能力更强"需要放到任务中评估:格式遵循、事实准确性、响应时间和成本都应进入评价,而不能只凭模型名字判断。
为自己的项目选择模型。 从模型目录查找候选项,确认输入输出模态、支持的端点、工具能力、上下文窗口及最大输出。账户能看到某个模型,并不等于它支持所有 API 或所有参数。将本文的 OPENAI_MODEL 设置为账户可用、支持 Chat Completions 的模型;本章完整示例还使用 developer 消息和 max_completion_tokens,商品工具示例要求支持函数调用、严格参数模式及指定工具选择。
别名与快照。 当模型提供快照版本时,固定快照可以减少升级引入的变化,但无法保证每次输出逐字一致。项目可保留一组代表性输入及期望条件,升级前运行比较:答案是否完整、工具参数是否合规、延迟是否可接受。模型版本和提示词版本应一起记录。
微调与知识接入。 微调的可用性应按当前模型能力查询。若任务主要是依据自己的文档回答问题,可以先尝试检索后提供相关材料;若要调整稳定的行为和格式,再评估微调。把文档放进提示词、检索资料和微调是不同操作,选择时要看任务需求、数据质量和维护成本。
3.在OpenAI Playground 中使用GPT模型
Playground 是理解模型行为的实验入口。它允许你在编写应用前设置模型、输入提示词并观察结果。使用时登录开发者平台,进入自己的项目和提示词试验界面,选择账户可用的模型。界面名称与按钮位置可能变化,因此这里按"输入---设置---运行---比较"的操作顺序讲解。
- 先写一个具体任务,例如"只返回修正后的英语句子"。
- 提供输入,例如
She no went to the market.。 - 选择支持该任务的模型,保留一份初始参数。
- 运行后检查语法是否正确、原意是否保留、是否多写了说明。
- 修改一个因素再次运行,并把模型、提示词、参数、结果和用量记录在一起。
生成会按所用模型与实际用量计费;ChatGPT 订阅与 API 的账户计费要分开理解。下面给出预算计算方法。试验前先查看项目可用额度与预算设置。
下面保留界面图,用来解释各功能的用途。

中心区域输入 As Descartes said, I think therefore,原例生成 I am。这个例子展示了续写:模型把已有文本作为上下文,继续生成后续内容。在现行聊天接口中,可把任务写成"补全这句名言,只返回完整句子",得到的仍然是文本生成结果。
截图中的 A、B、C 分别展示撤销、重新生成及历史记录等操作。它们帮助比较试验,但界面中看不到某条记录,不等于所有服务端数据都已删除;数据管理应按具体设置处理。
把界面分为聊天、补全和编辑模式。在本版的代码中,这些任务可以通过不同提示词使用同一个聊天接口:聊天保留相关消息历史,补全说明需要续写的文本,编辑说明原文和修改要求。

将助手设定为喜欢猫、只谈论猫并给出简短回答。这里的核心做法是把应用要求与用户问题分开:应用要求规定行为,用户输入给出当前任务。继续对话时加入新的用户消息,并携带必要历史,模型才能解释"它""刚才那个"等指代。

要求将"准备旅行的年轻男子"改成"一位年长女士"。读这个例子时,可把编辑任务拆成三项:需要处理的原文、允许改变的部分、必须保留的部分。若只要求替换人物,地点、时间和情节应保持原样。这样既容易编写提示词,也容易检验结果。
模型和采样参数用于控制生成行为。对于支持 temperature 的模型,温度可以改变采样的随机性;降低温度通常让结果更集中,但不能替代事实核验。不要同时修改任务要求、模型和多项参数,否则很难知道哪个因素导致了变化。不同模型支持的参数不同,本文示例不额外设置温度。

预设可以作为任务模板:摘要、翻译、分类、纠错等都可以用输入与输出要求来表达。模板有用的部分是任务结构,使用时仍应替换为自己的材料,并准备能覆盖常见情况和边界情况的例子。

对于 She no went to the market.,期望的纠错结果可以是 She did not go to the market.。这是一条用于讲解的期望答案,实际生成措辞应逐次检查。完整脚本会把续写、人物替换和语法纠错放在同一接口下运行。
实验保存时,应至少保留模型标识、完整提示词、选项、结果、用量和评价。若界面支持查看或导出代码,导出的代码可以帮助理解请求;复制到自己的项目后,需要按本章的客户端与环境变量方式整理。共享试验记录时只保留完成任务所需的内容,不放入密钥。

4.开始使用OpenAI Python库
本节建立一个后续示例可以直接复用的运行环境。所有脚本都通过 OpenAI 客户端访问远程服务。不要求在本机部署语言模型,也不需要把密钥写进 Python 文件。
4.1OpenAI访问权限和API密钥
API 密钥用于鉴权,并将请求关联到相应项目。先登录开发者平台,在自己的项目中创建具有所需权限的 API 密钥。展示的是当时的个人菜单入口;本版操作以当前项目的密钥管理页面为准。

标准 API 密钥只放在后端、受控的本地脚本或密钥管理服务中,不放进网页 JavaScript、移动端安装包、Git 仓库、截图和公开日志。环境变量让程序与凭据分离,但访问该环境的人仍可能接触到密钥,因此仍需控制系统账户和运行权限。
macOS 或 Linux 可在终端里启动 Python 包装进程,通过隐藏输入设置当前子进程环境,再启动脚本。下面的方法不会在命令行历史里保存你输入的密钥,也不会打印它;密码输入需要在支持隐藏输入的真实终端中进行。
bash
python -c 'import getpass, os, subprocess, sys; env = os.environ.copy(); env["OPENAI_API_KEY"] = getpass.getpass("API key: "); subprocess.run([sys.executable, "code/hello.py"], env=env, check=True)'
运行上面这一行之前,先完成下一节的安装,并配置 OPENAI_MODEL。它只把密钥传给本次 Python 进程;如果运行其他脚本,把 code/hello.py 换成对应脚本路径即可。已在后端安全配置环境变量的读者可直接运行脚本,不需要这层包装。
在 macOS 的 zsh/Linux shell 中设置非敏感的模型 ID:
bash
export OPENAI_MODEL='填入账户可用且支持本章接口的模型ID'
Windows PowerShell 对应写法为:
powershell
$env:OPENAI_MODEL = '填入账户可用且支持本章接口的模型ID'
若已经通过系统或密钥管理服务配置 OPENAI_API_KEY,可用以下命令仅检查是否设置,不显示密钥内容:
bash
python -c 'import os; print("已设置" if os.getenv("OPENAI_API_KEY") else "未设置")'
这里的模型占位文字必须换成实际模型 ID。密钥与模型是两项独立配置:密钥决定访问身份,模型 ID 决定请求哪个能力。密钥失效时应在平台中撤销并重新配置,而不是把它复制到更多文件中。

4.2Hello World示例程序
在文件夹打开终端,创建虚拟环境并安装 Python SDK:
bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade openai
python -m pip show openai
Windows 的虚拟环境激活命令为 .venv\Scripts\Activate.ps1。安装后记录实际 SDK 版本;将环境纳入项目时,再用依赖文件固定自己已验证的版本。后续示例只使用 openai 和 Python 标准库。
先定义公共模块 common.py。它检查配置,设置 60 秒请求超时和最多 2 次自动重试,并把"完整文本回答"的读取集中在一个地方。max_retries=2 是最多额外重试两次,包含重试的总等待时间可以长于单次超时。
python
import os
from openai import OpenAI
from openai.types.chat import ChatCompletion
REQUEST_TIMEOUT_SECONDS = 60.0
MAX_RETRIES = 2
MAX_COMPLETION_TOKENS = 4096
def required_env(name: str) -> str:
value = os.getenv(name, "").strip()
if not value:
raise RuntimeError(f"请先设置 {name}")
return value
def create_client() -> OpenAI:
required_env("OPENAI_API_KEY")
return OpenAI(timeout=REQUEST_TIMEOUT_SECONDS, max_retries=MAX_RETRIES)
def chat_model() -> str:
return required_env("OPENAI_MODEL")
def completed_text(response: ChatCompletion) -> str:
if not response.choices:
raise RuntimeError("响应没有 choices,不能作为完整答案")
choice = response.choices[0]
if choice.finish_reason != "stop":
raise RuntimeError(f"文本未正常完成:{choice.finish_reason}")
if choice.message.refusal:
raise RuntimeError("模型拒绝回答此请求")
text = choice.message.content
if not text or not text.strip():
raise RuntimeError("响应没有可显示的文本")
return text
MAX_COMPLETION_TOKENS=4096 是教学用输出预算,需要在选定模型允许的范围内使用;推理模型可能把部分预算用于不可见的推理 token。本文示例要求所选模型支持该参数,遇到 length 时应重新评估预算或缩短任务,不能把截断内容当作完整答案。
接下来是 Hello World。OpenAI() 会读取 OPENAI_API_KEY;模型来自 OPENAI_MODEL。同一目录中的其他脚本都会导入上面的 common.py,因此复制示例时一起保留 code/ 即可。
python
from common import (
MAX_COMPLETION_TOKENS,
chat_model,
completed_text,
create_client,
)
def main() -> None:
model = chat_model()
with create_client() as client:
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Hello World!"}],
max_completion_tokens=MAX_COMPLETION_TOKENS,
store=False,
)
print(completed_text(response))
if response.usage is not None:
print(response.usage.model_dump_json())
if __name__ == "__main__":
main()
在已配置环境变量的终端执行:
bash
python code/hello.py
第一次输出是模型回答,例如 Hello! How can I help you today?;第二次输出是本次请求可用的 token 用量。示例句子只说明输出形态,不承诺模型逐字返回同一句话。
代码中的 with 在结束时关闭客户端。网络超时、限流和鉴权失败会由 SDK 抛出异常;此入门脚本保留异常供本地诊断。若将其做成网页应用,应在服务端记录必要的错误类型和请求标识,在界面给出简短提示,避免把完整请求内容或凭据写入公开日志。
本版采用环境变量直接加载,不依赖 .env。如果项目本来就使用 dotenv,应先加载环境变量,再创建 OpenAI() 客户端;导入模块本身和创建客户端是两件事。不要为了运行本文额外安装 dotenv。
5.使用GPT-4和ChatGPT
聊天接口以消息列表为输入。下面保留"算法老师"例子:用户先问算法有哪些衡量方式,助手提到空间复杂度,用户再问"它是什么意思"。要让这次调用理解"它",相关的前文必须包含在请求里。
python
from common import (
MAX_COMPLETION_TOKENS,
chat_model,
completed_text,
create_client,
)
def main() -> None:
model = chat_model()
messages = [
{"role": "developer", "content": "你是一位耐心的算法老师。"},
{"role": "user", "content": "除了时间复杂度,还能用什么衡量算法?"},
{"role": "assistant", "content": "还可以使用空间复杂度。"},
{"role": "user", "content": "它是什么意思?请用一个例子解释。"},
]
with create_client() as client:
response = client.chat.completions.create(
model=model,
messages=messages,
max_completion_tokens=MAX_COMPLETION_TOKENS,
store=False,
)
answer = completed_text(response)
messages.append({"role": "assistant", "content": answer})
print(answer)
if __name__ == "__main__":
main()
这段程序把最后的回答追加到 messages,方便同一个程序继续对话。若后续再调用 Chat Completions,应把需要的历史随请求发送;仅在本地列表中追加消息不会自动通知远程服务。应用可以保存历史、压缩过长的内容,或者只选取与当前问题相关的段落,但应保留用户约束与工具结果之间的对应关系。
聊天模型也可以完成单轮任务。若只有一条用户消息,"请用三句话概括这段文章"与"你好"一样,都是合法的聊天请求。选择模型后,还应检查角色、输入模态和参数兼容性;不能只替换模型名字就假定所有能力都一致。
5.1ChatCompletion端点的输入选项
01.主要的输入参数
Python 调用形式为 client.chat.completions.create(...)。本章最常用的输入是 model 和 messages。
聊天请求的主要输入
| 字段名称 | 类型 | 本章用法 |
|---|---|---|
model |
字符串 | 从 OPENAI_MODEL 读取账户可用且支持本例能力的模型 ID。模型列表可通过 client.models.list() 查询,但接口兼容性仍须查看模型说明 |
messages |
对象数组 | 按对话顺序传入消息。文本示例使用 role 和 content;工具消息还需要 tool_call_id |
developer 消息放应用要求;user 消息放用户任务;assistant 消息表示助手此前的回答或工具请求;tool 消息表示应用执行工具后的结果。部分模型使用或兼容 system 角色,具体按模型说明选择。用户上传的文档是任务材料,不应因为其中写着命令就替换应用指令。
助手消息也可以用来示范期望格式,例如先给一组输入输出,再给新的输入。但示范必须真实、相关且不会与当前要求矛盾。模型生成的历史内容不是可信的数据库记录,涉及订单、价格等事实时应重新读取业务系统。
02.对话长度和标记数量
对话长度同时影响费用、等待时间和可用上下文。请求中的应用指令、用户文本、历史消息、工具说明及工具结果都可能占用输入预算。输出还需要自己的预算;"上下文窗口很大"不代表可以生成同样长度的答案。
管理长对话时,可先去掉与本轮无关的重复材料,保留最近对话与必要的长期事实;如果摘要旧对话,应将摘要作为可修正的派生信息,保留关键来源。检索型应用还可以只加入最相关的片段,而不把整个知识库复制到每次请求中。
tiktoken 可辅助估算文本的 token 数,但要使用对应模型的分词规则。消息封装、工具定义及多模态输入还会带来额外用量,所以本地估算与服务端 usage 应分开记录。单轮成功后再核对实际用量,可以逐步修正预算。
03.可选参数
常见选项
| 字段名称 | 类型 | 用途 |
|---|---|---|
tools |
对象数组 | 描述允许模型请求的工具。本章使用 type="function" |
tool_choice |
字符串或对象 | auto 由模型选择是否调用,none 不调用,required 要求调用工具;指定函数对象可要求调用某个函数 |
parallel_tool_calls |
布尔型 | 控制是否允许一轮提出并行工具调用;本章示例设为 False |
temperature |
数值 | 对支持该参数的模型控制采样随机性;本章默认省略,不把低温度当作正确性保证 |
n |
整数 | 对支持的模型请求多个候选;多个候选的输出会增加用量,本章使用默认的一个 |
stream |
布尔型 | 逐步返回增量内容,应用需按流事件处理,不能按完整响应对象读取 |
max_completion_tokens |
整数 | 生成预算上限,包含适用模型的可见输出与推理 token;需要符合模型限制 |
store |
布尔型 | 本章设为 False,控制此响应是否用于受该参数控制的存储;其他数据保留机制见 2.7.2 |
参数名称和能力以接口参考为依据。最容易复现的起点是少量必需参数,再围绕具体目标逐项增加选项。
5.2ChatCompletion端点的输出格式
SDK 返回一个对象,常规文本通过 response.choices[0].message.content 读取。需要保存为字典或 JSON 时,使用 model_dump() 或 model_dump_json()。这样不会把 Python 的显示形式误当成 JSON。
下面是人为构造的结构示例,ID、模型名与 token 数均为演示值,不是真实调用记录;实际响应可能有更多字段。
json
{
"id": "chatcmpl-example",
"object": "chat.completion",
"created": 1700000000,
"model": "example-model-id",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "你好!", "refusal": null},
"finish_reason": "stop"
}],
"usage": {"prompt_tokens": 12, "completion_tokens": 6, "total_tokens": 18}
}
聊天响应的主要字段
| 字段名称 | 类型 | 如何使用 |
|---|---|---|
choices |
对象数组 | 保存候选回答。本章先检查非空,再读取第一个候选 |
choices[i].message |
对象 | 通常含文本,也可能含 tool_calls 或拒绝信息;content 不保证总是非空 |
choices[i].finish_reason |
字符串 | stop 表示正常停止;length 表示达到生成限制;tool_calls 表示提出工具调用;content_filter 表示内容被过滤 |
created |
整数 | Unix 时间戳,用来记录响应创建时间 |
id |
字符串 | 响应标识,可用于必要的追踪诊断 |
model |
字符串 | 实际返回的模型标识;使用别名时可能看到对应快照 |
object |
字符串 | 完整聊天响应通常为 chat.completion;流式增量使用不同对象形态 |
usage |
对象或空值 | 包含可用的输入、输出与总 token 数及细分信息,应在读取前检查是否存在 |
finish_reason 描述生成如何结束。鉴权失败、网络错误或限流通常通过异常或 HTTP 错误表达,应由另一条错误处理路径接收。普通文本示例调用 completed_text(),工具示例则先处理 tool_calls,两种结果不能混作同一种文本读取。
5.3从文本补全到函数
工具调用把自然语言任务连接到程序。模型提出工具名和参数,应用检查后执行本地代码,再把结果作为工具消息回传,最后让模型组织自然语言回答。这个过程适合查商品、查天气和读取已有业务记录。

函数工具定义
| 字段名称 | 类型 | 作用 |
|---|---|---|
type |
字符串 | 此处为 function |
function.name |
字符串 | 应用允许调用的函数名 |
function.description |
字符串 | 说明用途、适用条件和单位 |
function.parameters |
JSON Schema 对象 | 描述参数结构与必需字段 |
function.strict |
布尔型 | 在支持的模型中启用严格参数结构;仍需由应用校验业务边界 |
下面把商品搜索任务实现成一个完整闭环。模型只传价格上限和数量;应用使用固定的参数化 SQL 查询本地演示目录。金额以整数"分"存储,结果按价格升序排列。目录包含蓝笔 1.99 美元、红笔 1.78 美元和黑色笔记本 3.50 美元,明确是教学数据,不代表真实库存。
这份程序依赖本文的 common.py,其余定义全部在同一文件内。第一次请求指定 find_product,确保本次练习实际经过工具流程;第二次请求用 tool_choice="none",让模型根据工具结果回答。通用助手可改用 auto 并增加有上限的多轮循环,但应始终保留工具白名单、参数校验和结果对应关系。
python
import json
import math
import sqlite3
from decimal import Decimal, ROUND_CEILING
from openai import OpenAI
from common import (
MAX_COMPLETION_TOKENS,
chat_model,
completed_text,
create_client,
)
MAX_RESULTS = 10
DEMO_PRODUCTS = [("pen", "blue", 199), ("pen", "red", 178),
("notebook", "black", 350)]
TOOLS = [{
"type": "function",
"function": {
"name": "find_product",
"description": "查询演示目录中单价低于上限的商品,按价格升序返回。价格单位为美元。",
"strict": True,
"parameters": {
"type": "object",
"properties": {
"max_price": {"type": "number", "description": "严格小于的价格上限"},
"limit": {"type": "integer", "description": "返回数量,1 到 10"},
},
"required": ["max_price", "limit"],
"additionalProperties": False,
},
},
}]
def validate_arguments(raw: str) -> tuple[float, int]:
args = json.loads(raw)
if not isinstance(args, dict) or set(args) != {"max_price", "limit"}:
raise ValueError("参数必须且只能包含 max_price 和 limit")
price, limit = args["max_price"], args["limit"]
if type(price) not in (int, float) or not math.isfinite(price):
raise ValueError("max_price 必须是有限数值")
if not 0 < price <= 10000:
raise ValueError("max_price 必须大于 0 且不超过 10000")
if type(limit) is not int or not 1 <= limit <= MAX_RESULTS:
raise ValueError("limit 必须是 1 到 10 的整数")
return float(price), limit
def find_product(max_price: float, limit: int) -> list[dict[str, object]]:
# 仅查询本地演示数据;SQL 固定,模型只能提供已校验的参数。
db = sqlite3.connect(":memory:")
try:
# 整数分严格小于金额上限,向上取整可保留非整数分边界。
threshold_cents = int(
(Decimal(str(max_price)) * 100).to_integral_value(rounding=ROUND_CEILING)
)
db.execute("CREATE TABLE products (name TEXT, color TEXT, price_cents INTEGER)")
db.executemany("INSERT INTO products VALUES (?, ?, ?)", DEMO_PRODUCTS)
rows = db.execute(
"SELECT name, color, price_cents FROM products "
"WHERE price_cents < ? ORDER BY price_cents ASC, name, color LIMIT ?",
(threshold_cents, limit),
).fetchall()
return [{"name": n, "color": c, "price": p / 100} for n, c, p in rows]
finally:
db.close()
def answer_product_question(client: OpenAI, model: str, question: str) -> str:
messages = [
{"role": "developer", "content": "根据工具返回的演示商品数据回答,保持价格升序。"},
{"role": "user", "content": question},
]
first = client.chat.completions.create(
model=model,
messages=messages,
tools=TOOLS,
tool_choice={"type": "function", "function": {"name": "find_product"}},
parallel_tool_calls=False,
max_completion_tokens=MAX_COMPLETION_TOKENS,
store=False,
)
if not first.choices or first.choices[0].finish_reason != "tool_calls":
raise RuntimeError("没有收到完整的工具调用")
message = first.choices[0].message
calls = message.tool_calls or []
if len(calls) != 1:
raise RuntimeError("此示例要求一次只调用一个工具")
call = calls[0]
if call.type != "function" or call.function.name != "find_product":
raise ValueError("不允许调用该工具")
max_price, limit = validate_arguments(call.function.arguments)
products = find_product(max_price, limit)
messages.append({
"role": "assistant",
"tool_calls": [{
"id": call.id,
"type": "function",
"function": {"name": call.function.name, "arguments": call.function.arguments},
}],
})
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps({"source": "local_demo", "products": products}, ensure_ascii=False),
})
final = client.chat.completions.create(
model=model,
messages=messages,
tools=TOOLS,
tool_choice="none",
max_completion_tokens=MAX_COMPLETION_TOKENS,
store=False,
)
return completed_text(final)
def main() -> None:
model = chat_model()
with create_client() as client:
print(answer_product_question(client, model, "找出价格低于 2 美元的两个最便宜商品。"))
if __name__ == "__main__":
main()
在本文文件夹执行 python code/function_calling.py。对于价格上限 2 美元、数量 2 的参数,本地查询会返回红笔 1.78 美元,再返回蓝笔 1.99 美元。模型最终回答应依据这个结果;若需要严格保证表格排序或金额格式,可以直接在界面渲染工具的结构化数据。
这里有两个独立的检查层。JSON Schema 约束字段结构;validate_arguments() 再检查有限数值、价格范围、整数数量及额外字段。模型不能借助一个字符串传入 SQL,也不能调用程序没有开放的函数。若响应被截断、工具名不匹配或参数非法,程序在执行工具之前失败,不会继续猜测参数。
工具结果通过 tool_call_id 与先前请求对应。若以后允许一轮多个调用,就需要逐个校验并返回每一个调用的结果。本示例明确限定一次一个调用,从而使闭环和失败位置容易观察。
6.使用其他文本补全模型
使用 Completion 端点说明"给定一段文本,继续写下去"。这是文本生成的一种任务形式,值得掌握;今天可以用同一个聊天接口完成,不需要为了续写而依赖的历史模型。
在历史 Completion 结构中,输入主要叫 prompt,生成文本通常从 choices[0].text 读取。本文操作代码统一使用 messages 和 choices[0].message.content。微调、插入和编辑的支持范围应按当前模型文档判断,不能把旧端点和某项能力永久绑定。
6.1文本补全任务的输入选项
01.主要的输入参数
把文本任务写成聊天请求
| 要表达的内容 | 请求中的位置 | 示例 |
|---|---|---|
| 选择模型 | model |
chat_model() 读取环境配置 |
| 任务指令与原文 | messages 中的 content |
"只返回修正后的句子:She no went to the market." |
| 插入位置与前后文 | 在任务文本中明确划分前文、空白、后文 | "只补写空白处;保留前后文不变" |
| 输出预算 | max_completion_tokens |
使用本章配置的 4096,按任务与模型限制调整 |
| 输出形式 | 在指令中明确要求 | "仅返回完整句子,不添加解释" |
明确输入结构有助于检查结果。例如要求改写人物时,可以把"人物身份"列为允许改变的部分,把其他信息列为必须保留的部分。若输出要给程序解析,应采用模型支持的结构化输出能力,并在程序侧验证结构,而不是只依赖"请输出 JSON"这句话。
02.对话长度和标记数量
单轮文本任务也有输入和输出预算。原文、修改要求与示例都会占用输入;生成结果会占用输出。处理长文档时,先确认任务是否真的需要全文;若分块处理,要定义各块的边界和如何合并结果,避免改写后丢失上下文。
03.可选参数
对支持采样选项的模型,可以比较不同设置的输出,但本节先保持默认。下面三个任务使用同一个客户端分别请求,每次只有本任务的输入,因此不会意外继承上一条任务的内容。运行整个脚本会发起三次请求。
python
from common import (
MAX_COMPLETION_TOKENS,
chat_model,
completed_text,
create_client,
)
TASKS = [
("续写", "补全下面的名言,只返回完整句子:As Descartes said, I think therefore"),
("改写", "只返回修改后的句子,将年轻男子换成一位年长女士:一个年轻男子准备去旅行。"),
("纠错", "修正英语语法,只返回正确句子:She no went to the market."),
]
def main() -> None:
model = chat_model()
with create_client() as client:
for name, prompt in TASKS:
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
max_completion_tokens=MAX_COMPLETION_TOKENS,
store=False,
)
print(f"{name}:{completed_text(response)}")
if __name__ == "__main__":
main()
6.2文本补全任务的输出格式
任务叫"续写"不会改变 API 响应类型。上面的脚本仍然得到 Chat Completions 对象,并通过 completed_text() 读取。示例中三行输出可能是:
text
续写:As Descartes said, I think therefore I am.
改写:一位年长女士准备去旅行。
纠错:She did not go to the market.
以上是期望结果示意,不是真实 API 运行日志。若实际模型多写了解释,应根据任务验收条件判断是否接受;如果应用必须只显示一句话,可在提示词和后处理规则中明确约束。事实核验、格式检查和截断检查分别解决不同的问题,应各自落实。
Responses API 也可以实现这些单轮任务。采用它时,输入可用 input,聚合文本通过 output_text 读取;同时需要按其状态与输出项目处理工具或其他结果。为了避免初学者在一个练习中混用两套返回结构,本文及后面聊天示例保持同一客户端模式。
7.考虑因素
一个能运行的例子只是起点。持续使用 API 时,需要知道每次请求花费多少、输入能否装进模型上下文,以及应用到底传出了哪些数据。
7.1定价和标记限制
成本估算应使用运行时所选模型的官方定价,并记录查询日期与计费单位。输入、缓存输入和输出可能有不同单价;某些工具或模态还会产生其他费用。原书的静态价目适合研究当年的产品阶段,本版采用能随单价更新的计算方法。
文本调用的成本计算项
| 项目 | 使用量 | 计算方式 |
|---|---|---|
| 非缓存输入 | 输入 token 减去已确认的缓存输入 token | 非缓存输入量 × 输入单价 ÷ 1,000,000 |
| 缓存输入 | 响应报告的、适用缓存费率的 token | 缓存输入量 × 缓存输入单价 ÷ 1,000,000 |
| 输出 | 输出 token | 输出量 × 输出单价 ÷ 1,000,000 |
| 工具/其他模态 | 按对应产品的计量方式统计 | 另加适用项目,避免与已包含的费用重复相加 |
表中假设单价单位为"美元/百万 token"。若页面采用不同单位,先统一单位再计算。total_tokens 不能直接乘某一个单价,因为输入和输出费率可能不同。
算例:以下所有价格都是教学假设,不对应任何实际模型报价。 假设输入每百万 token 2 美元、缓存输入 0.5 美元、输出 8 美元。某次请求有 1200 个输入 token,其中 400 个命中适用缓存,输出 300 个 token;不使用收费工具或其他模态。那么:
text
非缓存输入:800 × 2 ÷ 1,000,000 = 0.0016 美元
缓存输入: 400 × 0.5 ÷ 1,000,000 = 0.0002 美元
输出: 300 × 8 ÷ 1,000,000 = 0.0024 美元
本次合计:0.0042 美元
1000 次同等用量调用:4.20 美元
函数调用闭环通常包含至少两次模型请求,两次用量都要累计。重试、更多候选和反复调试也可能增加总用量。用量统计可用于应用预算,最终费用仍应与项目账单核对。
除了钱,还要分别确认三个限制:模型上下文窗口、最大生成量,以及项目的请求/token 速率限制。大窗口不代表低延迟,也不代表应用应把所有历史永久附加。建议为任务设定输入上限和输出预算,在接近上限时缩减材料或分阶段处理。业务预算和界面中设置的预算提醒也应分别确认具体行为。
7.2安全和隐私
数据保护涉及多个层面:模型训练使用、滥用监测日志、应用状态、自己系统中的日志与数据库。OpenAI API 数据默认不用于训练,除非用户选择共享;部分端点的滥用监测日志可能保留至多 30 天,具体端点、项目设置、资格与例外会影响保留方式。store=False 只控制相应的响应存储行为,不能等同于所有系统都零保留。
实际开发可按数据流落实保护:
- 在应用侧筛选完成任务所必需的字段,例如查询商品只需价格条件,无需发送客户身份资料。
- 标准 API 密钥由后端读取;给不同项目配置适当权限,并对密钥轮换保留操作记录。
- 不把完整用户输入默认写进日志;记录错误类型、耗时、用量和必要请求标识即可。
- 为自己存储的会话、文件和检索索引设置访问控制与删除周期,避免只处理模型侧而忘记本地副本。
- 接入外部工具时由程序做权限和参数校验;模型的工具请求不能直接越过业务授权。
如果业务涉及特定数据驻留或个人信息处理要求,应按所选项目、端点和组织安排确认数据路径。这里的技术设置帮助落实数据最小化,不能替代具体业务的数据治理。
8.其他OpenAI API和功能
语言生成只是应用的一部分。检索通常需要嵌入,用户输入可能需要审核,语音和图像任务则需要相应的模型与端点。本文说明这些能力在系统中的位置。
8.1嵌入
嵌入把文本表示为数值向量,便于比较相似程度。语义相近的文本往往有较接近的表示,因此可以用于搜索、推荐、聚类与异常检测。例如用户问"怎么找回密码",检索系统希望找到"账户密码重置流程",即使两段文本没有逐字相同。
聊天模型与嵌入模型承担不同任务。设置 OPENAI_EMBEDDING_MODEL 为账户可用的嵌入模型 ID;向量库中已有文档和新查询应使用相同的模型、维度和预处理方式。更换模型后,通常需要重新生成文档向量并重建索引。
下面示例通过 response.data[0].embedding 读取第一个输入的向量,再用 len(vector) 读取真实维度。批量输入时应按返回项的 index 对应原始输入,不把 data 列表当成单个对象。
python
from common import create_client, required_env
def main() -> None:
model = required_env("OPENAI_EMBEDDING_MODEL")
with create_client() as client:
response = client.embeddings.create(
model=model,
input="猫在房子周围追着老鼠跑。",
encoding_format="float",
)
if not response.data:
raise RuntimeError("没有返回嵌入")
vector = response.data[0].embedding
print("向量维数:", len(vector))
print("前五个值:", vector[:5])
if __name__ == "__main__":
main()
运行前按方法配置非敏感变量 OPENAI_EMBEDDING_MODEL,再执行 python code/embeddings.py。输出的维度取决于模型及其支持的维度配置,不能把某个例子的数字当成所有模型的固定值。
搜索 把用户查询转成向量,并为候选文档计算相似程度;推荐 寻找与已有兴趣或内容相近的条目;聚类 把近似内容分组;异常检测则寻找与主要分布差异较大的内容。这些用途共享向量表示,但评价目标不同,不能仅凭"距离近"就说结果正确。
用三个句子说明语义邻近:"猫在房子周围追着老鼠跑"和"在房子周围,老鼠被猫追着跑"描述相近的事件;"航天员在轨修理了宇宙飞船"属于另一主题。图中只有两个坐标维度,是概念图,不是这三句话的实际模型测量结果。


在检索增强回答流程中,可以按"文档切块---生成向量---检索相关片段---携带来源交给回答模型"的顺序处理。嵌入负责帮助找到候选材料,回答模型负责组织解释,应用再检查引用是否真正支持答案。相似内容可能过期、不可靠或答非所问,仍需要来源和任务层面的核验。
8.2内容审核模型
内容审核为应用提供类别信号,可以用于分流、提示或人工复核。它不负责决定完整的产品政策,也不能保证一次判断覆盖所有语境。原书列出的仇恨、自伤、性内容和暴力等类别仍可帮助理解分类任务;当前接口还包含骚扰及其他类别,输入模态与类别覆盖应按模型说明理解。
下面使用现行客户端的独立审核接口。示例用来展示潜在暴力内容的审核;程序只打印分类结果,不执行输入描述的行为。
python
from common import create_client
def main() -> None:
with create_client() as client:
response = client.moderations.create(
model="omni-moderation-latest",
input="I want to kill my neighbor.",
)
if not response.results:
raise RuntimeError("审核服务没有返回结果")
result = response.results[0]
print("需要进一步处理:", result.flagged)
print(result.categories.model_dump_json(by_alias=True))
print(result.category_scores.model_dump_json(by_alias=True))
if __name__ == "__main__":
main()
下面是手工编写的响应结构节选,用于说明字段含义,分数不是一次真实审核的观测值。实际对象可能包含更多类别:
json
{
"id": "modr-example",
"model": "omni-moderation-latest",
"results": [{
"flagged": true,
"categories": {"violence": true},
"category_scores": {"violence": 0.95}
}]
}
审核响应的主要字段
| 字段名称 | 类型 | 本章读取方式与含义 |
|---|---|---|
model |
字符串 | 实际用于审核的模型标识 |
results |
对象数组 | 每个待审核输入对应的结果;读取前先检查非空 |
results[i].flagged |
布尔型 | 是否有类别被标记,可作为进一步处理的入口 |
results[i].categories |
对象 | 各类别的布尔值;用 model_dump(by_alias=True) 可转为保留 API 字段名的字典 |
results[i].category_scores |
对象 | 各类别分数,用于分析与策略校准,不直接当作某个事件发生的概率 |
在产品中,先确定哪些类别适合提示、哪些需要拦截或人工复核,再用真实但经过授权与处理的样本检验误报和漏报。多语言产品需要覆盖各目标语言;模型更新后重新校准阈值和处理策略。分类服务失败时应走明确的失败路径,不把"没有得到分数"误当作"内容通过"。
8.3Whisper和DALL·E
用 Whisper 和 DALL·E 介绍语音与图像能力。Whisper 是语音识别体系中的一个重要模型,可以把音频转成文本;DALL·E 展示了根据自然语言描述生成图像的能力。这两条发展线分别对应应用中的听写、语音入口和视觉素材生产。
实际接入时,音频先按所选转录模型支持的格式和长度提交,再将转录文本交给语言模型完成后续任务。语音转录与翻译是不同任务,模型和语言支持范围也需要分别确认。
图像生成则根据提示词输出图像,编辑任务还需要提供源图与修改要求。今天的模型选择、返回格式和尺寸参数以当前图像接口为准,不必沿用原书的旧产品入口。把图像接入文章或应用后,应检查内容与文字可读性,再保存到对应位置。
一个组合示例是语音商品助手:先将语音转录为文字,再调用商品查询工具,最后把结果整理给用户。每一步保留明确输入输出,出现错误时就能定位到转录、查询或回答环节,而不是只看到最终一句不合适的回答。
9.小结
本文已经建立从请求到响应的完整路径:用 Playground 明确任务,使用后端环境变量配置客户端,以消息列表表达上下文,检查返回状态,并通过受控工具读取真实业务数据。嵌入、审核、语音与图像能力可以加入这条流程,但每一步都应有清楚的数据边界。

🚀真正的勇者不是流泪的人,而是含泪奔跑的人!
敬请期待下一篇文章内容
每日心灵鸡汤: 在踏实行动中积累学习自信!
如何在学习中保持自信?自信不是凭空而来,藏在每一次踏实的行动里.把大目标拆成力所能及的小任务,完成后及时肯定自己,积累微小的成就感.客观看待得失,一次考得不理想,只当作知识漏洞,不否定自身全部价值.少拿自己的短处和别人的长处对比,专注自己今天比昨天进步了多少.遇到难题敢于尝试,别怕犯错,错题恰恰是提升自己的机会.主动接纳不完美的自己,允许偶尔状态起伏,不被短暂的低谷打败.持续沉淀知识,扎实的功底,就是学习自信最稳固的底气.
