HTTP 与 API 基础
一个程序向另一个程序查询订单
Section titled “一个程序向另一个程序查询订单”运行第 42 章实践前,请先完成本 Primer。读懂例子不需要账号或编程经验;运行程序需要 Python Primer、Python 3.12,以及 code/part-vii/README.md 规定的依赖环境。第 43、54 章还会复用这些基础。
语言模型说出订单号,并不会自动打开订单数据库。必须有一个程序发出请求,另一个有权读取记录的程序作出回应。发请求的一方叫客户端(client),回应的一方叫服务器(server)。这是一次通信中的角色,不一定对应两台计算机。本例两端都在本机运行,只读取虚构记录。
API(application programming interface,应用程序编程接口)约定双方怎样交流,包括允许请求什么、结果表示什么。HTTP提供通用的请求与响应规则,JSON 则可以承载消息中的数据。这些都不依赖语言模型。
沿着一组请求和响应往下读
Section titled “沿着一组请求和响应往下读”例 HTTP.1:读取虚构订单 A-104。 本地脚手架绑定到 127.0.0.1,这是指向本机的回环地址,再由操作系统分配空闲端口。仅为说明地址结构,假设分到的是 8765,那么 URL 就是 http://127.0.0.1:8765/orders/A-104。http 是协议方案,地址和端口标识接收服务,/orders/A-104 是该服务中的路径。远程服务可能改用 HTTPS 主机名;HTTPS 通过 TLS 保护传输,却不能保证业务答案正确。
GET /orders/A-104 HTTP/1.1Host: 127.0.0.1:8765Authorization: Bearer demo-placeholder-keyGET 请求读取一份表示。首行还写明路径和 HTTP 版本。随后是请求头(headers),例如接收请求的主机和客户端提供的凭证。一个空行把头部与可能存在的消息体(body)分开,本请求没有消息体。这里展示的密钥是特意公开的占位值,只用于本地样例。
HTTP/1.0 200 OKContent-Type: application/json
{"order_id":"A-104","status":"processing","as_of":"2026-09-14T00:00:00Z","source_version":"ka3-orders-v1"}脚手架使用的标准库处理器以 HTTP/1.0 响应;收到 HTTP/1.1 请求,不等于必须原样返回版本号。为便于阅读,这里省略自动生成的 Date、Server 和 Content-Length 头,消息体完整保留。200 是状态码,Content-Type 说明怎样解释消息体。这里的消息体是 JSON:花括号中放带引号的字段名和字符串值,用逗号分隔。status 表示订单状态,200 表示这次 HTTP 请求的结果,它们回答的是不同问题。
as_of 是快照时间,末尾的 Z 表示 UTC。响应成功,只能证明这份虚构快照怎么记载,并不是刚刚观测了真实订单。在真实服务中,重复读取可能得到更新后的状态,仍然符合“读取”的含义。
- 模型之外客户端:URL + 凭证
- 文本片段GET /orders/A-104
- 处理步骤服务器核对订单归属
实际本地端口由操作系统分配。
- 文本片段HTTP 200:请求成功
- 模型之外消息体:processing + as_of
- 处理步骤使用数据前先校验
超时是客户端观察,不是订单状态。
图 HTTP.1 标出了 URL、凭证、请求结果和业务数据分别处于哪里。客户端先检查响应,再使用订单状态。若应用随后请求模型服务,它就在第二次通信中充当客户端;订单服务器与模型服务器仍然是不同角色。
收到响应,不等于业务成功
Section titled “收到响应,不等于业务成功”表 HTTP.1:实际本地接口及其含义。
| 请求条件 | 结果 | 客户端如何处理 |
|---|---|---|
| 带占位凭证 GET 自己的 A-104 | 200,处理中快照 | 校验消息体,保留时间 |
| 凭证缺失或错误 | 401,unauthenticated | 修正身份认证,不盲目重试 |
| GET 另一位虚构用户的 B-205 | 403,forbidden | 停止;身份有效不代表有权限 |
| GET 不存在的 A-999 | 404,not_found | 说明没有可用记录 |
| 用 POST 尝试未提供的写操作 | 405,method_not_allowed | 脚手架只允许 GET |
GET /rate-limit | 429,rate_limited;Retry-After: 1 | 若重试预算允许,至少等待指定的 1 秒 |
客户端等 /slow 0.02 秒,服务器延迟 0.15 秒 | 客户端超时 | 仅凭这一观察,不知道服务器是否完成 |
HTTP 状态码首位表示大类:2xx 通常表示成功,4xx 表示请求不能按原样完成,5xx 表示服务器一侧失败。还需要看具体代码。超时(timeout)是客户端给等待设置的上限,不是另一种 HTTP 状态码。客户端停止等待之后,服务器仍可能完成工作。
重试(retry)会再发一次请求。本书只读路径最多尝试两次,每次超时上限 0.2 秒,总期限 1 秒,退避等待从 0.05 秒开始。若 429 要求 Retry-After 1,剩余预算已经不够,就停止而不是提前重试。真实服务可能需要不同设置;这些是明确给定的教学选择,不是性能建议。限流(rate limit)是服务规则,API Key 不能绕过它。
读取通常可以重试,因为它没有请求改变业务状态。HTTP 把这种方法称为安全方法(safe method)。幂等(idempotent)指重复同一操作的预期效果与只做一次相同,不要求响应文字完全一致,也不表示服务器不记录日志。取消订单超时则不同:如果服务没有持久的幂等约定,重发可能重复产生副作用。第 42 章会用静态例子解释写操作。
凭证不要进入 Notebook 输出和记录
Section titled “凭证不要进入 Notebook 输出和记录”API Key 是服务器核验的凭证,不是模型知识。本地客户端选做练习通过环境变量把值交给进程:
export BOOK_DEMO_KEY=demo-placeholder-keyimport oskey = os.environ["BOOK_DEMO_KEY"]第一行给从当前 shell 启动的进程提供一个命名值,Python 表达式负责读取。变量不存在时会抛出 KeyError,应报告配置错误,而不是带空凭证发请求。不要打印变量、把真实密钥放进 URL,或存进 Notebook 输出。环境变量能避免把值写死在代码中,但本身不是密钥保险箱。保存的 trace 用 redacted 替代凭证;自动演示只使用固定的公开占位值。
本地与远程 API 共享请求—响应结构,但信任关系、延迟、身份认证和传输保护不同。本例绑定回环地址,避免作为远程服务暴露。它不是生产用身份认证实现,不能只改一个监听地址就拿去对外提供服务。
流式传输改变的是片段到达的时机
Section titled “流式传输改变的是片段到达的时机”服务器不必等整个消息体准备好才发送,也可以边处理边送出片段,这叫流式传输(streaming)。脚手架的 /stream 使用 text/event-stream,发送以下三个事件:
data: {"order_id": "A-104"}
data: {"status": "processing"}
data: {"complete": true}空行结束一个事件。一次网络读取可能停在 processing 中间,也可能同时读到几个事件;网络数据块既不等于事件,也不等于模型 token。接收方需要缓存字节、正确解码 UTF-8,并收集完整事件。本样例的应用规则规定,只有收到 complete: true 才形成可用结果。如果第一个事件之后连接就断了,即使 HTTP 状态为 200,响应也未完成。流式 JSON 答案同样可能需要完整拼接和校验后才能使用。
运行本地路径,查看成功与失败
Section titled “运行本地路径,查看成功与失败”按要求创建环境后,在书籍根目录运行 python code/knowledge-assistant/http_scaffold.py。程序会自行启动回环服务器、取得空闲端口、执行表中的读取及流式通信、演示超时,最后关闭服务。输出不含真实凭证。data/part-vii/http-run.json 保存实际运行记录,明确标注为本地 HTTP,而不是模型生成。
Notebook 展示 get(base, "/orders/A-104"):base 是 server() 给出的实际地址,不是示意用的 8765。返回值依次为 HTTP 状态、解析后的消息体和 Retry-After。流式消息体保留为文本,供检查事件结构。程序与 Tool、MCP 适配器共用 lookup 函数,不是三套独立订单数据库。
客户端通过约定的 API 向服务器发请求。HTTP 规定消息含义,JSON 表示数据。凭证、状态码、超时和流式完成标记都要分别解释。一次成功读取报告的是来源中的记录;写操作还需要另一份授权与效果契约。
- 响应同时出现 HTTP 200 和 JSON
"status":"processing",哪一个告诉你订单是否发货? - 读取请求等了 0.02 秒后超时,能否认定服务器从未查过订单?Trace 应怎样记录?
- 流式响应只收到订单 ID,客户端能否显示“处理中”?还缺少什么?
- 为什么 401 与暂时性 503 的重试处理不同?在总预算 1 秒的规则下,Retry-After 1 是否允许立即重试?
- 消息体字段描述订单,200 描述请求。本快照说的是处理中,不是已发货。把它当作当前状态前,还要检查时间。
- 不能。超时只证明客户端没有在时限内收到可用结果。应记录尝试以及完成状态未知,不能标记成“确定未执行”。
- 不能。还缺状态事件和完成标记。订单号本身不能证明状态,即使前一个例子用了同一个号。
- 401 需要有效身份认证,原样重复错误凭证不会修好它。暂时性 503 可能允许有限重试。Retry-After 1 要求等待;剩余时间不足 1 秒时,应停止,不能忽略要求提前重发。
- RFC 9110,§§3—6、9.2、10.2.3、11、15。定义 HTTP 角色、消息、安全与幂等语义、身份认证及状态码。
- RFC 8259,§§2—8。规定 JSON 消息体和 UTF-8 数据交换。
- RFC 6585,§4。定义 429 和可选的 Retry-After,不规定统一限流策略。
- WHATWG Server-sent events,§§9.2.4—9.2.6,核验于 2026-09-14。解释本地流式样例采用的事件边界。