Skip to content

Make repo Python 3.9 compatible #135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading