【大模型应用开发:基于GPT-4和ChatGPT】(深入了解GPT-4和ChatGPT的API)


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

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

目录

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 是理解模型行为的实验入口。它允许你在编写应用前设置模型、输入提示词并观察结果。使用时登录开发者平台,进入自己的项目和提示词试验界面,选择账户可用的模型。界面名称与按钮位置可能变化,因此这里按"输入---设置---运行---比较"的操作顺序讲解。

  1. 先写一个具体任务,例如"只返回修正后的英语句子"。
  2. 提供输入,例如 She no went to the market.。
  3. 选择支持该任务的模型,保留一份初始参数。
  4. 运行后检查语法是否正确、原意是否保留、是否多写了说明。
  5. 修改一个因素再次运行,并把模型、提示词、参数、结果和用量记录在一起。

生成会按所用模型与实际用量计费;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 只控制相应的响应存储行为,不能等同于所有系统都零保留。

实际开发可按数据流落实保护:

  1. 在应用侧筛选完成任务所必需的字段,例如查询商品只需价格条件,无需发送客户身份资料。
  2. 标准 API 密钥由后端读取;给不同项目配置适当权限,并对密钥轮换保留操作记录。
  3. 不把完整用户输入默认写进日志;记录错误类型、耗时、用量和必要请求标识即可。
  4. 为自己存储的会话、文件和检索索引设置访问控制与删除周期,避免只处理模型侧而忘记本地副本。
  5. 接入外部工具时由程序做权限和参数校验;模型的工具请求不能直接越过业务授权。

如果业务涉及特定数据驻留或个人信息处理要求,应按所选项目、端点和组织安排确认数据路径。这里的技术设置帮助落实数据最小化,不能替代具体业务的数据治理。


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 明确任务,使用后端环境变量配置客户端,以消息列表表达上下文,检查返回状态,并通过受控工具读取真实业务数据。嵌入、审核、语音与图像能力可以加入这条流程,但每一步都应有清楚的数据边界。

🚀真正的勇者不是流泪的人,而是含泪奔跑的人!


敬请期待下一篇文章内容


每日心灵鸡汤: 在踏实行动中积累学习自信!

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

相关推荐
高频因子挖掘机1 小时前
多股票行情为什么容易对不齐?用 Pandas MultiIndex 管理标的、日期与缺失值
后端·github·api
高频因子挖掘机2 小时前
Python 量化数据接口设计实战:用数据契约解决字段混乱与回测不一致
后端·github·api
dozenyaoyida10 小时前
AI与大模型新闻日报 | 2026-10-17
人工智能·ai·chatgpt·新闻
XLYcmy13 小时前
《数字经济赋能城市低碳治理的路径研究检索报告》审阅与修改指导意见——基于完整检索报告体例的完整性诊断、学术规范核查与方法论纠偏 上
数据库·ai·chatgpt·文心一言·检索·知网·豆包
Summer-Bright15 小时前
浏览器成了AI应用的新入口:智能体接管标签页之后,三本账谁买单
人工智能·chatgpt·agent·浏览器·智能体
用户77833661321117 小时前
选 SERP API 别靠感觉:用 SerpBase 实测的评估脚本与判定标准
python·api
P1Browser18 小时前
浏览器指纹参数校验:时区、语言、分辨率的一致性原理
web安全·api·前端开发·跨境电商·环境隔离·浏览器指纹
system_error_x1 天前
API的使用
api·oneapi
高频因子挖掘机1 天前
分钟线跨午休后时间轴不连续,是数据错了还是 A 股交易时段本来如此?
后端·github·api