跳到主要内容

调一次模型 — 请求、回答、账单

这一章讲三件事: 一次调用发过去的到底是什么;回来的对象里有哪几样; 以及有哪几个旋钮能拧、拧了会怎样。

它在全书链条里的位置: 这是全书的地基动作。 后面每一章——写提示词、把资料算成一串数、串成一条问答链、做界面——都是在这一次调用上加东西。

本章主走查的输入:两条消息。 第一条身份是「系统」,内容是 You are a helpful assistant.; 第二条身份是「用户」,内容是 Hello!。 这一次调用的回答、用掉的字数、花的钱,全部是原书里真实跑出来的1

1. 一次调用就是三行代码

这一节先把整件事的规模摆正:它比你想的小得多。

原书调 ChatGPT 的完整代码只有这么点1:

client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))

completion = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
]
)

没有把模型下载下来,没有装任何大文件,没有显卡。 你的程序把这几个值打包成一次网络请求发出去, 对方那台机器算完,把结果发回来。整个过程跟调用任何一个网站的服务没有区别。

那串 OPENAI_API_KEY 是你的密钥,相当于账号密码,调一次扣一次钱。 书的做法是把它写进项目最外层那个叫 .env 的文件里,再用代码读进来2—— 这样密钥不会出现在代码里,也就不会跟着代码一起被传到别处。

2. 发过去的不是一句话,是一个带身份的列表

这一节是主走查的第 1 步,也是最容易被想简单的一步。

messages 是一个列表,列表里每一项都有 role(身份)和 content(内容)两个格子。 原书列的身份一共三种3:

身份谁说的干什么用
system你(开发者)预先设定的定人设、定规矩,整场对话里一直有效
user用户这一轮真正要它回答的话
assistant模型自己上一轮说过的话把历史贴回去,让它知道前面发生过什么

第一种身份有个专门的名字:系统提示词(System Prompt)—— 就是那条你预先写好、在整场对话里持续影响它回复的话。 原书特意点明:它并不在模型训练里体现,是各家服务方为提升体验自己加的一层策略4

这三种身份的区别不是修辞,是待遇。 书里给的例子很直观: 把「你是一个幽默风趣的个人知识库助手」放进 system, 再把用户那句「我今天有什么安排」放进 user,它就会用幽默风趣的口气回答5

走查第 1 步: 我们发过去的两条是 system: You are a helpful assistant.user: Hello!注意这两条加起来才 30 个英文字符出头——这个规模等下算账时要用到。

3. 回来的不是一段文字,是一个带账单的对象

这一节是主走查的第 2、3 步:先看正文,再看账单。

调用返回的东西原书原样打印了出来,长这样(为了看清楚,我们只留了三处最要紧的格子)1:

ChatCompletion(
choices=[ Choice(finish_reason='stop',
message=ChatCompletionMessage(
content='Hello! How can I assist you today?',
role='assistant')) ],
model='gpt-3.5-turbo-0125',
usage=CompletionUsage(completion_tokens=9, prompt_tokens=19, total_tokens=28)
)

图说:你要的正文藏在 choices[0].message.content 这条路径底下。
最后那一行 usage 是账单,三个数依次是:回答用了多少、提问用了多少、一共多少。

词元(token)就是模型眼里的最小单位——一段文字进模型之前会被切成一串小块, 一个常见英文单词大约就是一块,中文通常一两个字一块。 账单按它算,不按字符算:这就是为什么 30 个字符出头的输入被算成了 19 个。

原书用到这个单位的地方还有一处:max_tokens 这个参数管输出的上限, 而提问和回答的词元数是合并计算的,加起来不能超过模型的上限6。 所以输入很长的时候,max_tokens 就要设小,否则会报超长。

现在算这一次的钱。 书里那张价目表写着:gpt-3.5-turbo-0125 的输入是 每百万词元 0.5 美元、输出是每百万词元 1.5 美元7。于是:

提问:19 × 0.5 ÷ 1 000 000 = 0.0000095 美元
回答: 9 × 1.5 ÷ 1 000 000 = 0.0000135 美元
合计: 0.000023 美元

图说:这一次调用花了两万三千分之一美元。
换个参照物:按这个价钱,你要调四万多次才花掉 1 美元。

这个量级值得记住,因为它解释了这门课后面很多设计: 便宜到可以随便试,所以「改一版提示词再跑一遍」是常规动作,而不是奢侈行为。 (把提示词从 v1 改到 v4、每版都重跑一遍,是第 10 章的主线。)

4. 三个能拧的旋钮

这一节讲你除了那段话之外还能控制什么。

第一个旋钮叫温度(temperature)——它管的是回答的发散程度。 原书的说法是:模型生成本来就带随机性,它在顶层从若干候选里挑一个; 温度取值一般在 0 到 1 之间,接近 0 时保守、可预测,接近 1 时更有创意也更意外8

原书拿同一个问题在两个刻度上各跑了一遍,让人一眼看出差别——问的是 「给我一个关于跨语言模型的硕士毕业论文选题」9:

温度它回了什么
0一个选题,连摘要、关键词和六章目录一起给全,格式像一份开题报告
0.95六个选题,每个一两句话,最后加一句「选适合自己兴趣的,并与导师确认」

这不是「一个更好」的问题,是场景不同。 书写明: 本教程搭的知识库助手一律把温度设为 0,为的是保证助手稳定地用知识库里的内容、规避错误内容和幻觉; 而个性化 AI、创意文案这类场景才调高10

第二个旋钮叫 top-p——它管的是候选范围。 做法和温度不同: 把所有候选按可能性从高到低排成一队,只在最靠前、加起来凑够 p 的那一小撮里挑。 书给的例子是 0.1 意味着只在最靠前的百分之十里取;默认值 0.711书还给了一条明确建议:两个旋钮调一个就好,别同时调。

第三个旋钮是 max_tokens,上一节已经讲过:它管输出上限,而且和输入合并算。

5. 把细节包起来:一个 get_completion 函数

这一节讲书全程都在用的那个封装,以及它包掉了什么。

上面那段代码每调一次都要写一遍 messages 的结构,很啰嗦。 原书把它包成了一个函数,之后整本书都调这个函数12:

def gen_gpt_messages(prompt):
return [{"role": "user", "content": prompt}]

def get_completion(prompt, model="gpt-3.5-turbo", temperature=0):
response = client.chat.completions.create(
model=model,
messages=gen_gpt_messages(prompt),
temperature=temperature,
)
if len(response.choices) > 0:
return response.choices[0].message.content
return "generate answer error"

调它一次:get_completion("你好"),拿回一句 '你好!有什么可以帮助你的吗?'12一行进,一行出,前面那个对象和那三层路径全部不见了。

但要看清它包掉了什么: 这个函数只发一条 user 消息, 系统提示词那一格空着,历史那一格也空着。原书自己写明这一点: 它封装了 messages 的细节,简单场景够用12

这个「够用」是有期限的。 第 08 章要让助手记住上一轮说过什么, 到那时这个函数就不够了——历史必须写回那个列表里,而这个函数根本没有放它的地方。

6. 换一家模型,有三处会绊倒你

这一节是本章唯一需要死记的部分:形状一样,细节不一样。

四家的接口(就是第 01 章说的那个 API,中文文档里更常写成这两个字)长得大同小异。 原书把它们(ChatGPT、文心一言、讯飞星火、智谱 GLM)都调了一遍,并且统一封装成同名的 get_completion,这样上层代码换一家只要换一个导入。下面是三处真正会让你报错的差异:

差在哪具体是什么
温度不许设 0文心的范围是 (0, 1.0],开区间,不能等于 0;智谱同理,默认 0.9513
人设从别处传文心的人设不放在 messages 里,而是走一个单独的 system 参数14
连法不一样星火不是普通网络请求,要用 WebSocket(一条建立之后不断开、双方随时能说话的连接)15

还有两条形状上的硬要求容易踩:文心的 messages 成员数目必须是奇数、 身份必须严格按 userassistant 交替16; 它的内容总长度不能超过 20480 个字符、5120 个词元17

这个上限大约是一篇两万字的长文,而第 05 章要处理的那本南瓜书有 30 万字符,是它的十几倍; 超了模型会自行遗忘前文。

注意最后那半句的措辞:超长不报错,而是「自行对前文依次遗忘」。 这是一种会静默出错的失败方式——你的程序照常返回,只是它已经忘了你前面说的话。

7. 边界:这一章里最先过期的东西

这一节告诉你哪些数字今天不能照抄。

第一,所有价格和额度都过期了。 书给的免费额度是:星火 10 万词元、 实名后再加 200 万18;智谱 100 万、实名后再加 400 万19这类促销数字寿命通常只有几个月,以官网当时的说明为准。

第二,那张价目表也过期了。 本章第 3 节那笔账用的是书里的价钱, 目的是让你学会怎么算,不是让你照着它做预算。 算法本身不会变:输入词元数 × 输入单价 + 输出词元数 × 输出单价。

第三,书里有一处对代码的修正提醒仍然有效但很具体: 星火官方示例文件直接跑会报错,要注释掉一行没用到的导入、 并把处理连接关闭的那个函数改成收三个参数15这类修补是版本相关的,遇到再查。

判断(我们的,不是书里的): 这一章真正长效的东西只有两样—— 「发过去的是一个带身份的列表」和「回来的东西带一张按词元计的账单」。 其余的参数名、价目、额度都会变。 如果错,会错在: 如果将来各家把接口形状换成别的(比如统一改成一次一个对象、不再是列表), 那么第 2 节那张身份表就要重学;但按目前各家接口越来越像的趋势看,这一层反而是最稳的。

8. 可带走的

  1. 调一次模型就是发一次网络请求——没有下载、没有显卡,三行代码;
  2. 密钥写进 .env 文件、用代码读进来,别写在代码里;
  3. 发过去的是一个列表,每条带身份:system(定规矩)、user(这一轮的话)、assistant(它上一轮说过的话);
  4. 系统提示词不是模型本身的东西,是各家服务方加的一层策略;
  5. 回来的是一个对象,正文在 choices[0].message.content,账单在 usage;
  6. 账单按词元算,不按字符算:30 个字符出头的英文输入被算成 19 个词元;
  7. 算钱的公式是固定的:输入词元 × 输入单价 + 输出词元 × 输出单价;本章那次是 0.000023 美元,调四万多次才花 1 美元;
  8. 温度管发散程度:0 时同一个问题给一份详细答案,0.95 时给六个方向;知识库助手一律用 0;
  9. top-p 管候选范围,和温度是两条路;两个只调一个;
  10. 换一家要当心三处:温度不许设 0、人设从别的参数传、星火走 WebSocket;
  11. 文心超长不报错,而是静默遗忘前文——这类失败最难查。

9. 原文地图

主题原书章原文位置
三行代码与返回对象使用 LLM APItext/03-p41-60.txt:162(搜「client.chat.completions.create」) · text/03-p41-60.txt:178(搜「ChatCompletion(id=」)
密钥写进 .env使用 LLM APItext/03-p41-60.txt:133(搜「保存到 .env 文件中」)
三种身份使用 LLM APItext/03-p41-60.txt:197(搜「即我们的 prompt」) · text/03-p41-60.txt:199(搜「assistant:助手」)
系统提示词是服务方加的策略基本概念text/03-p41-60.txt:83(搜「新兴概念」) · text/03-p41-60.txt:88(搜「该种 Prompt 内容会在整个会话过程中持久地影响模型的回复」)
max_tokens 与合并计算使用 LLM APItext/03-p41-60.txt:207(搜「总 token 数不能超过模型上限」)
价目表大型语言模型(LLM)理论简介text/01-p1-20.txt:255(搜「经济,专门对话」)
温度的定义与两个刻度的对照基本概念text/03-p41-60.txt:14(搜「LLM 生成是具有随机性的」) · text/03-p41-60.txt:23(搜「题目:基于跨语言模型的机器翻译性能优化研究」) · text/03-p41-60.txt:60(搜「当我们将 temperature 设置为 0.95 时」)
知识库助手用温度 0基本概念text/03-p41-60.txt:78(搜「规避错误内容、模型幻觉」)
top-p使用 LLM APItext/04-p61-80.txt:49(搜「核取样」)
get_completion 封装使用 LLM APItext/03-p41-60.txt:235(搜「def get_completion(prompt, model=」) · text/03-p41-60.txt:257(搜「有什么可以帮助你的吗」)
文心的温度范围与人设参数使用 LLM APItext/03-p41-60.txt:363(搜「参数要求范围为 (0, 1.0]」) · text/03-p41-60.txt:310(搜「模型人设是通过另一个参数 system 字」)
星火走 WebSocket使用 LLM APItext/03-p41-60.txt:373(搜「星火 API 需要使用 WebSocket」)
文心的成员数与长度上限使用 LLM APItext/03-p41-60.txt:359(搜「成员数目必须为」) · text/03-p41-60.txt:357(搜「20480」)
免费额度使用 LLM APItext/03-p41-60.txt:379(搜「100000 tokens 的试用量」) · text/03-p41-60.txt:493(搜「100w token 的体验包」)

Footnotes

  1. 出处:「使用 LLM API」第 162 段(text/03-p41-60.txt:162,搜「client.chat.completions.create」)与第 178 段(text/03-p41-60.txt:178,搜「ChatCompletion(id=」)。原书打印的对象里还有 idcreated(时间戳)、system_fingerprint 等字段,本章为了看清楚只留了正文、模型名和账单三处。这一次调用是全书唯一一处把 usage 打印出来的地方,后面那些用封装函数调的都只拿正文。 2 3

  2. 出处:「使用 LLM API」第 133 段(text/03-p41-60.txt:133,搜「保存到 .env 文件中」)。原书用 python-dotenv 这个包的 find_dotenv()load_dotenv() 把它读进环境变量。补充(不在书里,来自通用知识):.env 这个文件名是社区约定,不是语言规定;它必须被写进版本库的忽略清单,否则跟着代码一起提交就等于把密钥公开了——书里没有提这一句。

  3. 出处:「使用 LLM API」第 197 至 200 段(text/03-p41-60.txt:197,搜「即我们的 prompt」;第三种见 text/03-p41-60.txt:199,搜「assistant:助手」)。原文对 assistant 的说明是「一般是模型历史回复,作为提供给模型的参考内容」——这句话是第 08 章那套记忆机制的全部原理。

  4. 出处:「基本概念」第 83 至 88 段(text/03-p41-60.txt:83,搜「新兴概念」;text/03-p41-60.txt:88,搜「该种 Prompt 内容会在整个会话过程中持久地影响模型的回复」)。原文还有一句限制:系统提示词在一个会话中一般只有一条。

  5. 出处:「基本概念」第 98 至 104 段(text/03-p41-60.txt:99,搜「幽默风趣的个人知识库助手」)。

  6. 出处:「使用 LLM API」第 203 至 211 段(text/03-p41-60.txt:207,搜「总 token 数不能超过模型上限」)。原文提醒:输入的提示词较长时要把 max_tokens 调小,否则会报超出限制长度。原书这一段的排版被转码打乱了,几个词的顺序看起来是错的,但意思清楚。

  7. 出处:「大型语言模型(LLM)理论简介」第 254 至 263 段(text/01-p1-20.txt:255,搜「经济,专门对话」)。表里同时列了 GPT-4(每百万词元输入 30 美元、输出 60 美元)——同样这次调用如果换成 GPT-4,花的是 0.0011 美元,是 GPT-3.5 的四十多倍。 这个倍数是我们按同一张表算的。补充(不在书里):OpenAI 在 2024-01-25 的公告里给的 gpt-3.5-turbo-0125 价格是每千词元输入 0.0005 美元、输出 0.0015 美元,和书里那张表一致。来源:OpenAI「New embedding models and API updates」https://openai.com/index/new-embedding-models-and-api-updates/(查阅于 2026-08-25)。

  8. 出处:「基本概念」第 14 至 20 段(text/03-p41-60.txt:14,搜「LLM 生成是具有随机性的」;text/03-p41-60.txt:17,搜「取值较低接近 0 时」)。注意书里说的范围是 01,而 OpenAI 接口实际允许 02——原书自己的 get_completion 函数注释里就写着「取值范围是 02」(text/03-p41-60.txt:243,搜「取值范围是 02」),两处口径不一致。

  9. 出处:「基本概念」第 21 至 74 段(text/03-p41-60.txt:23,搜「题目:基于跨语言模型的机器翻译性能优化研究」;另一半见 text/03-p41-60.txt:60,搜「当我们将 temperature 设置为 0.95 时」)。本章那张两行的对照表是我们从两大段原文里概括出来的,原文是两份完整的回答全文。

  10. 出处:「基本概念」第 76 至 80 段(text/03-p41-60.txt:78,搜「规避错误内容、模型幻觉」)。原文另举了两类要稳定性的场景:产品智能客服、科研论文写作。

  11. 出处:「使用 LLM API」第 49 至 55 段(text/04-p61-80.txt:49,搜「核取样」)。这一段出自智谱接口的参数说明。最后那句「建议您根据应用场景调整 top_p 或 temperature 参数,但不要同时调整两个参数」是书的原话。

  12. 出处:「使用 LLM API」第 235 至 260 段(text/03-p41-60.txt:235,搜「def get_completion(prompt, model=」;返回值见 text/03-p41-60.txt:257,搜「有什么可以帮助你的吗」)。原文的原话是:我们封装了 messages 的细节,仅使用 user prompt 来实现调用,在简单场景中该函数足够满足使用需求 2 3

  13. 出处:「使用 LLM API」第 363 段(text/03-p41-60.txt:363,搜「参数要求范围为 (0, 1.0]」)与第 46 至 48 段(text/04-p61-80.txt:46,搜「必须为正数取值范围」)。文心的默认值是 0.8,智谱是 0.95。

  14. 出处:「使用 LLM API」第 310 段(text/03-p41-60.txt:310,搜「模型人设是通过另一个参数 system 字」)。书里那个例子把人设设成了「你是一名个人助理-小鲸鱼」,回答里模型就自称小鲸鱼。

  15. 出处:「使用 LLM API」第 373 段(text/03-p41-60.txt:373,搜「星火 API 需要使用 WebSocket」)。原文的评价是「对企业友好,但对初学者、新手开发者来说调用难度较大」。两处必须做的修补见第 389 至 396 段(text/03-p41-60.txt:390,搜「import openpyxl」)。补充(不在书里,来自通用知识):WebSocket 是一种建立之后不主动断开的双向连接,和普通网络请求「问一次答一次就断」不同。 2

  16. 出处:「使用 LLM API」第 358 至 360 段(text/03-p41-60.txt:359,搜「成员数目必须为」)。原文三条要求:一个成员为单轮对话、最后一个成员是当前对话、成员数目必须为奇数且身份依次是 user、assistant。

  17. 出处:「使用 LLM API」第 357 段(text/03-p41-60.txt:357,搜「20480」)。原文注明这是 ERNIE-Bot 这一个型号的限制,不同型号不一样,要去官网查。

  18. 出处:「使用 LLM API」第 379 至 381 段(text/03-p41-60.txt:379,搜「100000 tokens 的试用量」)。这是通过书里给的专属链接申请时的额度。

  19. 出处:「使用 LLM API」第 493 段(text/03-p41-60.txt:493,搜「100w token 的体验包」)。原文写明有效期一个月,实名认证后再加 400 万。