目录
1.为什么我们需要API
在中大型开发项目实践中,我们往往需要构建和串联多个不同的功能模块,但这些功能模块可能会使用不同的语言来进行实现,或是虽然使用了相同的语言来实现,但不归属于同一个环境,导致无法直接进行数据交换、功能调用。
API(应用程序编程接口,Application Programming Interface)是不同软件组件之间进行交互和通信的接口,它允许不同的应用程序或服务相互调用和交换数据。可以把API看作是软件之间的桥梁,让它们能够“谈话”并共享功能。
API是复杂开发项目中进行模块串联的关键构件,它能够让我们无视掉不同功能模块的具体实现,着眼于基于项目需要的架构规划、模块调用关系、数据流动进行设计。
常见场景:
- 前后端通讯:使用JS的前端脚本和使用JAVA的业务后端服务通讯
- 服务间通讯:使用Python的算法服务和使用Golang的业务后端服务通讯
- 跨服务商通讯:业务后端服务通过网络直接请求服务商提供的API,完成如大模型请求、订单查询、天气查询等操作
2.API的关键组成
以OpenAI的对话补全接口为例,接口说明文档地址:https://platform.openai.com/docs/api-reference/chat/create
请求部分
端点/路径(Endpoints/Path)
端点是API的“入口”,它是你与API交互的具体路径,OpenAI的对话补全接口端点是:/v1/chat/completions
端点的作用:
- 帮助API服务做版本管理
- 标识功能或资源,直观展现层级关系举例:
- `https://examples.com/v1/users/info/{user_id}`
请求方法(Methods)
常见的API大多基于HTTP协议,通过HTTP协议的标准请求方法来区分API的操作类型
- PUT:更新数据(修改操作)Create
- GET:请求数据(读取操作)Read
- POST:提交数据(创建操作)Update
- DELETE:删除数据(移除操作)Delete> 注:还有一个不太常见的方法:PATCH - 部分修改,和PUT - 全量修改对应,但是实践中PUT一般就等于修改,不区分部分还是全量了
不同请求方法携带数据的方式也有区别,这一点在下面的《请求参数》部分再加以介绍
举例:
- `PUT https://examples.com/v1/users/info/{user_id}`
- `GET https://examples.com/v1/users/info/{user_id}`
请求参数(Parameters)
通常我们会把和API对应功能相关的数据放在请求中进行传递,以实现在对同一个API接口的请求时,能够操作不同的资源对象,或是对操作的细节进行控制。
常见的请求参数类型有:
- 路径参数(Path Parameters):直接嵌入在请求端点路径里的参数,比如上面的
{user_id}
- 查询参数(Query Parameters):查询参数通常会被放在URL的?之后,比如Serper提供的搜索API就支持使用这样的参数
https://google.serper.dev/search?q=zhihu&apiKey=xxxxxxxxxxxxxx
- 请求体(Request Body):请求体通常会被放在请求内部,在URL上不直接可见
不同参数的区别:
- 路径参数(Path Parameters)和查询参数(Query Parameters)会直接体现在URL上,数据传递长度受到URL长度的限制,比如Apache和Nginx的默认限制URL长度为8192字符,现代浏览器支持的URL长度限制在2000到8000字符之间,对于大数据量传递或是因为安全考虑需要对数据进行加密而造成的字符长度增加都不合适
- 通常而言请求体(Request Body)默认支持的数据长度更长,比如Apache默认就不对RequestBody长度进行限制,Nginx默认为1M,Express.js默认为100K,现代浏览器通常支持大约4GB的数据请求长度,对于更大体量的数据传递,我们通常就要考虑使用流式数据传输的方式将数据切成更小的数据块(chunk),而不是一次性传递了,这在今天的课程中就不展开讨论了。
不同请求方法使用的请求参数
- GET、DELETE方法:通常只能使用路径参数(Path Parameters)和查询参数(Query Parameters)
- PUT、POST方法:通常能使用上述全部三种参数,并且通常都会将业务数据结构化,放入请求体(Request Body)进行传递,部分敏感数据还要使用双向加密技术进行加密后传递
请求头(Headers)
如果说请求参数主要负责传递和业务操作相关的信息,那么请求头就更多负责传递与请求本身相关的元信息,例如通用请求头就有以下常见作用:
类型说明:
Content-Type:说明本次请求传递的请求数据体类型,常见值如:
text/plain
:纯文本型数据application/x-www-form-urlencoded
:通过页面表单(HTML中的<form>
标签)传递的数据application/json
:JSON格式的数据application/xml
:XML格式的数据Accept:说明本次请求接收的响应体格式,帮助服务端根据此内容决定返回值的数据格式,常见值如:
*/*
:说明可以接收任何格式的返回结果text/html
:说明期望接收的返回结果是一个HTML格式的网页内容text/plian
:说明期望接收的返回结果是纯文本内容application/json
:说明期望接收的返回结果是JSON格式的数据application/xml
:说明期望接收的返回结果是XML格式的数据image/jpeg
、image/png
、image/gif
:说明期望接收的返回结果是指定类型的图片注:类型说明请求头对于不同语言的请求包也有很大作用,大部分请求包都为常见的请求和接收格式做了格式校验和转义的便捷功能
身份标记及认证:
- User-Agent:说明本次请求的客户端软件标识,通常是浏览器或者应用程序名称(服务端模拟浏览器爬取网页信息时也会使用),例如:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
- Authorization:请求中携带的认证信息,例如目前各大模型接口,尤其是OpenAI-Like格式模型接口的认证方式,就是使用Bearer Token的方式,将API-Key放在该请求头中,例如:
Authorization: Bearer sk-xxxxxxxx
常见身份认证方式相关扩展阅读
- API-Key:直接明文放入请求头中,如果使用请求抓包工具,容易被抓获,适合跨服务商的服务端-服务端请求,不适合客户端直接请求(浏览器可以直接看到明文API-Key造成信息泄露)
- OAuth 2.0:官方说明文档:OAuth 2.0 — OAuth ,核心思想如下图,即客户端通过原始授权信息(如用户名、密码)询问资源所有者获取资源访问许可(通常是一个有效时长很短的许可),使用该需求向授权服务器获取授权令牌(Access Token),后续通过授权令牌访问资源。这么设计的好处是在资源访问时(通常都是大量的网络请求)不会暴露用户名、密码,只会暴露令牌,令牌通过其他校验机制(比如过期校验、客户端ID哈希校验等)来确保可信。
+--------+ +---------------+ | |--(A)- Authorization Request ->| Resource | | | | Owner | | |<-(B)-- Authorization Grant ---| | | | +---------------+ | | | | +---------------+ | |--(C)-- Authorization Grant -->| Authorization | | Client | | Server | | |<-(D)----- Access Token -------| | | | +---------------+ | | | | +---------------+ | |--(E)----- Access Token ------>| Resource | | | | Server | | |<-(F)--- Protected Resource ---| | +--------+ +---------------+
- JWT:官方说明文档:JSON Web Tokens - jwt.io ,核心思路是将加密算法名、Token类型放入头部(Header)密文,加密数据本体放入负载(Payload)密文,验证签名放入签名(Signature)密文,将三段密文整合成一个密文体,与双向加密的公私钥配合,实现轻量级自包含(即所有与加解密相关的信息都在密文中)的验证方式。Python中提供了PyJWT库来验
响应部分
状态码(Status Codes)
API请求的响应会包含一个HTTP状态码,状态码能够帮助我们最快理解本次请求的处理结果状态,常见的状态码说明:
- 200 OK:请求成功,服务器返回请求的数据。
- 201 Created:请求成功,并且已创建资源(常见于POST请求)。
- 400 Bad Request:请求格式错误,服务器无法理解。
- 401 Unauthorized:请求未授权,可能缺少有效的认证信息。
- 404 Not Found:请求的资源不存在。
- 429 Too Many Requests:请求超出速率限制。
- 500 Internal Server Error:服务器出错,无法处理请求。
注:响应状态码和API接口请求返回的数据体中可能包含的
status
字段不一样,响应状态码通常由提供API的服务框架决定,遵从HTTP规范,每一个状态码值都有对应的共识的含义,正常情况下不应更改,如我们不会使用200表示请求失败,也不会用404表示格式错误
响应体(Response Body)
与请求体(Request Body)对应,API请求的主要业务数据返回结果都会被放在响应体(Response Body)中,响应体的内容格式和通常的请求头
Accept
要求的格式一致,也通常会在API说明文档中明确。例如,OpenAI的对话补全接口给出的响应体结构如下:
{ "id": "chatcmpl-123", "object": "chat.completion", "created": 1677652288, "model": "gpt-4o-mini", "system_fingerprint": "fp_44709d6fcb", "choices": [{ "index": 0, "message": { "role": "assistant", "content": "\n\nHello there, how may I assist you today?", }, "logprobs": null, "finish_reason": "stop" }], "usage": { "prompt_tokens": 9, "completion_tokens": 12, "total_tokens": 21, "completion_tokens_details": { "reasoning_tokens": 0, "accepted_prediction_tokens": 0, "rejected_prediction_tokens": 0 } } }
显然,这是一个JSON格式的响应体返回结果,也是目前最常见的响应体返回格式。
3.API的调用实践
终端指令
curl -x http://127.0.0.1:7890 \
https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [
{
"role": "developer",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Hello!"
}
]
}'
Python请求
!pip install httpx
Requirement already satisfied: httpx in /opt/conda/lib/python3.11/site-packages (0.26.0)
Requirement already satisfied: anyio in /opt/conda/lib/python3.11/site-packages (from httpx) (3.7.1)
Requirement already satisfied: certifi in /opt/conda/lib/python3.11/site-packages (from httpx) (2023.11.17)
Requirement already satisfied: httpcore==1.* in /opt/conda/lib/python3.11/site-packages (from httpx) (1.0.2)
Requirement already satisfied: idna in /opt/conda/lib/python3.11/site-packages (from httpx) (3.4)
Requirement already satisfied: sniffio in /opt/conda/lib/python3.11/site-packages (from httpx) (1.3.0)
Requirement already satisfied: h11<0.15,>=0.13 in /opt/conda/lib/python3.11/site-packages (from httpcore==1.*->httpx) (0.14.0)
为什么选择httpx?
httpx是目前最常用的大模型请求客户端openai客户端的网络请求核心依赖包 熟悉httpx的使用,对于未来自定义openai客户端的网络请求部分有很大帮助
httpx官方网站:HTTPX
!pip install requests
Requirement already satisfied: requests in /opt/conda/lib/python3.11/site-packages (2.31.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /opt/conda/lib/python3.11/site-packages (from requests) (3.2.0)
Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.11/site-packages (from requests) (3.4)
Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.11/site-packages (from requests) (1.26.18)
Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.11/site-packages (from requests) (2023.11.17)
python-requests 是一个非常流行的 Python HTTP 库 用于发送各种类型的 HTTP 请求。 它提供了一个简单而优雅的 API,用于访问 HTTP 服务。 requests 库封装了 urllib,提供了更高级的功能,例如自动处理会话、持久连接、线程安全、HTTP 内容解码、以及客户端 SSL/TLS 验证等
import os
import requests
import json
response = requests.post(
f"{os.environ['OPENAI_BASE_URL']}/chat/completions",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer { os.environ['OPENAI_API_KEY'] }",
},
json={
"model": "gpt-4o-mini",
"messages": [
{
"role": "developer",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Hello!"
}
],
},
#proxy="http://127.0.0.1:7890",
)
print(response.status_code)
print(response.content)
response_data = json.loads(response.content.decode())
print(response_data)
print(response_data["choices"][0]["message"])