当下,大模型领域已然步入 “百模大战” 时期,形形色色的大模型如雨后春笋般不断涌现。那么,若我们打算学习大模型的 API,是否真的需要逐一去钻研呢?答案无疑是否定的。
尽管大模型的数量极为可观,然而就编程接口层面而言,它们大体上颇为相似。用编程领域的专业表述来讲,即便各模型的内部实现千差万别,但其接口却基本保持一致。所以,只要深入研习其中一个模型的 API,掌握其他 API 对于我们而言也并非难事,能够较为迅速地入门上手。
既然确定要展开学习,那自然需要设定一个具体的学习目标。在此,我选定的是 OpenAI API。之所以做出这样的抉择,首要原因便是 GPT 模型在行业内所产生的深远影响力,众多后来者在设计自身 API 时,或多或少都会对其予以参考借鉴。
此外,还有一个极为关键的因素,OpenAI API 几乎已成为行业内公认的事实标准。在当下的众多项目中,许多都倾向于提供与 OpenAI API 相兼容的接口。甚至存在一些具有中间件特性的项目,无论其后台实际接入的是何种模型,面向自身用户所提供的均为 OpenAI 兼容的 API。鉴于如此这般的行业现状,倘若仅打算学习一个 API,那么 OpenAI API 显然是不二之选。
OpenAI API
为了让你有一个直观的感受,我们来看一个具体的例子:
curl https://api.openai.com/v1/chat/completions
-H "Content-Type: application/json"
-H "Authorization: Bearer $OPENAI_API_KEY"
-d '{
"model": "gpt-4o",
"messages": [
{"role": "user", "content": "写一首关于AI的诗"}
]
}'
在了解具体内容之前,我们先从整体上看一下这个 API。虽然我们的主要工作是面向大模型进行编程,但 Open AI API 其实提供了许多功能,比如:Text Generation:生成和处理文本Embeddings:文本转向量Speech to Text:语音转文本Image Generation:生成图像Vision:处理图像输入……
这些不同的接口是通过不同的路径进行区分。这里看到的 /v1/chat/completions 就是我们常用的大模型的编程接口。除了各种强大的功能,OpenAI API 还有两个关键的参数是各个接口统一的,一个是访问地址,一个是 API Key。
访问地址之所以需要被单独提及,原因在于实际运用该 API 之际,我们并非总是径直访问 OpenAI API。有可能是借助他人所提供的兼容 API,亦或是通过代理途径所给予的访问方式。总而言之,我们所运用的地址会有所不同,所以需要专门进行配置。
而 API Key 的概念则相对容易理解,不同的 Key 主要用于区分不同的用户。从上述实例中能够清晰地看出,此 Key 是被写在 HTTP 访问的头部信息之中。
倘若我们采用的是 OpenAI 所提供的程序库,那么这两个参数既能够借助程序来设定,也可以通过命令行来进行设置。以下即为使用命令行设置的相关示例:
export OPENAI_API_BASE="your_api_base_here"
export OPENAI_API_KEY="your_api_key_here"
有了这些通用的知识,接下来,我们来具体关注一下最核心的大模型编程接口。
Chat Completions
大模型编程接口所对应的路径为 /v1/chat/completions 。在此路径中,“v1” 代表的是版本编号,而该接口被命名为 “聊天补全”。“补全” 这一名称恰好与我们之前所阐述的 GPT 工作原理相互呼应。实际上,OpenAI 起初拥有一个单纯执行补全功能的接口,然而在 ChatGPT 迅速走红之后,聊天形式逐渐成为主流应用模式,于是 “聊天补全” 接口便成为了我们重点关注且广泛使用的核心接口。
新旧两种补全 API 之间最为显著的区别在于,旧版 API 所接收传入的是一个单一的提示词,而新版 API 接收传入的则是一个消息列表。
请求
我们来看看这个接口的具体内容。作为一个 HTTP 请求,它分为请求和应答两个部分。我们先来看请求。请求的参数非常多,我们会做一个通览。有一点需要提示一下,你学习到这篇内容时,可能与最新的 OpenAI API 文档对不上,因为这个 API 本身也在不断的演化过程中,它会不断地增删参数。为了方便你理解,我把请求参数划分成四类:
- 核心参数
- 工程参数
- 工具参数
- 模型参数
核心参数
首先来看最为核心的两个参数,它们在之前的示例中已然有所呈现。
其一为 “model”,此参数用于指定与哪一个模型进行交互沟通,在相关示例里所采用的便是 “gpt - 4o” 模型。
其二是 “messages”,它代表着发送给模型的消息,这里的消息呈现为一个列表形式,简单来讲,可将其视作一个包含历史消息的列表,其目的在于为模型提供更为丰富的上下文信息。
每条消息通常由两个主要部分构成:“角色(role)” 和 “内容(content)”。其中 “内容” 较为直观,即消息自身所包含的具体信息。而对于常规对话场景而言,“角色” 主要分为 “系统(system)” 和 “用户(user)”。可以理解为开发者所设置的相关内容对应 “系统” 角色,而用户提出的问题则属于 “用户” 角色。有时,我们还会把大模型所生成的消息添加进去,例如在聊天历史记录中,这类消息的角色即为 “助理(assistant)”。
除了上述这两个关键参数之外,还有三个常用参数。
“temperature”,其概念在之前已经有所提及,主要用于设定大模型回复的确定性程度。当该值越小,表明回复的确定性越强;反之,值越大,则意味着回复的随机性越强。
“max_completion_tokens”,它表示大模型生成应答时的最大 token 数量。由于大模型生成内容通常依据 token 数量来计费,所以对 “max_completion_tokens” 进行合理限制是一种有效控制成本的方式。不过,也不能将其设置得过大,因为每个模型内部实际上都存在自身的最大限制,当我们所设置的限制值超出其内部限制时,模型将会忽略我们的设置。值得注意的是,该字段曾被命名为 “max_tokens”,所以在许多地方,包括一些参考 OpenAI 设计的框架之中,仍然能够看到 “max_tokens” 的存在。
“stream”,此参数用于确定是否需要流式应答。流式应答的主要作用在于提升聊天过程中的响应速度,关于这部分内容将会在下一讲中展开详细探讨。
需要特别指出的是,对于不同的模型,这些参数的取值范围可能会存在差异。例如不同模型各自拥有其特定的 “max_tokens” 取值范围。此外,鉴于众多服务供应商提供了兼容的 OpenAI API,即便是相同的参数,其取值范围也可能不尽相同。比如在 OpenAI API 里,“temperature” 的取值范围是从 0 到 2,其默认值为 1,然而对于某些兼容模型而言,其取值范围或许会有所变化,具体情况需要依据相关文档来确定。
通过以上梳理,我们对大模型编程中最核心的参数有了较为清晰的认识。在常规聊天场景下,多数情况中我们所进行调节的便是这些参数。接下来,我们将进一步探讨其他参数。
工程参数
在请求参数之中,存在一部分是基于工程目的而设立的。
首先是 “user” 参数,它代表着终端用户标识。这一参数由我们作为开发者提供给 OpenAI,其主要用途在于实现对 API 滥用情况的监控与检测,并且其监控的精细程度能够具体到单个用户个体层面。
接着是 “n” 参数,该参数用于指定针对每条输入消息生成回复的数量。尽管从表面上看,设置此参数能够获取更多的生成内容,但由于生成内容是需要计费的,所以在没有特殊需求的情况下,建议不要对这个参数进行额外设置。
还有 “response_format” 参数,即应答格式。在默认状态下,此接口仅仅生成文本内容。然而在开发过程中,我们常常会用到 JSON 格式。对于获取 JSON 格式的输出,我们既可以借助提示词要求大模型进行返回,也能够通过设置 “response_format” 参数使 API 直接返回 JSON 格式,具体的操作方法可以参考 OpenAI 的结构化输出相关内容。
不过需要说明的是,这些参数虽然存在被使用的可能性,但在实际应用中真正用到它们的机会并不多。例如,若发现某个用户存在滥用 API 的行为,我们通常不会将其相关信息传递给 OpenAI,而是自行在本地进行处理。再比如,尽管 OpenAI 支持 JSON 格式输出,但考虑到模型的兼容性问题,我们可能仍然会倾向于选择使用提示词的方式,让大模型直接返回所需内容。
工具参数
在当下,除了用于常规的聊天场景之外,大模型还有一种较为常见的应用方式,那便是构建 Agent。Agent 有着十分重要的用途,其中关键的一点在于它能够拓展大模型的能力边界,使其可以完成更多类型的任务,像查询天气、搜索网页这类操作都涵盖在内。那么,大模型是如何知晓自己能够做这些事情的呢?即通过工具参数来进行传递。
这里的 “工具” 这一说法其实是经过改进之后的称呼,之所以进行这样的改进,主要是为了与社区的习惯保持一致。最开始的时候,它被称作 “函数(Function)”。之所以要特别说明这一点,是因为在部分资料当中,我们依然能够看到 “函数” 这种表述方式。并且在 API 文档里也存在 “函数” 相关内容,只不过目前已经被标记为废弃状态了。
工具参数里最常用的是这两个:
对于 “tools” 参数,它代表着模型能够调用的工具列表。在该列表中的每一个工具均包含 “type”(类型)与 “function”(函数)这两个组成部分。
就当前而言,“type” 所表示的工具类型仅有 “function” 这一种。
而 “function” 的主要作用在于告知模型该函数的调用方式,它涵盖了 “description”(函数的描述)、“name”(函数名)以及 “parameters”(函数参数)这些要素。
另外,“tool_choice” 参数则用于确定如何调用工具。当参数值为 “none” 时,意味着不调用任何工具;若参数值为 “auto”,则表示由模型自行决定是生成消息还是调用工具;而参数值为 “required” 时,则表明必须调用工具。此外,该参数值还可以是一个对象形式,例如,如下这行代码便是告知模型需要调用指定的这个工具。
{"type": "function", "function": {"name": "my_function"}}
模型参数
若上述所提及的部分参数还算比较易于理解,仅通过参数名便能知晓其大致用途的话,那么还有相当多的参数看起来则令人颇为费解,就比如 “frequency_penalty”。实际上,这些参数通常并非是供开发应用的工程师使用的,我们可以将其视为是为模型开发者所准备的,所以,我把它们归到了模型参数的类别之中。大家对此仅作简单了解即可,待日后真有需要用到它们的时候,再深入探究也为时不晚。
先来说说 “seed”,即种子值。种子值的设立主要是为了解决可重复输出的问题。具体而言,如果采用相同的种子值并且搭配相同的参数,那么所生成的输出结果理应是完全一致的。从开发的角度来看,我们可以将这种现象理解为缓存操作。
接着是 “stop”,也就是停止序列。它的作用在于告知大模型,在生成文本的进程中,一旦遇到停止序列,便立即停止生成文本。
再看 “frequency_penalty”(频率惩罚)和 “presence_penalty”(存在惩罚)。这两个参数主要是用于降低内容重复出现的概率,这也是它们名称中都带有 “惩罚” 二字的原因。二者的区别在于,“frequency_penalty” 是依据一个 token 在已生成文本里出现的频率来进行计算的,而 “presence_penalty” 则是根据一个 token 是否已经出现过进行计算。
“logit_bias”,即 logit 偏差。这里的 logit 是统计学中的一个函数。该参数的作用是在 logit 函数计算过程中对计算结果进行调整,其主要目的是改变某些 token 出现的可能性。例如,当我们不希望某些词汇出现在最终结果中时,便可以借助此参数来实现。
“logprobs”:它用于确定是否返回对数概率。正如我们之前所提到的,大模型生成每个 token 都是有相应概率的。若设置了此参数,就能够将这些概率以对数的形式返回。对于大模型开发人员而言,这有助于他们进行调试工作。
“top_logprobs”:该参数用于设定返回每个位置最有可能返回的 token 数量。在调试大模型时,除了想要知晓概率信息之外,有时我们还希望了解排名较为靠前的 token 都有哪些。通过设置这个参数,就能够让大模型返回这些排名靠前的 token。
最后是 “top_p”:这是另一种采样方式,与 “temperature” 相对应。我们之前阐述过,“temperature” 决定了大模型如何选取下一个 token,而 “top_p” 则是另外一种采样策略,即从概率排名靠前的若干 token 中进行选择。在实际使用过程中,选择 “top_p” 和 “temperature” 其中之一即可。
总结:
图片