From e3f4545803878c18f637a2426318f93e1640d349 Mon Sep 17 00:00:00 2001 From: leoguillaume Date: Tue, 17 Sep 2024 19:16:42 +0200 Subject: [PATCH] feat: add pre-commit --- .pre-commit-config.yaml | 12 + CONTRIBUTING.md | 3 +- app/endpoints/chat.py | 3 +- app/endpoints/chunks.py | 1 + app/endpoints/collections.py | 1 + app/endpoints/completions.py | 4 +- app/endpoints/embeddings.py | 4 +- app/endpoints/models.py | 5 +- app/helpers/__init__.py | 4 +- app/helpers/_s3fileloader.py | 21 +- app/helpers/_textcleaner.py | 7 +- app/helpers/_universalparser.py | 126 +--- app/schemas/chat.py | 8 +- app/schemas/collections.py | 5 +- app/schemas/params.py | 18 - app/schemas/tools.py | 3 +- app/tests/conftest.py | 6 +- app/tests/test_chat.py | 23 +- app/tests/test_files.py | 2 +- app/tests/test_models.py | 7 +- app/tests/test_tools.py | 3 +- app/tools/_baserag.py | 1 - app/tools/_fewshots.py | 8 +- app/utils/data.py | 1 - app/utils/lifespan.py | 1 + app/utils/security.py | 4 +- docs/tutorials/chat_completions.ipynb | 2 +- .../retrival_augmented_generation.ipynb | 626 +++++++++--------- pyproject.toml | 16 +- ui/chat.py | 2 - ui/pages/files.py | 18 +- ui/utils.py | 5 +- 32 files changed, 423 insertions(+), 527 deletions(-) create mode 100644 .pre-commit-config.yaml delete mode 100644 app/schemas/params.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..d47be863 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,12 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.6.5 + hooks: + # Run the linter. + - id: ruff + types_or: [ python, pyi ] + args: [ --fix ] + # Run the formatter. + - id: ruff-format + types_or: [ python, pyi ] \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33e70d59..8087a33b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,8 @@ feat(collections): collection name retriever 2. Dans un environnement virtuel Python, installez les packages Python présents dans le fichier *[pyproject.toml](./pyproject.toml)* ```bash - pip install ".[ui,app]" + pip install ".[ui,app,dev]" + pre-commit install ``` # Tests diff --git a/app/endpoints/chat.py b/app/endpoints/chat.py index 9a5711c6..6d227eca 100644 --- a/app/endpoints/chat.py +++ b/app/endpoints/chat.py @@ -15,6 +15,7 @@ router = APIRouter() + # @TODO: remove tooling from here @router.post("/chat/completions") async def chat_completions(request: ChatCompletionRequest, user: str = Security(check_api_key)) -> Union[ChatCompletion, ChatCompletionChunk]: @@ -65,7 +66,7 @@ async def chat_completions(request: ChatCompletionRequest, user: str = Security( return ChatCompletion(**data) # stream case - async def forward_stream(url:str, headers:dict, request: dict): + async def forward_stream(url: str, headers: dict, request: dict): async with httpx.AsyncClient(timeout=20) as async_client: async with async_client.stream(method="POST", url=url, headers=headers, json=request) as response: i = 0 diff --git a/app/endpoints/chunks.py b/app/endpoints/chunks.py index 6757374e..6cef9d7e 100644 --- a/app/endpoints/chunks.py +++ b/app/endpoints/chunks.py @@ -10,6 +10,7 @@ router = APIRouter() + # @TODO: add pagination @router.get("/chunks/{collection}/{chunk}") @router.post("/chunks/{collection}") diff --git a/app/endpoints/collections.py b/app/endpoints/collections.py index 4c541bdd..0efb531c 100644 --- a/app/endpoints/collections.py +++ b/app/endpoints/collections.py @@ -11,6 +11,7 @@ router = APIRouter() + # @TODO: remove get one collection and a /collections/search to similarity search (remove /tools) @router.get("/collections/{collection}") @router.get("/collections") diff --git a/app/endpoints/completions.py b/app/endpoints/completions.py index e7ebf0a4..dbb00878 100644 --- a/app/endpoints/completions.py +++ b/app/endpoints/completions.py @@ -9,9 +9,7 @@ @router.post("/completions") -async def completions( - request: CompletionRequest, user: str = Security(check_api_key) -) -> Completions: +async def completions(request: CompletionRequest, user: str = Security(check_api_key)) -> Completions: """ Completion API similar to OpenAI's API. See https://platform.openai.com/docs/api-reference/completions/create for the API specification. diff --git a/app/endpoints/embeddings.py b/app/endpoints/embeddings.py index 1d63cdab..3dc53e1d 100644 --- a/app/endpoints/embeddings.py +++ b/app/endpoints/embeddings.py @@ -9,9 +9,7 @@ @router.post("/embeddings") -async def embeddings( - request: EmbeddingsRequest, user: str = Security(check_api_key) -) -> Embeddings: +async def embeddings(request: EmbeddingsRequest, user: str = Security(check_api_key)) -> Embeddings: """ Embedding API similar to OpenAI's API. See https://platform.openai.com/docs/api-reference/embeddings/create for the API specification. diff --git a/app/endpoints/models.py b/app/endpoints/models.py index ef40c3b3..c51f7f93 100644 --- a/app/endpoints/models.py +++ b/app/endpoints/models.py @@ -9,11 +9,10 @@ router = APIRouter() + @router.get("/models/{model:path}") @router.get("/models") -async def models( - model: Optional[str] = None, user: str = Security(check_api_key) -) -> Union[Models, Model]: +async def models(model: Optional[str] = None, user: str = Security(check_api_key)) -> Union[Models, Model]: """ Model API similar to OpenAI's API. See https://platform.openai.com/docs/api-reference/models/list for the API specification. diff --git a/app/helpers/__init__.py b/app/helpers/__init__.py index a384b9cd..126e36e5 100644 --- a/app/helpers/__init__.py +++ b/app/helpers/__init__.py @@ -1,5 +1,7 @@ +from ._gristkeymanager import GristKeyManager from ._s3fileloader import S3FileLoader from ._textcleaner import TextCleaner from ._universalparser import UniversalParser -from ._gristkeymanager import GristKeyManager from ._vectorstore import VectorStore + +__all__ = ["S3FileLoader", "TextCleaner", "GristKeyManager", "UniversalParser", "VectorStore"] diff --git a/app/helpers/_s3fileloader.py b/app/helpers/_s3fileloader.py index 80531d09..96b3b3ba 100644 --- a/app/helpers/_s3fileloader.py +++ b/app/helpers/_s3fileloader.py @@ -1,15 +1,11 @@ import os import tempfile -from typing import TYPE_CHECKING, Any, Callable, List, Optional -import magic +from typing import Any, Callable, List, Optional from langchain_community.document_loaders.unstructured import UnstructuredBaseLoader from ._universalparser import UniversalParser -if TYPE_CHECKING: - import botocore - class S3FileLoader(UnstructuredBaseLoader): """Load from `Amazon AWS S3` files into Langchain documents.""" @@ -21,8 +17,8 @@ def __init__( mode: str = "single", post_processors: Optional[List[Callable]] = None, chunk_size: Optional[int], - chunk_overlap: Optional[int] , - chunk_min_size: Optional[int] , + chunk_overlap: Optional[int], + chunk_min_size: Optional[int], **unstructured_kwargs: Any, ): """Initialize loader. @@ -40,11 +36,7 @@ def __init__( self.chunk_overlap = chunk_overlap self.chunk_min_size = chunk_min_size - def _get_elements( - self, - bucket: str, - file_id: str, - ) -> List: + def _get_elements(self, bucket: str, file_id: str) -> List: """Get elements. Args: @@ -61,10 +53,7 @@ def _get_elements( # Returns a list of Langchain documents return self.parser.parse_and_chunk( - file_path=file_path, - chunk_size=self.chunk_size, - chunk_overlap=self.chunk_overlap, - chunk_min_size=self.chunk_min_size, + file_path=file_path, chunk_size=self.chunk_size, chunk_overlap=self.chunk_overlap, chunk_min_size=self.chunk_min_size ) def _get_metadata(self, bucket, file_id) -> dict: diff --git a/app/helpers/_textcleaner.py b/app/helpers/_textcleaner.py index 649c39bf..28fd1c36 100644 --- a/app/helpers/_textcleaner.py +++ b/app/helpers/_textcleaner.py @@ -3,7 +3,6 @@ class TextCleaner: - def __init__(self): pass @@ -15,12 +14,12 @@ def clean_string(self, input_string): return input_string # Remove NUL bytes - input_string = input_string.replace('\x00', '') + input_string = input_string.replace("\x00", "") # Remove non-printable characters - input_string = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', input_string) + input_string = re.sub(r"[\x00-\x1f\x7f-\x9f]", "", input_string) # Normalize Unicode characters to NFC (Normalization Form C) - input_string = unicodedata.normalize('NFC', input_string) + input_string = unicodedata.normalize("NFC", input_string) return input_string diff --git a/app/helpers/_universalparser.py b/app/helpers/_universalparser.py index 45324458..713e565d 100644 --- a/app/helpers/_universalparser.py +++ b/app/helpers/_universalparser.py @@ -30,13 +30,7 @@ def __init__(self): self.cleaner = TextCleaner() pass - def parse_and_chunk( - self, - file_path: str, - chunk_size: int, - chunk_overlap: int, - chunk_min_size: int, - ): + def parse_and_chunk(self, file_path: str, chunk_size: int, chunk_overlap: int, chunk_min_size: int): """ Parses a file and splits it into text chunks based on the file type. @@ -54,6 +48,7 @@ def parse_and_chunk( """ file_type = magic.from_file(file_path, mime=True) + # @TODO: check if it a possible option ? if file_type == self.TXT_TYPE: # In the case the json file is stored as text/plain instead of application/json with open(file_path, "r") as file: @@ -68,27 +63,12 @@ def parse_and_chunk( file_type = "unknown" if file_type == self.PDF_TYPE: - chunks = self._pdf_to_chunks( - file_path=file_path, - chunk_size=chunk_size, - chunk_overlap=chunk_overlap, - chunk_min_size=chunk_min_size, - ) + chunks = self._pdf_to_chunks(file_path=file_path, chunk_size=chunk_size, chunk_overlap=chunk_overlap, chunk_min_size=chunk_min_size) elif file_type == self.DOCX_TYPE: - chunks = self._docx_to_chunks( - file_path=file_path, - chunk_size=chunk_size, - chunk_overlap=chunk_overlap, - chunk_min_size=chunk_min_size, - ) + chunks = self._docx_to_chunks(file_path=file_path, chunk_size=chunk_size, chunk_overlap=chunk_overlap, chunk_min_size=chunk_min_size) elif file_type == self.JSON_TYPE: - chunks = self._json_to_chunks( - file_path=file_path, - chunk_size=chunk_size, - chunk_overlap=chunk_overlap, - chunk_min_size=chunk_min_size, - ) + chunks = self._json_to_chunks(file_path=file_path, chunk_size=chunk_size, chunk_overlap=chunk_overlap, chunk_min_size=chunk_min_size) else: raise NotImplementedError(f"Unsupported input file format ({file_path}): {file_type}") @@ -97,9 +77,7 @@ def parse_and_chunk( ## Parser and chunking functions - def _pdf_to_chunks( - self, file_path: str, chunk_size: int, chunk_overlap: int, chunk_min_size: int - ) -> List[LangchainDocument]: + def _pdf_to_chunks(self, file_path: str, chunk_size: int, chunk_overlap: int, chunk_min_size: int) -> List[LangchainDocument]: """ Parse a PDF file and converts it into a list of text chunks. @@ -118,11 +96,7 @@ def _pdf_to_chunks( chunks = [] text_splitter = RecursiveCharacterTextSplitter( - chunk_size=chunk_size, - chunk_overlap=chunk_overlap, - length_function=len, - is_separator_regex=False, - separators=["\n\n", "\n"], + chunk_size=chunk_size, chunk_overlap=chunk_overlap, length_function=len, is_separator_regex=False, separators=["\n\n", "\n"] ) # Splitting text because too long splitted_text = text_splitter.split_text(doc[0].page_content) @@ -130,30 +104,14 @@ def _pdf_to_chunks( for k, text in enumerate(splitted_text): if chunk_min_size: if len(text) > chunk_min_size: # We avoid meaningless little chunks - chunk = LangchainDocument( - page_content=self.cleaner.clean_string(text), - metadata={ - "file_id": file_path.split("/")[-1], - }, - ) + chunk = LangchainDocument(page_content=self.cleaner.clean_string(text), metadata={"file_id": file_path.split("/")[-1]}) else: - chunk = LangchainDocument( - page_content=self.cleaner.clean_string(text), - metadata={ - "file_id": file_path.split("/")[-1], - }, - ) + chunk = LangchainDocument(page_content=self.cleaner.clean_string(text), metadata={"file_id": file_path.split("/")[-1]}) chunks.append(chunk) return chunks # List of langchain documents - def _docx_to_chunks( - self, - file_path: str, - chunk_size: int, - chunk_overlap: int, - chunk_min_size: int, - ) -> List[LangchainDocument]: + def _docx_to_chunks(self, file_path: str, chunk_size: int, chunk_overlap: int, chunk_min_size: int) -> List[LangchainDocument]: """ Parse a DOCX file and converts it into a list of text chunks. @@ -169,10 +127,7 @@ def _docx_to_chunks( documents = [] text_splitter = RecursiveCharacterTextSplitter( - chunk_size=chunk_size, - chunk_overlap=chunk_overlap, - length_function=len, - is_separator_regex=False, + chunk_size=chunk_size, chunk_overlap=chunk_overlap, length_function=len, is_separator_regex=False ) doc = Document(file_path) @@ -191,19 +146,11 @@ def _docx_to_chunks( if chunk_min_size: if len(text) > chunk_min_size: # We avoid meaningless little chunks chunk = LangchainDocument( - page_content=self.cleaner.clean_string(text), - metadata={ - "file_id": file_path.split("/")[-1], - "title": title, - }, + page_content=self.cleaner.clean_string(text), metadata={"file_id": file_path.split("/")[-1], "title": title} ) else: chunk = LangchainDocument( - page_content=self.cleaner.clean_string(text), - metadata={ - "file_id": file_path.split("/")[-1], - "title": title, - }, + page_content=self.cleaner.clean_string(text), metadata={"file_id": file_path.split("/")[-1], "title": title} ) documents.append(chunk) # Updating title for new subpart @@ -222,19 +169,11 @@ def _docx_to_chunks( if chunk_min_size: if len(text) > chunk_min_size: # We avoid meaningless little chunks chunk = LangchainDocument( - page_content=self.cleaner.clean_string(text), - metadata={ - "file_id": file_path.split("/")[-1], - "title": title, - }, + page_content=self.cleaner.clean_string(text), metadata={"file_id": file_path.split("/")[-1], "title": title} ) else: chunk = LangchainDocument( - page_content=self.cleaner.clean_string(text), - metadata={ - "file_id": file_path.split("/")[-1], - "title": title, - }, + page_content=self.cleaner.clean_string(text), metadata={"file_id": file_path.split("/")[-1], "title": title} ) documents.append(chunk) @@ -246,30 +185,18 @@ def _docx_to_chunks( if chunk_min_size: if len(text) > chunk_min_size: # We avoid meaningless little chunks chunk = LangchainDocument( - page_content=self.cleaner.clean_string(text), - metadata={ - "file_id": file_path.split("/")[-1], - "title": title, - }, + page_content=self.cleaner.clean_string(text), metadata={"file_id": file_path.split("/")[-1], "title": title} ) else: chunk = LangchainDocument( - page_content=self.cleaner.clean_string(text), - metadata={ - "file_id": file_path.split("/")[-1], - "title": title, - }, + page_content=self.cleaner.clean_string(text), metadata={"file_id": file_path.split("/")[-1], "title": title} ) documents.append(chunk) return documents def _json_to_chunks( - self, - file_path: str, - chunk_size: Optional[int], - chunk_overlap: Optional[int], - chunk_min_size: Optional[int], + self, file_path: str, chunk_size: Optional[int], chunk_overlap: Optional[int], chunk_min_size: Optional[int] ) -> List[LangchainDocument]: """ Converts a JSON file into a list of chunks. @@ -288,15 +215,9 @@ def _json_to_chunks( data = json.load(file) data = JsonFile(**data) # Validate the JSON file - (file_path.split("/")[-1],) - chunks = [] text_splitter = RecursiveCharacterTextSplitter( - chunk_size=chunk_size, - chunk_overlap=chunk_overlap, - length_function=len, - is_separator_regex=False, - separators=["\n"], + chunk_size=chunk_size, chunk_overlap=chunk_overlap, length_function=len, is_separator_regex=False, separators=["\n"] ) for i, document in enumerate(data.documents): @@ -307,15 +228,10 @@ def _json_to_chunks( splitted_text = text_splitter.split_text(document.text) for k, text in enumerate(splitted_text): - if ( - chunk_min_size and len(text) < chunk_min_size - ): # We avoid meaningless little chunks + if chunk_min_size and len(text) < chunk_min_size: # We avoid meaningless little chunks continue - chunk = LangchainDocument( - page_content=self.cleaner.clean_string(text), - metadata=document.metadata, - ) + chunk = LangchainDocument(page_content=self.cleaner.clean_string(text), metadata=document.metadata) chunks.append(chunk) return chunks diff --git a/app/schemas/chat.py b/app/schemas/chat.py index 1a20dd7b..df7c27ab 100644 --- a/app/schemas/chat.py +++ b/app/schemas/chat.py @@ -1,13 +1,13 @@ -from typing import Dict, List, Optional, Union, Literal +from typing import Dict, List, Literal, Optional, Union -from pydantic import BaseModel, Field from openai.types.chat import ( - ChatCompletionMessageParam, ChatCompletion, + ChatCompletionChunk, + ChatCompletionMessageParam, ChatCompletionToolChoiceOptionParam, ChatCompletionToolParam, - ChatCompletionChunk, ) +from pydantic import BaseModel, Field from app.schemas.tools import ToolOutput diff --git a/app/schemas/collections.py b/app/schemas/collections.py index 460b64fb..aa47d2ab 100644 --- a/app/schemas/collections.py +++ b/app/schemas/collections.py @@ -8,7 +8,7 @@ class Collection(BaseModel): object: Literal["collection"] = "collection" - id: str # name + id: str # name type: Literal[PUBLIC_COLLECTION_TYPE, PRIVATE_COLLECTION_TYPE] model: str user: Optional[str] = None @@ -27,6 +27,7 @@ class Document(BaseModel): page_content: str metadata: Dict[str, Any] + class CollectionMetadata(BaseModel): id: str name: Optional[str] = None @@ -35,4 +36,4 @@ class CollectionMetadata(BaseModel): user: Optional[str] = None description: Optional[str] = None created_at: Optional[int] = None - updated_at: Optional[int] = None \ No newline at end of file + updated_at: Optional[int] = None diff --git a/app/schemas/params.py b/app/schemas/params.py deleted file mode 100644 index b0abe8ea..00000000 --- a/app/schemas/params.py +++ /dev/null @@ -1,18 +0,0 @@ -from pydantic import RootModel, validator -from fastapi import HTTPException - -from app.schemas.config import EMBEDDINGS_MODEL_TYPE -from app.utils.lifespan import clients - -from pydantic import BaseModel -class EmbeddingsModel(BaseModel): - root: str - - @validator("root") - def check_model(cls, v, values, **kwargs): - if clients["models"][v].type != EMBEDDINGS_MODEL_TYPE: - - raise HTTPException( - status_code=400, detail=f"Model type must be {EMBEDDINGS_MODEL_TYPE}" - ) - return v diff --git a/app/schemas/tools.py b/app/schemas/tools.py index a4dac959..a0ca514d 100644 --- a/app/schemas/tools.py +++ b/app/schemas/tools.py @@ -13,6 +13,7 @@ class Tools(BaseModel): object: Literal["list"] = "list" data: List[Tool] + class ToolOutput(BaseModel): prompt: str - metadata: dict \ No newline at end of file + metadata: dict diff --git a/app/tests/conftest.py b/app/tests/conftest.py index 712e86fc..6ebde43c 100644 --- a/app/tests/conftest.py +++ b/app/tests/conftest.py @@ -2,14 +2,17 @@ import requests import logging + def pytest_addoption(parser): parser.addoption("--base-url", action="store", default="http://localhost:8080/v1") parser.addoption("--api-key", action="store", default="EMPTY") + @pytest.fixture(autouse=True) def setup_logging(caplog): caplog.set_level(logging.DEBUG) + @pytest.fixture def args(request): return { @@ -17,8 +20,9 @@ def args(request): "api_key": request.config.getoption("--api-key"), } + @pytest.fixture def session(args): s = requests.session() s.headers = {"Authorization": f"Bearer {args['api_key']}"} - return s \ No newline at end of file + return s diff --git a/app/tests/test_chat.py b/app/tests/test_chat.py index e5fe2da4..aa47aaa4 100644 --- a/app/tests/test_chat.py +++ b/app/tests/test_chat.py @@ -1,9 +1,8 @@ import logging -import json import pytest -from app.schemas.chat import ChatCompletion, ChatCompletionChunk +from app.schemas.chat import ChatCompletion # , ChatCompletionChunk from app.schemas.config import LANGUAGE_MODEL_TYPE @@ -15,9 +14,7 @@ def test_chat_completions_unstreamed_response(self, args, session): response = session.get(f"{args['base_url']}/models") assert response.status_code == 200, f"error: retrieve models ({response.status_code})" response_json = response.json() - model = [ - model["id"] for model in response_json["data"] if model["type"] == LANGUAGE_MODEL_TYPE - ][0] + model = [model["id"] for model in response_json["data"] if model["type"] == LANGUAGE_MODEL_TYPE][0] logging.debug(f"model: {model}") params = { @@ -28,9 +25,7 @@ def test_chat_completions_unstreamed_response(self, args, session): "max_tokens": 10, } response = session.post(f"{args['base_url']}/chat/completions", json=params) - assert ( - response.status_code == 200 - ), f"error: retrieve chat completions ({response.status_code})" + assert response.status_code == 200, f"error: retrieve chat completions ({response.status_code})" def test_chat_completions_unstreamed_response_schemas(self, args, session): """Test the GET /chat/completions response schemas.""" @@ -38,9 +33,7 @@ def test_chat_completions_unstreamed_response_schemas(self, args, session): response = session.get(f"{args['base_url']}/models") assert response.status_code == 200, f"error: retrieve models ({response.status_code})" response_json = response.json() - model = [ - model["id"] for model in response_json["data"] if model["type"] == LANGUAGE_MODEL_TYPE - ][0] + model = [model["id"] for model in response_json["data"] if model["type"] == LANGUAGE_MODEL_TYPE][0] logging.debug(f"model: {model}") params = { @@ -63,9 +56,7 @@ def test_chat_completions_streamed_response(self, args, session): response = session.get(f"{args['base_url']}/models") assert response.status_code == 200, f"error: retrieve models ({response.status_code})" response_json = response.json() - model = [ - model["id"] for model in response_json["data"] if model["type"] == LANGUAGE_MODEL_TYPE - ][0] + model = [model["id"] for model in response_json["data"] if model["type"] == LANGUAGE_MODEL_TYPE][0] logging.debug(f"model: {model}") params = { @@ -76,9 +67,7 @@ def test_chat_completions_streamed_response(self, args, session): "max_tokens": 10, } response = session.post(f"{args['base_url']}/chat/completions", json=params) - assert ( - response.status_code == 200 - ), f"error: retrieve chat completions ({response.status_code})" + assert response.status_code == 200, f"error: retrieve chat completions ({response.status_code})" # def test_chat_completions_streamed_response_schemas(self, args, session): # """Test the GET /chat/completions response schemas.""" diff --git a/app/tests/test_files.py b/app/tests/test_files.py index 1514af24..e11fc35f 100644 --- a/app/tests/test_files.py +++ b/app/tests/test_files.py @@ -138,7 +138,7 @@ def test_delete_file(self, args, session, setup): params = {"embeddings_model": EMBEDDINGS_MODEL, "collection": COLLECTION} files = {"files": (os.path.basename(FILE_NAME), open(FILE_NAME, "rb"), "application/pdf")} response = session.post(f"{args['base_url']}/files", params=params, files=files, timeout=30) - + uploads = response.json() uploads["data"] = [Upload(**upload) for upload in uploads["data"]] uploads = Uploads(**uploads) diff --git a/app/tests/test_models.py b/app/tests/test_models.py index 76d5e2cc..2059a66f 100644 --- a/app/tests/test_models.py +++ b/app/tests/test_models.py @@ -19,15 +19,14 @@ def test_get_model_retrieve_model(self, args, session): response = session.get(f"{args['base_url']}/models") assert response.status_code == 200, f"error: retrieve models ({response.status_code})" - model = response.json()["data"][0]["id"] + model = response.json()["data"][0]["id"] response = session.get(f"{args['base_url']}/models/{model}") assert response.status_code == 200, f"error: retrieve model ({response.status_code})" - + model = Model(**response.json()) assert isinstance(model, Model) - + def test_get_models_non_existing_model(self, args, session): """Test the GET /models response status code for a non-existing model.""" response = session.get(f"{args['base_url']}/models/non-existing-model") assert response.status_code == 404, f"error: retrieve non-existing model ({response.status_code})" - diff --git a/app/tests/test_tools.py b/app/tests/test_tools.py index 60e5e689..989c43a7 100644 --- a/app/tests/test_tools.py +++ b/app/tests/test_tools.py @@ -2,6 +2,7 @@ from app.schemas.tools import Tools, Tool + @pytest.mark.usefixtures("args", "session") class TestTools: def test_get_tools_response_status_code(self, args, session): @@ -17,4 +18,4 @@ def test_get_tools_response_schemas(self, args, session): tools = Tools(data=[Tool(**tool) for tool in response_json["data"]]) assert isinstance(tools, Tools) - assert all(isinstance(tool, Tool) for tool in tools.data) \ No newline at end of file + assert all(isinstance(tool, Tool) for tool in tools.data) diff --git a/app/tools/_baserag.py b/app/tools/_baserag.py index 49523f1e..af635645 100644 --- a/app/tools/_baserag.py +++ b/app/tools/_baserag.py @@ -1,7 +1,6 @@ from typing import List, Optional from fastapi import HTTPException -from qdrant_client.http.models import FieldCondition, Filter, MatchAny from app.helpers import VectorStore from app.schemas.tools import ToolOutput diff --git a/app/tools/_fewshots.py b/app/tools/_fewshots.py index 47107e66..8d1510e4 100644 --- a/app/tools/_fewshots.py +++ b/app/tools/_fewshots.py @@ -1,7 +1,5 @@ from typing import Optional -from fastapi import HTTPException -from qdrant_client.http import models as rest from app.helpers import VectorStore from app.schemas.tools import ToolOutput @@ -56,9 +54,9 @@ async def get_prompt( prompt = request["messages"][-1]["content"] results = vectorstore.search(collection_names=[collection.name], prompt=prompt, k=k, model=embeddings_model) - context = "\n\n\n".join([ - f"Question: {result.payload.get('question', 'N/A')}\n" f"Réponse: {result.payload.get('answer', 'N/A')}" for result in results - ]) + context = "\n\n\n".join( + [f"Question: {result.payload.get('question', 'N/A')}\n" f"Réponse: {result.payload.get('answer', 'N/A')}" for result in results] + ) prompt = self.DEFAULT_PROMPT_TEMPLATE.format(context=context, prompt=prompt) metadata = {"chunks": [result.id for result in results]} diff --git a/app/utils/data.py b/app/utils/data.py index b9d040cf..b5049242 100644 --- a/app/utils/data.py +++ b/app/utils/data.py @@ -11,7 +11,6 @@ def delete_contents(s3: Boto3Client, vectorstore: VectorStore, collection_name: Optional[str] = None, file: Optional[str] = None) -> Response: - collections = vectorstore.get_collection_metadata(collection_names=[collection_name], type=PRIVATE_COLLECTION_TYPE) for collection in collections: diff --git a/app/utils/lifespan.py b/app/utils/lifespan.py index cc5ec7a9..10430fc1 100644 --- a/app/utils/lifespan.py +++ b/app/utils/lifespan.py @@ -26,6 +26,7 @@ def __getitem__(self, key: str): clients = {"models": ModelDict(), "cache": None, "vectors": None, "files": None} + # @TODO: create a ClientsManager helper to manage clients @asynccontextmanager async def lifespan(app: FastAPI): diff --git a/app/utils/security.py b/app/utils/security.py index 4d1458fa..d49d1d66 100644 --- a/app/utils/security.py +++ b/app/utils/security.py @@ -1,14 +1,12 @@ from typing import Annotated import hashlib -import secrets import base64 -from functools import wraps from fastapi import HTTPException, Depends from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from app.utils.lifespan import clients -from app.schemas.collections import PUBLIC_COLLECTION_TYPE + def encode_string(input: str) -> str: """ diff --git a/docs/tutorials/chat_completions.ipynb b/docs/tutorials/chat_completions.ipynb index b107b1a5..7e66b511 100644 --- a/docs/tutorials/chat_completions.ipynb +++ b/docs/tutorials/chat_completions.ipynb @@ -79,7 +79,7 @@ "for chunk in response:\n", " if chunk.choices[0].finish_reason is not None:\n", " break\n", - " print(chunk.choices[0].delta.content, end='', flush=True)\n" + " print(chunk.choices[0].delta.content, end=\"\", flush=True)" ] } ], diff --git a/docs/tutorials/retrival_augmented_generation.ipynb b/docs/tutorials/retrival_augmented_generation.ipynb index 7e6b7d54..33d7ad7b 100644 --- a/docs/tutorials/retrival_augmented_generation.ipynb +++ b/docs/tutorials/retrival_augmented_generation.ipynb @@ -1,326 +1,326 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "f6f333a0-7450-4136-b8cc-416e07426279", - "metadata": { - "id": "f6f333a0-7450-4136-b8cc-416e07426279" - }, - "source": [ - "# Interroger des documents (RAG)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5f9ca9bf", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "5f9ca9bf", - "outputId": "4112b46b-4271-4696-cf31-393e9e7ff8b3" - }, - "outputs": [], - "source": [ - "%pip install -qU wget openai" - ] - }, - { - "cell_type": "markdown", - "id": "daadba81-54dd-48ba-b6f0-fc8307e822c3", - "metadata": { - "id": "daadba81-54dd-48ba-b6f0-fc8307e822c3" - }, - "source": [ - "Commencez par télécharger le document qui va vous servir de base de connaissance." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e80daa99-3416-4b81-a8aa-4fb7427bbe6c", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 35 - }, - "id": "e80daa99-3416-4b81-a8aa-4fb7427bbe6c", - "outputId": "abf16516-2ef9-40c3-dcad-74b6f9aa42e6" - }, - "outputs": [], - "source": [ - "# Download a file\n", - "import wget\n", - "import os\n", - "\n", - "file_path = \"my_document.pdf\"\n", - "if os.path.exists(file_path):\n", - " os.remove(file_path)\n", - "doc_url = \"https://www.legifrance.gouv.fr/download/file/rxcTl0H4YnnzLkMLiP4x15qORfLSKk_h8QsSb2xnJ8Y=/JOE_TEXTE\"\n", - "\n", - "wget.download(doc_url, out=file_path)" - ] - }, - { - "cell_type": "markdown", - "id": "80cf5b47-3ae4-4d86-9012-f2f8379d8f0b", - "metadata": { - "id": "80cf5b47-3ae4-4d86-9012-f2f8379d8f0b" - }, - "source": [ - "Puis instancier la connexion à l'API Albert." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "db90166d", - "metadata": { - "id": "db90166d" - }, - "outputs": [], - "source": [ - "# OpenAI client configuration\n", - "import requests\n", - "from openai import OpenAI\n", - "\n", - "base_url = \"https://albert.api.etalab.gouv.fr/v1\"\n", - "api_key = \"YOUR_API_KEY\"\n", - "\n", - "client = OpenAI(base_url=base_url, api_key=api_key)\n", - "\n", - "session = requests.session()\n", - "session.headers = {\"Authorization\": f\"Bearer {api_key}\"}" - ] - }, - { - "cell_type": "markdown", - "id": "RkAjTc20Agr9", - "metadata": { - "id": "RkAjTc20Agr9" - }, - "source": [ - "Vous aurez besoin pour la suite d'un modèle de langage ainsi qu'un modèle d'embeddings (pour le RAG). Pour cela vous pouvez appelez le endpoint `/v1/models` pour obtenir la liste des modèles. Les modèles de langage ont le type *text-generation* et les modèles d'embeddings le type *text-embeddings-inference*." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "Q_5YNzmR_JcK", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "Q_5YNzmR_JcK", - "outputId": "01554f0f-3d01-4946-993f-56e657904898" - }, - "outputs": [], - "source": [ - "language_model, embeddings_model = None, None\n", - "\n", - "for model in client.models.list().data:\n", - " if model.type == \"text-generation\" and language_model is None:\n", - " language_model = model.id\n", - " if model.type == \"text-embeddings-inference\" and embeddings_model is None:\n", - " embeddings_model = model.id\n", - "\n", - "print(f\"language model: {language_model}\\nembeddings model: {embeddings_model}\")" - ] - }, - { - "cell_type": "markdown", - "id": "a9615d41-5ce2-471b-bd6c-90cfb2b78d21", - "metadata": { - "id": "a9615d41-5ce2-471b-bd6c-90cfb2b78d21" - }, - "source": [ - "Enfin pour vous importer le document dans une collection de notre base vectorielle à l'aide du endpoint POST `/v1/files`.\n", - "\n", - "Vous devez spécifier le modèle d'embeddings qui sera utilisé pour vectoriser votre document. Vous pouvez trouver la liste des modèles avec le endpoint `/v1/models`. Les modèles d'embeddings sont indiqués avec le type _feature-extraction_.\n", - "\n", - "Le endpoint POST `/v1/files` doit retourner un status _success_." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5ac03e4c", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "5ac03e4c", - "outputId": "6e1c29d9-7d36-4e6c-d7d5-a79305d539cf" - }, - "outputs": [], - "source": [ - "# Remove previous files\n", - "collection = \"tutorial\"\n", - "response = session.delete(f\"{base_url}/files/{collection}\")\n", - "response.status_code" - ] + "cells": [ + { + "cell_type": "markdown", + "id": "f6f333a0-7450-4136-b8cc-416e07426279", + "metadata": { + "id": "f6f333a0-7450-4136-b8cc-416e07426279" + }, + "source": [ + "# Interroger des documents (RAG)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5f9ca9bf", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - { - "cell_type": "code", - "execution_count": null, - "id": "6852fc7a-0b09-451b-bbc2-939fa96a4d28", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "6852fc7a-0b09-451b-bbc2-939fa96a4d28", - "outputId": "8555033d-d20f-4b0b-8bfa-7fa5c83a299b" - }, - "outputs": [], - "source": [ - "# Upload a file\n", - "model = \"intfloat/multilingual-e5-large\"\n", - "params = {\"collection\": collection, \"embeddings_model\": embeddings_model}\n", - "\n", - "files = {'files': (os.path.basename(file_path), open(file_path, 'rb'), \"application/pdf\")}\n", - "response = session.post(f\"{base_url}/files\", params=params , files=files)\n", - "\n", - "response.json()" - ] + "id": "5f9ca9bf", + "outputId": "4112b46b-4271-4696-cf31-393e9e7ff8b3" + }, + "outputs": [], + "source": [ + "%pip install -qU wget openai" + ] + }, + { + "cell_type": "markdown", + "id": "daadba81-54dd-48ba-b6f0-fc8307e822c3", + "metadata": { + "id": "daadba81-54dd-48ba-b6f0-fc8307e822c3" + }, + "source": [ + "Commencez par télécharger le document qui va vous servir de base de connaissance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e80daa99-3416-4b81-a8aa-4fb7427bbe6c", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 35 }, - { - "cell_type": "markdown", - "id": "f78ec73c-3e83-4266-a8de-c6a198f317b4", - "metadata": { - "id": "f78ec73c-3e83-4266-a8de-c6a198f317b4" - }, - "source": [ - "Vous pouvez observer les fichiers que vous avez importer dans une collection à l'aide du endpoint GET `/v1/files.`" - ] + "id": "e80daa99-3416-4b81-a8aa-4fb7427bbe6c", + "outputId": "abf16516-2ef9-40c3-dcad-74b6f9aa42e6" + }, + "outputs": [], + "source": [ + "# Download a file\n", + "import wget\n", + "import os\n", + "\n", + "file_path = \"my_document.pdf\"\n", + "if os.path.exists(file_path):\n", + " os.remove(file_path)\n", + "doc_url = \"https://www.legifrance.gouv.fr/download/file/rxcTl0H4YnnzLkMLiP4x15qORfLSKk_h8QsSb2xnJ8Y=/JOE_TEXTE\"\n", + "\n", + "wget.download(doc_url, out=file_path)" + ] + }, + { + "cell_type": "markdown", + "id": "80cf5b47-3ae4-4d86-9012-f2f8379d8f0b", + "metadata": { + "id": "80cf5b47-3ae4-4d86-9012-f2f8379d8f0b" + }, + "source": [ + "Puis instancier la connexion à l'API Albert." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "db90166d", + "metadata": { + "id": "db90166d" + }, + "outputs": [], + "source": [ + "# OpenAI client configuration\n", + "import requests\n", + "from openai import OpenAI\n", + "\n", + "base_url = \"https://albert.api.etalab.gouv.fr/v1\"\n", + "api_key = \"YOUR_API_KEY\"\n", + "\n", + "client = OpenAI(base_url=base_url, api_key=api_key)\n", + "\n", + "session = requests.session()\n", + "session.headers = {\"Authorization\": f\"Bearer {api_key}\"}" + ] + }, + { + "cell_type": "markdown", + "id": "RkAjTc20Agr9", + "metadata": { + "id": "RkAjTc20Agr9" + }, + "source": [ + "Vous aurez besoin pour la suite d'un modèle de langage ainsi qu'un modèle d'embeddings (pour le RAG). Pour cela vous pouvez appelez le endpoint `/v1/models` pour obtenir la liste des modèles. Les modèles de langage ont le type *text-generation* et les modèles d'embeddings le type *text-embeddings-inference*." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "Q_5YNzmR_JcK", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - { - "cell_type": "code", - "execution_count": null, - "id": "bd6d6140-5c91-4c3e-9350-b6c8550ab145", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "bd6d6140-5c91-4c3e-9350-b6c8550ab145", - "outputId": "0ddea4bb-889e-4ebc-a912-7a2e461ea987" - }, - "outputs": [], - "source": [ - "# Retrieve the file ID for RAG\n", - "response = session.get(f\"{base_url}/files/{collection}\")\n", - "response.json()\n", - "file_id = response.json()[\"data\"][0][\"id\"]\n", - "print(file_id)" - ] + "id": "Q_5YNzmR_JcK", + "outputId": "01554f0f-3d01-4946-993f-56e657904898" + }, + "outputs": [], + "source": [ + "language_model, embeddings_model = None, None\n", + "\n", + "for model in client.models.list().data:\n", + " if model.type == \"text-generation\" and language_model is None:\n", + " language_model = model.id\n", + " if model.type == \"text-embeddings-inference\" and embeddings_model is None:\n", + " embeddings_model = model.id\n", + "\n", + "print(f\"language model: {language_model}\\nembeddings model: {embeddings_model}\")" + ] + }, + { + "cell_type": "markdown", + "id": "a9615d41-5ce2-471b-bd6c-90cfb2b78d21", + "metadata": { + "id": "a9615d41-5ce2-471b-bd6c-90cfb2b78d21" + }, + "source": [ + "Enfin pour vous importer le document dans une collection de notre base vectorielle à l'aide du endpoint POST `/v1/files`.\n", + "\n", + "Vous devez spécifier le modèle d'embeddings qui sera utilisé pour vectoriser votre document. Vous pouvez trouver la liste des modèles avec le endpoint `/v1/models`. Les modèles d'embeddings sont indiqués avec le type _feature-extraction_.\n", + "\n", + "Le endpoint POST `/v1/files` doit retourner un status _success_." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ac03e4c", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - { - "cell_type": "code", - "execution_count": null, - "id": "2e5cd813-5c19-4219-a404-6ed154991dfc", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "2e5cd813-5c19-4219-a404-6ed154991dfc", - "outputId": "d25fe84d-d7b4-4e84-ef26-e7986347d5aa" - }, - "outputs": [], - "source": [ - "# Display tools parameters\n", - "response = session.get(f\"{base_url}/tools\")\n", - "for tool in response.json()[\"data\"]:\n", - " if tool[\"id\"] == \"BaseRAG\":\n", - " print(tool[\"description\"].strip())" - ] + "id": "5ac03e4c", + "outputId": "6e1c29d9-7d36-4e6c-d7d5-a79305d539cf" + }, + "outputs": [], + "source": [ + "# Remove previous files\n", + "collection = \"tutorial\"\n", + "response = session.delete(f\"{base_url}/files/{collection}\")\n", + "response.status_code" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6852fc7a-0b09-451b-bbc2-939fa96a4d28", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - { - "cell_type": "code", - "execution_count": null, - "id": "f374c1ad-b5ec-4870-a11a-953c7d219f94", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "f374c1ad-b5ec-4870-a11a-953c7d219f94", - "outputId": "64279978-1ae5-4bac-f028-bb0899d83d22" - }, - "outputs": [], - "source": [ - "# Chat completions\n", - "data = {\n", - " \"model\": language_model,\n", - " \"messages\": [{\"role\": \"user\", \"content\": \"Qui est Ulrich Tan ?\"}],\n", - " \"stream\": False,\n", - " \"n\": 1,\n", - " \"tools\": [\n", - " {\n", - " \"function\": {\n", - " \"name\": \"BaseRAG\",\n", - " \"parameters\": {\n", - " \"embeddings_model\": embeddings_model,\n", - " \"collections\": [collection],\n", - " \"k\": 2,\n", - " },\n", - " },\n", - " \"type\": \"function\",\n", - " }\n", - " ],\n", - "}\n", - "\n", - "response = client.chat.completions.create(**data)\n", - "print(response.choices[0].message.content)" - ] + "id": "6852fc7a-0b09-451b-bbc2-939fa96a4d28", + "outputId": "8555033d-d20f-4b0b-8bfa-7fa5c83a299b" + }, + "outputs": [], + "source": [ + "# Upload a file\n", + "model = \"intfloat/multilingual-e5-large\"\n", + "params = {\"collection\": collection, \"embeddings_model\": embeddings_model}\n", + "\n", + "files = {\"files\": (os.path.basename(file_path), open(file_path, \"rb\"), \"application/pdf\")}\n", + "response = session.post(f\"{base_url}/files\", params=params, files=files)\n", + "\n", + "response.json()" + ] + }, + { + "cell_type": "markdown", + "id": "f78ec73c-3e83-4266-a8de-c6a198f317b4", + "metadata": { + "id": "f78ec73c-3e83-4266-a8de-c6a198f317b4" + }, + "source": [ + "Vous pouvez observer les fichiers que vous avez importer dans une collection à l'aide du endpoint GET `/v1/files.`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd6d6140-5c91-4c3e-9350-b6c8550ab145", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - { - "cell_type": "markdown", - "id": "fb9c9b07", - "metadata": { - "id": "fb9c9b07" - }, - "source": [ - "Vous pouvez récupérer les metadata des tools utilisées. Ces metadata vous donnera, entre autre, le prompt envoyé au modèle." - ] + "id": "bd6d6140-5c91-4c3e-9350-b6c8550ab145", + "outputId": "0ddea4bb-889e-4ebc-a912-7a2e461ea987" + }, + "outputs": [], + "source": [ + "# Retrieve the file ID for RAG\n", + "response = session.get(f\"{base_url}/files/{collection}\")\n", + "response.json()\n", + "file_id = response.json()[\"data\"][0][\"id\"]\n", + "print(file_id)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e5cd813-5c19-4219-a404-6ed154991dfc", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - { - "cell_type": "code", - "execution_count": null, - "id": "dda2be68", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "dda2be68", - "outputId": "d7a05cbf-b35a-46a6-e365-1f97d74a3c34" - }, - "outputs": [], - "source": [ - "print(response.metadata[0][\"BaseRAG\"][\"prompt\"])" - ] - } - ], - "metadata": { + "id": "2e5cd813-5c19-4219-a404-6ed154991dfc", + "outputId": "d25fe84d-d7b4-4e84-ef26-e7986347d5aa" + }, + "outputs": [], + "source": [ + "# Display tools parameters\n", + "response = session.get(f\"{base_url}/tools\")\n", + "for tool in response.json()[\"data\"]:\n", + " if tool[\"id\"] == \"BaseRAG\":\n", + " print(tool[\"description\"].strip())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f374c1ad-b5ec-4870-a11a-953c7d219f94", + "metadata": { "colab": { - "provenance": [] + "base_uri": "https://localhost:8080/" }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" + "id": "f374c1ad-b5ec-4870-a11a-953c7d219f94", + "outputId": "64279978-1ae5-4bac-f028-bb0899d83d22" + }, + "outputs": [], + "source": [ + "# Chat completions\n", + "data = {\n", + " \"model\": language_model,\n", + " \"messages\": [{\"role\": \"user\", \"content\": \"Qui est Ulrich Tan ?\"}],\n", + " \"stream\": False,\n", + " \"n\": 1,\n", + " \"tools\": [\n", + " {\n", + " \"function\": {\n", + " \"name\": \"BaseRAG\",\n", + " \"parameters\": {\n", + " \"embeddings_model\": embeddings_model,\n", + " \"collections\": [collection],\n", + " \"k\": 2,\n", + " },\n", + " },\n", + " \"type\": \"function\",\n", + " }\n", + " ],\n", + "}\n", + "\n", + "response = client.chat.completions.create(**data)\n", + "print(response.choices[0].message.content)" + ] + }, + { + "cell_type": "markdown", + "id": "fb9c9b07", + "metadata": { + "id": "fb9c9b07" + }, + "source": [ + "Vous pouvez récupérer les metadata des tools utilisées. Ces metadata vous donnera, entre autre, le prompt envoyé au modèle." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dda2be68", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.3" - } + "id": "dda2be68", + "outputId": "d7a05cbf-b35a-46a6-e365-1f97d74a3c34" + }, + "outputs": [], + "source": [ + "print(response.metadata[0][\"BaseRAG\"][\"prompt\"])" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" }, - "nbformat": 4, - "nbformat_minor": 5 + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/pyproject.toml b/pyproject.toml index 791dafae..f1106c63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,15 +31,21 @@ app = [ "grist-api==0.1.0", "pdfminer.six==20240706", ] +dev = [ + "ruff==0.6.5", + "pre-commit==3.6.2", +] -[tool.setuptools] -packages = [] +[tool.ruff] +line-length = 150 -[ruff] -line-length = 15 +[tool.ruff.lint] +ignore = ["F403", "F841"] # import * and never used variables [ruff.isort] force-sort-within-sections = true known-first-party = ["config", "utils", "app"] forced-separate = ["tests"] -sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] \ No newline at end of file +sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] + + diff --git a/ui/chat.py b/ui/chat.py index 61e7002e..4976e584 100644 --- a/ui/chat.py +++ b/ui/chat.py @@ -1,5 +1,3 @@ -import time - import streamlit as st from config import BASE_URL from openai import OpenAI diff --git a/ui/pages/files.py b/ui/pages/files.py index 119b9d18..d55edb9d 100644 --- a/ui/pages/files.py +++ b/ui/pages/files.py @@ -22,14 +22,16 @@ table = [] for collection, files in file_data.items(): for file in files: - table.append([ - collection, - [collection["model"] for collection in collections if collection["id"] == collection][0], - file["id"], - file["filename"], - f"{file['bytes'] / (1024 * 1024):.2f} MB", - dt.datetime.fromtimestamp(file["created_at"]).strftime("%Y-%m-%d"), - ]) + table.append( + [ + collection, + [collection["model"] for collection in collections if collection["id"] == collection][0], + file["id"], + file["filename"], + f"{file['bytes'] / (1024 * 1024):.2f} MB", + dt.datetime.fromtimestamp(file["created_at"]).strftime("%Y-%m-%d"), + ] + ) columns = ["Collection", "Embeddings model", "ID", "Name", "Size", "Created at"] df = pd.DataFrame(table, columns=columns) diff --git a/ui/utils.py b/ui/utils.py index aee51b19..ab0c131f 100644 --- a/ui/utils.py +++ b/ui/utils.py @@ -7,6 +7,7 @@ from config import BASE_URL, EMBEDDINGS_MODEL_TYPE, LANGUAGE_MODEL_TYPE, LOCAL_STORAGE_KEY + def set_config(): st.set_page_config( page_title="Albert", @@ -35,10 +36,10 @@ def header(): local_storage.deleteItem(LOCAL_STORAGE_KEY) st.rerun() st.markdown("***") - + return API_KEY - + def check_api_key(base_url: str, api_key: str): headers = {"Authorization": f"Bearer {api_key}"} response = requests.get(url=base_url.replace("/v1", "/health"), headers=headers)