diff --git a/PAD b/PAD index 95f1f1b..1d76e6e 100644 --- a/PAD +++ b/PAD @@ -70,3 +70,6 @@ pip install rank-bm25 python -m streamlit run app.py +pip install spacy +python -m spacy download fr_core_news_md + diff --git a/rag.py b/rag.py index 63d0146..3fffd68 100644 --- a/rag.py +++ b/rag.py @@ -5,6 +5,7 @@ import copy import logging import json import requests +import spacy logging.basicConfig(filename='rag.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') @@ -24,8 +25,9 @@ class RAG: Votre mission : =============== -Vous êtes un assistant IA qui répond à des questions sur des produits et \ -services de la Caisse d'Epargne Rhône-Alpes, une banque régionale française. +Vous aidez un conseiller de la Caisse d'Epargne Rhône-Alpes, \ +une banque française, à répondre aux questions de son client. +Ces questions porteront sur des produits et services de la banque. Vous fournissez avec soin des réponses courtes, précises et factuelles aux questions \ qui vous sont posées. @@ -33,7 +35,7 @@ Instructions pour l'utilisation du contexte : ============================================= Vous répondez de façon brève et factuelle à la question posée \ -en utilisant un contexte formé de passages exraits du site web commercial \ +en utilisant un contexte formé de passages exraits du site web \ de la banque. Le contexte est délimité entre <<< et >>>. Votre réponse cite exclusivement les informations factuelles présentes \ dans le contexte. Vous utilisez les informations du contexte \ @@ -71,7 +73,8 @@ Question à laquelle répondre en utilisant le contexte : {tag_end} {tag_assistant} -Voici des informations factuelles et brèves qui répondent à la question : +Voici des informations factuelles et brèves qui pourront vous aider à \ +répondre à la question de votre client : """ self.query_reformulate_prompt = """ @@ -111,12 +114,13 @@ Question reformulée : " self.collection = self.chroma_client.get_collection(name=collection_name) # ./llamafile -n -1 -c 4096 --mlock --gpu APPLE -m a/a/zephyr-7b-beta.Q5_K_M.gguf self.llm_url = llm_url + self.nlp = spacy.load('fr_core_news_md') def answer(self, prompt, stream): post_params = { "prompt": prompt, - "temp": 0, + "temp": 0.1, "repeat_penalty": 1.2, "min_p": 0.05, "top_p": 0.5, @@ -130,38 +134,73 @@ Question reformulée : " if stream: return response else: return response.json()['content'] - def query_collection(self, query, n_results=3): + def keep_token(self, tok): + return tok.pos_ == 'NOUN' or tok.pos_ == 'VERB' or \ + tok.pos_ == 'PROPN' or tok.pos_ == 'ADJ' + + def lemmatize(self, str): + res = [] + for tok in self.nlp(str): + if self.keep_token(tok): + res.append(tok.lemma_) + return res + + def len_query_inter_doc(self, query_str, doc_str): + query_tok = self.lemmatize(query_str) + doc_tok = self.lemmatize(doc_str) + return len(set(query_tok) & set(doc_tok)) + + def query_collection(self, query, n_results=4): logging.info(f"query_collection / query: \n{query}") if self.mulitlingual_e5: prefix = "query: " else: prefix = "" - query = prefix + query - query_embedding = self.embed_model.encode(query, normalize_embeddings=True) + query_embedding = self.embed_model.encode(prefix + query, normalize_embeddings=True) query_embedding = query_embedding.tolist() results = self.collection.query( query_embeddings=[query_embedding], n_results=n_results, ) - ids_sources = "" - for i in range(len(results["documents"][0])): - id = results["ids"][0][i] - ids_sources += id + " ; " - logging.info(f"query_collection / sources: \n{ids_sources}") + res = {"passage": [], "url": [], "cat": [], "id": [], "nb_query_tok": [], "nb_tok": [], "dist": []} - self.urls = [] for i in range(len(results["documents"][0])): - self.urls.append(results["metadatas"][0][i]["url"]) + passage = results["documents"][0][i] + # compute the passage's number of tokens, apart from the title and subtitle + # which correspond to the first two '\n\n'-separated elements + nb_tok = len(self.lemmatize('\n\n'.join(passage.split('\n\n')[2:]))) + # Retain only long-enough passages + if nb_tok > 20: + res['id'].append(results["ids"][0][i]) + res['url'].append(results["metadatas"][0][i]["url"]) + res['cat'].append(results["metadatas"][0][i]["category"]) + res['passage'].append(passage) + res['nb_query_tok'].append(self.len_query_inter_doc(query, passage)) + res['nb_tok'].append(nb_tok) + res['dist'].append(results["distances"][0][i]) - return results + # Sort the best passages by their number of tokens in common with the query + # and then by their cosine distance of their embeddings to the query's embedding + sorted_res_values = sorted(zip(*res.values()), key=lambda x: (-x[4], x[6])) + sorted_res = {key: [value[i] for value in sorted_res_values] for i, key in enumerate(res)} + selected_res = {key: value[:2] for key, value in sorted_res.items()} + + ids_str = "" + for id in selected_res['id']: + ids_str += id + " ; " + logging.info(f"query_collection / sources: \n{ids_str}") + + self.urls = selected_res['url'] + + return selected_res def format_passages(self, query_results): result = [] - for i in range(len(query_results["documents"][0])): - passage = query_results["documents"][0][i] - url = query_results["metadatas"][0][i]["url"] - category = query_results["metadatas"][0][i]["category"] + for i in range(len(query_results["passage"])): + passage = query_results["passage"][i] + url = query_results["url"][i] + category = query_results["cat"][i] lines = passage.split('\n') if lines[0].startswith('passage: '): lines[0] = lines[0].replace('passage: ', '') @@ -189,7 +228,6 @@ Question reformulée : " formated_urls += f"* {url}\n" yield f""" -### Réponse {self.prefix_assistant_prompt} """ diff --git a/rag_bm25.ipynb b/rag_bm25.ipynb index 8051fe0..17a2395 100644 --- a/rag_bm25.ipynb +++ b/rag_bm25.ipynb @@ -8,7 +8,8 @@ "outputs": [], "source": [ "from rank_bm25 import BM25Okapi\n", - "import chromadb" + "import chromadb\n", + "import spacy" ] }, { @@ -26,7 +27,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 3, "id": "021e4cfa-49d9-460f-9b43-1524c327e4c6", "metadata": {}, "outputs": [ @@ -36,7 +37,7 @@ "dict_keys(['ids', 'embeddings', 'metadatas', 'documents', 'uris', 'data'])" ] }, - "execution_count": 7, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -47,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 4, "id": "5ae7d104-f8f6-45ad-a2af-b8475e400262", "metadata": {}, "outputs": [], @@ -57,7 +58,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 5, "id": "96e8acca-e0dc-4dbd-9130-4f3ff64ba86a", "metadata": {}, "outputs": [], @@ -67,7 +68,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 6, "id": "536f29fa-9377-41a8-a9d4-a52ace99ed74", "metadata": {}, "outputs": [ @@ -77,7 +78,7 @@ "2896" ] }, - "execution_count": 9, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -88,7 +89,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 7, "id": "8cf9b3e2-c960-4a88-8974-848611a4d5d7", "metadata": {}, "outputs": [], @@ -98,7 +99,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 8, "id": "87085da2-edac-4e9c-8586-1139ac814dcd", "metadata": { "collapsed": true, @@ -164,7 +165,7 @@ " 'confidentiel.']" ] }, - "execution_count": 16, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -175,7 +176,149 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 9, + "id": "8e0ece14-bee4-4842-a010-7f8016fbf09d", + "metadata": {}, + "outputs": [], + "source": [ + "nlp = spacy.load('fr_core_news_md')" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "f68969e6-2fc7-45a7-b920-3790cd05ad5e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Le le DET O \n", + "paiement paiement NOUN O \n", + "sans sans ADP O \n", + "contact contact NOUN O \n", + "\n", + "\n", + " \n", + "\n", + " SPACE O \n", + "Les le DET O \n", + "points point NOUN O \n", + "clés clé ADJ O \n", + "\n", + "\n", + " \n", + "\n", + " SPACE O \n", + "Pratique pratique NOUN B MISC\n", + "Plus plus ADV I MISC\n", + "besoin besoin NOUN O \n", + "de de ADP O \n", + "chercher chercher VERB O \n", + "de de ADP O \n", + "la le DET O \n", + "monnaie monnaie NOUN O \n", + "pour pour ADP O \n", + "régler régler VERB O \n", + "votre votre DET O \n", + "café café NOUN O \n", + ", , PUNCT O \n", + "parking parking NOUN O \n", + ", , PUNCT O \n", + "repas repas NOUN O \n", + ", , PUNCT O \n", + "journal journal NOUN O \n", + "… … PUNCT O \n", + "\n", + " \n", + " SPACE O \n", + "Gratuit gratuit NOUN B MISC\n", + "Cette ce DET O \n", + "fonctionnalité fonctionnalité NOUN O \n", + "est être AUX O \n", + "ajoutée ajouter VERB O \n", + "automatiquement automatiquement ADV O \n", + "et et CCONJ O \n", + "gratuitement gratuitement NOUN O \n", + "à à ADP O \n", + "votre votre DET O \n", + "carte carte NOUN O \n", + ". . PUNCT O \n", + "\n", + " \n", + " SPACE O \n", + "Rapide rapide NOUN B MISC\n", + "Vous vous PRON I MISC\n", + "réglez régler VERB I MISC\n", + "votre votre DET I MISC\n", + "achat achat NOUN I MISC\n", + "d’ d’ ADP I MISC\n", + "un un DET I MISC\n", + "simple simple ADJ I MISC\n", + "geste geste NOUN I MISC\n", + ", , PUNCT O \n", + "sans sans ADP O \n", + "insérer insérer VERB O \n", + "votre votre DET O \n", + "carte carte NOUN O \n", + "ni ni CCONJ O \n", + "composer composer VERB O \n", + "votre votre DET O \n", + "code code NOUN O \n", + "confidentiel confidentiel ADJ O \n", + ". . PUNCT O \n" + ] + } + ], + "source": [ + "for tok in nlp(docs[0]):\n", + " print(tok, tok.lemma_, tok.pos_, tok.ent_iob_, tok.ent_type_)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "0ae7033e-2f7e-4076-ba15-1c569bbe7cec", + "metadata": {}, + "outputs": [], + "source": [ + "def keep_token(tok):\n", + " return tok.pos_ == 'NOUN' or tok.pos_ == 'VERB' or \\\n", + " tok.pos_ == 'PROPN' or tok.pos_ == 'ADJ'" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "dfb71f32-8b13-44d0-a686-4f45fc21aaf6", + "metadata": {}, + "outputs": [], + "source": [ + "def lemmatize(str):\n", + " res = []\n", + " for tok in nlp(str):\n", + " if keep_token(tok):\n", + " res.append(tok.lemma_)\n", + " return res" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "8c290d03-4c05-4770-87f3-d5766e34bf7a", + "metadata": {}, + "outputs": [], + "source": [ + "docs_tokenized = []\n", + "for doc in docs:\n", + " toks = lemmatize(doc)\n", + " if len(toks)>0: docs_tokenized.append(toks)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, "id": "0ba5858d-9cdb-4159-a9ab-88b485a8991b", "metadata": {}, "outputs": [], @@ -185,28 +328,7 @@ }, { "cell_type": "code", - "execution_count": 19, - "id": "c854970c-6e40-4209-9e02-a7e4cb9fb509", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bm25_db" - ] - }, - { - "cell_type": "code", - "execution_count": 20, + "execution_count": 36, "id": "61d10ce1-38fe-4d36-b8c6-1845c116b626", "metadata": {}, "outputs": [], @@ -216,18 +338,49 @@ }, { "cell_type": "code", - "execution_count": 21, - "id": "8c362a7b-5d78-4ba5-be8f-165ca5e9e023", + "execution_count": 37, + "id": "49479a83-d401-41ce-9172-99117a3c0753", "metadata": {}, "outputs": [], "source": [ - "query_tokenized = query.split(\" \")\n", - "doc_scores = bm25_db.get_scores(query_tokenized)" + "query_toks = lemmatize(query)" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 38, + "id": "4eac99f8-347f-424a-ba27-81beba5c2f8c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['procéder', 'déclarer', 'sinistre', 'Visa', 'premier']" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query_toks" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "8c362a7b-5d78-4ba5-be8f-165ca5e9e023", + "metadata": {}, + "outputs": [], + "source": [ + "# query_tokenized = query.split(\" \")\n", + "doc_scores = bm25_db.get_scores(query_toks)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, "id": "99c5cc3d-5b30-478d-b3b8-c252dff6e1fc", "metadata": {}, "outputs": [ @@ -237,7 +390,7 @@ "2896" ] }, - "execution_count": 24, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } @@ -248,7 +401,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 41, "id": "e5c91dd9-ebda-42ba-9ac8-d7fec2be981e", "metadata": {}, "outputs": [], @@ -258,7 +411,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 42, "id": "5865d7f6-f10d-4c08-873d-89ae60059008", "metadata": {}, "outputs": [], @@ -268,19 +421,19 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 43, "id": "1964be5f-8ab7-437c-986c-d5ef8039748d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'d42d7174ce.html-1': 21.04962022219261,\n", - " '25dcc77e00.html-6': 18.00344127762922,\n", - " '19864d414b.html-1': 16.616167931951313}" + "{'d42d7174ce.html-1': 18.401661345931544,\n", + " 'c98558432b.html-2': 16.991284762126078,\n", + " 'd42d7174ce.html-2': 13.443119955565379}" ] }, - "execution_count": 31, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } @@ -291,35 +444,153 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 57, + "id": "2f4eadd8-b25e-4cf3-8540-3283f50144a7", + "metadata": {}, + "outputs": [], + "source": [ + "def size_query_inter_doc(query_str, doc_id):\n", + " query_toks = lemmatize(query_str)\n", + " doc_toks = lemmatize(collection.get(ids=[doc_id])['documents'][0])\n", + " return len(set(query_toks) & set(doc_toks))" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "c1f3fc8f-153b-4138-b5f0-393f29589d44", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "size_query_inter_doc(query, 'd42d7174ce.html-1')" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "3113b8d6-c2e8-4930-b7e2-13ddd59d6897", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Visa', 'déclarer', 'premier', 'sinistre'}" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "set(query_toks) & set(lemmatize(collection.get(ids=['c98558432b.html-2'])['documents'][0]))" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "c5bc2d06-bdcf-4476-a029-c27285dd30c9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'déclarer', 'procéder', 'sinistre'}" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "set(query_toks) & set(lemmatize(collection.get(ids=['d42d7174ce.html-1'])['documents'][0]))" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "341d8c95-cd0d-4cb3-b279-6f9230ceae4c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'déclarer', 'sinistre'}" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "set(query_toks) & set(lemmatize(collection.get(ids=['d42d7174ce.html-2'])['documents'][0]))" + ] + }, + { + "cell_type": "code", + "execution_count": 47, "id": "f0ff43ff-2cb1-47a7-aaaf-f9dbecd041ff", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'ids': ['19864d414b.html-1', 'd42d7174ce.html-1', '25dcc77e00.html-6'],\n", + "{'ids': ['c98558432b.html-2', 'd42d7174ce.html-1', 'd42d7174ce.html-2'],\n", " 'embeddings': None,\n", - " 'metadatas': [{'category': 'professionnels',\n", - " 'url': 'https://www.caisse-epargne.fr/rhone-alpes/professionnels/proteger-moi-mon-activite/numeros-utiles-contrat-assurance/'},\n", + " 'metadatas': [{'category': 'comptes-cartes',\n", + " 'url': 'https://www.caisse-epargne.fr/rhone-alpes/comptes-cartes/garanties-assurances-et-assistances-des-cartes-visa/'},\n", " {'category': 'assurer',\n", " 'url': 'https://www.caisse-epargne.fr/rhone-alpes/assurer/declaration-sinistre-intemperies/'},\n", - " {'category': 'comptes-cartes',\n", - " 'url': 'https://www.caisse-epargne.fr/rhone-alpes/comptes-cartes/carte-visa-premier/'}],\n", - " 'documents': ['Numéros Utiles Contrat Assurance Professionnel\\n\\nVotre contrat Assurance Multirisque Professionnelle ou Assurance Auto des Professionnels\\n\\nUn numéro unique et non surtaxé est mis à votre disposition pour déclarer un sinistre ou mettre à jour votre contrat.Appelez-le\\xa0:\\nPour nous contacter par mail\\xa0:• Contrat Assurance Multirisque Professionnelle\\xa0:service-client.ird@aismail.fr• Contrat Assurance Auto des Professionnels\\xa0: service-client.auto@aismail.fr\\n',\n", + " {'category': 'assurer',\n", + " 'url': 'https://www.caisse-epargne.fr/rhone-alpes/assurer/declaration-sinistre-intemperies/'}],\n", + " 'documents': ['Garanties Assurances et Assistance des cartes Visa\\n\\nGaranties Assurances et Assistance des cartes Visa\\n\\n• La déclaration de sinistre doit être faite dans les 15 jours suivant la date à laquelle a eu lieu le sinistre ;\\n\\n• Les indemnisations interviennent a posteriori du sinistre sur présentation de justificatifs.\\nLe porteur de la Carte Visa, ainsi que son conjoint ou concubin vivant sous le même toit et ses enfants célibataires de moins de 25 ans fiscalement à sa charge, même s’ils voyagent séparément.\\n• Tout déplacement ou séjour à une distance supérieure à 100 km de la résidence principale de l’Assuré ou de son lieu de travail habituel, dans la limite des 180 premiers jours consécutifs ;\\n\\n• Sans franchise kilométrique pour la Garantie Neige et Montagne.\\nPour déclarer un sinistre, vous devez vous rendre sur le site :\\nLes garanties d’assistance sont valables toute la durée du séjour/déplacement dans la limite de 90 jours.\\nLe porteur de la Carte Visa, ainsi que son conjoint ou concubin vivant sous le même toit et ses enfants célibataires de moins de 25 ans fiscalement à sa charge, même s’ils voyagent séparément.\\nLes garanties d’assistance s’appliquent dans le monde entier :\\n\\n• Il n’y a pas de franchise kilométrique si l’événement est survenu en dehors du pays de résidence ;\\n\\n• Si l’événement est survenu dans le pays de résidence, la franchise kilométrique est de 100 km ;\\n\\n• Lors de tout déplacement privé ou professionnel ;\\n\\n• Elles ne se substituent pas aux organismes locaux de secours d’urgence.\\nPour faire une demande d’Assistance en ligne et pour connaître les services d’Assistance liés à votre carte Visa ou demander une attestation, rendez-vous sur le site.',\n", " 'Intempéries, déclarer son sinistre en ligne !\\n\\nEn bref\\n\\nDécouvrez comment déclarer votre sinistre en ligne\\nLes conseils en cas de sinistre\\nTout savoir sur l’indemnisation\\nEn cas d’événement climatique, le besoin d’assistance ou de prise en charge est immédiat\\xa0!C’est pourquoi la Caisse d’Epargne vous facilite la déclaration de sinistre grâce à votre espace personnel internet. Si vous êtes détenteur d’un contrat d’assurance habitation ou automobile, il vous suffit de vous connecter à votre espace personnel et de procéder à la déclaration en ligne.',\n", - " 'Carte Visa Premier\\n\\nEt si vous optiez pour la carte Visa Premier Izicarte associée à votre crédit renouvelable ?\\n\\nVotre carte Visa Premier Izicarte vous permet de disposer d’un crédit renouvelable pour faire face à des imprévus ou saisir des opportunités sans perdre de temps.\\nVous choisissez librement le mode de règlement que vous souhaitez :\\nAu comptant : par débit de votre compte de dépôt ;\\nÀ crédit, dans la limite du montant disponible sur votre crédit renouvelable.\\nGrâce à votre crédit renouvelable associé à votre carte Visa Premier Izicarte, vous pouvez étaler en plusieurs fois vos dépenses à crédit et choisir votre rythme de remboursement.\\nUn crédit vous engage et doit être remboursé. Vérifiez vos capacités de remboursement avant de vous engager.'],\n", + " 'Intempéries, déclarer son sinistre en ligne !\\n\\nDepuis votre application mobile\\n\\nOuvrez votre application Caisse d’Epargne puis saisissez votre identifiant\\xa0et votre code confidentiel\\nCliquez sur la rubrique « Assurance »\\nSélectionnez votre contrat et cliquez sur\\xa0« Déclarer un sinistre »'],\n", " 'uris': None,\n", " 'data': None}" ] }, - "execution_count": 34, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "collection.get(ids=['d42d7174ce.html-1', '25dcc77e00.html-6', '19864d414b.html-1'])" + "collection.get(ids=list(dict(sorted_ids_scores[:3]).keys()))" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "0125c4f3-63ef-4499-9dbd-043b943098d4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'• La déclaration de sinistre doit être faite dans les 15 jours suivant la date à laquelle a eu lieu le sinistre ;\\n\\n• Les indemnisations interviennent a posteriori du sinistre sur présentation de justificatifs.\\nLe porteur de la Carte Visa, ainsi que son conjoint ou concubin vivant sous le même toit et ses enfants célibataires de moins de 25 ans fiscalement à sa charge, même s’ils voyagent séparément.\\n• Tout déplacement ou séjour à une distance supérieure à 100 km de la résidence principale de l’Assuré ou de son lieu de travail habituel, dans la limite des 180 premiers jours consécutifs ;\\n\\n• Sans franchise kilométrique pour la Garantie Neige et Montagne.\\nPour déclarer un sinistre, vous devez vous rendre sur le site :\\nLes garanties d’assistance sont valables toute la durée du séjour/déplacement dans la limite de 90 jours.\\nLe porteur de la Carte Visa, ainsi que son conjoint ou concubin vivant sous le même toit et ses enfants célibataires de moins de 25 ans fiscalement à sa charge, même s’ils voyagent séparément.\\nLes garanties d’assistance s’appliquent dans le monde entier :\\n\\n• Il n’y a pas de franchise kilométrique si l’événement est survenu en dehors du pays de résidence ;\\n\\n• Si l’événement est survenu dans le pays de résidence, la franchise kilométrique est de 100 km ;\\n\\n• Lors de tout déplacement privé ou professionnel ;\\n\\n• Elles ne se substituent pas aux organismes locaux de secours d’urgence.\\nPour faire une demande d’Assistance en ligne et pour connaître les services d’Assistance liés à votre carte Visa ou demander une attestation, rendez-vous sur le site.'" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "'\\n\\n'.join((collection.get(ids=['c98558432b.html-2'])['documents'][0]).split('\\n\\n')[2:])" ] }, {