-
Notifications
You must be signed in to change notification settings - Fork 124
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #27 from pamelafox/port-quart
Port to Quart
- Loading branch information
Showing
18 changed files
with
212 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
.env | ||
.azure | ||
.pyc | ||
__pycache__/ | ||
__pycache__/ | ||
.coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
repos: | ||
- repo: https://github.com/pre-commit/pre-commit-hooks | ||
rev: v4.4.0 | ||
hooks: | ||
- id: check-yaml | ||
- id: end-of-file-fixer | ||
- id: trailing-whitespace | ||
- repo: https://github.com/astral-sh/ruff-pre-commit | ||
rev: v0.0.288 | ||
hooks: | ||
- id: ruff | ||
- repo: https://github.com/psf/black | ||
rev: 23.9.1 | ||
hooks: | ||
- id: black |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,6 @@ | |
black | ||
ruff | ||
pre-commit | ||
pytest | ||
pytest-asyncio | ||
pytest-cov |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
from flaskapp import create_app | ||
from quartapp import create_app | ||
|
||
app = create_app() |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import json | ||
import os | ||
|
||
import azure.identity.aio | ||
import openai | ||
from quart import Blueprint, Response, current_app, render_template, request, stream_with_context | ||
|
||
bp = Blueprint("chat", __name__, template_folder="templates", static_folder="static") | ||
|
||
|
||
@bp.before_app_serving | ||
async def configure_openai(): | ||
openai.api_base = os.getenv("AZURE_OPENAI_ENDPOINT") | ||
openai.api_version = "2023-03-15-preview" | ||
if os.getenv("AZURE_OPENAI_KEY"): | ||
openai.api_type = "azure" | ||
openai.api_key = os.getenv("AZURE_OPENAI_KEY") | ||
else: | ||
openai.api_type = "azure_ad" | ||
if client_id := os.getenv("AZURE_OPENAI_CLIENT_ID"): | ||
default_credential = azure.identity.aio.ManagedIdentityCredential(client_id=client_id) | ||
else: | ||
default_credential = azure.identity.aio.DefaultAzureCredential(exclude_shared_token_cache_credential=True) | ||
token = await default_credential.get_token("https://cognitiveservices.azure.com/.default") | ||
openai.api_key = token.token | ||
|
||
|
||
@bp.get("/") | ||
async def index(): | ||
return await render_template("index.html") | ||
|
||
|
||
@bp.post("/chat") | ||
async def chat_handler(): | ||
request_message = (await request.get_json())["message"] | ||
|
||
@stream_with_context | ||
async def response_stream(): | ||
chat_coroutine = openai.ChatCompletion.acreate( | ||
engine=os.getenv("AZURE_OPENAI_CHATGPT_DEPLOYMENT", "chatgpt"), | ||
messages=[ | ||
{"role": "system", "content": "You are a helpful assistant."}, | ||
{"role": "user", "content": request_message}, | ||
], | ||
stream=True, | ||
) | ||
async for event in await chat_coroutine: | ||
current_app.logger.info(event) | ||
yield json.dumps(event, ensure_ascii=False) + "\n" | ||
|
||
return Response(response_stream()) |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
Flask==2.3.3 | ||
quart==0.18.4 | ||
gunicorn==21.2.0 | ||
uvicorn[standard]==0.23.2 | ||
openai==0.28.0 | ||
azure-identity==1.14.0 | ||
python-dotenv==1.0.0 # Pinned due to docker-in-docker issue: https://github.com/devcontainers/features/issues/616 |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import openai | ||
import pytest | ||
import pytest_asyncio | ||
|
||
from . import mock_cred | ||
from src import quartapp | ||
|
||
|
||
@pytest.fixture | ||
def mock_openai_chatcompletion(monkeypatch): | ||
class AsyncChatCompletionIterator: | ||
def __init__(self, answer: str): | ||
self.answer_index = 0 | ||
self.answer_deltas = answer.split(" ") | ||
|
||
def __aiter__(self): | ||
return self | ||
|
||
async def __anext__(self): | ||
if self.answer_index < len(self.answer_deltas): | ||
answer_chunk = self.answer_deltas[self.answer_index] | ||
self.answer_index += 1 | ||
return openai.util.convert_to_openai_object({"choices": [{"delta": {"content": answer_chunk}}]}) | ||
else: | ||
raise StopAsyncIteration | ||
|
||
async def mock_acreate(*args, **kwargs): | ||
return AsyncChatCompletionIterator("The capital of France is Paris.") | ||
|
||
monkeypatch.setattr(openai.ChatCompletion, "acreate", mock_acreate) | ||
|
||
|
||
@pytest.fixture | ||
def mock_defaultazurecredential(monkeypatch): | ||
monkeypatch.setattr("azure.identity.aio.DefaultAzureCredential", mock_cred.MockAzureCredential) | ||
|
||
|
||
@pytest_asyncio.fixture | ||
async def client(monkeypatch, mock_openai_chatcompletion, mock_defaultazurecredential): | ||
monkeypatch.setenv("AZURE_OPENAI_ENDPOINT", "test-openai-service.openai.azure.com") | ||
monkeypatch.setenv("AZURE_OPENAI_CHATGPT_DEPLOYMENT", "test-chatgpt") | ||
|
||
quart_app = quartapp.create_app() | ||
|
||
async with quart_app.test_app() as test_app: | ||
quart_app.config.update({"TESTING": True}) | ||
|
||
yield test_app.test_client() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from collections import namedtuple | ||
|
||
MockToken = namedtuple("MockToken", ["token", "expires_on"]) | ||
|
||
|
||
class MockAzureCredential: | ||
def __init__(self, *args, **kwargs): | ||
pass | ||
|
||
async def get_token(self, uri): | ||
return MockToken("mock_token", 9999999999) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import openai | ||
import pytest | ||
|
||
from . import mock_cred | ||
from src import quartapp | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_index(client): | ||
response = await client.get("/") | ||
assert response.status_code == 200 | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_chat_stream_text(client): | ||
response = await client.post( | ||
"/chat", | ||
json={"message": "What is the capital of France?"}, | ||
) | ||
assert response.status_code == 200 | ||
result = await response.get_data() | ||
assert ( | ||
result | ||
== b'{"choices": [{"delta": {"content": "The"}}]}\n{"choices": [{"delta": {"content": "capital"}}]}\n{"choices": [{"delta": {"content": "of"}}]}\n{"choices": [{"delta": {"content": "France"}}]}\n{"choices": [{"delta": {"content": "is"}}]}\n{"choices": [{"delta": {"content": "Paris."}}]}\n' # noqa | ||
) | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_openai_key(monkeypatch): | ||
monkeypatch.setenv("AZURE_OPENAI_KEY", "test-key") | ||
monkeypatch.setenv("AZURE_OPENAI_ENDPOINT", "test-openai-service.openai.azure.com") | ||
monkeypatch.setenv("AZURE_OPENAI_CHATGPT_DEPLOYMENT", "test-chatgpt") | ||
|
||
quart_app = quartapp.create_app() | ||
|
||
async with quart_app.test_app(): | ||
assert openai.api_type == "azure" | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_openai_managedidentity(monkeypatch): | ||
monkeypatch.setenv("AZURE_OPENAI_CLIENT_ID", "test-client-id") | ||
monkeypatch.setenv("AZURE_OPENAI_ENDPOINT", "test-openai-service.openai.azure.com") | ||
monkeypatch.setenv("AZURE_OPENAI_CHATGPT_DEPLOYMENT", "test-chatgpt") | ||
|
||
monkeypatch.setattr("azure.identity.aio.ManagedIdentityCredential", mock_cred.MockAzureCredential) | ||
|
||
quart_app = quartapp.create_app() | ||
|
||
async with quart_app.test_app(): | ||
assert openai.api_type == "azure_ad" |