Skip to content

Commit

Permalink
Merge pull request #5 from tmeftah/nikhil_raikar
Browse files Browse the repository at this point in the history
Cleaned backend and added q-card with a bit of small minor style changes in the frontend
  • Loading branch information
tmeftah authored Aug 4, 2024
2 parents dbfab5e + b3d6790 commit b806711
Show file tree
Hide file tree
Showing 18 changed files with 177 additions and 94 deletions.
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
name: FastAPI CI

on: [push, pull_request]
on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v4
with:
python-version: '3.10'
cache: 'pip'
cache: 'pip'

- name: Install dependencies
run: |
Expand All @@ -25,8 +25,8 @@ jobs:
run: |
cd backend
nohup fastapi dev > /dev/null 2>&1 &
echo $! > uvicorn.pid
sleep 10
echo $! > uvicorn.pid
sleep 10
- name: Check FastAPI /health endpoint
run: |
Expand Down
2 changes: 1 addition & 1 deletion backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,4 @@ cython_debug/
#.idea/
*.db
*docs
chroma_db
chroma_db
42 changes: 42 additions & 0 deletions backend/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# .pre-commit-config.yaml

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0 # Replace with the latest version if needed
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict

- repo: https://github.com/psf/black
rev: 23.1.0 # Replace with the latest version if needed
hooks:
- id: black
language_version: python3 # Adjust if you use a specific Python version

- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
hooks:
- id: flake8



- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.4.1 # Replace with the latest version if needed
hooks:
- id: mypy
args: [--ignore-missing-imports, --disallow-untyped-calls, --disallow-untyped-defs]

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0 # Replace with the latest version if needed
hooks:
- id: check-json
- id: check-xml

- repo: https://github.com/pre-commit/mirrors-pylint
rev: v3.0.0a5 # Replace with the latest version if needed
hooks:
- id: pylint
args: ["--max-line-length=88"]
1 change: 0 additions & 1 deletion backend/api/health.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

from fastapi import APIRouter

health_router = APIRouter()
Expand Down
10 changes: 6 additions & 4 deletions backend/api/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@
@query_router.get("/query")
async def query(query: str):
# if current_user.role < 5:
# raise HTTPException(status_code=403, detail="Only admin users can delete other users")
# raise HTTPException(status_code=403,
# detail="Only admin users can delete other users")
store = get_vectorstore()
docs = store.invoke(query)

print(20*"*", "docs", 20*"*", "\n", docs)
print(20 * "*", "docs", 20 * "*", "\n", docs)

async def stream_generator():
# Use the LangChain model to generate text
print(20*'*', "\n", query)
print(20 * "*", "\n", query)
async for text in chain.astream({"input": query, "context": docs}):
yield json.dumps({"event_id": str(uuid.uuid4()), "data": text})

# TODO here we have to add the metadata/source

return StreamingResponse(stream_generator(), media_type="application/x-ndjson")
return StreamingResponse(stream_generator(),
media_type="application/x-ndjson")
3 changes: 1 addition & 2 deletions backend/api/token.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

from fastapi import APIRouter, Depends, HTTPException
from backend.pydantic_models import Token
from backend.oauth import authenticate_user, create_access_token
Expand All @@ -23,4 +22,4 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
return {"access_token": access_token, "token_type": "bearer"}
42 changes: 29 additions & 13 deletions backend/api/user.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

from fastapi import APIRouter, Depends, HTTPException
from backend.sqlalchemy_models import User
from backend.sessions import session
Expand All @@ -8,7 +7,6 @@
user_router = APIRouter()



@user_router.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
return {"username": current_user.username, "role": current_user.role}
Expand All @@ -18,38 +16,55 @@ async def read_users_me(current_user: User = Depends(get_current_user)):
async def read_users(current_user: User = Depends(get_current_user)):
if current_user.role < 5:
raise HTTPException(
status_code=403, detail="Only admin users can view all users")
return [{"username": user.username, "role": user.role} for user in session.query(User).all()]
status_code=403, detail="Only admin users can view all users"
)
return [
{"username": user.username, "role": user.role}
for user in session.query(User).all()
]


@user_router.get("/users/{user_id}")
async def read_user(user_id: int, current_user: User = Depends(get_current_user)):
if current_user.id != user_id and current_user.role < 5:
raise HTTPException(
status_code=403, detail="Only admin users can view other users")
status_code=403, detail="Only admin users can view other users"
)
user = session.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
return {"username": user.username, "role": user.role}


@user_router.post("/users/")
async def create_user(username: str, password: str, role: int, current_user: User = Depends(get_current_user)):
async def create_user(
username: str,
password: str,
role: int,
current_user: User = Depends(get_current_user),
):
if current_user.role < 5:
raise HTTPException(
status_code=403, detail="Only admin users can create new users")
user = User(username=username,
password_hash=encrypt_password(password), role=role)
status_code=403, detail="Only admin users can create new users"
)
user = User(username=username, password_hash=encrypt_password(password), role=role)
session.add(user)
session.commit()
return {"username": user.username, "role": user.role}


@user_router.put("/users/{user_id}")
async def update_user(user_id: int, username: str, password: str, role: int, current_user: User = Depends(get_current_user)):
async def update_user(
user_id: int,
username: str,
password: str,
role: int,
current_user: User = Depends(get_current_user),
):
if current_user.id != user_id and current_user.role < 5:
raise HTTPException(
status_code=403, detail="Only admin users can update other users")
status_code=403, detail="Only admin users can update other users"
)
user = session.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
Expand All @@ -64,10 +79,11 @@ async def update_user(user_id: int, username: str, password: str, role: int, cur
async def delete_user(user_id: int, current_user: User = Depends(get_current_user)):
if current_user.id != user_id and current_user.role < 5:
raise HTTPException(
status_code=403, detail="Only admin users can delete other users")
status_code=403, detail="Only admin users can delete other users"
)
user = session.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
session.delete(user)
session.commit()
return {"message": "User deleted"}
return {"message": "User deleted"}
4 changes: 1 addition & 3 deletions backend/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# No origins are limited for now
# All origins are allowed for now and this
# All origins are allowed for now and this
# must be changed
origins = [
"http://localhost",
"http://127.0.0.1:8000",
]


29 changes: 15 additions & 14 deletions backend/embeddings/ingest.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# LangChain supports many other chat models. Here, we're using Ollama
from langchain_community.chat_models import ChatOllama
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
# from langchain_community.chat_models import ChatOllama
# from langchain_core.output_parsers import StrOutputParser
# from langchain_core.prompts import ChatPromptTemplate
# from langchain_core.runnables import RunnablePassthrough
# from langchain_core.documents import Document

from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

from langchain_chroma import Chroma
from langchain_community.embeddings import OllamaEmbeddings
from langchain_core.documents import Document
import os
import argparse

Expand All @@ -23,13 +24,13 @@ def create_vectorstore():
# Set a really small chunk size, just to show.
chunk_size=1300,
chunk_overlap=110,
length_function=len
length_function=len,
)

documents = []
for file in os.listdir('docs'):
if file.endswith('.pdf'):
pdf_path = './docs/' + file
for file in os.listdir("docs"):
if file.endswith(".pdf"):
pdf_path = "./docs/" + file
loader = PyPDFLoader(pdf_path)
doc = loader.load()
document_split = text_splitter.split_documents(doc)
Expand All @@ -39,24 +40,24 @@ def create_vectorstore():
collection_name="kardex",
documents=documents,
embedding=OllamaEmbeddings(model="mxbai-embed-large"),
persist_directory="./vectorstore/chroma_db"
persist_directory="./vectorstore/chroma_db",
)

print("vectorstore created...")


def get_vectorstore():
persistent_client = chromadb.PersistentClient(
path="./vectorstore/chroma_db")
persistent_client = chromadb.PersistentClient(path="./vectorstore/chroma_db")
langchain_chroma = Chroma(
client=persistent_client,
collection_name="kardex",
embedding_function=OllamaEmbeddings(model="mxbai-embed-large"),
)
# print("There are", langchain_chroma._collection.count(), "in the collection")
# print("There are", langchain_chroma.similarity_search("bmw?"))
return langchain_chroma.as_retriever(search_type="mmr",
search_kwargs={'k': 3, 'lambda_mult': 0.25})
return langchain_chroma.as_retriever(
search_type="mmr", search_kwargs={"k": 3, "lambda_mult": 0.25}
)


if __name__ == "__main__":
Expand Down
4 changes: 2 additions & 2 deletions backend/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from backend.api.health import health_router
from backend.api.health import health_router
from backend.api.user import user_router
from backend.api.token import token_router
from backend.api.query import query_router
Expand All @@ -12,7 +12,7 @@

app.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
Expand Down
10 changes: 7 additions & 3 deletions backend/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def encrypt_password(password: str):

def encrypt_password(password: str) -> bytes:
return hashpw(password.encode(), gensalt())


def verify_password(plain_password: str, hashed_password: str):
def verify_password(plain_password: str, hashed_password: str) -> bytes:
return checkpw(plain_password.encode(), hashed_password)


def get_user(username: str):
return session.query(User).filter(User.username == username).first()

Expand All @@ -31,6 +33,7 @@ def authenticate_user(username: str, password: str):
return False
return user


async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=401,
Expand All @@ -52,6 +55,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
raise credentials_exception
return user


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
Expand All @@ -60,4 +64,4 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
return encoded_jwt
3 changes: 2 additions & 1 deletion backend/pydantic_models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from pydantic import BaseModel
from typing import Optional


class Token(BaseModel):
access_token: str
token_type: str


class TokenData(BaseModel):
username: Optional[str] = None
username: Optional[str] = None
2 changes: 1 addition & 1 deletion backend/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
Base.metadata.create_all(engine)

session_maker = sessionmaker(bind=engine)
session = session_maker()
session = session_maker()
3 changes: 2 additions & 1 deletion backend/sqlalchemy_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

Base = declarative_base()


class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
username = Column(String, unique=True)
password_hash = Column(String)
# 1 = user, 4 = manager, 5 = admin, 6 = superadmin
role = Column(Integer, default=1)
role = Column(Integer, default=1)
Loading

0 comments on commit b806711

Please sign in to comment.