# O jeito certo de integrar o FastAPI com a OpenAI

## Introdução

O **FastAPI** é uma ferramenta moderna e super eficiente, com uma comunidade que não para de crescer, conquistou seu espaço, principalmente em projetos que envolvem **IA**. A **performance** e a **facilidade de uso** são incríveis!

Mas, como nem tudo são flores, esse sucesso todo traz alguns desafios. Com muitas empresas começando a usar, algumas implementações podem acabar tropeçando em armadilhas comuns, simplesmente pela falta de experiência dos desenvolvedores com a ferramenta ou por seguirmos o caminho que *parece* mais fácil de início.

Esse post nasceu justamente de uma situação real que enfrentei num projeto e de uma *thread* bem interessante que acompanhei lá no [GitHub do FastAPI.](https://github.com/fastapi/fastapi/discussions/9234) Vamos falar sobre como **gerenciar o cliente da OpenAI** (ou qualquer outro recurso similar) do jeito certo dentro do FastAPI usando o **lifespan**.

## O cenário

Imagine que estamos construindo o backend para um chat, tipo um **ChatGPT** mais simples. Poderia ser para uma interface customizada, para consumo sob demanda, ou até para usar modelos open-source compatíveis com **a biblioteca openai do Python**.

E para essa missão, claro, vamos usar o FastAPI, já que até as [SPAs eu estou substituindo pelo FastAPI](https://blog.ricardotenv.dev/renderizando-html-dinamico-com-fastapi-e-jinja2-uma-alternativa-simples-aos-spas).

Pra você que está lendo, não ficar perdido. Eu vou mostrar primeiro a forma como muita gente começa: o jeito simples, que *funciona* na hora, mas que esconde alguns riscos e pode trazer muita dor de cabeça lá na frente.

Depois, vamos mergulhar na solução **usando o lifespan do FastAPI**. O foco desse artigo é mostrar como usar o lifespan especificamente para integrar o cliente da biblioteca openai de forma correta. O lifespan tem mais utilidades, mas não vamos nos aprofundar em todas elas hoje, talvez em um post futuro!

## O jeito ruim de fazer: Instânciando o cliente no topo do arquivo

Quando estamos começando um projeto, a intuição (e muitos exemplos por aí) nos leva a fazer algo assim: **instanciar o cliente assíncrono da openai diretamente no escopo global** do nosso arquivo principal. É simples, né?

```python
# main.py
from fastapi import FastAPI
from openai import AsyncOpenAI
import os

client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))

app = FastAPI()

@app.post("/chat")
async def chat(prompt: str):
    # Usando o cliente global diretamente
    response = await client.chat.completions.create(
        model="..."
        messages=[{"role": "user", "content": prompt}]
    )
    return {"reply": response.choices[0].message.content}
```

Bom, como eu disse, isso *funciona*. Você **roda o Uvicorn** e consegue fazer chamadas. Mas por baixo dos panos, vários problemas estão se acumulando:

1. **Instanciação prematura:** O cliente é criado *antes* mesmo do servidor FastAPI estar totalmente pronto para receber requisições. Se você precisasse carregar alguma configuração assíncrona *antes* de criar o cliente, já não daria certo.
    
2. **Gerenciamento de recursos falhos:** Essa instância cliente fica "viva" durante todo o tempo que o processo do servidor rodar. Mais importante: ela **não é fechada corretamente** quando o servidor termina. O **AsyncOpenAI** usa o **httpx.AsyncClient** por baixo, que mantém **um pool de conexões HTTP**. Sem chamar `await client.aclose()`, essas conexões ficam abertas, vazando recursos (sockets, memória).
    
3. **Dificuldade em testes:** Testar endpoints que dependem de estado global é um pé no saco. Mockar ou substituir essa instância cliente para testes unitários ou de integração fica bem mais complicado.
    
4. **Replicação por worker:** Se você **rodar sua aplicação com múltiplos workers** (como o Gunicorn faz em produção), *cada worker* vai criar sua própria instância global do cliente, multiplicando o **desperdício de recursos** e os **problemas de conexão**.
    

## “O jeito certo”: Usando o lifespan do FastAPI

Eu coloquei entre aspas pois existem outras formas de fazer, como o *before* ou *on\_event* e outras formas que também pode funcionar e ao mesmo tempo seguir as boas práticas. Cagar regra na nossa área definitivamente não é uma boa prática.

Voltando ao assunto. Felizmente, o FastAPI oferece uma solução elegante para **gerenciar o ciclo de vida de recursos** como nosso cliente openai: **o parâmetro lifespan**.

Ele utiliza um **async context manager** (gerenciador de contexto assíncrono) para garantir que seu recurso seja inicializado *depois* que a aplicação começa e finalizado *antes* que ela termine completamente.

Veja como fica bem mais interessante:

```python
# main.py
import os
from openai import AsyncOpenAI
from fastapi import FastAPI, Request
from contextlib import asynccontextmanager

# O gerenciador de contexto para o ciclo de vida
@asynccontextmanager
async def lifespan(app: FastAPI):
    # Setup: Roda antes da aplicação iniciar
    print("Iniciando cliente OpenAI...")
    app.state.openai_client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
    print("Cliente OpenAI pronto!")

    yield # Essa função congela nesse momento e aplicação entra em ação

    # Teardown: A mágica acontece aqui, depois que voc6e para a aplicação esse código é executado
    print("Fechando cliente OpenAI...")
    await app.state.openai_client.close()
    print("Cliente OpenAI fechado.")

app = FastAPI(lifespan=lifespan)

@app.post("/chat")
async def chat(request: Request, prompt: str): # Simplificado para receber só o prompt
    # Acessa o cliente via app.state
    client: AsyncOpenAI = request.app.state.openai_client

    response = await client.chat.completions.create(c
        model="...",
        messages=[{"role": "user", "content": prompt}]
    )
    return {"reply": response.choices[0].message.content}
```

Entendendo a mágica: **asynccontextmanager** e **yield**

O decorador `@asynccontextmanager` (da biblioteca **contextlib** do Python) é a chave aqui. Ele transforma nossa função `lifespan` em um **gerenciador de contexto assíncrono** especial.

1. **Antes do yield:** Todo o código antes da linha **yield** é a fase de **setup**. O FastAPI executa isso durante a inicialização *(aqui eu imagino que você já entendeu o lance)*. É aqui que criamos nosso cliente **AsyncOpenAI** e o guardamos em `app.state.openai_client`. O `app.state` é um objeto tipo dicionário feito exatamente para **guardar recursos que precisam viver junto com a aplicação**.
    
2. **O yield:** A palavra ***yield*** é o ponto de "pausa". A função `lifespan` fica congelada aqui, e o controle volta para o FastAPI, que finalmente começa a aceitar e processar requisições. Enquanto a aplicação está rodando, nossos endpoints (como `/chat`) podem acessar o cliente via [`request.app`](http://request.app)`.state.openai_client`.
    
3. **Depois do yield:** Quando o FastAPI recebe um sinal para desligar (por exemplo, quando você pressiona Ctrl+C no terminal), ele "descongela" a função `lifespan` *depois* do `yield`. Essa é a fase de **teardown**. Aqui, executamos a limpeza: `await client.close()`, **garantindo que as conexões HTTP sejam fechadas corretamente**.
    

## Conclusão

Sim, instanciar o cliente globalmente *vai funcionar* no começo. O FastAPI é robusto e vai saber lidar com isso. Mas essa abordagem não escala bem e esconde problemas que vão te assombrar em produção ou em aplicações maiores.

E pra ser honesto, eu usei a openai para chamar a sua atenção mas a minha verdadeira intenção era te mostrar o uso do lifespan.
