Skip to content

Commit

Permalink
Merge pull request #135 from Azure-Samples/python39
Browse files Browse the repository at this point in the history
Make repo Python 3.9 compatible
  • Loading branch information
pamelafox authored Nov 4, 2024
2 parents e477b96 + 6f7192b commit 1f86926
Show file tree
Hide file tree
Showing 15 changed files with 65 additions and 59 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ services:
context: ..
dockerfile: .devcontainer/Dockerfile
args:
IMAGE: python:3.11
IMAGE: python:3.12

volumes:
- ..:/workspace:cached
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/app-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ jobs:
fail-fast: false
matrix:
os: ["ubuntu-latest", "macos-latest-xlarge", "macos-13", "windows-latest"]
python_version: ["3.10"]
python_version: ["3.9", "3.10", "3.11", "3.12"]
exclude:
- os: macos-latest-xlarge
python_version: "3.9"
- os: macos-latest-xlarge
python_version: "3.10"
env:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ A related option is VS Code Dev Containers, which will open the project in your

* [Azure Developer CLI (azd)](https://aka.ms/install-azd)
* [Node.js 18+](https://nodejs.org/download/)
* [Python 3.10+](https://www.python.org/downloads/)
* [Python 3.9+](https://www.python.org/downloads/)
* [PostgreSQL 14+](https://www.postgresql.org/download/)
* [pgvector](https://github.com/pgvector/pgvector)
* [Docker Desktop](https://www.docker.com/products/docker-desktop/)
Expand Down
5 changes: 3 additions & 2 deletions evals/generate_ground_truth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
from collections.abc import Generator
from pathlib import Path
from typing import Union

from azure.identity import AzureDeveloperCliCredential, get_bearer_token_provider
from dotenv_azd import load_azd_env
Expand Down Expand Up @@ -77,9 +78,9 @@ def answer_formatter(answer, source) -> str:
return f"{answer} [{source['id']}]"


def get_openai_client() -> tuple[AzureOpenAI | OpenAI, str]:
def get_openai_client() -> tuple[Union[AzureOpenAI, OpenAI], str]:
"""Return an OpenAI client based on the environment variables"""
openai_client: AzureOpenAI | OpenAI
openai_client: Union[AzureOpenAI, OpenAI]
OPENAI_CHAT_HOST = os.getenv("OPENAI_CHAT_HOST")
if OPENAI_CHAT_HOST == "azure":
if api_key := os.getenv("AZURE_OPENAI_KEY"):
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[tool.ruff]
line-length = 120
target-version = "py312"
target-version = "py39"
lint.select = ["E", "F", "I", "UP"]
lint.ignore = ["D203"]
lint.isort.known-first-party = ["fastapi_app"]

[tool.mypy]
check_untyped_defs = true
python_version = 3.12
python_version = 3.9
exclude = [".venv/*"]

[tool.pytest.ini_options]
Expand Down
6 changes: 3 additions & 3 deletions src/backend/fastapi_app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import TypedDict
from typing import TypedDict, Union

import fastapi
from azure.monitor.opentelemetry import configure_azure_monitor
Expand All @@ -27,8 +27,8 @@
class State(TypedDict):
sessionmaker: async_sessionmaker[AsyncSession]
context: FastAPIAppContext
chat_client: AsyncOpenAI | AsyncAzureOpenAI
embed_client: AsyncOpenAI | AsyncAzureOpenAI
chat_client: Union[AsyncOpenAI, AsyncAzureOpenAI]
embed_client: Union[AsyncOpenAI, AsyncAzureOpenAI]


@asynccontextmanager
Expand Down
18 changes: 9 additions & 9 deletions src/backend/fastapi_app/api_models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from enum import Enum
from typing import Any
from typing import Any, Optional

from openai.types.chat import ChatCompletionMessageParam
from pydantic import BaseModel
Expand Down Expand Up @@ -27,8 +27,8 @@ class ChatRequestOverrides(BaseModel):
temperature: float = 0.3
retrieval_mode: RetrievalMode = RetrievalMode.HYBRID
use_advanced_flow: bool = True
prompt_template: str | None = None
seed: int | None = None
prompt_template: Optional[str] = None
seed: Optional[int] = None


class ChatRequestContext(BaseModel):
Expand All @@ -38,7 +38,7 @@ class ChatRequestContext(BaseModel):
class ChatRequest(BaseModel):
messages: list[ChatCompletionMessageParam]
context: ChatRequestContext
sessionState: Any | None = None
sessionState: Optional[Any] = None


class ThoughtStep(BaseModel):
Expand All @@ -50,7 +50,7 @@ class ThoughtStep(BaseModel):
class RAGContext(BaseModel):
data_points: dict[int, dict[str, Any]]
thoughts: list[ThoughtStep]
followup_questions: list[str] | None = None
followup_questions: Optional[list[str]] = None


class ErrorResponse(BaseModel):
Expand All @@ -60,13 +60,13 @@ class ErrorResponse(BaseModel):
class RetrievalResponse(BaseModel):
message: Message
context: RAGContext
sessionState: Any | None = None
sessionState: Optional[Any] = None


class RetrievalResponseDelta(BaseModel):
delta: Message | None = None
context: RAGContext | None = None
sessionState: Any | None = None
delta: Optional[Message] = None
context: Optional[RAGContext] = None
sessionState: Optional[Any] = None


class ItemPublic(BaseModel):
Expand Down
14 changes: 7 additions & 7 deletions src/backend/fastapi_app/dependencies.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import os
from collections.abc import AsyncGenerator
from typing import Annotated
from typing import Annotated, Optional, Union

import azure.identity
from fastapi import Depends, Request
Expand All @@ -17,7 +17,7 @@ class OpenAIClient(BaseModel):
OpenAI client
"""

client: AsyncOpenAI | AsyncAzureOpenAI
client: Union[AsyncOpenAI, AsyncAzureOpenAI]
model_config = {"arbitrary_types_allowed": True}


Expand All @@ -28,9 +28,9 @@ class FastAPIAppContext(BaseModel):

openai_chat_model: str
openai_embed_model: str
openai_embed_dimensions: int | None
openai_chat_deployment: str | None
openai_embed_deployment: str | None
openai_embed_dimensions: Optional[int]
openai_chat_deployment: Optional[str]
openai_embed_deployment: Optional[str]
embedding_column: str


Expand Down Expand Up @@ -77,9 +77,9 @@ async def common_parameters():


async def get_azure_credential() -> (
azure.identity.AzureDeveloperCliCredential | azure.identity.ManagedIdentityCredential
Union[azure.identity.AzureDeveloperCliCredential, azure.identity.ManagedIdentityCredential]
):
azure_credential: azure.identity.AzureDeveloperCliCredential | azure.identity.ManagedIdentityCredential
azure_credential: Union[azure.identity.AzureDeveloperCliCredential, azure.identity.ManagedIdentityCredential]
try:
if client_id := os.getenv("APP_IDENTITY_ID"):
# Authenticate using a user-assigned managed identity on Azure
Expand Down
10 changes: 4 additions & 6 deletions src/backend/fastapi_app/embeddings.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
from typing import (
TypedDict,
)
from typing import Optional, TypedDict, Union

from openai import AsyncAzureOpenAI, AsyncOpenAI


async def compute_text_embedding(
q: str,
openai_client: AsyncOpenAI | AsyncAzureOpenAI,
openai_client: Union[AsyncOpenAI, AsyncAzureOpenAI],
embed_model: str,
embed_deployment: str | None = None,
embedding_dimensions: int | None = None,
embed_deployment: Optional[str] = None,
embedding_dimensions: Optional[int] = None,
) -> list[float]:
SUPPORTED_DIMENSIONS_MODEL = {
"text-embedding-ada-002": False,
Expand Down
13 changes: 7 additions & 6 deletions src/backend/fastapi_app/openai_clients.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import os
from typing import Union

import azure.identity
import openai
Expand All @@ -8,9 +9,9 @@


async def create_openai_chat_client(
azure_credential: azure.identity.AzureDeveloperCliCredential | azure.identity.ManagedIdentityCredential,
) -> openai.AsyncAzureOpenAI | openai.AsyncOpenAI:
openai_chat_client: openai.AsyncAzureOpenAI | openai.AsyncOpenAI
azure_credential: Union[azure.identity.AzureDeveloperCliCredential, azure.identity.ManagedIdentityCredential],
) -> Union[openai.AsyncAzureOpenAI, openai.AsyncOpenAI]:
openai_chat_client: Union[openai.AsyncAzureOpenAI, openai.AsyncOpenAI]
OPENAI_CHAT_HOST = os.getenv("OPENAI_CHAT_HOST")
if OPENAI_CHAT_HOST == "azure":
api_version = os.environ["AZURE_OPENAI_VERSION"] or "2024-03-01-preview"
Expand Down Expand Up @@ -57,9 +58,9 @@ async def create_openai_chat_client(


async def create_openai_embed_client(
azure_credential: azure.identity.AzureDeveloperCliCredential | azure.identity.ManagedIdentityCredential,
) -> openai.AsyncAzureOpenAI | openai.AsyncOpenAI:
openai_embed_client: openai.AsyncAzureOpenAI | openai.AsyncOpenAI
azure_credential: Union[azure.identity.AzureDeveloperCliCredential, azure.identity.ManagedIdentityCredential],
) -> Union[openai.AsyncAzureOpenAI, openai.AsyncOpenAI]:
openai_embed_client: Union[openai.AsyncAzureOpenAI, openai.AsyncOpenAI]
OPENAI_EMBED_HOST = os.getenv("OPENAI_EMBED_HOST")
if OPENAI_EMBED_HOST == "azure":
api_version = os.environ["AZURE_OPENAI_VERSION"] or "2024-03-01-preview"
Expand Down
14 changes: 8 additions & 6 deletions src/backend/fastapi_app/postgres_searcher.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Optional, Union

import numpy as np
from openai import AsyncAzureOpenAI, AsyncOpenAI
from sqlalchemy import Float, Integer, column, select, text
Expand All @@ -11,10 +13,10 @@ class PostgresSearcher:
def __init__(
self,
db_session: AsyncSession,
openai_embed_client: AsyncOpenAI | AsyncAzureOpenAI,
embed_deployment: str | None, # Not needed for non-Azure OpenAI or for retrieval_mode="text"
openai_embed_client: Union[AsyncOpenAI, AsyncAzureOpenAI],
embed_deployment: Optional[str], # Not needed for non-Azure OpenAI or for retrieval_mode="text"
embed_model: str,
embed_dimensions: int | None,
embed_dimensions: Optional[int],
embedding_column: str,
):
self.db_session = db_session
Expand All @@ -38,7 +40,7 @@ def build_filter_clause(self, filters) -> tuple[str, str]:
return "", ""

async def search(
self, query_text: str | None, query_vector: list[float] | list, top: int = 5, filters: list[dict] | None = None
self, query_text: Optional[str], query_vector: list[float], top: int = 5, filters: Optional[list[dict]] = None
):
filter_clause_where, filter_clause_and = self.build_filter_clause(filters)
table_name = Item.__tablename__
Expand Down Expand Up @@ -100,11 +102,11 @@ async def search(

async def search_and_embed(
self,
query_text: str | None = None,
query_text: Optional[str] = None,
top: int = 5,
enable_vector_search: bool = False,
enable_text_search: bool = False,
filters: list[dict] | None = None,
filters: Optional[list[dict]] = None,
) -> list[Item]:
"""
Search rows by query text. Optionally converts the query text to a vector if enable_vector_search is True.
Expand Down
10 changes: 5 additions & 5 deletions src/backend/fastapi_app/rag_advanced.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from collections.abc import AsyncGenerator
from typing import Any, Final
from typing import Any, Final, Optional, Union

from openai import AsyncAzureOpenAI, AsyncOpenAI, AsyncStream
from openai.types.chat import ChatCompletion, ChatCompletionChunk, ChatCompletionMessageParam
Expand All @@ -24,9 +24,9 @@ def __init__(
self,
*,
searcher: PostgresSearcher,
openai_chat_client: AsyncOpenAI | AsyncAzureOpenAI,
openai_chat_client: Union[AsyncOpenAI, AsyncAzureOpenAI],
chat_model: str,
chat_deployment: str | None, # Not needed for non-Azure OpenAI
chat_deployment: Optional[str], # Not needed for non-Azure OpenAI
):
self.searcher = searcher
self.openai_chat_client = openai_chat_client
Expand All @@ -39,8 +39,8 @@ async def generate_search_query(
original_user_query: str,
past_messages: list[ChatCompletionMessageParam],
query_response_token_limit: int,
seed: int | None = None,
) -> tuple[list[ChatCompletionMessageParam], Any | str | None, list]:
seed: Optional[int] = None,
) -> tuple[list[ChatCompletionMessageParam], Union[Any, str, None], list]:
"""Generate an optimized keyword search query based on the chat history and the last question"""

tools = build_search_function()
Expand Down
5 changes: 3 additions & 2 deletions src/backend/fastapi_app/rag_simple.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections.abc import AsyncGenerator
from typing import Optional, Union

from openai import AsyncAzureOpenAI, AsyncOpenAI, AsyncStream
from openai.types.chat import ChatCompletion, ChatCompletionChunk, ChatCompletionMessageParam
Expand All @@ -22,9 +23,9 @@ def __init__(
self,
*,
searcher: PostgresSearcher,
openai_chat_client: AsyncOpenAI | AsyncAzureOpenAI,
openai_chat_client: Union[AsyncOpenAI, AsyncAzureOpenAI],
chat_model: str,
chat_deployment: str | None, # Not needed for non-Azure OpenAI
chat_deployment: Optional[str], # Not needed for non-Azure OpenAI
):
self.searcher = searcher
self.openai_chat_client = openai_chat_client
Expand Down
7 changes: 4 additions & 3 deletions src/backend/fastapi_app/routes/api_routes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import logging
from collections.abc import AsyncGenerator
from typing import Union

import fastapi
from fastapi import HTTPException
Expand Down Expand Up @@ -93,7 +94,7 @@ async def search_handler(
return [ItemPublic.model_validate(item.to_dict()) for item in results]


@router.post("/chat", response_model=RetrievalResponse | ErrorResponse)
@router.post("/chat", response_model=Union[RetrievalResponse, ErrorResponse])
async def chat_handler(
context: CommonDeps,
database_session: DBSession,
Expand All @@ -110,7 +111,7 @@ async def chat_handler(
embed_dimensions=context.openai_embed_dimensions,
embedding_column=context.embedding_column,
)
rag_flow: SimpleRAGChat | AdvancedRAGChat
rag_flow: Union[SimpleRAGChat, AdvancedRAGChat]
if chat_request.context.overrides.use_advanced_flow:
rag_flow = AdvancedRAGChat(
searcher=searcher,
Expand Down Expand Up @@ -154,7 +155,7 @@ async def chat_stream_handler(
embedding_column=context.embedding_column,
)

rag_flow: SimpleRAGChat | AdvancedRAGChat
rag_flow: Union[SimpleRAGChat, AdvancedRAGChat]
if chat_request.context.overrides.use_advanced_flow:
rag_flow = AdvancedRAGChat(
searcher=searcher,
Expand Down
10 changes: 5 additions & 5 deletions tests/mocks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any
from typing import Any, Optional

from azure.core.credentials import AccessToken, TokenCredential

Expand All @@ -7,8 +7,8 @@ class MockAzureCredential(TokenCredential):
def get_token(
self,
*scopes: str,
claims: str | None = None,
tenant_id: str | None = None,
claims: Optional[str] = None,
tenant_id: Optional[str] = None,
enable_cae: bool = False,
**kwargs: Any,
) -> AccessToken:
Expand All @@ -22,8 +22,8 @@ def __init__(self):
def get_token(
self,
*scopes: str,
claims: str | None = None,
tenant_id: str | None = None,
claims: Optional[str] = None,
tenant_id: Optional[str] = None,
enable_cae: bool = False,
**kwargs: Any,
) -> AccessToken:
Expand Down

0 comments on commit 1f86926

Please sign in to comment.