FastAPI на Colab через ngrok и uvicorn на Python

Столкнулся тут с проблемкой. Нужно было запустить FastAPI через uvicorn. При этом обращения с сервера должны были идти на API OpenAI. Код писал на Python в VSCode. Запустить надо было по-быстрому. Нормальных VPN не нашел. «Спасибо» добрякам из Роскомнадзора…

В общем, как самый быстрый способ проверки работоспособностии работы OpenAI API с FastAPI получился такой:

  • На ПК установлен Yandex Disk. На нем создана папка проекта.
  • В VSCode проект на Python сохраняется на Yandex Disk.
  • В Colab во первых строках кода исходники проекта скачиваются в локальную папку и добавляются в путь для поиска модулей.
  • Как скачать и развернуть файлы проекта в Google Colab с Yandex Disk описано в другой статье.
  • Подготавливается FastAPI.
  • Поднимается ngrok.
  • Запускается uvicorn.

Для подготовки FastAPI к запуску пример кода:

!pip install fastapi nest-asyncio pyngrok uvicorn

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

# инициализация индексной базы
chunk = Chunk(path_to_base="Simble.txt")

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=['*'],
    allow_credentials=True,
    allow_methods=['*'],
    allow_headers=['*'],
)

# класс с типами данных параметров 
class Item(BaseModel): 
    text: str

# функция обработки get запроса + декоратор 
@app.get("/")
async def read_root():
    return {"message": "answer"}

@app.get("/api/access_counter")
async def get_accesscouner():
  counter = chunk.get_accesscouner()
  return {"access_counter": counter}

# функция обработки post запроса + декоратор 
@app.post("/api/get_answer")
async def get_answer(question: Item):
    answer = chunk.get_answer(query=question.text)
    return {"message": answer}

Далее поднимаем ngrok. Передаем токен полученный при регистрации на Ngrok и предварительно сохраненый в .env. Подгружаем его через load_dotenv из папки загруженными с Yandex Disk исходниками:

  dotenv_path = "/content/" + dirs[0] + "/.env"
  load_dotenv(dotenv_path)

и запускаем веб сервер uvicorn:

import nest_asyncio
from pyngrok import ngrok
import uvicorn

ngrok.set_auth_token(os.environ.get("NGROK_AUTHTOKEN"))

ngrok_tunnel = ngrok.connect(8000)
print('Public URL:', ngrok_tunnel.public_url)
nest_asyncio.apply()
uvicorn.run(app, port=8000)

Сервер uvicorn повисает на временно сгенерированном ngrok внешнем домене.

Чтобы протестировать запуск сервера можно использовать запуск в командной строке curl:

curl -i -X POST -H "Content-Type: application/json" -d "{\"text\": \"Привет\"}" http://127.0.0.1:8000/api/get_answer

Для бесплатных пользователей при создании аккаунта на Ngrok предоставляется один домен, который нужно создать в консоли ngrok (CloudEdge -> Domains). Выглядит он как-то так: «xxx.ngrok-free.app». Несколько доменов можно создать, купив платную подписку, но с проплатой из России проблемки. 🙂

Веб сервер Uvicorn туннелируется (пробрасывается) из Colab наружу через Ngrok и становится доступным для обращений по сгенерированному на Ngrok доменному имени. При обращениях по доменному имени запросы перенаправляются через Ngrok в Colab на Uvicorn и ответы возвращаются обратно.

ngrok_tunnel = ngrok.connect(8000, domain="your_domain.ngrok-free.app")

Возможно, есть достойные альтернативы ngrok, но времени на эксперименты не было.

Public URL: https://bb2f-34-16-145-243.ngrok-free.app
INFO:     Started server process [23974]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     81.222.179.30:0 - "GET /api/access_counter HTTP/1.1" 200 OK
INFO:     81.222.179.30:0 - "GET /api/access_counter HTTP/1.1" 200 OK
INFO:     Shutting down
INFO:     Waiting for application shutdown.
INFO:     Application shutdown complete.
INFO:     Finished server process [23974]

К сожалению, ячейка Colab запускается не асинхронно, т.е. управление с неё не передается на следующую ячейку. Гипотетически можно это обойти, запустив сервер через командную строку.

Для простоты я для тестирования использовал Postman. Вот так выглядит отправка запроса к серверу поднятому через ngrok на Colab.

Нюансы отправки REST API с помощью JavaScript через Ngrok

При отправке GET запросов на сервер через ngrok наверняка столкнетесь с тем, что стандартный JavaScript запрос выдает ошибку вида:

"Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON"

или

Unexpected token '<', \"<!DOCTYPE ";... is not valid JSON"

При этом POST запрос у меня почему-то проходил без вопросов. Я сломал всю голову, не понимая, что происходит, поскольку в браузере и в Postman возвращался нормальный JSON и только при запуске обращения к сервере через JavaScript возникала эта ошибка. Затем наткнулся на этот сервер: https://reqbin.com/post-online для проверки GET запросов. Подсунув туда URL на ngrok я получил в response HTML страницу, вместо JSON.

Странно, что при отправке запросов через Postman и другие сервера они всегда возвращали правильный JSON. Чтобы bypass ngrok browser warning нужно добавить в header строку:

'ngrok-skip-browser-warning': 'any'

Добавление нестандартного User-Agent, как указано в рекомендациях, проблему решает в https://reqbin.com/post-online, но при вызове в JavaScript возникает та-же ошибка. Рабочий JavaScript код для обхода ошибки:

&amp;lt;script>
const url = 'https://xxx.ngrok-free.app/api/counter';
//const url = "https://jsonplaceholder.typicode.com/posts/1";
var headers = new Headers(
	{
        'User-Agent': 'PostmanRuntime/7.36.0',
        'Accept': 'application/json',
        'ngrok-skip-browser-warning': 'any',
        'Host': 'hugely-easy-pipefish.ngrok-free.app',
        'Content-Type': 'application/json'
  }
)
fetch(url, {
    method: "GET",
    headers: headers,  
    redirect: "follow"
  }
).then(response => {
    //console.log(response);
    return response.json();
  }
).then(data => {
    console.log(data);
  }  
).catch(error => {
    console.log(error.message);
  }
)
&amp;lt;/script>

Запускать скрипт для отладки удобно в https://jsfiddle.net/. Если раскомментарить строчку console.log(response), то в консоли jsfiddle отобразится объект response со всеми свойствами и методами.

Spread the love
Запись опубликована в рубрике IT рецепты. Добавьте в закладки постоянную ссылку.

Обсуждение закрыто.