Day 8:模型 API 调用基础

学习目标

Week 1 建立了认知框架,你知道了 AI 应用长什么样、由哪些模块组成、大模型的基本原理是什么。从今天开始,你要动手了。

今天的目标很明确:学会通过程序调用大模型的 API,把”跟 ChatGPT 聊天”变成”代码调用模型”。听起来简单,但这里面有一整套工程问题需要处理——密钥怎么管、请求怎么发、流式输出怎么接、出错怎么重试、限流怎么应对、日志怎么记。这些问题的答案构成了你后续所有 AI 应用开发的基础设施。

完成今天的学习后,你应该能稳定地通过代码调用大模型 API,能处理常见的错误场景,能统计每次调用的 Token 消耗和耗时,能设计一个基础的 LLM Client 封装。

核心概念

一、API Key 管理

API Key 是你调用模型服务的凭证。没有它,你发出去的请求会被拒绝;有了它,你每一次调用都会被计费。所以 API Key 的管理不是小事,它直接关系到你的钱包和系统安全。

先说 API Key 是怎么来的。无论你用的是 OpenAI、Anthropic 还是国内的模型服务商,流程大同小异:注册账号、进入控制台、创建 API Key。创建后你会得到一串随机字符串,这串字符串就是你的通行证。把它放在 HTTP 请求的 Header 里,服务端就知道你是谁、有没有权限、该怎么计费。

API Key 的管理有几个原则必须遵守。

第一,永远不要把 API Key 硬编码在代码里。很多人在教程里看到直接把 Key 写在代码中,就照着做了。这在学习阶段可能没问题,但一旦代码提交到 Git 仓库(尤其是公开仓库),你的 Key 就泄露了。市面上有无数机器人专门扫描 GitHub 上的泄露 Key,几秒钟内就能被滥用。正确做法是把 Key 存在环境变量中,程序运行时从环境变量读取。

第二,不同环境使用不同的 Key。开发环境用一个 Key,测试环境用一个 Key,生产环境用一个 Key。这样做的目的是隔离——开发时你的代码万一出了 bug 导致疯狂重试,不会影响生产环境的额度和费用。生产环境的 Key 还应该设置使用上限和告警。

第三,Key 要定期轮换。即使你没有泄露 Key,定期更换也是一个好习惯。设置一个周期(比如 90 天),到期后生成新 Key、替换旧 Key、禁用旧 Key。这能降低 Key 泄露带来的风险。

第四,为不同的项目或服务使用不同的 Key。如果你同时开发三个 AI 应用,每个应用用独立的 Key。这样你能清楚知道每个应用的调用量和成本,某个应用的 Key 出问题也不会影响其他应用。

从工程实现的角度,API Key 的管理通常包括以下部分:一个配置模块负责从环境变量或配置文件中读取 Key;一个 Key 池支持配置多个 Key 并做轮询,当某个 Key 触发限流时自动切换到下一个;一个监控模块统计每个 Key 的使用量和剩余额度。

二、Chat API 调用

Chat API 是调用大模型最核心的接口。它的名字叫 Chat,但它的用途远不止聊天——文本分析、内容生成、信息提取、代码编写,几乎所有与大模型的交互都通过这个接口完成。

Chat API 的基本工作方式是:你发送一组消息(Messages),模型返回一条回复。消息有两种主要角色——system 和 user。system 消息定义模型的行为规则(你是谁、你要怎么做、你的输出格式是什么),user 消息是用户的具体请求。

一个典型的 Chat API 请求包含以下要素:

模型名称。你告诉服务端要用哪个模型,比如 gpt-4o、claude-sonnet、deepseek-chat。不同模型的定价、能力、速度不同,你需要根据任务来选择。

消息列表。这是一个数组,包含 system 消息和 user 消息。也可以包含 assistant 消息,代表模型之前的回复,用于实现多轮对话。

参数设置。最重要的几个参数是 Temperature(控制随机性)、Max Tokens(控制输出长度)、Top P(控制采样范围)。这些参数直接影响模型的输出风格和质量。

理解 Chat API 的关键在于弄清它的请求-响应模式。你发出一个 HTTP POST 请求,请求体是 JSON 格式,包含上述要素。服务端处理后返回一个 JSON 响应,包含模型生成的文本、Token 使用统计、结束原因等信息。

响应中的几个关键字段值得特别关注。content 是模型生成的文本内容。usage 包含 Token 统计——prompt_tokens 是输入消耗的 Token 数,completion_tokens 是输出消耗的 Token 数,total_tokens 是总消耗。finish_reason 告诉你模型为什么停止生成——“stop”表示正常结束,“length”表示达到了 Max Tokens 限制被截断,“content_filter”表示内容被安全过滤器拦截。

多轮对话的实现方式是把历史消息累加到消息列表中。第一轮只有 system 和 user 消息;模型回复后,把 assistant 的回复追加到消息列表;第二轮用户提问时,消息列表包含 system、user(第一轮)、assistant(第一轮)、user(第二轮),模型基于完整上下文生成回复。这种方式简单直接,但随着对话轮次增加,消息列表会越来越长,Token 消耗也会越来越大。这在后面设计记忆管理时需要重点考虑。

三、Streaming 输出

Streaming(流式输出)是提升用户体验的重要技术。普通的 API 调用是”等模型生成完毕后一次性返回全部内容”,而 Streaming 是”模型生成一个 Token 就立即发送一个 Token”,用户能实时看到内容逐渐出现。

为什么 Streaming 这么重要?因为大模型生成速度有限,一段 500 字的内容可能需要 10-20 秒才能生成完毕。如果用普通方式调用,用户要盯着空白屏幕等 10-20 秒才能看到结果。这种体验在网页聊天中勉强能接受,但在实际业务系统中是不可忍受的。Streaming 让用户在 1-2 秒内就能看到第一个词,然后内容逐渐展开,体感速度快了很多倍。

从技术实现的角度,Streaming 使用的是 Server-Sent Events(SSE)协议。服务端不会一次性返回完整的 JSON 响应,而是持续发送一系列 data 事件,每个事件包含模型新生成的一小段文本。客户端监听这些事件,收到一个就追加到显示内容中,直到收到表示结束的特殊事件。

Streaming 的实现有几个注意点。

拼接逻辑。每个 Chunk 只包含一小段文本(通常一两个 Token),你需要把它们按顺序拼接成完整内容。注意处理好换行符、代码块标记等特殊情况,逐字追加时不要破坏格式。

结束判断。Streaming 的结束通常通过一个特殊事件来标记(比如 data: [DONE])。你必须正确处理这个事件,否则可能会无限等待或者提前结束。

错误处理。Streaming 过程中可能出错(网络中断、服务端异常),你需要能检测到这些错误并做相应处理。有些错误发生在中间,导致内容不完整,你需要能识别并重试。

Token 统计。Streaming 模式下,Token 统计通常在最后一个事件中返回。你不能在接收过程中统计 Token,而要等全部内容接收完毕后从最终事件中提取。

结构化输出兼容。如果你要求模型输出 JSON,Streaming 模式下你会在生成过程中收到不完整的 JSON 片段。在生成完毕之前,你不能对内容做 JSON 解析。需要等全部内容接收完毕后再做整体解析。

在实际应用中,几乎所有面向用户的场景都应该使用 Streaming。但后台批处理任务(比如批量生成报告、离线数据分析)可以不用 Streaming,直接用同步调用更简单。

四、Temperature 与 Max Tokens

这两个参数直接影响模型的输出行为,理解它们对于调优模型表现至关重要。

Temperature 控制模型输出的随机性。取值范围通常是 0 到 2,默认值一般是 1。Temperature 越低,输出越确定——每次调用相同的 Prompt,结果越一致。Temperature 越高,输出越随机——同样的 Prompt 可能产生差异很大的结果。

理解 Temperature 的一个好方法:把它想象成从一堆可能的下一个词中选择的方式。模型在生成每个词时,会计算所有候选词的概率。Temperature 低的时候,模型倾向于选择概率最高的词,所以输出更”安全”、更”标准”。Temperature 高的时候,模型更愿意选择概率较低的词,所以输出更”有创意”、更”多样”。

在实际工作中,Temperature 的选择取决于任务类型:

需要精确和一致的任务(数据提取、分类、格式化输出),Temperature 应该设低,0 到 0.3 之间。你希望模型按照规则来,不要自由发挥。

需要创意和多样性的任务(文案写作、头脑风暴、故事生成),Temperature 可以设高,0.7 到 1.2 之间。你希望模型给出不一样的回答。

Max Tokens 控制模型输出的最大长度。一个 Token 大约对应 0.75 个英文单词或 0.5 个中文字。如果你设置 Max Tokens 为 1000,模型最多输出约 500 个中文字。

设置 Max Tokens 有几个考虑:

设太低,模型可能说一半就停了。输出会被截断,finish_reason 会是 “length” 而不是 “stop”。这在需要完整输出的场景中是个问题——比如你要求模型输出一份结构化的行业分析报告,结果只输出了三分之二就被截断了。

设太高,不会影响输出质量(模型会在说完后自动停止),但会影响计费。有些服务商按 Max Tokens 而非实际输出长度来计算费用上限。

一个实用的策略是根据任务设定合理的上限。简单的分类任务设 100-200 就够了,短文本生成设 500-1000,长报告生成设 2000-4000。

五、Token 统计

Token 统计是成本控制和性能优化的基础。你需要知道每次调用消耗了多少 Token,才能计算成本、发现异常、优化使用。

Token 是大模型处理文本的基本单位。一段文本在送入模型之前会被”分词”——切成一个个 Token。英文中,一个常见单词通常是一个 Token;中文中,一个汉字通常是一到两个 Token。标点符号、数字、特殊字符也都算 Token。

每次 API 调用的响应中都会包含 Token 统计信息:

prompt_tokens 是输入部分消耗的 Token 数,包括 system 消息、user 消息以及历史对话。这个数字告诉你你的 Prompt 有多”重”。

completion_tokens 是输出部分消耗的 Token 数。这个数字告诉你模型生成了多少内容。

total_tokens 是两者之和,代表这次调用的总消耗。

Token 统计的工程价值体现在几个方面:

成本核算。知道了 Token 消耗,乘以模型的单价,就是这次调用的费用。比如 GPT-4o 的输入价格是每百万 Token 2.5 美元,输出价格是每百万 Token 10 美元。一次调用如果输入 1000 Token、输出 2000 Token,费用就是 1000/1000000 * 2.5 + 2000/1000000 * 10 = 0.0225 美元。

异常检测。如果某次调用的 Token 消耗突然暴增,可能是 Prompt 被注入了超长内容,或者模型进入了死循环反复输出。设定一个阈值,超过就告警。

性能分析。Token 数量直接影响响应时间。输入 Token 越多,模型处理越慢。如果发现某类请求特别慢,看看是不是 Prompt 太长了。

配额管理。大多数服务商都有速率限制——每分钟最多处理多少 Token。统计 Token 消耗可以帮助你预测会不会撞上限制。

在 LLM Client 中,你应该设计一个日志模块,记录每次调用的输入 Token 数、输出 Token 数、耗时、模型名称。这些数据积累下来就是你优化系统的基础。

六、Error Handling

调用大模型 API 不是每次都能成功的。网络会抖动,服务会宕机,参数会出错,余额会不足。如果你的代码不处理错误,一个 API 调用失败就能让整个应用崩溃。

常见的错误类型包括:

认证错误(401)。API Key 无效、过期或被撤销。这种错误不会自动恢复,需要人工检查 Key 配置。

权限错误(403)。你的账号没有权限使用请求的模型,或者你的账号因为违规被封禁。同样是需要人工处理的问题。

请求错误(400)。请求参数有问题——模型名称拼写错误、消息格式不对、参数值超出范围。检查并修改请求参数即可。

速率限制(429)。你在短时间内发了太多请求,超过了服务商的限制。这是最常见的临时性错误,通过等待和重试来解决。

服务端错误(5xx)。服务商那边出了问题——服务器过载、内部错误、网关超时。这也是临时性的,重试通常能解决。

超时。请求发出后,服务端在规定时间内没有返回结果。可能是网络问题,也可能是模型处理时间太长。

对于每种错误,处理策略不同:

认证和权限类错误,应该立即报告,不要重试。重试也不会成功,只会浪费时间。

速率限制错误,应该按照响应中的建议等待时间(通常在响应 Header 中)进行等待,然后重试。

服务端错误和超时,应该用指数退避(Exponential Backoff)策略重试——第一次等 1 秒重试,第二次等 2 秒,第三次等 4 秒,以此类推。

一个好的错误处理设计应该包含:错误分类(区分可重试和不可重试的错误)、错误日志(记录错误类型、错误消息、发生时间、请求参数)、降级策略(当主模型不可用时切换到备用模型)、用户友好的错误信息(不要把原始错误堆栈暴露给最终用户)。

七、Retry 机制

重试机制是处理临时性错误的标准手段。但重试不是无脑地再来一次,而是有策略、有边界、有记录的。

一个好的重试机制需要考虑以下几个维度:

重试条件。不是所有错误都值得重试。认证失败、参数错误、余额不足这些错误,重试一百次也不会成功。只有临时性错误(速率限制、服务端错误、网络超时)才值得重试。你的重试逻辑首先要区分这两类错误。

重试策略。最常用的策略是指数退避——每次重试的等待时间是前一次的两倍(或某个倍数),并加上一个随机抖动(Jitter)。为什么要加抖动?因为如果很多客户端同时遇到限流,它们会在相同的时刻重试,造成”惊群效应”,服务端再次被冲击。加上随机抖动后,各客户端的重试时间错开了。

重试次数上限。不能无限重试。一般设 3 到 5 次。如果重试了这么多次还是失败,就应该放弃并返回错误。无限重试不仅浪费资源,还可能导致请求积压。

重试超时总控。除了单次请求的超时,还要设置整个重试过程的总超时。比如单次超时 30 秒,最多重试 3 次,那总超时应该是 90 秒加上等待时间。超过总超时就放弃。

重试日志。每次重试都应该记录:第几次重试、等待了多长时间、上一次的错误是什么。这些日志在排查问题时非常有价值。

幂等性考虑。重试的前提是操作是幂等的——重复执行不会产生不同结果。对于简单的 Chat API 调用,这通常不是问题,因为调用不产生副作用。但如果你的调用链中包含”保存结果到数据库”之类的操作,就要确保重试时不会重复保存。

一个实用的重试实现通常是一个装饰器或中间件,包装在原始的 API 调用函数外面。业务代码只管调用,重试逻辑由装饰器自动处理。这样业务代码和重试逻辑解耦,各自维护起来都更清晰。

八、Timeout

Timeout(超时)设定是保护系统不被长时间阻塞的防线。

为什么需要超时?大模型 API 的响应时间不是固定的。一个简单的问答可能 1 秒就返回了,一个复杂的分析任务可能需要 30 秒甚至更久。如果模型服务出了问题,请求可能永远没有响应。如果没有超时设置,你的程序就会一直等下去,线程被占用、资源被浪费、用户被晾着。

超时应该设置在两个层面:

连接超时(Connect Timeout)。发起 HTTP 请求时建立连接的最大等待时间。如果服务端无法连接(比如网络不通、DNS 解析失败),超过这个时间就放弃。一般设 5 到 10 秒。大模型服务的连接通常很快,如果 10 秒还连不上,大概率是网络出了问题。

读取超时(Read Timeout)。连接建立后,等待服务端返回数据的最大时间。对于大模型 API,这个时间需要设得比较长,因为模型生成确实需要时间。普通调用设 30 到 60 秒,长文本生成设 60 到 120 秒。Streaming 模式下的读取超时有所不同——因为服务端会持续发送数据,所以超时应该是”两次数据之间的最大间隔时间”,而不是”总等待时间”。

超时值的设定需要在”等得起”和”等不起”之间找平衡。设太短,正常的慢请求也会被中断;设太长,异常请求会长时间占用资源。一个合理的做法是根据历史数据来设定——统计过去一段时间内 95% 的请求在多少秒内完成,把这个值作为超时参考。

超时发生后的处理也要考虑。超时不等于失败——请求可能已经到达服务端并被处理了,只是响应还没来得及返回。如果是非 Streaming 调用,超时后重试是安全的(因为不会产生副作用)。如果是 Streaming 调用且已经接收了部分数据,重试可能会导致重复生成。

九、Rate Limit

Rate Limit(速率限制)是模型服务商保护自身资源的一种手段。服务商不可能让你无限制地调用,所以会对每个账号设定调用频率上限。

速率限制通常有以下几个维度:

每分钟请求数(RPM,Requests Per Minute)。你的账号每分钟最多发送多少个请求。不同模型、不同套餐的 RPM 限制不同。

每分钟 Token 数(TPM,Tokens Per Minute)。你的账号每分钟最多处理多少 Token。这个限制和 RPM 是独立的——即使你每分钟只发一个请求,如果每个请求都很大,也可能触发 TPM 限制。

并发请求数。同时进行的请求数量上限。如果你的应用需要处理大量并发请求,这个限制尤其需要注意。

每日/每月用量上限。某些服务商还会设定每日或每月的调用次数或金额上限。

当你的请求超过限制时,服务端会返回 429 状态码,并在响应中告诉你当前的限制情况和建议等待时间。

应对速率限制的策略:

请求队列。把需要发送的请求放入队列,按顺序发送,控制发送速率不超过限制。这比直接发送更可控,但会增加延迟。

令牌桶算法。维护一个”令牌桶”,以固定速率往桶里放令牌,每次发送请求消耗一个令牌。桶满了令牌就不再增加。这样既能保证平均速率不超过限制,又允许短时间内的突发请求。

多 Key 轮询。配置多个 API Key,当一个 Key 触发限制时切换到另一个。这个方法成本更高(多个 Key 意味着多份费用),但在高并发场景下是有效的。

分级处理。把请求分成不同优先级——用户实时请求优先处理,后台批量任务延后处理。当接近限制时,只处理高优先级请求。

十、日志记录

日志记录是 AI 应用开发中容易被忽视但极其重要的环节。

为什么日志重要?因为大模型的行为有不确定性。同样的输入可能产生不同的输出,同一个错误可能是多种原因导致的。没有日志,你只能凭记忆和猜测来排查问题。有了日志,你可以回溯每一次调用,看到底发了什么、返回了什么、花了多少钱、耗时多少。

一个完善的调用日志应该记录以下信息:

请求信息。时间戳、请求 ID、模型名称、消息内容(或消息摘要,避免日志过大)、参数设置(Temperature、Max Tokens 等)。

响应信息。模型输出的内容(或内容摘要)、finish_reason、Token 统计(prompt_tokens、completion_tokens、total_tokens)。

性能信息。请求耗时(从发出到收到完整响应的时间)、如果是 Streaming 还要记录首 Token 时间(从发出到收到第一个数据块的时间)。

错误信息。如果调用失败,记录错误类型、错误消息、HTTP 状态码、重试次数、最终结果。

上下文信息。调用来源(哪个接口、哪个功能模块触发的这次调用)、用户信息(哪个用户的请求)。

日志的设计有几个实践要点:

结构化格式。用 JSON 格式记录日志,而不是自由文本。这样后续可以用程序分析日志——统计平均耗时、计算每日成本、查找错误模式。

分级记录。不是所有日志都要详细记录。正常的成功调用可以只记摘要(模型名、Token 数、耗时),错误调用需要记录完整信息(包括完整的请求和响应内容)。

敏感信息脱敏。如果消息中包含用户的敏感信息(手机号、身份证号等),日志中应该脱敏处理。

日志轮转。日志文件会越来越大,需要设定轮转策略(比如按日期分割、超过一定大小分割、保留最近 N 天的日志)。

在实际项目中,你可以设计一个统一的日志中间件,所有 API 调用都经过它。它负责记录请求、拦截响应、计算耗时、写入日志。业务代码不需要关心日志逻辑。

概念关系图

                          +------------------+
                          |   你的应用代码    |
                          +--------+---------+
                                   |
                          +--------v---------+
                          |    LLM Client    |
                          |  (统一封装层)     |
                          +--------+---------+
                                   |
              +--------------------+--------------------+
              |                    |                    |
     +--------v--------+  +-------v--------+  +-------v--------+
     |   API Key 管理   |  |  请求构造与发送 |  |  日志与统计    |
     | - 环境变量读取    |  | - Chat API     |  | - Token 统计   |
     | - 多 Key 轮询    |  | - Streaming    |  | - 耗时记录     |
     | - Key 轮换       |  | - 参数设置      |  | - 错误日志     |
     +-----------------+  +-------+--------+  +----------------+
                                   |
                    +--------------+--------------+
                    |                             |
           +--------v--------+           +-------v--------+
           |  错误处理与重试   |           |  速率限制应对   |
           | - 错误分类       |           | - 请求队列     |
           | - 指数退避       |           | - 令牌桶       |
           | - 超时控制       |           | - 多 Key 轮询  |
           +-----------------+           +----------------+

实战分析

任务:设计一个基础 LLMClient 封装

这个实战任务的核心是把你今天学的所有知识点整合到一个客户端类里。这个类是你后续所有 AI 应用的基础依赖。

设计思路

LLMClient 的定位是一个通用的模型调用封装层。上层业务代码不需要知道底层用的是哪个模型服务商、怎么处理错误、怎么记录日志,只需要调用一个方法、传入消息、拿到结果。

它应该提供两类核心方法:chat 方法用于同步调用,stream 方法用于流式调用。

chat 方法的设计要点

输入参数:消息列表(包含 system 消息和 user 消息)、可选的参数覆盖(Temperature、Max Tokens 等)。方法内部组装成完整的 API 请求,发送请求,处理响应,记录日志,返回结果。

内部流程:从配置中读取 API Key 和默认参数 组装请求体 发送 HTTP 请求(带超时) 收到响应 检查是否成功 如果失败,判断是否可重试 如果可重试,按指数退避策略重试 记录日志(包含 Token 统计和耗时) 返回结果。

stream 方法的设计要点

与 chat 方法类似的输入参数,但返回的不是完整结果,而是一个迭代器或回调。上层代码每收到一个数据块就处理一个。

内部流程与 chat 方法类似,但有几个区别:HTTP 请求使用 Streaming 模式;不等待完整响应,而是持续接收数据块;Token 统计在最后一个数据块中获取;超时设置要调整为”两次数据块之间的间隔”而不是总等待时间。

错误处理的设计要点

把错误分成两类:不可重试的错误(认证失败、参数错误)直接抛出异常;可重试的错误(速率限制、服务端错误、超时)按照指数退避策略重试,最多重试 N 次。

每次重试都要记录日志。最终失败时,把所有重试的历史信息一起打包到异常中,方便排查。

日志记录的设计要点

每次调用(包括重试)都记录一条日志。日志包含时间戳、请求 ID、模型名称、Token 统计、耗时、是否成功、错误信息(如果失败)。

成功调用的日志级别设为 INFO,失败调用的日志级别设为 WARNING,重试达到上限仍然失败的设为 ERROR。

当日产物说明

llm_client.py

这是今天最重要的产物。它是一个 Python 模块,包含 LLMClient 类。

这个类应该包含以下能力:初始化时读取配置(API Key、默认模型、默认参数);chat 方法封装同步调用;stream 方法封装流式调用;内置错误处理和重试逻辑;内置日志记录功能;内置 Token 统计功能。

设计时要考虑扩展性——未来可能需要支持多个模型服务商,所以 API 调用的具体实现应该通过适配器模式来隔离,LLMClient 本身不直接依赖某个服务商的 SDK。

模型 API 调用说明

一份文档,说明你的 LLMClient 怎么使用。包含:初始化方式(怎么传入配置)、chat 方法的参数和返回值、stream 方法的参数和返回值、支持的模型列表、参数说明、使用示例。

这份文档的读者是未来的你自己或者使用这个 Client 的同事。写得清楚比写得漂亮重要。

错误处理与重试机制说明

一份专门说明错误处理策略的文档。包含:支持的错误类型列表、每种错误的处理方式、重试策略的详细参数(最大重试次数、退避基数、最大等待时间)、超时设置(连接超时、读取超时)、降级策略(主模型不可用时的备选方案)。

这份文档应该能让别人理解你的错误处理逻辑,而不是让他们去看代码。

常见误区与避坑

误区一:觉得 API 调用就是发个 HTTP 请求

很多人以为调用大模型 API 和调用普通 REST API 没什么区别。区别大了。大模型 API 的响应时间不稳定(从 1 秒到 60 秒都有可能),有速率限制,有 Token 计费,有内容安全过滤,有 Streaming 模式。如果你用对待普通 API 的方式来对待大模型 API,你的应用很快就会出问题——要么被限流打崩,要么因为超时导致用户体验极差,要么因为没统计 Token 而账单爆炸。

误区二:不做错误重试

第一次调用失败了就放弃,直接给用户报错。这在演示阶段没问题,但在生产环境不可接受。大模型 API 的临时性错误(速率限制、服务端抖动)很常见,重试能解决大部分问题。一个没有重试机制的 LLM Client,在生产环境中的可用性可能只有 95%,加上重试可以提升到 99% 以上。

误区三:日志只记错误

只记录失败的调用,成功的调用什么都不记。这种做法让你失去了优化系统的数据基础。你不知道每天消耗了多少 Token、平均耗时多少、哪些 Prompt 特别浪费 Token。好的日志策略应该是所有调用都记录,成功和失败用不同的级别区分。

误区四:API Key 管理不重视

学习阶段随便写写就算了,但这个习惯一定要从一开始就养成。把 Key 硬编码在代码中,一旦提交到公开仓库,几分钟内就会被扫描到并滥用。我在现实中见过太多这样的案例了——某天突然收到一大笔账单,才发现 Key 泄露了。

误区五:不理解 Token 计费模型

以为”输出 500 字就是 500 Token”。中文的 Token 计算和英文不同,一个汉字通常是 1-2 个 Token。而且计费不只看输出,输入也要算。如果你的 System Prompt 有 2000 字(约 3000 Token),每次调用光输入就要花 3000 Token 的费用。100 次调用就是 30 万 Token。算清楚账,才知道你的应用能不能赚钱。

延伸思考

今天的 LLM Client 封装看起来是一个基础组件,但它的影响会贯穿整个学习计划。

在后续的 Week 2 内容中,你会基于这个 Client 做结构化输出(Day 9)、Function Calling(Day 10)、Prompt 模板调用(Day 11)。如果 Client 设计得好,这些功能都是自然扩展;如果设计得不好,每次加新功能都要大改。

到了 Week 3 的 RAG 系统,你会在检索到文档后调用 Client 来生成回答。到了 Week 4 的 Agent 系统,Agent 的每一步决策都需要通过 Client 调用模型。到了 Week 5 的工程化阶段,你需要给 Client 加上缓存、监控、链路追踪。

所以今天的设计要为未来留好接口。几个关键的设计决策:是否支持多模型切换?是否支持自定义的请求拦截器(比如在发送前自动加上安全检查)?是否支持调用链追踪(记录一次业务流程中所有模型调用的关联关系)?

从更宏观的角度看,LLM Client 就是你和 AI 大模型之间的”外交官”。它负责翻译你的意图、传达给模型、处理各种意外情况、把结果带回来。一个好的外交官能让双方的合作顺畅高效,一个差的外交官会让双方频繁产生摩擦。

自测问题

  1. API Key 应该存储在什么位置?为什么不能硬编码在代码中?
  2. Chat API 的请求中,system 消息和 user 消息的作用分别是什么?
  3. Streaming 输出和同步输出在用户体验上有什么区别?在技术实现上有什么区别?
  4. Temperature 参数设为 0 和设为 1 时,模型输出行为有什么不同?什么场景适合用低 Temperature?什么场景适合用高 Temperature?
  5. 什么类型的 API 错误可以重试?什么类型的不应该重试?
  6. 指数退避重试策略的原理是什么?为什么要加入随机抖动?
  7. 速率限制(Rate Limit)通常从哪几个维度限制?至少说出两种应对策略。
  8. 一次完整的 API 调用日志应该记录哪些信息?
  9. 如果 Max Tokens 设置太低,模型的输出会怎样?finish_reason 会是什么值?
  10. 设计 LLM Client 时,为什么要考虑多模型支持?这对后续开发有什么好处?

关键词

  • API Key:调用模型服务的身份凭证,需通过环境变量管理,不可硬编码
  • Chat API:大模型的核心调用接口,通过消息列表进行交互
  • System Message:定义模型行为规则的消息角色,设定人设和输出格式
  • User Message:用户的具体请求内容
  • Streaming:流式输出模式,逐 Token 发送结果,提升用户体感速度
  • Temperature:控制输出随机性的参数,值越低越确定,值越高越随机
  • Max Tokens:限制模型最大输出长度的参数
  • Token:大模型处理文本的基本单位,约等于 0.75 个英文单词或 0.5 个中文字
  • Error Handling:对 API 调用错误的分类处理,区分可重试和不可重试错误
  • Retry:对临时性错误的自动重试机制,通常采用指数退避策略
  • Timeout:防止请求长时间阻塞的超时设置,包括连接超时和读取超时
  • Rate Limit:服务商对调用频率的限制,通过请求队列或令牌桶算法应对
  • SSE:Server-Sent Events,Streaming 输出使用的协议
  • Exponential Backoff:指数退避,每次重试等待时间翻倍的策略
  • LLM Client:封装模型调用逻辑的客户端类,是 AI 应用的基础设施组件