LLM成长笔记(三):API 开发基础

API 开发基础学习博客(通俗原理 + 详细注释 · AI应用强化版)

API 是连接前端与后端、服务与服务之间的桥梁。这篇博客从实际问题出发 ,用生活化类比 帮你建立直觉,在遇到新术语时先做一个简单的"术语详解",再深入浅出讲解核心原理 ,最后通过带详尽注释的代码 和输出结果带你动手实践。每个知识点末尾都增加了 AI 应用场景提示,让你看清这些技术在真实开发中的位置。


一、初级篇

1. FastAPI 快速构建 RESTful API

问题

想快速搭建一个提供 JSON 数据的 Web 接口,同时希望自动生成交互式文档、验证请求数据,该如何入手?

生活化类比
FastAPI 就像智能餐厅点餐系统:你定义好菜单和食材要求(Pydantic 模型),顾客点单时系统会自动检查订单是否合规,不合规立即拒绝;后台自动生成漂亮的菜单(Swagger 文档),顾客、服务员都能一眼看懂。

术语详解:Swagger 和 JSON Schema

  • Swagger(现在叫 OpenAPI) 是一套描述 RESTful API 的规范,可以理解为"接口说明书"的标准写法。你可以在 http://服务器地址/docs 看到一个像网页版的"接口说明书",里面有每个接口的地址、参数、返回格式,甚至可以当场测试,这就是 Swagger UI
  • JSON Schema 是 JSON 数据的"形状描述",比如规定一个 JSON 对象里必须有 name 字段且为字符串,price 字段必须是数字。FastAPI 用 Pydantic 模型自动生成 JSON Schema,用它来校验数据,也用它来生成文档。

原理

FastAPI 基于 Starlette 异步框架构建,利用 Python 类型注解和 Pydantic 模型在运行时自动完成数据校验、序列化/反序列化。它还根据这些注解自动生成 OpenAPI 规范(即 JSON Schema 格式的接口说明),并挂载 Swagger UI 和 ReDoc 交互式文档。路由通过装饰器注册,支持依赖注入。
async def 让接口函数成为协程,当函数内部需要等待 I/O(如数据库查询、调用另一个 API)时,可以释放控制权处理其他请求,从而提升并发能力。整体性能接近 Node.js 和 Go。

演示用例

python 复制代码
# server.py
from fastapi import FastAPI, Depends, HTTPException, BackgroundTasks
from pydantic import BaseModel

app = FastAPI()

# ---- 模拟依赖:注入配置或模型实例 ----
def get_model_config():
    """在真实场景中,这里会加载模型或读取配置"""
    return {"model_name": "gpt-4", "max_tokens": 100}

# ---- Pydantic 请求体模型 ----
class Item(BaseModel):
    name: str
    price: float
    is_offer: bool = False

# ---- GET 路由:路径参数 + 依赖注入 ----
@app.get("/items/{item_id}")
async def read_item(
    item_id: int,
    q: str | None = None,                     # 新式可选参数写法
    config: dict = Depends(get_model_config)   # 注入配置
):
    """查询商品,同时注入模型配置"""
    return {
        "item_id": item_id,
        "q": q,
        "model": config["model_name"]
    }

# ---- POST 路由:请求体验证 + 错误处理 ----
@app.post("/items/")
async def create_item(item: Item):
    # 模拟一个条件:如果商品名为空字符串,返回 400
    if item.name.strip() == "":
        raise HTTPException(status_code=400, detail="商品名不能为空")
    discount = item.price * 0.9 if item.is_offer else item.price
    return {"name": item.name, "final_price": discount}

# ---- 后台任务示例 ----
def write_log(message: str):
    """模拟一个耗时的写日志操作"""
    with open("api.log", "a") as f:
        f.write(message + "\n")

@app.post("/items/{item_id}/notify")
async def notify_item(item_id: int, background_tasks: BackgroundTasks):
    """触发后台写日志,不阻塞接口响应"""
    background_tasks.add_task(write_log, f"Item {item_id} was notified")
    return {"message": "通知已加入后台任务"}

启动服务(在终端中运行):

bash 复制代码
uvicorn server:app --reload

用 Python 脚本模拟客户端调用,输出结果

python 复制代码
# client_test.py
# 请确保 server.py 已在另一个终端运行
import requests

resp = requests.get("http://127.0.0.1:8000/items/42?q=test")
print("GET /items/42 =>", resp.json())

resp = requests.post("http://127.0.0.1:8000/items/",
                     json={"name": "手机", "price": 2999.0, "is_offer": True})
print("POST /items/ =>", resp.json())

# 测试错误情况
resp = requests.post("http://127.0.0.1:8000/items/",
                     json={"name": "", "price": 100})
print("POST with empty name =>", resp.status_code, resp.json())

输出结果

复制代码
GET /items/42 => {'item_id': 42, 'q': 'test', 'model': 'gpt-4'}
POST /items/ => {'name': '手机', 'final_price': 2699.1}
POST with empty name => 400 {'detail': '商品名不能为空'}

打开浏览器访问 http://127.0.0.1:8000/docs 即可看到自动生成的 Swagger UI 交互式文档。
AI 应用场景:依赖注入常用于在接口中共享模型实例或数据库连接;后台任务用于异步写日志或更新缓存;HTTPException 让模型推理失败时返回明确错误。


2. Flask 构建 RESTful API

问题

更喜欢轻量、灵活的微框架,如何用 Flask 快速搭建 RESTful 接口?

生活化类比
Flask 就像手工面包房:工具简单(面粉、水、烤箱),你需要亲自动手揉面、造型、控制火候,自由度极高,但每一步都得自己把握。

原理

Flask 是 WSGI 应用程序,核心是 Werkzeug 工具箱。通过 @app.route 装饰器将 URL 规则绑定到视图函数,函数返回字符串、字典或 Response 对象。jsonify 函数将字典序列化为 JSON 响应。它不强制任何项目结构,依赖按需安装。如果觉得手动校验麻烦,Flask 社区提供了 Flask-RESTfulmarshmallow 等扩展来简化开发,甚至可以集成 Flask-Pydantic 获得类似 FastAPI 的校验体验。

演示用例

python 复制代码
# app.py
from flask import Flask, jsonify, request

app = Flask(__name__)

items = [{"id": 1, "name": "笔记本", "price": 5999.0}]

@app.route("/items", methods=["GET"])
def get_items():
    return jsonify(items)

@app.route("/items/<int:item_id>", methods=["GET"])
def get_item(item_id):
    item = next((i for i in items if i["id"] == item_id), None)
    if item:
        return jsonify(item)
    return jsonify({"error": "商品不存在"}), 404

@app.route("/items", methods=["POST"])
def create_item():
    data = request.get_json()
    if not data or "name" not in data:
        return jsonify({"error": "缺少字段 name"}), 400
    new_id = max(item["id"] for item in items) + 1 if items else 1
    new_item = {"id": new_id, "name": data["name"], "price": data.get("price", 0)}
    items.append(new_item)
    return jsonify(new_item), 201

if __name__ == "__main__":
    app.run(debug=True)

客户端测试

python 复制代码
# 请确保 Flask app.py 已在另一个终端运行
import requests

resp = requests.get("http://127.0.0.1:5000/items")
print("GET /items =>", resp.json())

resp = requests.post("http://127.0.0.1:5000/items",
                     json={"name": "键盘", "price": 299.9})
print("POST /items =>", resp.json())

输出结果

复制代码
GET /items => [{'id': 1, 'name': '笔记本', 'price': 5999.0}]
POST /items => {'id': 2, 'name': '键盘', 'price': 299.9}

FastAPI vs Flask 选择建议

  • 新项目(尤其是 AI 服务):推荐 FastAPI。自动文档、类型校验、异步支持、依赖注入开箱即用,开发效率更高。
  • 已有 Flask 生态或极简需求:Flask 更轻量,适合微服务或对自由度要求极高的场景。可以通过扩展补齐功能。

二、中级篇

1. 流式响应(SSE / WebSocket)

问题

如何让服务器主动向客户端推送实时数据,比如股票报价、聊天消息,而不是等客户端轮询?

生活化类比

  • SSE(Server-Sent Events)就像广播电台:服务器持续播报节目,客户端只需打开收音机调好频率,就能听到不断更新的内容,但无法向电台发声音。
  • WebSocket 就像打电话:接通后双方可以同时说话、听话,是真正的双向实时交流。

术语详解:流和帧

  • 可以理解为一条持续流动的"数据水管",服务器不断往里注入数据,客户端在水管另一端接收,连接不中断。
  • 在 WebSocket 中是数据传输的最小单位,就像把一句话拆成一个个汉字写在卡片上,一张一张地递给对方,拼起来就是完整消息。

原理

  • SSE 基于 HTTP 协议,服务器端设置响应头 Content-Type: text/event-stream,保持连接不关闭,持续发送 data: ...\n\n 格式的数据块。浏览器可通过 EventSource API 原生支持。
  • WebSocket 通过 HTTP 升级握手后,切换为全双工通信协议,使用帧传输文本或二进制数据。
  • 在生成器中捕获 GeneratorExit 或在 finally 块中清理资源,可以确保客户端断开后服务端立即停止生成数据,避免资源泄漏。

演示用例:SSE 时间推送(带清理)

python 复制代码
# sse_server.py
import asyncio
from fastapi import FastAPI
from starlette.responses import StreamingResponse

app = FastAPI()

async def time_events():
    """生成器,每秒产生一个 SSE 事件,客户端断开时自动清理"""
    try:
        while True:
            import datetime
            now = datetime.datetime.now().isoformat()
            yield f"data: 服务器时间:{now}\n\n"
            await asyncio.sleep(1)
    except GeneratorExit:
        # 客户端断开连接时,生成器被关闭,可以在此做清理工作
        print("客户端已断开,停止推送")

@app.get("/events")
async def sse_endpoint():
    return StreamingResponse(
        time_events(),
        media_type="text/event-stream"
    )

客户端模拟

python 复制代码
# 请确保 sse_server.py 已在另一个终端运行
import httpx

async with httpx.AsyncClient() as client:
    async with client.stream("GET", "http://localhost:8000/events") as response:
        async for line in response.aiter_lines():
            if line.startswith("data:"):
                print(line)
                break  # 只接收一条就断开,触发服务端清理

演示用例:WebSocket 回声服务(带心跳提示)

python 复制代码
# websocket_server.py
from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            # 实际生产环境应在此处加入 ping/pong 心跳检测
            # 可使用 asyncio.wait_for 或后台任务定期发送 ping
            data = await websocket.receive_text()
            await websocket.send_text(f"Echo: {data}")
    except WebSocketDisconnect:
        print("客户端断开连接")

客户端测试

python 复制代码
# 请确保 websocket_server.py 已在另一个终端运行
import asyncio
import websockets

async def test_ws():
    async with websockets.connect("ws://localhost:8000/ws") as ws:
        await ws.send("Hello!")
        reply = await ws.recv()
        print(reply)
asyncio.run(test_ws())

输出结果

复制代码
Echo: Hello!

流式通信方式速查表

技术 方向 协议 典型 AI 应用场景
SSE 服务端→客户端单向 HTTP ChatGPT 式逐字输出、进度推送
WebSocket 双向全双工 WebSocket 语音助手、在线协作推理
gRPC 流 单向/双向 HTTP/2 批量 embedding 传输、实时音视频流

2. gRPC 初步

问题

微服务之间需要高性能、跨语言的远程过程调用,传统的 REST + JSON 序列化开销大,如何更高效?

生活化类比
gRPC 就像国际快递标准化流程:你用统一表格(Protocol Buffers)填写包裹内容,快递公司按严格规范装箱、运输,地球另一端收件人用同样表格打开,百分百无歧义。

术语详解:Protocol Buffers(protobuf)

Protocol Buffers 是 Google 发明的一种数据格式,比 JSON 更小、更快。你需要先写一个 .proto 文件来描述数据结构和接口方法,然后自动生成对应语言的代码(存根)。这样,客户端调用服务端就像调用本地函数一样自然。

原理

gRPC 基于 HTTP/2 传输,使用 Protocol Buffers 定义服务接口和消息结构。HTTP/2 支持多路复用 (一个连接同时处理多个请求)、二进制帧传输和头部压缩,大幅降低了通信开销。

通过 protoc 编译器,将 .proto 文件生成客户端和服务端的"存根"(stub),开发者只需调用存根方法,底层序列化、传输、错误处理全部自动完成。支持四种调用方式:一元 RPC、服务端流、客户端流、双向流。

.proto 文件中,每个字段后的 = 1= 2 是字段编号,一旦发布就不能随意修改,以此保证数据向后兼容。

演示用例

安装依赖:pip install grpcio grpcio-tools

定义 hello.proto

protobuf 复制代码
syntax = "proto3";
package hello;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;  // 字段编号 1,表示第一个字段
}

message HelloReply {
  string message = 1;
}

生成代码:python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. hello.proto

服务端代码 (server.py):

python 复制代码
import grpc
from concurrent import futures
import hello_pb2, hello_pb2_grpc

class GreeterServicer(hello_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        return hello_pb2.HelloReply(
            message=f"你好,{request.name}!"
        )

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    hello_pb2_grpc.add_GreeterServicer_to_server(GreeterServicer(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    print("gRPC 服务启动,端口 50051")
    server.wait_for_termination()

if __name__ == "__main__":
    serve()

客户端代码 (client.py):

python 复制代码
# 请确保 server.py 已在另一个终端运行
import grpc
import hello_pb2, hello_pb2_grpc

def run():
    # 设置 2 秒超时,避免长时间阻塞
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = hello_pb2_grpc.GreeterStub(channel)
        try:
            response = stub.SayHello(
                hello_pb2.HelloRequest(name="Alice"),
                timeout=2.0       # 超时控制(deadline)
            )
            print("客户端收到:", response.message)
        except grpc.RpcError as e:
            print(f"调用失败:{e.code()} - {e.details()}")

if __name__ == "__main__":
    run()

运行输出

服务端:

复制代码
gRPC 服务启动,端口 50051

客户端:

复制代码
客户端收到: 你好,Alice!

AI 应用场景:gRPC 适合微服务间高性能模型推理调用,其流式模式特别适合传输大批量 embedding 或实时音视频流。


3. 接口文档自动化

问题

接口文档总是和代码脱节,开发一忙就忘了更新,如何让文档与代码实时同步、无需额外维护?

生活化类比
FastAPI 的文档就像餐厅自动更新的电子菜单:你改了厨房的菜式(代码),点餐屏幕(Swagger UI)立刻反映最新变化,不用重新印刷纸质菜单。

原理

FastAPI 在启动时收集所有路由及其类型注解、Pydantic 模型定义,依照 OpenAPI 规范生成完整的 API Schema(JSON 格式)。通过为路由添加 tagssummarydescription,可以让生成的文档更有条理。Pydantic 模型的 Field(example=...) 还可在文档中显示示例值。

运行 app.openapi() 即可导出整个 OpenAPI JSON,供 Postman、API 网关等工具使用。

演示用例:丰富文档信息

python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI(title="AI 推理服务", version="1.0.0")

class PredictRequest(BaseModel):
    text: str = Field(..., example="今天天气真好", description="要推理的文本")

    class Config:
        schema_extra = {
            "example": {"text": "今天天气真好"}
        }

@app.post("/predict",
          tags=["推理接口"],
          summary="文本情感分析",
          description="输入一段文本,返回正面/负面/中性")
async def predict(req: PredictRequest):
    return {"text": req.text, "sentiment": "正面"}

现在访问 /docs 时,推理接口会被分组到"推理接口"标签下,并展示示例请求体。

导出 OpenAPI JSON:

在代码中调用 print(app.openapi()) 或访问 http://localhost:8000/openapi.json

AI 应用场景:自动生成的文档让调用方直接看到推理接口的输入输出格式,大幅降低沟通成本;导出的 OpenAPI 可导入到 API 网关做统一管理。


面试模拟题

1. 对比型:FastAPI 和 Flask 在构建 AI 推理服务时,各自适合什么场景?

答案要点:新项目推荐 FastAPI------自动文档(方便调用方理解接口)、Pydantic 类型校验(减少输入错误)、原生异步(提升并发推理吞吐)。Flask 适合已有生态的小型服务或需要极简控制的场景,可通过 Flask-Pydantic 等扩展补齐校验能力。


2. 场景型:你的模型推理服务收到大请求时,同步推理会阻塞其他用户请求。如何让推理异步化?

答案要点 :用 async def 定义接口,在需要调用同步推理代码时使用 loop.run_in_executor(None, model.predict, input) 将阻塞操作放入线程池,这样事件循环可以继续处理其他请求。对于完全异步的推理框架(如 vLLM),可直接 await


3. 原理型:为什么 AI 对话流(如 ChatGPT 逐字输出)适合用 SSE 而非 WebSocket?

答案要点 :SSE 基于 HTTP,单向服务端推送,实现简单,浏览器 EventSource 原生支持自动重连。对话流是单向的(模型输出→用户),不需要 WebSocket 的双向能力。WebSocket 适合需要双向实时交互的场景(如语音助手、协作推理)。


4. 场景型 :你部署了一个 AI 推理的 gRPC 服务,客户端调用时偶尔报 DEADLINE_EXCEEDED。可能是什么原因?怎么优化?

答案要点:推理耗时超过了客户端设置的 deadline。可能原因:模型推理本身慢、并发请求过多导致排队。排查:检查服务端负载、考虑设置合理的 deadline 值(如 10s)、对长推理任务使用流式 gRPC 分批返回结果、或对超时请求返回部分结果而非报错。


总结

从 FastAPI 的现代化开发体验到 Flask 的极简自由,再到 SSE/WebSocket 的实时推送和 gRPC 的高性能通信,最后回归自动化的文档生成,你已掌握构建专业 API 的全套武器。每个知识点都配上了选型建议和生产化细节,建议亲自敲一遍,把服务跑起来,然后用客户端或浏览器实际观察请求与响应。API 开发的世界很大,但这篇指南是你最扎实的起点。

相关推荐
Upsy-Daisy2 小时前
AI Agent 项目学习笔记(八):Tool Calling 工具调用机制总览
人工智能·笔记·学习
LuminousCPP3 小时前
数据结构 - 线性表第四篇:C 语言通讯录优化升级全记录(踩坑 + 思考)
c语言·开发语言·数据结构·经验分享·笔记·学习
一只机电自动化菜鸟4 小时前
一建机电备考笔记(40) 建筑机电施工—排水管道施工(含考频+题型)
经验分享·笔记·学习·职场和发展·课程设计
你干嘛?哎哟5 小时前
4月工作笔记
笔记
tom02185 小时前
软考中级《嵌入式系统设计师》全套备考资料(真题 + 教材 + 笔记)
笔记·嵌入式·软考·自学·电子技术·电子资料·变成
问心无愧05137 小时前
ctf show web入门156
笔记
咸甜适中7 小时前
rust语言学习笔记Trait(八)Iterator(迭代器)
笔记·学习·rust
ZhiqianXia9 小时前
流畅的Python笔记
笔记·python
玄米乌龙茶1239 小时前
LLM成长笔记(四):大语言模型(LLM)基础认知
人工智能·笔记·语言模型