From 3828f24913a01772f0ca4c9757ed750600154b2a Mon Sep 17 00:00:00 2001 From: mazzasaverio Date: Mon, 1 Apr 2024 16:59:03 +0200 Subject: [PATCH] REFACTOR --- backend/app/api/__init__.py | 0 backend/app/api/main.py | 11 +++ backend/app/api/routes/__init__.py | 0 backend/app/api/routes/qa.py | 113 +++++++++++++++++++++++++++++ backend/app/config.yml | 17 +++++ backend/app/core/config.py | 17 ++++- backend/app/core/db.py | 89 ++++++++--------------- backend/app/main.py | 25 ++++--- backend/app/schemas/chat_schema.py | 5 ++ backend/ingestion/core/config.py | 43 +++++++++++ backend/ingestion/helpers/db.py | 69 ++++++++++++++++++ backend/ingestion/pdf_ingestion.py | 2 +- backend/logging_config.py | 15 ---- backend/poetry.lock | 92 ++++++++++++++++++++++- backend/pyproject.toml | 4 + backend/requirements.txt | 26 ------- 16 files changed, 411 insertions(+), 117 deletions(-) create mode 100644 backend/app/api/__init__.py create mode 100644 backend/app/api/main.py create mode 100644 backend/app/api/routes/__init__.py create mode 100644 backend/app/api/routes/qa.py create mode 100644 backend/app/config.yml create mode 100644 backend/app/schemas/chat_schema.py create mode 100644 backend/ingestion/core/config.py create mode 100644 backend/ingestion/helpers/db.py delete mode 100644 backend/logging_config.py delete mode 100644 backend/requirements.txt diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/api/main.py b/backend/app/api/main.py new file mode 100644 index 0000000..8df2684 --- /dev/null +++ b/backend/app/api/main.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter + +from app.api.routes import qa + +api_router = APIRouter() + +api_router.include_router( + qa.router, + prefix="/qa", + tags=["qa"], +) diff --git a/backend/app/api/routes/__init__.py b/backend/app/api/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/api/routes/qa.py b/backend/app/api/routes/qa.py new file mode 100644 index 0000000..657cdbb --- /dev/null +++ b/backend/app/api/routes/qa.py @@ -0,0 +1,113 @@ +from app.core.db import SessionLocal +import os +import yaml +from fastapi import APIRouter + + +from app.core.config import logger + + +from operator import itemgetter + +from langchain_community.vectorstores import FAISS +from langchain_core.output_parsers import StrOutputParser +from langchain_core.prompts import ChatPromptTemplate +from langchain_core.runnables import RunnableLambda, RunnablePassthrough +from langchain_openai import ChatOpenAI, OpenAIEmbeddings +from langchain_core.messages import AIMessage, HumanMessage, get_buffer_string +from langchain_core.prompts import format_document +from langchain_core.runnables import RunnableParallel +from langchain_community.vectorstores.pgvector import PGVector +from langchain.memory import ConversationBufferMemory +from app.core.config import settings +from langchain.prompts.prompt import PromptTemplate +from pydantic import BaseModel + +router = APIRouter() + +config_path = os.path.join(os.path.dirname(__file__), "..", "..", "config.yml") +with open(config_path, "r") as config_file: + config = yaml.load(config_file, Loader=yaml.FullLoader) + +chat_config = config.get("CHAT_CONFIG", None) + + +class ChatRequest(BaseModel): + message: str + + +@router.post("/chat") +async def chat_action(request: ChatRequest): + + embeddings = OpenAIEmbeddings() + + store = PGVector( + collection_name="docs", + connection_string=settings.SYNC_DATABASE_URI, + embedding_function=embeddings, + ) + + retriever = store.as_retriever() + + # Load prompts from configuration + _template_condense = chat_config["PROMPTS"]["CONDENSE_QUESTION"] + _template_answer = chat_config["PROMPTS"]["ANSWER_QUESTION"] + _template_default_document = chat_config["PROMPTS"]["DEFAULT_DOCUMENT"] + + # Your existing logic here, replace hardcoded prompt templates with loaded ones + # Example of using loaded prompts: + CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(_template_condense) + ANSWER_PROMPT = ChatPromptTemplate.from_template(_template_answer) + DEFAULT_DOCUMENT_PROMPT = PromptTemplate.from_template(_template_default_document) + + def _combine_documents( + docs, document_prompt=DEFAULT_DOCUMENT_PROMPT, document_separator="\n\n" + ): + doc_strings = [format_document(doc, document_prompt) for doc in docs] + + return document_separator.join(doc_strings) + + memory = ConversationBufferMemory( + return_messages=True, output_key="answer", input_key="question" + ) + + # First we add a step to load memory + # This adds a "memory" key to the input object + loaded_memory = RunnablePassthrough.assign( + chat_history=RunnableLambda(memory.load_memory_variables) + | itemgetter("history"), + ) + # Now we calculate the standalone question + standalone_question = { + "standalone_question": { + "question": lambda x: x["question"], + "chat_history": lambda x: get_buffer_string(x["chat_history"]), + } + | CONDENSE_QUESTION_PROMPT + | ChatOpenAI(temperature=0) + | StrOutputParser(), + } + # Now we retrieve the documents + retrieved_documents = { + "docs": itemgetter("standalone_question") | retriever, + "question": lambda x: x["standalone_question"], + } + # Now we construct the inputs for the final prompt + final_inputs = { + "context": lambda x: _combine_documents(x["docs"]), + "question": itemgetter("question"), + } + + logger.info(f"Final inputs: {final_inputs}") + # And finally, we do the part that returns the answers + answer = { + "answer": final_inputs | ANSWER_PROMPT | ChatOpenAI(), + "docs": itemgetter("docs"), + } + + final_chain = loaded_memory | standalone_question | retrieved_documents | answer + + inputs = {"question": request.message} + result = final_chain.invoke(inputs) + + return result["answer"].content diff --git a/backend/app/config.yml b/backend/app/config.yml new file mode 100644 index 0000000..92e98da --- /dev/null +++ b/backend/app/config.yml @@ -0,0 +1,17 @@ +CHAT_CONFIG: + LLM_MODEL: "gpt-3.5-turbo" + MAX_TOKEN_LIMIT: 1000 + PROMPTS: + CONDENSE_QUESTION: > + Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language. + + Chat History: + {chat_history} + Follow Up Input: {question} + Standalone question: + ANSWER_QUESTION: > + Answer the question based only on the following context: + {context} + + Question: {question} + DEFAULT_DOCUMENT: "{page_content}" diff --git a/backend/app/core/config.py b/backend/app/core/config.py index b93d1d7..26a7f09 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -1,12 +1,13 @@ from pydantic_settings import BaseSettings from typing import List -from backend.logging_config import logger +from loguru import logger class Settings(BaseSettings): API_VERSION: str = "v1" API_V1_STR: str = f"/api/{API_VERSION}" + PROJECT_NAME: str DB_HOST: str DB_PORT: str @@ -14,10 +15,22 @@ class Settings(BaseSettings): DB_PASS: str DB_USER: str + OPENAI_API_KEY: str + OPENAI_ORGANIZATION: str + + REDIS_HOST: str + REDIS_PORT: str + + TAVILY_API_KEY: str + @property - def SQLALCHEMY_DATABASE_URI(self) -> str: + def ASYNC_DATABASE_URI(self) -> str: return f"postgresql+asyncpg://{self.DB_USER}:{self.DB_PASS}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}" + @property + def SYNC_DATABASE_URI(self) -> str: + return f"postgresql+psycopg2://{self.DB_USER}:{self.DB_PASS}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}" + class Config: env_file = "../.env" diff --git a/backend/app/core/db.py b/backend/app/core/db.py index cba5862..92edba8 100644 --- a/backend/app/core/db.py +++ b/backend/app/core/db.py @@ -1,67 +1,36 @@ from sqlmodel import SQLModel from sqlalchemy.ext.asyncio import create_async_engine from app.core.config import settings -import asyncpg -import psycopg2 from loguru import logger - -engine = create_async_engine(str(settings.SQLALCHEMY_DATABASE_URI), echo=True) - -async def create_extension(): - conn: asyncpg.Connection = await asyncpg.connect( - user=settings.DB_USER, - password=settings.DB_PASS, - database=settings.DB_NAME, - host=settings.DB_HOST, - ) - try: - await conn.execute("CREATE EXTENSION IF NOT EXISTS vector") - logger.info("pgvector extension created or already exists.") - except Exception as e: - logger.error(f"Error creating pgvector extension: {e}") - finally: - await conn.close() - - -def create_database(database_name, user, password, host, port): - try: - # Connect to the default database - conn = psycopg2.connect( - dbname=database_name, user=user, password=password, host=host, port=port - ) - conn.autocommit = True - cur = conn.cursor() - - # Check if database exists - cur.execute( - f"SELECT 1 FROM pg_catalog.pg_database WHERE datname = '{database_name}'" +from sqlalchemy.orm import sessionmaker +from sqlmodel.ext.asyncio.session import AsyncSession + +DB_POOL_SIZE = 83 +WEB_CONCURRENCY = 9 +POOL_SIZE = max( + DB_POOL_SIZE // WEB_CONCURRENCY, + 5, +) + + +def _get_local_session() -> sessionmaker: + engine = ( + create_async_engine( + url=settings.ASYNC_DATABASE_URI, + future=True, + pool_size=POOL_SIZE, + max_overflow=64, ) - exists = cur.fetchone() - if not exists: - - cur.execute(f"CREATE DATABASE {database_name}") - logger.info(f"Database '{database_name}' created.") - else: - logger.info(f"Database '{database_name}' already exists.") - - cur.close() - conn.close() - except Exception as e: - logger.error(f"Error creating database: {e}") - -async def init_db() -> None: - create_database( - settings.DB_NAME, - settings.DB_USER, - settings.DB_PASS, - settings.DB_HOST, - settings.DB_PORT, + if settings.ASYNC_DATABASE_URI is not None + else None + ) + return sessionmaker( + autocommit=False, + autoflush=False, + bind=engine, + class_=AsyncSession, + expire_on_commit=False, ) - async with engine.begin() as conn: - # Use run_sync to execute the create_all method in an asynchronous context - await conn.run_sync(SQLModel.metadata.create_all) - # Your existing database initialization logic here - # For example, creating extensions or setting up initial data - await create_extension() - logger.info("Database initialized and all tables created if they didn't exist.") + +SessionLocal = _get_local_session() diff --git a/backend/app/main.py b/backend/app/main.py index 56adf72..cb67357 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,18 +1,16 @@ - from fastapi import FastAPI -from fastapi.responses import RedirectResponse from contextlib import asynccontextmanager from fastapi.middleware.cors import CORSMiddleware -from app.core.db import init_db - +from app.api.main import api_router +from app.core.config import settings -@asynccontextmanager -async def app_lifespan(app: FastAPI): - await init_db() - yield +from typing import Dict +app = FastAPI( + openapi_url=f"{settings.API_V1_STR}/openapi.json", + docs_url=f"{settings.API_V1_STR}/docs", +) -app = FastAPI(lifespan=app_lifespan) app.add_middleware( CORSMiddleware, @@ -22,13 +20,16 @@ async def app_lifespan(app: FastAPI): allow_headers=["*"], ) + @app.get("/metrics") def metrics(): return {"message": "Metrics endpoint"} -@app.get("/") -async def home(): - return {"data": "Hello"} +@app.get("/") +async def root() -> Dict[str, str]: + """An example "Hello world" FastAPI route.""" + return {"message": "FastAPI backend"} +app.include_router(api_router, prefix=settings.API_V1_STR) diff --git a/backend/app/schemas/chat_schema.py b/backend/app/schemas/chat_schema.py new file mode 100644 index 0000000..2e3a849 --- /dev/null +++ b/backend/app/schemas/chat_schema.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel + + +class ChatBody(BaseModel): + message: str diff --git a/backend/ingestion/core/config.py b/backend/ingestion/core/config.py new file mode 100644 index 0000000..d86b097 --- /dev/null +++ b/backend/ingestion/core/config.py @@ -0,0 +1,43 @@ +from pydantic_settings import BaseSettings +from typing import List +from loguru import logger +import sys + + +class Settings(BaseSettings): + + API_VERSION: str = "v1" + API_V1_STR: str = f"/api/{API_VERSION}" + + DB_HOST: str + DB_PORT: str + DB_NAME: str + DB_PASS: str + DB_USER: str + + OPENAI_API_KEY: str + + @property + def ASYNC_DATABASE_URI(self) -> str: + return f"postgresql+asyncpg://{self.DB_USER}:{self.DB_PASS}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}" + + class Config: + env_file = "../.env" + + +class LogConfig: + LOGGING_LEVEL = "DEBUG" + LOGGING_FORMAT = "{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}" + + @staticmethod + def configure_logging(): + logger.remove() + + logger.add( + sys.stderr, format=LogConfig.LOGGING_FORMAT, level=LogConfig.LOGGING_LEVEL + ) + + +LogConfig.configure_logging() + +settings = Settings() diff --git a/backend/ingestion/helpers/db.py b/backend/ingestion/helpers/db.py new file mode 100644 index 0000000..0976959 --- /dev/null +++ b/backend/ingestion/helpers/db.py @@ -0,0 +1,69 @@ +from sqlmodel import SQLModel +from sqlalchemy.ext.asyncio import create_async_engine +from app.core.config import settings +import asyncpg +import psycopg2 +from loguru import logger + +engine = create_async_engine(str(settings.ASYNC_DATABASE_URI), echo=True) + + +async def create_extension(): + conn: asyncpg.Connection = await asyncpg.connect( + user=settings.DB_USER, + password=settings.DB_PASS, + database=settings.DB_NAME, + host=settings.DB_HOST, + ) + try: + await conn.execute("CREATE EXTENSION IF NOT EXISTS vector") + logger.info("pgvector extension created or already exists.") + except Exception as e: + logger.error(f"Error creating pgvector extension: {e}") + finally: + await conn.close() + + +def create_database(database_name, user, password, host, port): + try: + # Connect to the default database + conn = psycopg2.connect( + dbname=database_name, user=user, password=password, host=host, port=port + ) + conn.autocommit = True + cur = conn.cursor() + + # Check if database exists + cur.execute( + f"SELECT 1 FROM pg_catalog.pg_database WHERE datname = '{database_name}'" + ) + exists = cur.fetchone() + if not exists: + + cur.execute(f"CREATE DATABASE {database_name}") + logger.info(f"Database '{database_name}' created.") + else: + logger.info(f"Database '{database_name}' already exists.") + + cur.close() + conn.close() + except Exception as e: + logger.error(f"Error creating database: {e}") + + +async def init_db() -> None: + create_database( + settings.DB_NAME, + settings.DB_USER, + settings.DB_PASS, + settings.DB_HOST, + settings.DB_PORT, + ) + async with engine.begin() as conn: + # Use run_sync to execute the create_all method in an asynchronous context + await conn.run_sync(SQLModel.metadata.create_all) + + # Your existing database initialization logic here + # For example, creating extensions or setting up initial data + await create_extension() + logger.info("Database initialized and all tables created if they didn't exist.") diff --git a/backend/ingestion/pdf_ingestion.py b/backend/ingestion/pdf_ingestion.py index ac908e6..3f96aa4 100644 --- a/backend/ingestion/pdf_ingestion.py +++ b/backend/ingestion/pdf_ingestion.py @@ -13,7 +13,7 @@ from dotenv import load_dotenv from langchain.vectorstores.pgvector import PGVector from langchain.embeddings import CacheBackedEmbeddings -from backend.logging_config import logger +from ..logging_config import logger from schemas.ingestion_schema import LOADER_DICT from fastapi.encoders import jsonable_encoder diff --git a/backend/logging_config.py b/backend/logging_config.py deleted file mode 100644 index 52d0811..0000000 --- a/backend/logging_config.py +++ /dev/null @@ -1,15 +0,0 @@ -from loguru import logger -import sys - - -def setup_logging(): - logger.remove() - - logger.add( - sys.stderr, - format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", - level="DEBUG", - ) - - -setup_logging() diff --git a/backend/poetry.lock b/backend/poetry.lock index 11f3d14..9162c29 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -227,6 +227,17 @@ tests = ["attrs[tests-no-zope]", "zope-interface"] tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +[[package]] +name = "case-converter" +version = "1.1.0" +description = "A string case conversion package." +optional = false +python-versions = "*" +files = [ + {file = "case-converter-1.1.0.tar.gz", hash = "sha256:2ed3fc6e3ffa8d601f9a31ffcbc8fbd19eaeb48671a79a8ef16394672824510e"}, + {file = "case_converter-1.1.0-py3-none-any.whl", hash = "sha256:d0fbc665a4fd410e454630bcf5168dce62e147a98512647ed48b0a6b556986e3"}, +] + [[package]] name = "certifi" version = "2024.2.2" @@ -778,6 +789,21 @@ langchain-core = ">=0.1.28,<0.2.0" [package.extras] extended-testing = ["lxml (>=5.1.0,<6.0.0)"] +[[package]] +name = "langchainhub" +version = "0.1.15" +description = "The LangChain Hub API client" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchainhub-0.1.15-py3-none-any.whl", hash = "sha256:89a0951abd1db255e91c6d545d092a598fc255aa865d1ffc3ce8f93bbeae60e7"}, + {file = "langchainhub-0.1.15.tar.gz", hash = "sha256:fa3ff81a31946860f84c119f1e2f6b7c7707e2bd7ed2394a7313b286d59f3bda"}, +] + +[package.dependencies] +requests = ">=2,<3" +types-requests = ">=2.31.0.2,<3.0.0.0" + [[package]] name = "langsmith" version = "0.1.37" @@ -1361,6 +1387,38 @@ files = [ {file = "PyMuPDFb-1.24.0-py3-none-win_amd64.whl", hash = "sha256:3e368ce2a8935881965343a7b87565b532a1787a3dc8f5580980dfb8b91d0c39"}, ] +[[package]] +name = "python-box" +version = "7.1.1" +description = "Advanced Python dictionaries with dot notation access" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-box-7.1.1.tar.gz", hash = "sha256:2a3df244a5a79ac8f8447b5d11b5be0f2747d7b141cb2866060081ae9b53cc50"}, + {file = "python_box-7.1.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:81ed1ec0f0ff2370227fc07277c5baca46d190a4747631bad7eb6ab1630fb7d9"}, + {file = "python_box-7.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8891735b4148e84d348c6eadd2f127152f751c9603e35d43a1f496183a291ac4"}, + {file = "python_box-7.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:0036fd47d388deaca8ebd65aea905f88ee6ef91d1d8ce34898b66f1824afbe80"}, + {file = "python_box-7.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aabf8b9ae5dbc8ba431d8cbe0d4cfe737a25d52d68b0f5f2ff34915c21a2c1db"}, + {file = "python_box-7.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c046608337e723ae4de3206db5d1e1202ed166da2dfdc70c1f9361e72ace5633"}, + {file = "python_box-7.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9266795e9c233874fb5b34fa994054b4fb0371881678e6ec45aec17fc95feac"}, + {file = "python_box-7.1.1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:f76b5b7f0cdc07bfdd4200dc24e6e33189bb2ae322137a2b7110fd41891a3157"}, + {file = "python_box-7.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ea13c98e05a3ec0ff26f254986a17290b69b5ade209fad081fd628f8fcfaa08"}, + {file = "python_box-7.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3f346e332dba16df0b0543d319d9e7ce07d93e5ae152175302894352aa2d28"}, + {file = "python_box-7.1.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:24c4ec0ee0278f66321100aaa9c615413da27a14ff43d376a2a3b4665e1d9494"}, + {file = "python_box-7.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d95e5eec4fc8f3fc5c9cc7347fc2eb4f9187c853d34c90b1658d1eff96cd4eac"}, + {file = "python_box-7.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:a0f1333c42e81529b6f68c192050df9d4505b803be7ac47f114036b98707f7cf"}, + {file = "python_box-7.1.1-py3-none-any.whl", hash = "sha256:63b609555554d7a9d4b6e725f8e78ef1717c67e7d386200e03422ad612338df8"}, +] + +[package.extras] +all = ["msgpack", "ruamel.yaml (>=0.17)", "toml"] +msgpack = ["msgpack"] +pyyaml = ["PyYAML"] +ruamel-yaml = ["ruamel.yaml (>=0.17)"] +toml = ["toml"] +tomli = ["tomli", "tomli-w"] +yaml = ["ruamel.yaml (>=0.17)"] + [[package]] name = "python-dotenv" version = "1.0.1" @@ -1435,6 +1493,24 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +[[package]] +name = "redis" +version = "5.0.3" +description = "Python client for Redis database and key-value store" +optional = false +python-versions = ">=3.7" +files = [ + {file = "redis-5.0.3-py3-none-any.whl", hash = "sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d"}, + {file = "redis-5.0.3.tar.gz", hash = "sha256:4973bae7444c0fbed64a06b87446f79361cb7e4ec1538c022d696ed7a5015580"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + [[package]] name = "regex" version = "2023.12.25" @@ -1774,6 +1850,20 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] +[[package]] +name = "types-requests" +version = "2.31.0.20240311" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-requests-2.31.0.20240311.tar.gz", hash = "sha256:b1c1b66abfb7fa79aae09097a811c4aa97130eb8831c60e47aee4ca344731ca5"}, + {file = "types_requests-2.31.0.20240311-py3-none-any.whl", hash = "sha256:47872893d65a38e282ee9f277a4ee50d1b28bd592040df7d1fdaffdf3779937d"}, +] + +[package.dependencies] +urllib3 = ">=2" + [[package]] name = "typing-extensions" version = "4.10.0" @@ -1955,4 +2045,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "acf3908a61b48d607b6c8782b19b7f393c3206671dbf9d6dfdc1ace3e2987444" +content-hash = "27db29dbdef7ff2ecf4c84787bceb8fd404e3029e2bef465b51630232e13fe12" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 785410e..94ba795 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -20,6 +20,10 @@ langchain = "^0.1.13" langchain-openai = "^0.1.1" pymupdf = "^1.24.0" pgvector = "^0.2.5" +case-converter = "^1.1.0" +python-box = "^7.1.1" +redis = "^5.0.3" +langchainhub = "^0.1.15" [build-system] diff --git a/backend/requirements.txt b/backend/requirements.txt deleted file mode 100644 index 68b9c27..0000000 --- a/backend/requirements.txt +++ /dev/null @@ -1,26 +0,0 @@ -aiopg==1.4.0 -alembic==1.13.1 -asyncpg==0.29.0 -brotli==1.1.0 -chardet==5.2.0 -email-validator==2.1.0.post1 -exceptiongroup==1.2.0 -fastapi==0.109.2 -gmpy2==2.1.5 -httpx==0.26.0 -importlib-metadata==7.0.1 -loguru==0.7.2 -pandas==2.2.0 -passlib==1.7.4 -pgvector==0.2.5 -pickleshare==0.7.5 -pydantic-settings==2.2.0 -pysocks==1.7.1 -requests -tqdm -# pytest==8.0.1 -python-jose==3.3.0 -python-multipart==0.0.9 -sentence-transformers==2.3.1 -torchvision==0.17.0 -uvicorn==0.27.1