Flask ou FastAPI? Primeira experiência do servidor Python

1. Introdução

Recentemente, por necessidades de trabalho, fui aprender sobre o trabalho relacionado de construção de serviços simples em python, principalmente para uso de modelos ou ferramentas desenvolvidas por mim para pessoas do mesmo grupo. A estrutura front-end Streamlit introduzida anteriormente, que é amigável para a pesquisa de ciência de dados, pode ser considerada uma ferramenta afiada. No entanto, com a popularidade do ChatGPT, há cada vez mais serviços baseados em bate-papo. streamlit tem um derivado de bate-papo streamlit-chat , mas pode fornecer apenas uma função de bate-papo simples e não pode ter exibição mais avançada, como marcação de suporte e streaming saída, etc Portanto, o FastChat, que é mais adequado para front-end de modelos grandes , pode ser uma escolha melhor.

Dito isto, o front-end é apenas uma interface de exibição e o serviço real precisa do back-end. A rigor, o backend fornece serviços de dados, ou seja, adiciona, exclui, verifica e modifica dados, é essencialmente inseparável, mas agora não está vinculado ao banco de dados, mas ao modelo ou serviço de modelo.

Nessa hora, quando fui procurar a biblioteca back-end do python, apareceu na minha frente o Flask, o framework web mais usado para aplicações de pequeno e médio porte. Mas ainda me sinto um pouco complicado, então pensei em um FastAPI baseado no desenvolvimento secundário do Starlette, que pode ser mais adequado. (Aqui está uma introdução comparativa entre eles, introdução 1 , introdução 2 , introdução 3 , introdução 4 ). Claro, algumas pessoas apontaram que comparar Flask e FastAPI é injusto, assim como comparar maçãs e suco de laranja, que é mais doce. Como uma estrutura geral da Web, o Flask deve ser comparado ao Starlette. Também concordo com este ponto. Mas isso mostra ainda que o FastAPI é uma escolha melhor quando é necessário um desenvolvimento rápido e os requisitos não são tão pesados. (Claro, eu também uso o Flask para desenvolver serviços suportados por multi-threading. Ambos têm suas próprias vantagens.)

2. Olá, mundo

Como em qualquer primeiro exemplo em qualquer linguagem, começamos com um exemplo muito simples de fornecimento de um serviço usando FastAPI.

A primeira etapa é preparar o código app.py

from fastapi import FastAPI
from pydantic import BaseModel
from gpt import GPT

app = FastAPI()
model = GPT()

class Message(BaseModel):
    new_message: str
    role: str = ""
    args: dict = {
    
    }

@app.post('/gpt')
def gpt_endpoint(message: Message):
    new_message = message.new_message
    role = message.role
    args = message.args

    response = model.call(new_message=new_message, role=role, args=args)
    return response

A partir dessas menos de 50 linhas de código, destaca-se a maioria dos recursos do FastAPI. Você pode ver o aplicativo principal, seu próprio modelo de recursos, uma mensagem de classe de dados e um serviço de pós-interface (denominado /gpt), realizar algumas operações e retornar uma resposta.

GPT aqui é uma classe simplificada cujo código de processamento pode ser escrito em outro arquivo.

A segunda etapa é instalar a biblioteca dependente

pip install fastapi
pip install uvicorn
pip install pydantic

A terceira etapa é executar o serviço

uvicorn app:app --host 0.0.0.0 --port 8000

Após as três etapas acima, um serviço FastAPI é criado. O endereço de acesso é, por exemplo: http://localhost:8000/gpt. Como testá-lo rapidamente? O FastAPI vem com o Docs, que pode ser acessado através da URL (http://localhost:8000/docs), conforme a figura a seguir:
insira a descrição da imagem aqui

Se houver comentários suficientes no código, não há necessidade de escrever outro manual.

Mas o código agora é apenas uma breve introdução aos recursos do FastAPI. E se eu precisar criar serviços mais complexos? Abaixo apresentamos o uso do FastAPI de forma mais abrangente através de dois exemplos práticos.

3. Expanda o combate real

3.1 Use FastAPI para fornecer exemplos de adição, exclusão, verificação e modificação

Digamos que estamos desenvolvendo um aplicativo simples de gerenciamento de tarefas. Queremos alcançar a seguinte funcionalidade:

  • Obtenha uma lista de todas as tarefas
  • Criar uma nova tarefa
  • Atualize o conteúdo de uma tarefa específica
  • Marcar tarefas específicas como concluídas
  • excluir uma tarefa específica

Podemos usar o FastAPI para obter essas funções. Veja a seguir um exemplo de código para endpoints de API definidos com diferentes tipos de decoradores:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

# 待办事项数据模型
class TodoItem(BaseModel):
    id: int
    title: str
    completed: bool = False

# 模拟的待办事项存储
todo_items = []

# 获取所有待办事项的列表
@app.get("/todos")
def get_todo_list():
    return todo_items

# 创建一个新的待办事项
@app.post("/todos")
def create_todo_item(item: TodoItem):
    todo_items.append(item)
    return item

# 更新特定待办事项的内容
@app.put("/todos/{item_id}")
def update_todo_item(item_id: int, item: TodoItem):
    for i in range(len(todo_items)):
        if todo_items[i].id == item_id:
            todo_items[i] = item
            return item

# 标记特定待办事项为已完成
@app.patch("/todos/{item_id}")
def complete_todo_item(item_id: int):
    for item in todo_items:
        if item.id == item_id:
            item.completed = True
            return item

# 删除特定待办事项
@app.delete("/todos/{item_id}")
def delete_todo_item(item_id: int):
    for item in todo_items:
        if item.id == item_id:
            todo_items.remove(item)
            return {
    
    "message": "Item deleted"}


No exemplo acima, usamos os seguintes diferentes tipos de decoradores:

  1. @app.get(path: str): define o endpoint da solicitação GET. Um endpoint para obter uma lista de todos é definido usando o decorador @app.get("/todos"). Este endpoint não precisa receber nenhum parâmetro e retorna a lista de tarefas diretamente.

  2. @app.post(path: str): define o endpoint da solicitação POST. O endpoint para criar novos todos é definido usando o decorador @app.post("/todos"). Esse endpoint recebe um item de parâmetro do corpo da solicitação, que é uma instância do modelo TodoItem e contém o conteúdo do item pendente. Na função do manipulador, adicionamos o novo item de tarefa à lista todo_items e retornamos o item de tarefa adicionado.

  3. @app.put(path: str): define o endpoint da solicitação PUT. O terminal para atualizar todos é definido usando o decorador @app.put("/todos/{item_id}"). Este terminal recebe um parâmetro de caminho item_id para especificar o ID do item de tarefa pendente e um item de parâmetro de corpo de solicitação, que é uma instância do modelo TodoItem e contém o novo conteúdo do item de tarefa pendente. Na função de processamento, percorremos a lista todo_items para encontrar o item todo correspondente ao ID, atualizamos com novo conteúdo e retornamos o item todo atualizado.

  4. @app.patch(path: str): Define o endpoint da solicitação PATCH. Use o decorador @app.patch("/todos/{item_id}") para definir um terminal que marque uma tarefa como concluída. Este terminal aceita um parâmetro de caminho item_id especificando o ID do item de tarefa. Na função do manipulador, iteramos na lista todo_items para encontrar o item todo com o ID correspondente, marcamos como concluído (item.completed = True) e retornamos o item todo atualizado.

  5. @app.delete(path: str): Define o endpoint da solicitação DELETE. O terminal para excluir todos é definido usando o decorador @app.delete("/todos/{item_id}"). Este terminal aceita um parâmetro de caminho item_id especificando o ID do item de tarefa. Na função de processamento, percorremos a lista todo_items para encontrar o item pendente com o ID correspondente, excluímos da lista e retornamos uma mensagem simples indicando que a exclusão foi bem-sucedida.

  6. Esses decoradores nos permitem definir diferentes tipos de endpoints de acordo com o método HTTP (GET, POST, PUT, PATCH, DELETE) e receber e processar diferentes dados por meio de parâmetros de caminho e parâmetros de corpo de solicitação. Dessa forma, podemos usar um aplicativo unificado para lidar com várias operações e projetar nossa API de acordo com os princípios da API RESTful.

Aqui, notamos que os métodos PUT e PATCH são muito parecidos, ambos são usados ​​para atualização, mas possuem as seguintes diferenças:

As solicitações PUT são usadas para substituir completamente (Substituir) recursos ou entidades no servidor. Quando um cliente envia uma solicitação PUT, ele precisa fornecer uma representação completa do recurso, incluindo todos os campos a serem atualizados.
Se o recurso não existir, um novo recurso será criado; se o recurso existir, o conteúdo do recurso existente será completamente substituído (substituído).
Os clientes geralmente devem fornecer uma representação completa do recurso, mesmo que apenas alguns campos tenham sido alterados. Isso significa que o cliente precisa fornecer todos os campos, não apenas aqueles a serem alterados.
As solicitações PUT são idempotentes, executar a mesma solicitação PUT várias vezes não terá impacto adicional nos recursos.

As solicitações PATCH são usadas para fazer atualizações parciais em recursos ou entidades no servidor. Quando um cliente envia uma solicitação PATCH, ele só precisa fornecer alguns dos campos ou atributos a serem atualizados.
As solicitações PATCH podem ser usadas para atualizar campos específicos de um recurso de forma incremental sem enviar uma representação de todo o recurso. Isso o torna adequado para casos em que apenas alguns campos são atualizados.
O servidor atualiza seletivamente os campos correspondentes do recurso de acordo com o conteúdo de atualização fornecido pelo cliente. Os campos não fornecidos permanecerão inalterados.
As solicitações PATCH podem ser idempotentes ou não idempotentes, dependendo da implementação e uso específicos.

Em aplicações práticas, você pode optar por usar solicitações PUT ou PATCH de acordo com requisitos de negócios específicos e critérios de design. Se você deseja atualizar um recurso ou entidade inteira, deve usar uma solicitação PUT. Se apenas alguns campos ou atributos precisam ser atualizados, uma solicitação PATCH deve ser usada.

3.2 Exemplo de uso do FastAPI para fornecer o modelo de linguagem de carregamento da GPU

import uvicorn
import torch
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel

# 示例GPT模型类
class GPTModel:
    def __init__(self):
        # 这里是你的GPT模型的初始化代码
        # ...

    def generate_text(self, input_text):
        # 这里是生成文本的代码
        # ...

# 示例后台任务函数
def load_model(background_tasks):
    # 加载和初始化GPT模型
    model = GPTModel()
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model.to(device)
    model.eval()

    # 将模型保存到FastAPI应用的state中
    app.state.model = model

# 示例请求模型的输入
class TextRequest(BaseModel):
    text: str

app = FastAPI()

@app.post("/generate")
def generate_text(request: TextRequest, background_tasks: BackgroundTasks):
    # 在后台任务中加载模型
    background_tasks.add_task(load_model, background_tasks)

    # 从应用程序状态中获取模型
    model = app.state.model

    # 在GPU上进行推理
    input_text = request.text
    generated_text = model.generate_text(input_text)

    # 返回生成的文本结果
    return {
    
    "generated_text": generated_text}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

4. Resumo

Este artigo apresenta brevemente alguns usos simples do FastAPI, que é conveniente para construir serviços de protótipo rapidamente.

(À medida que grandes modelos como o ChatGPT penetram gradualmente em nossas vidas, os blogs que podem registrar detalhes técnicos dessa maneira estão se tornando cada vez mais inúteis. Como o ChatGPT pode fornecer orientação interativa para coisas que não sabemos e não há necessidade de ler blogs como este mais. Embora este blog também seja inseparável das instruções do ChatGPT, ainda leio alguns blogs relacionados e tentei o código fornecido pelo ChatGPT por mim mesmo para aumentar alguma credibilidade.)

Acho que você gosta

Origin blog.csdn.net/qq_35082030/article/details/130917423
Recomendado
Clasificación