Skip to content

Commit

Permalink
Merge pull request #77 from ansari-project/code-reorg
Browse files Browse the repository at this point in the history
Moving files around to adhere to Python standards
  • Loading branch information
waleedkadous authored Nov 11, 2024
2 parents cf47a5e + fc02ec1 commit 8baf93a
Show file tree
Hide file tree
Showing 40 changed files with 131 additions and 64 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ jobs:
WHATSAPP_BUSINESS_PHONE_NUMBER_ID: ${{ secrets.WHATSAPP_BUSINESS_PHONE_NUMBER_ID }}
WHATSAPP_ACCESS_TOKEN_FROM_SYS_USER: ${{ secrets.WHATSAPP_ACCESS_TOKEN_FROM_SYS_USER }}
WHATSAPP_VERIFY_TOKEN_FOR_WEBHOOK: ${{ secrets.WHATSAPP_VERIFY_TOKEN_FOR_WEBHOOK }}

PYTHONPATH: src

container: python:3.10
services:
postgres:
Expand Down
9 changes: 0 additions & 9 deletions main_file.py

This file was deleted.

23 changes: 23 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "ansari-backend"
version = "0.1.0"
description = "Ansari is an AI assistant to enhance understanding and practice of Islam."
authors = [
{ name = "Ansari Project", email = "feedback@ansari.chat" }
]
requires-python = ">=3.8"

[project.urls]
Homepage = "https://github.com/ansari-project/ansari-backend"
Documentation = "https://github.com/ansari-project/ansari-backend"
Source = "https://github.com/ansari-project/ansari-backend"
Tracker = "https://github.com/ansari-project/ansari-backend/issues"

[tool.ruff]
line-length = 88
lint.select = ["E", "F", "W"]
lint.ignore = ["E501"]
3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
markers =
asyncio: mark a test as asyncio
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
bcrypt
build
discord.py
diskcache
fastapi
Expand All @@ -14,10 +15,13 @@ psycopg2-binary
pydantic_settings
pyislam
pyjwt
pytest-asyncio
rich
sendgrid
setuptools
tenacity
tiktoken
typer
uvicorn
wheel
zxcvbn
4 changes: 4 additions & 0 deletions src/ansari/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This file marks the directory as a Python package.
from .config import Settings, get_settings

__all__ = ["Settings", "get_settings"]
4 changes: 4 additions & 0 deletions src/ansari/agents/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .ansari import Ansari
from .ansari_workflow import AnsariWorkflow

__all__ = ["Ansari", "AnsariWorkflow"]
14 changes: 6 additions & 8 deletions agents/ansari.py → src/ansari/agents/ansari.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
import json
import logging
import os
import re
import sys
import time
import traceback
from datetime import date, datetime
Expand All @@ -12,11 +10,11 @@
import litellm
from langfuse.decorators import langfuse_context, observe

from config import get_settings
from tools.search_hadith import SearchHadith
from tools.search_vectara import SearchVectara
from tools.search_quran import SearchQuran
from util.prompt_mgr import PromptMgr
from ansari.config import get_settings
from ansari.tools.search_hadith import SearchHadith
from ansari.tools.search_vectara import SearchVectara
from ansari.tools.search_quran import SearchQuran
from ansari.util.prompt_mgr import PromptMgr

logger = logging.getLogger(__name__ + ".Ansari")
logging_level = get_settings().LOGGING_LEVEL.upper()
Expand Down Expand Up @@ -49,7 +47,7 @@ def __init__(self, settings, message_logger=None, json_format=False):
sm.get_tool_name(): sm,
}
self.model = settings.MODEL
self.pm = PromptMgr()
self.pm = PromptMgr(src_dir=settings.PROMPT_PATH)
self.sys_msg = self.pm.bind(settings.SYSTEM_PROMPT_FILE_NAME).render()
self.tools = [
x.get_tool_description() for x in self.tool_name_to_instance.values()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

import litellm

from tools.search_hadith import SearchHadith
from tools.search_vectara import SearchVectara
from tools.search_quran import SearchQuran
from util.prompt_mgr import PromptMgr
from ansari.tools.search_hadith import SearchHadith
from ansari.tools.search_vectara import SearchVectara
from ansari.tools.search_quran import SearchQuran
from ansari.util.prompt_mgr import PromptMgr

logger = logging.getLogger(__name__ + ".AnsariWorkflow")

Expand Down
7 changes: 3 additions & 4 deletions ansari_db.py → src/ansari/ansari_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
import logging
from contextlib import contextmanager
from datetime import datetime, timedelta, timezone

from typing import Union
import bcrypt
import jwt
import psycopg2
import psycopg2.pool
from fastapi import HTTPException, Request
from jwt import ExpiredSignatureError, InvalidTokenError

from config import Settings, get_settings
from ansari.config import Settings, get_settings

logger = logging.getLogger(__name__)
logging_level = get_settings().LOGGING_LEVEL.upper()
Expand Down Expand Up @@ -588,7 +588,7 @@ def store_quran_answer(self, surah: int, ayah: int, question: str, ansari_answer
)
conn.commit()

def get_quran_answer(self, surah: int, ayah: int, question: str) -> str | None:
def get_quran_answer(self, surah: int, ayah: int, question: str) -> Union[str, None]:
"""
Retrieve the stored answer for a given surah, ayah, and question.
Expand All @@ -612,7 +612,6 @@ def get_quran_answer(self, surah: int, ayah: int, question: str) -> str | None:
"""
cur.execute(select_cmd, (surah, ayah, question))
result = cur.fetchone()

if result:
return result[0]
else:
Expand Down
File renamed without changes.
24 changes: 12 additions & 12 deletions main_api.py → src/ansari/app/main_api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import os
from typing import Union
import uuid

import psycopg2
Expand All @@ -16,12 +17,12 @@
from sendgrid.helpers.mail import Mail
from zxcvbn import zxcvbn

from agents.ansari import Ansari
from agents.ansari_workflow import AnsariWorkflow
from ansari_db import AnsariDB, MessageLogger
from config import Settings, get_settings
from main_whatsapp import router as whatsapp_router
from presenters.api_presenter import ApiPresenter
from ansari.agents import Ansari
from ansari.agents import AnsariWorkflow
from ansari.ansari_db import AnsariDB, MessageLogger
from ansari.config import Settings, get_settings
from ansari.app.main_whatsapp import router as whatsapp_router
from ansari.presenters.api_presenter import ApiPresenter

logger = logging.getLogger(__name__)
logging_level = get_settings().LOGGING_LEVEL.upper()
Expand All @@ -36,7 +37,6 @@
def main():
add_app_middleware()


def add_app_middleware():
app.add_middleware(
CORSMiddleware,
Expand All @@ -46,8 +46,7 @@ def add_app_middleware():
allow_headers=["*"],
)

main()

main()
db = AnsariDB(get_settings())
ansari = Ansari(get_settings())

Expand Down Expand Up @@ -689,7 +688,7 @@ class AyahQuestionRequest(BaseModel):
surah: int
ayah: int
question: str
augment_question: bool | None = False
augment_question: Union[bool,None] = False
apikey: str

@app.post("/api/v2/ayah")
Expand All @@ -703,6 +702,7 @@ async def answer_ayah_question(

try:
# Create AnsariWorkflow instance
logging.debug("Creating AnsariWorkflow instance for {req.surah}:{req.ayah}")
ansari_workflow = AnsariWorkflow(settings)

ayah_id = req.surah*1000 + req.ayah
Expand Down Expand Up @@ -750,6 +750,6 @@ async def answer_ayah_question(
db.store_quran_answer(req.surah, req.ayah, req.question, ansari_answer)

return {"response": ansari_answer}
except Exception as e:
logger.error(f"Error in answer_ayah_question: {str(e)}")
except Exception:
logger.error("Error in answer_ayah_question", exc_info=True)
raise HTTPException(status_code=500, detail="Internal server error")
4 changes: 2 additions & 2 deletions main_discord.py → src/ansari/app/main_discord.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from agents.ansari import Ansari
from config import get_settings
from ansari.agents import Ansari
from ansari.config import get_settings
from presenters.discord_presenter import DiscordPresenter

# This work involves 3 agents, with Ansari as primary.
Expand Down
9 changes: 9 additions & 0 deletions src/ansari/app/main_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import typer

from ansari.agents import Ansari
from ansari.config import get_settings
from ansari.presenters.file_presenter import FilePresenter

if __name__ == "__main__":
ansari = Ansari(get_settings())
typer.run(FilePresenter(ansari).present)
6 changes: 3 additions & 3 deletions main_gradio.py → src/ansari/app/main_gradio.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from agents.ansari import Ansari
from config import get_settings
from presenters.gradio_presenter import GradioPresenter
from ansari.agents import Ansari
from ansari.config import get_settings
from ansari.presenters.gradio_presenter import GradioPresenter

if __name__ == "__main__":
agent = Ansari(get_settings())
Expand Down
6 changes: 3 additions & 3 deletions main_stdio.py → src/ansari/app/main_stdio.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import logging

from agents.ansari import Ansari
from config import get_settings
from presenters.stdio_presenter import StdioPresenter
from ansari.agents import Ansari
from ansari.config import get_settings
from ansari.presenters.stdio_presenter import StdioPresenter

if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
Expand Down
6 changes: 3 additions & 3 deletions main_whatsapp.py → src/ansari/app/main_whatsapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import HTMLResponse

from agents.ansari import Ansari
from config import get_settings
from presenters.whatsapp_presenter import WhatsAppPresenter
from ansari.agents import Ansari
from ansari.config import get_settings
from ansari.presenters.whatsapp_presenter import WhatsAppPresenter

# Initialize logging
logger = logging.getLogger(__name__)
Expand Down
26 changes: 19 additions & 7 deletions config.py → src/ansari/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from pathlib import Path
from functools import lru_cache
from typing import Literal, Optional, Union

Expand Down Expand Up @@ -33,6 +34,15 @@ class Settings(BaseSettings):
missing="ignore",
)

def get_resource_path(filename):
# Get the directory of the current script
script_dir = Path(__file__).resolve()
# Construct the path to the resources directory
resources_dir = script_dir.parent / 'resources'
# Construct the full path to the resource file
path = resources_dir / filename
return path

DATABASE_URL: PostgresDsn = Field(
default="postgresql://postgres:password@localhost:5432/ansari"
)
Expand Down Expand Up @@ -82,12 +92,12 @@ class Settings(BaseSettings):
TAFSIR_FN_NAME: str = Field(default="search_tafsir")
TAFSIR_FN_DESCRIPTION: str = Field(
default="""
Queries Tafsir Ibn Kathir (the renowned Qur'anic exegesis) for relevant
interpretations and explanations. You call this function when you need to
provide authoritative Qur'anic commentary and understanding based on Ibn
Kathir's work. Regardless of the language used in the original conversation,
you will translate the query into English before searching the tafsir. The
function returns a list of **potentially** relevant matches, which may include
Queries Tafsir Ibn Kathir (the renowned Qur'anic exegesis) for relevant
interpretations and explanations. You call this function when you need to
provide authoritative Qur'anic commentary and understanding based on Ibn
Kathir's work. Regardless of the language used in the original conversation,
you will translate the query into English before searching the tafsir. The
function returns a list of **potentially** relevant matches, which may include
multiple passages of interpretation and analysis.
"""
)
Expand All @@ -114,13 +124,15 @@ class Settings(BaseSettings):
WHATSAPP_ACCESS_TOKEN_FROM_SYS_USER: Optional[SecretStr] = Field(default=None)
WHATSAPP_VERIFY_TOKEN_FOR_WEBHOOK: Optional[SecretStr] = Field(default=None)

template_dir: DirectoryPath = Field(default="resources/templates")
template_dir: DirectoryPath = Field(default=get_resource_path("templates"))
diskcache_dir: str = Field(default="diskcache_dir")

MODEL: str = Field(default="gpt-4o")
MAX_TOOL_TRIES: int = Field(default=3)
MAX_FAILURES: int = Field(default=1)
SYSTEM_PROMPT_FILE_NAME: str = Field(default="system_msg_tool")
PROMPT_PATH: str = Field(default=str(get_resource_path("prompts")))


LOGGING_LEVEL: str = Field(default="INFO")

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from config import get_settings
from ansari.config import get_settings

# Initialize logging
logger = logging.getLogger(__name__)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import logging
import requests


Expand Down
4 changes: 4 additions & 0 deletions src/ansari/util/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This file makes the 'util' directory a package.
from .prompt_mgr import PromptMgr

__all__ = ["PromptMgr"]
12 changes: 11 additions & 1 deletion util/prompt_mgr.py → src/ansari/util/prompt_mgr.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Union

from pydantic import BaseModel
from pathlib import Path


class Prompt(BaseModel):
Expand All @@ -16,7 +17,16 @@ def render(self, **kwargs) -> str:


class PromptMgr:
def __init__(self, hot_reload: bool = True, src_dir: str = "resources/prompts"):
def get_resource_path(filename):
# Get the directory of the current script
script_dir = Path(__file__).resolve()
# Construct the path to the resources directory
resources_dir = script_dir.parent.parent / 'resources'
# Construct the full path to the resource file
path = resources_dir / filename
return path

def __init__(self, hot_reload: bool = True, src_dir: str = str(get_resource_path("prompts"))):
"""Creates a prompt manager.
Args:
Expand Down
Loading

0 comments on commit 8baf93a

Please sign in to comment.