diff --git a/README.md b/README.md index 3e74ca7..443c141 100644 --- a/README.md +++ b/README.md @@ -34,31 +34,55 @@ En ce base sur le [client officiel python d'OpenAI](https://github.com/openai/op Ce formalisme permet d'intégrer facilement l'API Albert avec des librairies tierces comme [Langchain](https://www.langchain.com/) ou [LlamaIndex](https://www.llamaindex.ai/). -### Multi models +### Converser avec un modèle de langage (chat memory) + +Albert API intègre nativement la mémorisation des messages pour les conversations sans surcharger d'arguments le endpoint `/v1/chat/completions` par rapport à la documentation d'OpenAI. Cela consiste à envoyer à chaque requête au modèle l'historique de la conversation pour lui fournir le contexte. + +> 📖 [Notebook de démonstration](./tutorials/chat_completions.ipynb) + +### Accéder à plusieurs modèles de langage (multi models) Grâce à un fichier de configuration (*[config.example.yml](./config.example.yml)*) vous pouvez connecter autant d'API de modèles que vous le souhaitez. L'API Albert se charge de mutualiser l'accès à tous ces modèles dans une unique API. Vous pouvez constater les différents modèles accessibles en appelant le endpoint `/v1/models`. > 📖 [Notebook de démonstration](./tutorials/models.ipynb) -### Chat history +### Fonctionnalités avancées (tools) -Albert API intègre nativement la mémorisation des messages pour les conversations sans surcharger d'arguments le endpoint `/v1/chat/completions` par rapport à la documentation d'OpenAI. Cela consiste à envoyer à chaque requête au modèle l'historique de la conversation pour lui fournir le contexte. +Les tools sont une fonctionnalité définie OpenAI que l'on surcharge dans le cas de l'API Albert pour permettre de configurer des tâches spéficiques comme du RAG ou le résumé. Vous pouvez appelez le endpoint `/tools` pour voir la liste des tools disponibles. -> 📖 [Notebook de démonstration](./tutorials/chat_completions.ipynb) +![](./assets/chatcompletion.png) -### Tools (multi agents, RAG, résumé...) +#### Interroger des documents (RAG) -Les tools sont une fonctionnalité définie OpenAI que l'on surcharge dans le cas de l'API Albert pour permettre de configurer des tâches spéficiques comme du RAG ou le résumé. Vous pouvez appelez le endpoint `/tools` pour voir la liste des tools disponibles. +> 📖 [Notebook de démonstration](./tutorials/retrival_augmented_generation.ipynb) -> 📖 [Notebook de démonstration : RAG](./tutorials/retrival_augmented_generation.ipynb) +### Résumer un document (summarize) -![](./assets/chatcompletion.png) +> 📖 [Notebook de démonstration](./tutorials/summarize.ipynb) -### Accès par token +## Déployer l'API Albert -Albert API permet de protégrer son accès avec un ou plusieurs tokens d'authentification, voir la section [Auth](#auth) pour plus d'informations. +### Quickstart -## Configuration +1. Installez [libmagic](https://man7.org/linux/man-pages/man3/libmagic.3.html) + +2. Installez les packages Python + + ```bash + cd app + pip install . + ``` + +3. Créez un fichier *config.yml* à la racine du repository sur la base du fichier d'exemple *[config.example.yml](./config.example.yml)* + + Si vous souhaitez configurer les accès aux modèles et aux bases de données, consultez la [Configuration](#configuration). + + Pour lancer l'API : + ```bash + uvicorn app.main:app --reload --port 8080 --log-level debug + ``` + +### Configuration Toute la configuration de l'API Albert se fait dans fichier de configuration qui doit respecter les spécifications suivantes (voir *[config.example.yml](./config.example.yml)* pour un exemple) : @@ -102,13 +126,13 @@ CONFIG_FILE= uvicorn main:app --reload --port 8080 --log-level La configuration permet de spéficier le token d'accès à l'API, les API de modèles auquel à accès l'API d'Albert ainsi que les bases de données nécessaires à sont fonctionnement. -### Auth +#### Auth Les IAM supportés, de nouveaux seront disponibles prochainements : * [Grist](https://www.getgrist.com/) -### Databases +#### Databases 3 bases de données sont à configurées dans le fichier de configuration (*[config.example.yml](./config.example.yml)*) : * vectors : pour le vector store @@ -125,6 +149,8 @@ Voici les types de base de données supportées, de nouvelles seront disponibles ## Tests +Vous pouvez vérifier le bon déploiement de votre API à l'aide en exécutant des tests unitaires : + ```bash cd app/tests CONFIG_FILE="../../config.yml" pytest test_models.py diff --git a/app/endpoints/__init__.py b/app/endpoints/__init__.py index 119b050..e69de29 100644 --- a/app/endpoints/__init__.py +++ b/app/endpoints/__init__.py @@ -1,7 +0,0 @@ -from .chat import router as ChatRouter -from .collections import router as CollectionsRouter -from .completions import router as CompletionsRouter -from .embeddings import router as EmbeddingsRouter -from .files import router as FilesRouter -from .models import router as ModelsRouter -from .tools import router as ToolsRouter diff --git a/app/endpoints/chat.py b/app/endpoints/chat.py index 8a35b96..43d2265 100644 --- a/app/endpoints/chat.py +++ b/app/endpoints/chat.py @@ -1,21 +1,19 @@ import uuid -import sys - from typing import Optional, Union + from fastapi import APIRouter, Security, HTTPException from fastapi.responses import StreamingResponse -sys.path.append("..") -from schemas.chat import ( +from app.schemas.chat import ( ChatHistory, ChatHistoryResponse, ChatCompletionRequest, ChatCompletionResponse, ) -from utils.security import check_api_key, secure_data -from utils.lifespan import clients -from tools import * -from tools import __all__ as tools_list +from app.utils.security import check_api_key, secure_data +from app.utils.lifespan import clients +from app.tools import * +from app.tools import __all__ as tools_list router = APIRouter() @@ -37,6 +35,17 @@ async def chat_completions( except KeyError: raise HTTPException(status_code=404, detail="Model not found.") + if request["user"]: + # retrieve chat history + if chat_id: + chat_history = clients["chathistory"].get_chat_history( + user_id=request["user"], chat_id=chat_id + ) + if "messages" in chat_history.keys(): # to avoid empty chat history + request["messages"] = chat_history["messages"] + request["messages"] + else: + chat_id = str(uuid.uuid4()) + # tools user_message = request["messages"][-1] # keep user message without tools for chat history tools = request.get("tools") @@ -57,17 +66,6 @@ async def chat_completions( request["messages"] = [{"role": "user", "content": prompt}] request.pop("tools") - if request["user"]: - # retrieve chat history - if chat_id: - chat_history = clients["chathistory"].get_chat_history( - user_id=request["user"], chat_id=chat_id - ) - if "messages" in chat_history.keys(): # to avoid empty chat history - request["messages"] = chat_history["messages"] + request["messages"] - else: - chat_id = str(uuid.uuid4()) - # non stream case if not request["stream"]: response = client.chat.completions.create(**request) diff --git a/app/endpoints/collections.py b/app/endpoints/collections.py index 6354471..88a4035 100644 --- a/app/endpoints/collections.py +++ b/app/endpoints/collections.py @@ -1,12 +1,10 @@ -import sys import re from fastapi import APIRouter, Security -sys.path.append("..") -from schemas.collections import CollectionResponse -from utils.security import check_api_key -from utils.lifespan import clients +from app.schemas.collections import CollectionResponse +from app.utils.security import check_api_key +from app.utils.lifespan import clients router = APIRouter() diff --git a/app/endpoints/completions.py b/app/endpoints/completions.py index e0d2994..2f11f1d 100644 --- a/app/endpoints/completions.py +++ b/app/endpoints/completions.py @@ -1,11 +1,8 @@ -import sys - from fastapi import APIRouter, Security -sys.path.append("..") -from schemas.completions import CompletionRequest, CompletionResponse -from utils.lifespan import clients -from utils.security import check_api_key +from app.schemas.completions import CompletionRequest, CompletionResponse +from app.utils.lifespan import clients +from app.utils.security import check_api_key router = APIRouter() diff --git a/app/endpoints/embeddings.py b/app/endpoints/embeddings.py index 68039c6..9797bcf 100644 --- a/app/endpoints/embeddings.py +++ b/app/endpoints/embeddings.py @@ -1,11 +1,8 @@ -import sys - from fastapi import APIRouter, Security -sys.path.append("..") -from schemas.embeddings import EmbeddingsRequest, EmbeddingResponse -from utils.lifespan import clients -from utils.security import check_api_key +from app.schemas.embeddings import EmbeddingsRequest, EmbeddingResponse +from app.utils.lifespan import clients +from app.utils.security import check_api_key router = APIRouter() @@ -24,4 +21,4 @@ async def embeddings( client = clients["openai"][request["model"]] response = client.embeddings.create(**request) - return EmbeddingResponse(**response) + return response diff --git a/app/endpoints/files.py b/app/endpoints/files.py index b1edb2e..031a8e7 100644 --- a/app/endpoints/files.py +++ b/app/endpoints/files.py @@ -1,6 +1,5 @@ import base64 import uuid -import sys from typing import List, Optional, Union @@ -10,12 +9,11 @@ from langchain_community.vectorstores import Qdrant as VectorStore from qdrant_client.http import models as rest -sys.path.append("..") -from schemas.files import File, FileResponse, FileUploadResponse -from utils.config import logging -from utils.security import check_api_key, secure_data -from utils.lifespan import clients -from helpers import S3FileLoader +from app.schemas.files import File, FileResponse, FileUploadResponse +from app.utils.config import logging +from app.utils.security import check_api_key, secure_data +from app.utils.lifespan import clients +from app.helpers import S3FileLoader router = APIRouter() diff --git a/app/endpoints/models.py b/app/endpoints/models.py index 7a036a6..d13c6df 100644 --- a/app/endpoints/models.py +++ b/app/endpoints/models.py @@ -1,13 +1,11 @@ import urllib from typing import Union, Optional -import sys from fastapi import APIRouter, Security -sys.path.append("..") -from schemas.models import Model, ModelResponse -from utils.lifespan import clients -from utils.security import check_api_key +from app.schemas.models import Model, ModelResponse +from app.utils.lifespan import clients +from app.utils.security import check_api_key router = APIRouter() diff --git a/app/endpoints/tools.py b/app/endpoints/tools.py index d34868e..e3f595b 100644 --- a/app/endpoints/tools.py +++ b/app/endpoints/tools.py @@ -1,12 +1,9 @@ -import sys - from fastapi import APIRouter, Security -sys.path.append("..") -from schemas.tools import ToolResponse -from utils.security import check_api_key -from tools import * -from tools import __all__ as tools_list +from app.schemas.tools import ToolResponse +from app.utils.security import check_api_key +from app.tools import * +from app.tools import __all__ as tools_list router = APIRouter() diff --git a/app/helpers/_universalparser.py b/app/helpers/_universalparser.py index a092cb2..ca6dcf7 100644 --- a/app/helpers/_universalparser.py +++ b/app/helpers/_universalparser.py @@ -1,7 +1,6 @@ from docx import Document from langchain.text_splitter import RecursiveCharacterTextSplitter -# from llmsherpa.readers import LayoutPDFReader from langchain_community.document_loaders import PDFMinerLoader from langchain.docstore.document import Document as langchain_doc import magic @@ -100,20 +99,6 @@ def _pdf_to_chunks( list: List of Langchain documents, where each document corresponds to a text chunk. """ - # Llmsherpa is replaced by PDFMiner for now - - # llmsherpa_api_url = ( - # "https://readers.llmsherpa.com/api/document/developer/parseDocument?renderFormat=all" - # ) - # pdf_reader = LayoutPDFReader(llmsherpa_api_url) - # doc = pdf_reader.read_pdf(file_path) - - # def concatene_chunks(doc): - # chunks = [] - # for chunk in doc.chunks(): - # chunks.append(chunk.to_text()) - # return "\n".join(chunks) - loader = PDFMinerLoader(file_path) doc = loader.load() diff --git a/app/main.py b/app/main.py index 4101149..b16d2a4 100644 --- a/app/main.py +++ b/app/main.py @@ -1,19 +1,18 @@ from fastapi import FastAPI, Security, Response -from utils.lifespan import lifespan -from utils.security import check_api_key -from endpoints import ( - ChatRouter, - CollectionsRouter, - CompletionsRouter, - EmbeddingsRouter, - FilesRouter, - ModelsRouter, - ToolsRouter, -) +from app.utils.lifespan import lifespan +from app.utils.security import check_api_key +from app.endpoints import chat, completions, collections, embeddings, files, models, tools +from app.utils.config import APP_CONTACT_URL, APP_CONTACT_EMAIL, APP_VERSION, APP_DESCRIPTION -# @TODO: add metadata: https://fastapi.tiangolo.com/tutorial/metadata/ -app = FastAPI(title="Albert API", version="1.0.0", lifespan=lifespan) +app = FastAPI( + title="Albert API", + version=APP_VERSION, + description=APP_DESCRIPTION, + contact={"url": APP_CONTACT_URL, "email": APP_CONTACT_EMAIL}, + licence_info={"name": "MIT License", "identifier": "MIT"}, + lifespan=lifespan, +) @app.get("/health") @@ -25,10 +24,10 @@ def health(api_key: str = Security(check_api_key)): return Response(status_code=200) -app.include_router(ModelsRouter, tags=["Models"], prefix="/v1") -app.include_router(ChatRouter, tags=["Chat"], prefix="/v1") -app.include_router(CompletionsRouter, tags=["Completions"], prefix="/v1") -app.include_router(EmbeddingsRouter, tags=["Embeddings"], prefix="/v1") -app.include_router(CollectionsRouter, tags=["Collections"], prefix="/v1") -app.include_router(FilesRouter, tags=["Files"], prefix="/v1") -app.include_router(ToolsRouter, tags=["Tools"], prefix="/v1") +app.include_router(models.router, tags=["Models"], prefix="/v1") +app.include_router(chat.router, tags=["Chat"], prefix="/v1") +app.include_router(completions.router, tags=["Completions"], prefix="/v1") +app.include_router(embeddings.router, tags=["Embeddings"], prefix="/v1") +app.include_router(collections.router, tags=["Collections"], prefix="/v1") +app.include_router(files.router, tags=["Files"], prefix="/v1") +app.include_router(tools.router, tags=["Tools"], prefix="/v1") diff --git a/app/pyproject.toml b/app/pyproject.toml index 8aba755..c9d9ca4 100644 --- a/app/pyproject.toml +++ b/app/pyproject.toml @@ -21,10 +21,10 @@ dependencies = [ "docx==0.2.4", "pyyaml==6.0.1", "python-docx==1.1.2", - "llmsherpa==0.1.4", "unstructured==0.14.9", "python-magic==0.4.27", "grist-api==0.1.0", + "pdfminer.six==20240706", ] [tool.setuptools] diff --git a/app/tests/test_chat.py b/app/tests/test_chat.py index fd20d2d..80b7fd1 100644 --- a/app/tests/test_chat.py +++ b/app/tests/test_chat.py @@ -1,9 +1,6 @@ -import sys - from fastapi.testclient import TestClient -sys.path.append("..") -from main import app +from app.main import app model = "AgentPublic/llama3-instruct-8b" prompt = "Hello world !" diff --git a/app/tests/test_models.py b/app/tests/test_models.py index 0bfd4f3..a7505eb 100644 --- a/app/tests/test_models.py +++ b/app/tests/test_models.py @@ -1,9 +1,6 @@ -import sys - from fastapi.testclient import TestClient -sys.path.append("..") -from main import app +from app.main import app def test_get_models(): diff --git a/app/tools/_baserag.py b/app/tools/_baserag.py index 54af3b9..4e1199e 100644 --- a/app/tools/_baserag.py +++ b/app/tools/_baserag.py @@ -1,13 +1,11 @@ from typing import List, Optional -import sys from langchain_huggingface import HuggingFaceEndpointEmbeddings from fastapi import HTTPException from qdrant_client.http import models as rest -sys.path.append("..") -from utils.security import secure_data -from utils.data import search_multiple_collections, get_all_collections +from app.utils.security import secure_data +from app.utils.data import search_multiple_collections, get_all_collections class BaseRAG: diff --git a/app/tools/_usefiles.py b/app/tools/_usefiles.py index 7478c2f..0162d14 100644 --- a/app/tools/_usefiles.py +++ b/app/tools/_usefiles.py @@ -1,11 +1,9 @@ -import sys from typing import List, Optional from fastapi import HTTPException -sys.path.append("..") -from utils.data import file_to_chunk -from utils.security import secure_data +from app.utils.data import file_to_chunk +from app.utils.security import secure_data class UseFiles: @@ -29,10 +27,6 @@ async def get_prompt( file_ids: Optional[List[str]] = None, **request, ) -> str: - if "user" not in request: - raise HTTPException( - status_code=400, detail="User parameter must be provide with UseFiles tool." - ) prompt = request["messages"][-1]["content"] if "{files}" not in prompt: raise HTTPException( diff --git a/app/utils/config.py b/app/utils/config.py index 5d0b56a..1b62f01 100644 --- a/app/utils/config.py +++ b/app/utils/config.py @@ -1,14 +1,20 @@ import os import logging -import sys - import yaml -sys.path.append("..") -from schemas.config import Config +from app.schemas.config import Config logging.basicConfig(format="%(levelname)s:%(asctime)s: %(message)s", level=logging.INFO) -CONFIG_FILE = os.getenv("CONFIG_FILE", "../config.yml") +CONFIG_FILE = os.getenv("CONFIG_FILE", "config.yml") logging.info(f"loading configuration file: {CONFIG_FILE}") CONFIG = Config(**yaml.safe_load(open(CONFIG_FILE, "r"))) + +# Metadata +APP_CONTACT_URL = os.getenv("APP_CONTACT_URL") +APP_CONTACT_EMAIL = os.getenv("APP_CONTACT_EMAIL") +APP_VERSION = os.getenv("APP_VERSION", "0.0.0") +APP_DESCRIPTION = os.getenv( + "APP_DESCRIPTION", + "[See documentation](https://github.com/etalab-ia/albert-api/blob/main/README.md)", +) \ No newline at end of file diff --git a/app/utils/data.py b/app/utils/data.py index d5d1964..cac24f0 100644 --- a/app/utils/data.py +++ b/app/utils/data.py @@ -56,12 +56,7 @@ def get_all_collections(vectorstore, api_key: str): def search_multiple_collections( - vectorstore, - emmbeddings, - prompt: str, - collections: list, - k: int = 4, - filter: dict = None, + vectorstore, emmbeddings, prompt: str, collections: list, k: int = 4, filter: dict = None ): docs = [] for collection in collections: @@ -73,4 +68,7 @@ def search_multiple_collections( docs.extend(vectorstore.similarity_search(prompt, k=k, filter=filter)) + # sort by similarity score and get top k + docs = sorted(docs, key=lambda x: x[1], reverse=True)[:k] + return docs diff --git a/app/utils/lifespan.py b/app/utils/lifespan.py index d92b642..00e2f1f 100644 --- a/app/utils/lifespan.py +++ b/app/utils/lifespan.py @@ -3,7 +3,7 @@ from fastapi import FastAPI, HTTPException from openai import OpenAI -from .config import CONFIG, logging +from app.utils.config import CONFIG, logging class ModelDict(dict): @@ -28,7 +28,7 @@ async def lifespan(app: FastAPI): # auth if CONFIG.auth: if CONFIG.auth.type == "grist": - from helpers import GristKeyManager + from app.helpers import GristKeyManager clients["auth"] = GristKeyManager(**CONFIG.auth.args) else: @@ -58,7 +58,7 @@ async def lifespan(app: FastAPI): # chathistory if CONFIG.databases.chathistory.type == "redis": - from helpers import RedisChatHistory + from app.helpers import RedisChatHistory clients["chathistory"] = RedisChatHistory(**CONFIG.databases.chathistory.args) diff --git a/app/utils/security.py b/app/utils/security.py index 1ed627b..5de7d80 100644 --- a/app/utils/security.py +++ b/app/utils/security.py @@ -7,7 +7,7 @@ from fastapi import HTTPException, Depends from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials -from .lifespan import clients +from app.utils.lifespan import clients def encode_string(input: str) -> str: diff --git a/compose.yml b/compose.yml index 92c0d90..ff1e7dd 100644 --- a/compose.yml +++ b/compose.yml @@ -1,7 +1,6 @@ version: "3.8" services: -<<<<<<< HEAD fastapi: image: ghcr.io/etalab-ia/albert-api/fastapi:latest command: uvicorn app.main:app --host 0.0.0.0 --port 8000 @@ -15,27 +14,12 @@ services: - 8000:8000 volumes: - ./conf:/home/albert/conf # a config.yml file should be in this folder -======= - # api: - # image: albert/api:latest - # command: uvicorn app.main:app --host 0.0.0.0 --port 8000 - # build: - # context: ./app - # dockerfile: Dockerfile - # restart: always - # environment: - # - CONFIG_FILE=${CONFIG_FILE:-"/home/albert/conf/config.yml"} - # ports: - # - 8000:8000 - # volumes: - # - ./conf:/home/albert/conf # a config.yml file should be in this folder ->>>>>>> 66b7378 (init commit) qdrant: image: qdrant/qdrant:v1.9.7-unprivileged restart: always - # environment: - # - QDRANT__SERVICE__API_KEY=${QDRANT_PASSWORD:-"changeme"} + environment: + - QDRANT__SERVICE__API_KEY=changeme ports: - 6333:6333 - 6334:6334 @@ -48,7 +32,7 @@ services: command: server /data --console-address ":9001" environment: - MINIO_ROOT_USER=minio - - MINIO_ROOT_PASSWORD=${MINIO_PASSWORD:-"changeme"} + - MINIO_ROOT_PASSWORD=changeme ports: - 9000:9000 - 9001:9001 @@ -57,7 +41,7 @@ services: redis: image: redis/redis-stack-server:7.2.0-v11 - command: redis-server --dir /data --requirepass ${REDIS_PASSWORD:-"changeme"} --save 60 1 --appendonly yes + command: redis-server --dir /data --requirepass changeme --save 60 1 --appendonly yes restart: always ports: - 6379:6379 diff --git a/tutorials/retrival_augmented_generation.ipynb b/tutorials/retrival_augmented_generation.ipynb index 17d8f1d..b44b8fb 100644 --- a/tutorials/retrival_augmented_generation.ipynb +++ b/tutorials/retrival_augmented_generation.ipynb @@ -164,20 +164,6 @@ "response = client.chat.completions.create(**data)\n", "print(response.choices[0].message.content)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6dd19051-bb51-4b0c-b300-f86b39f17ee0", - "metadata": {}, - "outputs": [], - "source": [ - "# View chat history\n", - "chat_id = response.id\n", - "\n", - "response = session.get(url=f\"{base_url}/chat/history/{user}/{chat_id}\")\n", - "print(response.json()[\"messages\"][0][\"content\"])" - ] } ], "metadata": { diff --git a/tutorials/summarize.ipynb b/tutorials/summarize.ipynb new file mode 100644 index 0000000..29ef665 --- /dev/null +++ b/tutorials/summarize.ipynb @@ -0,0 +1,335 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4ec838ca-2391-4aac-b71a-d6800b4d9b05", + "metadata": {}, + "source": [ + "# Résumer un document" + ] + }, + { + "cell_type": "markdown", + "id": "19d15dde-f884-4d38-8f85-f18e5c6b7fb2", + "metadata": {}, + "source": [ + "Commencez par télécharger le document que vous souhaitez résumer." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e80daa99-3416-4b81-a8aa-4fb7427bbe6c", + "metadata": {}, + "outputs": [], + "source": [ + "# Download a file\n", + "import wget\n", + "\n", + "file_path = \"my_document.pdf\"\n", + "doc_url = \"https://beta.gouv.fr/content/docs/mooc/23-lancez-vous-avec-demarches-simplifiees.pdf\"\n", + "\n", + "wget.download(doc_url, out=file_path)" + ] + }, + { + "cell_type": "markdown", + "id": "c9e8bcec-a669-4719-b657-7bbb87c833c6", + "metadata": {}, + "source": [ + "Puis instancier la connexion à l'API Albert." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "9e17262f-ab1a-439e-8342-10334e377d76", + "metadata": {}, + "outputs": [], + "source": [ + "# Request configuration\n", + "import requests\n", + "import os\n", + "\n", + "base_url = os.getenv(\"API_URL\") # e.g. \"http://localhost:8000/v1/\"\n", + "api_key = os.getenv(\"API_KEY\") # e.g. \"your api key\"\n", + "\n", + "session = requests.session()\n", + "session.headers = {\"Authorization\": f\"Bearer {api_key}\"} # skip headers if no api_key is setup in config.ini file" + ] + }, + { + "cell_type": "markdown", + "id": "11e7ec99-edf5-4669-9e6f-5d98d9008b09", + "metadata": {}, + "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": 8, + "id": "6852fc7a-0b09-451b-bbc2-939fa96a4d28", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'object': 'list',\n", + " 'data': [{'object': 'upload',\n", + " 'id': 'cefd07f0-de07-439a-9b20-d3aae6985a3b',\n", + " 'filename': 'my_document.pdf',\n", + " 'status': 'success'}]}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Upload a file\n", + "collection = \"leo\"\n", + "model = \"BAAI/bge-m3\"\n", + "params = {\"collection\": collection, \"model\": 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": "b5d8bb34-c325-483a-a548-f7c15684d2a5", + "metadata": {}, + "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": {}, + "outputs": [], + "source": [ + "# Retrieve the file ID for RAG\n", + "response = session.get(f\"{base_url}/files/{collection}\")\n", + "\n", + "response.json()" + ] + }, + { + "cell_type": "markdown", + "id": "d0fed942-cd46-4623-9b0f-9946c2fb157f", + "metadata": {}, + "source": [ + "Chaque fichier importé est associé un _file_id_, stocker le, il sera nécessaire pour la suite." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0957f09-948e-4e42-9c7c-7283d72d4d89", + "metadata": {}, + "outputs": [], + "source": [ + "file_id = response.json()[\"data\"][0][\"id\"]" + ] + }, + { + "cell_type": "markdown", + "id": "98b86848-f250-457c-8a04-39b034147c08", + "metadata": {}, + "source": [ + "Pour résumer un document vous devez utiliser le _tool_ qui permet de fournir au modèle un ou plusieurs document préalablement importés : _UseFile_. Vous trouverez la liste des _tools_ ainsi que leur paramètres avec le endpoint GET `/v1/tools`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2e5cd813-5c19-4219-a404-6ed154991dfc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fill your prompt with file contents. Your prompt must contain \"{files}\" placeholder.\n", + "\n", + " Args:\n", + " collection (str): Collection name.\n", + " file_ids (List[str]): List of file ids in the selected collection.\n" + ] + } + ], + "source": [ + "# Display tools parameters\n", + "response = session.get(f\"{base_url}/tools\")\n", + "for tool in response.json()[\"data\"]:\n", + " if tool[\"id\"] == \"UseFiles\":\n", + " print(tool[\"description\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "d8f7bfe0-161f-442a-ae00-b2e4a64a7681", + "metadata": {}, + "outputs": [], + "source": [ + "# OpenAI client configuration\n", + "from openai import OpenAI\n", + "\n", + "client = OpenAI(base_url=base_url, api_key=api_key)" + ] + }, + { + "cell_type": "markdown", + "id": "58b308e4-4c35-41f6-9399-04254623c4c8", + "metadata": {}, + "source": [ + "Nous pouvons maintenant interroger le modèle avec le endpoint POST `/v1/chat/completions`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f374c1ad-b5ec-4870-a11a-953c7d219f94", + "metadata": {}, + "outputs": [], + "source": [ + "# Chat completions\n", + "message = \"Résume le document suivant : {files}\"\n", + "user = \"leo\" # for chat history\n", + "data = {\n", + " \"model\": \"AgentPublic/llama3-instruct-8b\",\n", + " \"user\": user,\n", + " \"messages\": [{\"role\": \"user\", \"content\": message}],\n", + " \"stream\": False,\n", + " \"n\": 1,\n", + " \"tools\": [\n", + " {\n", + " \"function\": {\n", + " \"name\": \"UseFiles\",\n", + " \"parameters\": {\n", + " \"collection\": collection,\n", + " \"file_ids\": [file_id]\n", + " },\n", + " },\n", + " \"type\": \"function\",\n", + " }\n", + " ],\n", + "}\n", + "\n", + "response = session.post(f\"{base_url}/chat/completions\", json=data)\n", + "print(response.json()[\"choices\"][0][\"content\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "ba841f60-7905-4569-9e00-659ead774d87", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Le document présente le service \"Demarches-simplifiées\" créé par la DINSIC (Direction interministérielle du numérique et des systèmes d'information et de communication) en 2015. Ce service en ligne permet aux acteurs publics de créer des formulaires en ligne pour les démarches administratives, sans nécessiter d'installation technique. \n", + "\n", + "L'objectif est de dématérialiser 100% des démarches administratives d'ici 2022. Pour y parvenir, Demarches-simplifiées permet aux acteurs publics de créer des formulaires en ligne de manière simple, en utilisant les ressources et données de la DINSIC. Les agents publics peuvent ainsi gérer les démarches en ligne sans avoir à posséder des compétences techniques en informatique.\n", + "\n", + "Le service a déjà été adopté par plus de 500 organismes publics et plus de 5000 agents publics sont inscrits comme instructeurs. L'objectif est de dépasser les 10 000 démarches en ligne d'ici la fin de l'année 2018.\n", + "\n", + "Le service est considéré comme une \"Startup d'État\" créée pour accompagner la dématérialisation des démarches administratives. Il a été développé avec un mode opératoire innovant et des interfaces simples, sans nécessiter de connaissances en informatique.\n" + ] + } + ], + "source": [ + "print(response.json()[\"choices\"][0][\"message\"][\"content\"])" + ] + }, + { + "cell_type": "markdown", + "id": "2e44020f-5263-4cbb-96a6-da5acccdf3a8", + "metadata": {}, + "source": [ + "A l'aide de l'historique de chat disponible en récupérant l'ID retourné puisque j'ai initié une nouvelle conversation, je peux continuer a affiner sa réponse." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "c8d25006-27a8-47fd-82ff-314980068c6f", + "metadata": {}, + "outputs": [], + "source": [ + "chat_id = response.json()[\"id\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "ee23a583-ed65-4161-b438-8e6a36081820", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Oui, bien sûr !\n", + "\n", + "Voici les chiffres clés extrait du résumé :\n", + "\n", + "* 500 : Nombre d'organismes publics qui ont adopté le service\n", + "* 5000 : Nombre d'agents publics inscrits comme instructeurs\n", + "* 10 000 : Objectif de démarches en ligne à atteindre d'ici la fin de l'année 2018\n", + "* 2022 : Objectif de dématérialiser 100% des démarches administratives\n" + ] + } + ], + "source": [ + "message = \"Peux-tu me sortir les chiffres clefs du résumé que tu as fait ?\"\n", + "\n", + "data = {\n", + " \"model\": \"AgentPublic/llama3-instruct-8b\",\n", + " \"user\": user,\n", + " \"id\": chat_id,\n", + " \"messages\": [{\"role\": \"user\", \"content\": message}],\n", + " \"stream\": False,\n", + " \"n\": 1,\n", + "}\n", + "\n", + "response = session.post(f\"{base_url}/chat/completions\", json=data)\n", + "print(response.json()[\"choices\"][0][\"message\"][\"content\"])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "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 +}