4. Flask基础API搭建

好,到了真正动手写代码的时候了。前面三章我们聊了推理服务的整体架构、环境准备和项目结构,现在该让服务器真正跑起来了。

这一章我们聚焦 Flask——Python 生态里最轻量的 Web 框架。为什么选它?说白了,推理服务的核心是「接收请求 → 调用模型 → 返回结果」,Flask 恰好用最少的代码帮你搞定这件事。我在生产环境里见过用 FastAPI 的、用 Django 的,但 Flask 的灵活性和低心智负担,让它成为原型验证和中小规模部署的首选。

本章核心目标:用 Flask 搭建一个能接收 POST 请求、处理 JSON 数据、返回推理结果的 REST API 端点。

4.1 Flask 框架到底是个啥?

Flask 是一个用 Python 写的微型 Web 框架。注意「微型」这个词——它核心只做一件事:把 HTTP 请求映射到你的 Python 函数上。没有 ORM、没有表单验证、没有认证中间件,这些统统需要你自己选配。

我个人习惯把 Flask 比作「一把螺丝刀」:功能单一但极其趁手。你不需要学一堆概念就能上手,这也是我推荐新手用它搭建推理服务的原因。

特性 Flask Django FastAPI
启动代码量 5-10 行 50+ 行 10-15 行
内置功能 极少(路由+模板) 极多(全栈) 中等(路由+校验)
异步支持 需扩展 需扩展 原生
学习曲线 平缓 陡峭 中等
推理服务适用性 ★★★★★ ★★☆☆☆ ★★★★☆

你看,Flask 在推理服务这个场景下几乎满分。为什么?因为推理服务不需要复杂的后台管理、不需要用户认证、不需要模板渲染——它只需要一个「输入 → 处理 → 输出」的管道。

4.2 安装与第一个「Hello World」

先装包。我建议你在虚拟环境里操作,别污染全局 Python。

pip install flask

装完写个文件,就叫 app.py:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello, 推理服务!"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

跑起来:

python app.py

打开浏览器访问 http://localhost:5000,看到「Hello, 推理服务!」就对了。

小提示:host='0.0.0.0' 表示监听所有网络接口,这样你局域网里的其他机器也能访问。如果只想本机访问,改成 host='127.0.0.1'。

这段代码干了什么?@app.route('/') 是一个装饰器,它告诉 Flask:当用户访问根路径 / 时,执行下面的 hello() 函数。就这么简单。

4.3 创建第一个 REST API 端点

「Hello World」太简单了,我们来写一个真正的 API 端点。假设我们要做一个文本分类的推理接口,输入一段文本,返回它的类别。

先定义一个 /predict 端点,用 GET 方法:

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/predict', methods=['GET'])
def predict_get():
    # 模拟推理结果
    result = {
        "text": "示例文本",
        "label": "positive",
        "confidence": 0.95
    }
    return jsonify(result)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

访问 http://localhost:5000/predict,你会看到:

{
  "text": "示例文本",
  "label": "positive",
  "confidence": 0.95
}

jsonify() 是 Flask 提供的函数,它把 Python 字典转成 JSON 格式的 HTTP 响应,并自动设置 Content-Type: application/json。我在项目中见过有人手动拼 JSON 字符串,结果漏了转义字符导致前端解析失败——用 jsonify 就永远不会出这种问题。

注意:GET 请求的参数通常放在 URL 里,不适合传输大量文本数据。真正的推理服务应该用 POST 请求。

4.4 处理 POST 请求与 JSON 数据

这才是本章的重头戏。推理服务的标准流程是:客户端把输入数据(比如一段文本、一张图片的 base64)放在 HTTP 请求的 body 里,以 JSON 格式发送过来。服务端解析 JSON,调用模型,返回结果。

看代码:

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/predict', methods=['POST'])
def predict_post():
    # 1. 获取请求中的 JSON 数据
    data = request.get_json()
    
    # 2. 校验数据
    if not data or 'text' not in data:
        return jsonify({"error": "缺少 'text' 字段"}), 400
    
    input_text = data['text']
    
    # 3. 模拟推理过程
    # 在实际项目中,这里会调用你的模型
    label = "positive" if len(input_text) > 5 else "negative"
    confidence = 0.85 if label == "positive" else 0.72
    
    # 4. 返回结果
    result = {
        "input": input_text,
        "label": label,
        "confidence": confidence
    }
    return jsonify(result), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

测试一下。用 curl 命令:

curl -X POST http://localhost:5000/predict \
  -H "Content-Type: application/json" \
  -d '{"text": "这部电影太棒了"}'

返回:

{
  "input": "这部电影太棒了",
  "label": "positive",
  "confidence": 0.85
}

如果忘了传 text 字段:

curl -X POST http://localhost:5000/predict \
  -H "Content-Type: application/json" \
  -d '{"foo": "bar"}'

返回 400 错误:

{
  "error": "缺少 'text' 字段"
}

这里有几个关键点:

  • request.get_json():解析请求 body 中的 JSON,返回 Python 字典。如果请求没有 Content-Type: application/json 头,这个方法会返回 None。
  • 状态码:成功返回 200,客户端错误返回 400,服务器错误返回 500。这是 REST API 的基本礼仪。
  • 错误处理:永远不要假设客户端会传正确的数据。我在生产环境里遇到过传空字符串、传数组、甚至传二进制文件的奇葩情况——校验这一步不能省。

避坑指南:我曾经在项目里忘了设置 Content-Type 头,结果 request.get_json() 一直返回 None,排查了半天。记住:客户端发请求时必须带 Content-Type: application/json,否则 Flask 不会自动解析 JSON。

4.5 完整的推理服务骨架

把上面的代码整合一下,加上模型加载的逻辑(先占位),就是一个可用的推理服务骨架:

from flask import Flask, request, jsonify
import time

app = Flask(__name__)

# 模拟模型加载
print("正在加载模型...")
time.sleep(2)  # 模拟加载耗时
print("模型加载完成")

def load_model():
    """实际项目中加载你的模型"""
    # 例如:model = torch.load('model.pth')
    return "model_loaded"

model = load_model()

@app.route('/health', methods=['GET'])
def health_check():
    """健康检查端点"""
    return jsonify({"status": "ok", "model_loaded": model is not None})

@app.route('/predict', methods=['POST'])
def predict():
    # 解析请求
    data = request.get_json()
    if not data or 'text' not in data:
        return jsonify({"error": "缺少 'text' 字段"}), 400
    
    input_text = data['text']
    
    try:
        # 调用模型推理
        # result = model.predict(input_text)
        # 这里用模拟数据
        label = "positive" if len(input_text) > 5 else "negative"
        confidence = 0.85
        
        return jsonify({
            "input": input_text,
            "label": label,
            "confidence": confidence
        }), 200
        
    except Exception as e:
        return jsonify({"error": f"推理失败: {str(e)}"}), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

这个骨架包含了:

  • 模型预加载:服务启动时加载模型,避免每次请求都加载
  • 健康检查:/health 端点用于监控服务状态
  • 输入校验:确保必要字段存在
  • 异常捕获:推理过程中的错误不会导致服务崩溃

4.6 本章知识体系

下面这张图帮你理清 Flask 推理服务的核心流程:

Flask 推理服务核心流程 客户端 HTTP POST 请求 Content-Type: application/json Flask 路由 @app.route('/predict') methods=['POST'] 数据解析与校验 request.get_json() 模型推理 model.predict(input) 返回 JSON 结果 客户端接收 核心原则:接收 JSON → 解析校验 → 模型推理 → 返回 JSON

这张图展示了从客户端发起 POST 请求到服务端返回结果的完整链路。你想想看,整个流程其实就四个步骤:接收、解析、推理、返回。Flask 帮我们搞定了第一步和第四步,中间两步才是你的业务逻辑。

4.7 本章小结

到这,你已经掌握了:

  • Flask 的基本概念和安装
  • 如何用 @app.route 创建 API 端点
  • GET 和 POST 请求的区别与使用场景
  • 如何解析 JSON 请求体并返回 JSON 响应
  • 一个完整的推理服务骨架代码