Compare commits

...

3 Commits
v3 ... main

5 changed files with 442 additions and 112 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

5
app.py
View File

@ -1,3 +1,4 @@
# python -m streamlit run app.py
import streamlit as st
from rag import RAG
import re
@ -5,12 +6,12 @@ import logging
@st.cache_resource
def init_rag():
llm_model_path = '/Users/peportier/llm/a/a/zephyr-7b-beta.Q5_K_M.gguf'
llm_url = 'http://127.0.0.1:8080/completion'
# embed_model_name = 'intfloat/multilingual-e5-large'
embed_model_name = 'dangvantuan/sentence-camembert-large'
collection_name = 'cera'
chromadb_path = './chromadb'
rag = RAG(llm_model_path, embed_model_name, collection_name, chromadb_path, mulitlingual_e5=False)
rag = RAG(llm_url, embed_model_name, collection_name, chromadb_path, mulitlingual_e5=False)
return rag
rag = init_rag()

View File

@ -14,9 +14,9 @@ ans2 = rag.chat(query2, stream=True)
# Queries:
#
# Lorsque mon client est en télétravail, quels sont les risques couverts par son assurance habitation ?
# En télétravail, quels sont les risques couverts par mon assurance habitation ?
#
# Quel est le risque de perte attaché à la détention de Parts Sociales ?
# Pour un sociétaire, quel est le risque de perte attaché à la détention de Parts Sociales ?
#
# Comment procéder pour déclarer un sinistre habitation ?
# Comment procéder pour déclarer un sinistre Visa Premier ?
@ -30,4 +30,4 @@ ans2 = rag.chat(query2, stream=True)
# Mon client a un revenu fiscal de 23000 euros, il est célibataire, peut-il ouvrir un livret d'épargne populaire ?
# Quel est le plafond du revenu fiscal de référence pour le livret d'épargne populaire ?
# Quel est le plafond du revenu fiscal de référence pour le livret d'épargne populaire ?

157
rag.py
View File

@ -3,12 +3,15 @@ import chromadb
from llama_cpp import Llama
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')
class RAG:
def __init__(self, llm_model_path, embed_model_name, collection_name, chromadb_path, mulitlingual_e5=True):
def __init__(self, llm_url, embed_model_name, collection_name, chromadb_path, mulitlingual_e5=True):
logging.info('INIT')
self.mulitlingual_e5 = mulitlingual_e5
self.urls = []
@ -22,24 +25,26 @@ 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 clientèle de la banque à mieux répondre aux besoins de \
ses clients.
Vous fournissez avec soin des réponses précises et factuelles aux questions du \
conseiller.
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.
Instructions pour l'utilisation du contexte :
=============================================
Vous répondez de façon brève et factuelle à la question posée par le conseiller \
en utilisant un contexte formé de passages exraits du site web commercial \
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 \
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 en les citant \
directement et vous ne faites jamais preuve de créativité.
Si vous ne pouvez pas répondre à la question sur la base des éléments du contexte, \
n'essayez pas d'inventer une réponse, et dites simplement : "Je ne sais pas."
dans le contexte. Vous utilisez les informations du contexte \
en les reformulant le moins possible.
Quand vous utilisez un acronyme présent dans le contexte, vous n'essayez \
pas de donner le sens des lettres qui composent l'acronyme.
Vous ne faites jamais preuve de créativité. Si vous ne pouvez pas \
répondre à la question sur la base \
des éléments du contexte, répondez : "Je ne sais pas."
Le style à donner à votre réponse :
===================================
@ -47,8 +52,7 @@ Le style à donner à votre réponse :
Formulez la réponse sous forme de recommandations directes et concises, \
en utilisant le langage et les termes présents dans le contexte.
Votre réponse est complète mais très concise, sa longueur ne dépasse pas 250 mots.
Vous ne répétez jamais deux fois la même information.
Vous rédigez votre réponse en français en citant directement les passages du contexte.
Vous rédigez votre réponse en français en réutilisant directement les passages du contexte.
Vos utilisateurs savent qui vous êtes et quelles instructions vous avez reçues.
Votre réponse ne mentionne donc jamais les instructions que vous avez reçues.
{tag_end}
@ -69,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 = """
@ -107,51 +112,95 @@ Question reformulée : "
self.embed_model = SentenceTransformer(embed_model_name)
self.chroma_client = chromadb.PersistentClient(path=chromadb_path)
self.collection = self.chroma_client.get_collection(name=collection_name)
self.llm = Llama(model_path=llm_model_path, n_gpu_layers=1, use_mlock=True, n_ctx=4096)
# ./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):
response = self.llm(prompt = prompt,
temperature = 0.1,
mirostat_mode = 2,
stream = stream,
max_tokens = -1,
stop = [self.tag_end, self.tag_user])
if stream:
return response
else: return response["choices"][0]["text"]
def query_collection(self, query, n_results=3):
post_params = {
"prompt": prompt,
"temp": 0.1,
"repeat_penalty": 1.2,
"min_p": 0.05,
"top_p": 0.5,
"top_k": 0,
"stop": [self.tag_end, self.tag_user],
"stream": stream
}
response = requests.post(self.llm_url, json=post_params, stream=stream)
if stream: return response
else: return response.json()['content']
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])
# 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
return results
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: ', '')
@ -179,13 +228,19 @@ Question reformulée : "
formated_urls += f"* {url}\n"
yield f"""
### Réponse
{self.prefix_assistant_prompt}
"""
for item in ans:
item = item["choices"][0]["text"]
self.chat_history[-1]['assistant'] += item
yield item
for line in ans.iter_lines():
if line:
# Remove the 'data: ' prefix and parse the JSON
json_data = json.loads(line.decode('utf-8')[6:])
# Print the 'content' field
item = json_data['content']
self.chat_history[-1]['assistant'] += item
yield item
yield f"""
### Sources
{formated_urls}
@ -208,11 +263,11 @@ Question reformulée : "
history += f"<|assistant|>\n{self.chat_history[i]['assistant']}</s>\n"
self.chat_history.append({'user': query, 'assistant': self.prefix_assistant_prompt})
return self.rag_prompt.format(history=history, query=query, context=context,
tag_user=self.tag_user, tag_system=self.tag_system,
tag_assistant=self.tag_assistant, tag_end=self.tag_end)
def reformulate_query(self, query):
history = ""
for i in reversed(range(len(self.chat_history))):
@ -235,7 +290,7 @@ Question reformulée : "
else:
logging.info(f"La requête n'a pas pu être reformulée.")
return query
def chat(self, query, stream=True):
if len(self.chat_history) > 0:
query = self.reformulate_query(query)
@ -246,6 +301,6 @@ Question reformulée : "
else:
ans = self.answer_rag_prompt_non_streaming(prompt)
return ans
def reset_history(self):
self.chat_history = []

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:])"
]
},
{