lemmatization of both query and doc to filter the best docs

This commit is contained in:
Pierre-Edouard Portier 2024-01-21 21:30:01 +01:00
parent b7c276da08
commit 3991528b33
3 changed files with 389 additions and 77 deletions

3
PAD
View File

@ -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

80
rag.py
View File

@ -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}
"""

View File

@ -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": [
"<rank_bm25.BM25Okapi at 0x10c7be6d0>"
]
},
"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 sils voyagent séparément.\\n• Tout déplacement ou séjour à une distance supérieure à 100 km de la résidence principale de lAssuré 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 dassistance 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 sils voyagent séparément.\\nLes garanties dassistance sappliquent dans le monde entier :\\n\\n• Il ny 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 durgence.\\nPour faire une demande dAssistance en ligne et pour connaître les services dAssistance 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 lindemnisation\\nEn cas dévénement climatique, le besoin dassistance ou de prise en charge est immédiat\\xa0!Cest pourquoi la Caisse dEpargne vous facilite la déclaration de sinistre grâce à votre espace personnel internet. Si vous êtes détenteur dun contrat dassurance 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 dun 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 dEpargne 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 sils voyagent séparément.\\n• Tout déplacement ou séjour à une distance supérieure à 100 km de la résidence principale de lAssuré 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 dassistance 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 sils voyagent séparément.\\nLes garanties dassistance sappliquent dans le monde entier :\\n\\n• Il ny 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 durgence.\\nPour faire une demande dAssistance en ligne et pour connaître les services dAssistance 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:])"
]
},
{