Spaces:
Sleeping
Sleeping
Upload 17 files
Browse files- src/__init__.py +0 -0
- src/__pycache__/__init__.cpython-312.pyc +0 -0
- src/__pycache__/agent.cpython-312.pyc +0 -0
- src/__pycache__/create_database.cpython-312.pyc +0 -0
- src/__pycache__/llm_interface.cpython-312.pyc +0 -0
- src/__pycache__/memory.cpython-312.pyc +0 -0
- src/__pycache__/prompts.cpython-312.pyc +0 -0
- src/__pycache__/utils.cpython-312.pyc +0 -0
- src/agent.py +339 -0
- src/create_database.py +88 -0
- src/gradio_app.py +58 -0
- src/llm_interface.py +19 -0
- src/main.py +78 -0
- src/memory.py +158 -0
- src/prompts.py +114 -0
- src/requirements.txt +0 -0
- src/utils.py +69 -0
src/__init__.py
ADDED
File without changes
|
src/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (137 Bytes). View file
|
|
src/__pycache__/agent.cpython-312.pyc
ADDED
Binary file (23.5 kB). View file
|
|
src/__pycache__/create_database.cpython-312.pyc
ADDED
Binary file (4.75 kB). View file
|
|
src/__pycache__/llm_interface.cpython-312.pyc
ADDED
Binary file (1.51 kB). View file
|
|
src/__pycache__/memory.cpython-312.pyc
ADDED
Binary file (12.6 kB). View file
|
|
src/__pycache__/prompts.cpython-312.pyc
ADDED
Binary file (10.8 kB). View file
|
|
src/__pycache__/utils.cpython-312.pyc
ADDED
Binary file (3.85 kB). View file
|
|
src/agent.py
ADDED
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/agent.py
|
2 |
+
from llama_cpp import Llama
|
3 |
+
from src.memory import MemoryManager
|
4 |
+
import os
|
5 |
+
import logging
|
6 |
+
from src.utils import extract_and_summarize # Import extract_and_summarize
|
7 |
+
from src.prompts import Prompts # Import system prompts
|
8 |
+
|
9 |
+
# Configure logging
|
10 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
11 |
+
|
12 |
+
class Agent:
|
13 |
+
def __init__(self, llm: Llama, db_path: str, system_prompt: str = "", max_tokens: int = 512, temperature: float = 0.7, top_p: float = 0.95):
|
14 |
+
self.llm = llm
|
15 |
+
self.memory = MemoryManager(db_path)
|
16 |
+
self.prompts = {
|
17 |
+
"general": Prompts.GENERAL_SYSTEM_PROMPT,
|
18 |
+
"whole_document": Prompts.WHOLE_DOCUMENT_SYSTEM_PROMPT,
|
19 |
+
"query_response": Prompts.QUERY_RESPONSE_SYSTEM_PROMPT,
|
20 |
+
"enhancement": Prompts.ENHANCEMENT_SYSTEM_PROMPT
|
21 |
+
}
|
22 |
+
self.max_tokens = max_tokens # Default value
|
23 |
+
self.temperature = temperature # Default value
|
24 |
+
self.top_p = top_p # Default value
|
25 |
+
|
26 |
+
def process_query(self, user_id: str, query: str) -> str:
|
27 |
+
# Normalize the query to lowercase
|
28 |
+
query = query.lower()
|
29 |
+
|
30 |
+
# Check if the query is specific (e.g., "chronique #441")
|
31 |
+
if "chronique #" in query:
|
32 |
+
section_number = query.split("chronique #")[1].split()[0]
|
33 |
+
section_description = self.get_section_description(f"chronique #{section_number}")
|
34 |
+
if section_description:
|
35 |
+
response = self.generate_specific_response(query, section_description)
|
36 |
+
self.memory.add_user_interaction(user_id, query, response)
|
37 |
+
return response
|
38 |
+
|
39 |
+
if "flash info fl-" in query:
|
40 |
+
section_number = query.split("flash info fl-")[1].split()[0]
|
41 |
+
section_description = self.get_section_description(f"flash info fl-{section_number}")
|
42 |
+
if section_description:
|
43 |
+
response = self.generate_specific_response(query, section_description)
|
44 |
+
self.memory.add_user_interaction(user_id, query, response)
|
45 |
+
return response
|
46 |
+
|
47 |
+
if "chronique-faq #" in query:
|
48 |
+
section_number = query.split("chronique-faq #")[1].split()[0]
|
49 |
+
section_description = self.get_section_description(f"chronique-faq #{section_number}")
|
50 |
+
if section_description:
|
51 |
+
response = self.generate_specific_response(query, section_description)
|
52 |
+
self.memory.add_user_interaction(user_id, query, response)
|
53 |
+
return response
|
54 |
+
|
55 |
+
# For general queries, use the existing multi-layer processing
|
56 |
+
initial_response = extract_and_summarize(query, self.memory, self.llm, self.dynamic_query_response_prompt(query), max_tokens=self.max_tokens, temperature=self.temperature, top_p=self.top_p)
|
57 |
+
|
58 |
+
# Evaluate the initial response
|
59 |
+
if not self.evaluate_response(initial_response, query):
|
60 |
+
# Retrieve additional data
|
61 |
+
additional_data = self.retrieve_additional_data(query, initial_response)
|
62 |
+
# Combine initial and additional data
|
63 |
+
combined_context = f"{initial_response}\n{additional_data}"
|
64 |
+
# Truncate the combined context to fit within the model's context window
|
65 |
+
max_context_length = 30000 # Adjust this based on your LLM's token limit
|
66 |
+
if len(combined_context) > max_context_length:
|
67 |
+
combined_context = combined_context[:max_context_length]
|
68 |
+
logging.info(f"Truncated combined context to {max_context_length} characters.")
|
69 |
+
|
70 |
+
# Generate final response
|
71 |
+
initial_response = self.llm.create_chat_completion(
|
72 |
+
messages=[{"role": "user", "content": f"Context: {combined_context}\nQuestion: {query}"}],
|
73 |
+
max_tokens=self.max_tokens,
|
74 |
+
temperature=self.temperature,
|
75 |
+
top_p=self.top_p
|
76 |
+
)['choices'][0]['message']['content']
|
77 |
+
|
78 |
+
refined_response = self.multi_layer_processing(query, initial_response)
|
79 |
+
self.memory.add_user_interaction(user_id, query, refined_response)
|
80 |
+
return refined_response
|
81 |
+
|
82 |
+
def generate_specific_response(self, query: str, section_description: str) -> str:
|
83 |
+
# Log the section description
|
84 |
+
logging.info(f"Found section: {query}")
|
85 |
+
logging.info(f"Section description for {query}: {section_description}")
|
86 |
+
|
87 |
+
# Format the context with a placeholder pre-set
|
88 |
+
context = f"Section description for {query}: {section_description}"
|
89 |
+
|
90 |
+
# Generate a response using the specific section description
|
91 |
+
response = self.llm.create_chat_completion(
|
92 |
+
messages=[{"role": "user", "content": f"Context: {context}\nQuestion: {query}"}],
|
93 |
+
max_tokens=self.max_tokens,
|
94 |
+
temperature=self.temperature,
|
95 |
+
top_p=self.top_p
|
96 |
+
)['choices'][0]['message']['content']
|
97 |
+
|
98 |
+
# Log the generated response
|
99 |
+
logging.info(f"Generated specific response: {response}")
|
100 |
+
|
101 |
+
return response
|
102 |
+
|
103 |
+
def multi_layer_processing(self, query: str, initial_response: str) -> str:
|
104 |
+
# First layer: High-level summary of the entire document
|
105 |
+
high_level_summary = self.generate_high_level_summary(query)
|
106 |
+
|
107 |
+
# Second layer: Refine the response using the high-level summary
|
108 |
+
refined_response = self.refine_response(query, initial_response, high_level_summary)
|
109 |
+
|
110 |
+
return refined_response
|
111 |
+
|
112 |
+
def generate_high_level_summary(self, query: str) -> str:
|
113 |
+
# Retrieve all memories from the database
|
114 |
+
all_memories = self.memory._get_all_memories()
|
115 |
+
|
116 |
+
# Count the number of each type of section
|
117 |
+
chronique_count = self.count_chroniques()
|
118 |
+
flash_info_count = self.count_flash_infos()
|
119 |
+
chronique_faq_count = self.count_chronique_faqs()
|
120 |
+
|
121 |
+
# Combine all descriptions into a single context
|
122 |
+
full_context = " ".join([memory['description'] for memory, _, _ in all_memories])
|
123 |
+
|
124 |
+
# Truncate the context if it exceeds the token limit
|
125 |
+
max_context_length = 30000 # Adjust this based on your LLM's token limit
|
126 |
+
if len(full_context) > max_context_length:
|
127 |
+
full_context = full_context[:max_context_length]
|
128 |
+
logging.info(f"Truncated full context to {max_context_length} characters.")
|
129 |
+
|
130 |
+
# Generate a high-level summary using the LLM
|
131 |
+
high_level_summary = self.llm.create_chat_completion(
|
132 |
+
messages=[{"role": "user", "content": f"Context: {full_context}\nQuestion: {query}"}],
|
133 |
+
max_tokens=self.max_tokens,
|
134 |
+
temperature=self.temperature,
|
135 |
+
top_p=self.top_p
|
136 |
+
)['choices'][0]['message']['content']
|
137 |
+
|
138 |
+
# Explicitly include the number of sections in the summary
|
139 |
+
high_level_summary += f"\nD'après les données disponibles, Michel Thomas a publié {chronique_count} chroniques, {flash_info_count} flash infos, et {chronique_faq_count} chronique-faqs."
|
140 |
+
|
141 |
+
logging.info(f"Generated high-level summary: {high_level_summary}")
|
142 |
+
|
143 |
+
return high_level_summary
|
144 |
+
|
145 |
+
def refine_response(self, query: str, initial_response: str, high_level_summary: str) -> str:
|
146 |
+
# Combine the initial response and the high-level summary
|
147 |
+
combined_context = f"Initial Response: {initial_response}\nHigh-Level Summary: {high_level_summary}"
|
148 |
+
|
149 |
+
# Truncate the combined context to fit within the model's context window
|
150 |
+
max_context_length = 30000 # Adjust this based on your LLM's token limit
|
151 |
+
if len(combined_context) > max_context_length:
|
152 |
+
combined_context = combined_context[:max_context_length]
|
153 |
+
logging.info(f"Truncated combined context to {max_context_length} characters.")
|
154 |
+
|
155 |
+
# Generate a refined response using the LLM
|
156 |
+
refined_response = self.llm.create_chat_completion(
|
157 |
+
messages=[{"role": "user", "content": f"Context: {combined_context}\nQuestion: {query}"}],
|
158 |
+
max_tokens=self.max_tokens,
|
159 |
+
temperature=self.temperature,
|
160 |
+
top_p=self.top_p
|
161 |
+
)['choices'][0]['message']['content']
|
162 |
+
|
163 |
+
# Enhance the response to include more details about the specific section
|
164 |
+
refined_response = self.enhance_response_with_details(query, refined_response)
|
165 |
+
|
166 |
+
logging.info(f"Generated refined response: {refined_response}")
|
167 |
+
|
168 |
+
return refined_response
|
169 |
+
|
170 |
+
def enhance_response_with_details(self, query: str, refined_response: str) -> str:
|
171 |
+
if "chronique #" in query:
|
172 |
+
section_number = query.split("chronique #")[1].split()[0]
|
173 |
+
section_description = self.get_section_description(f"chronique #{section_number}")
|
174 |
+
elif "flash info fl-" in query:
|
175 |
+
section_number = query.split("flash info fl-")[1].split()[0]
|
176 |
+
section_description = self.get_section_description(f"flash info fl-{section_number}")
|
177 |
+
elif "chronique-faq #" in query:
|
178 |
+
section_number = query.split("chronique-faq #")[1].split()[0]
|
179 |
+
section_description = self.get_section_description(f"chronique-faq #{section_number}")
|
180 |
+
else:
|
181 |
+
section_description = ""
|
182 |
+
|
183 |
+
if section_description:
|
184 |
+
refined_response += f"\n\nVoici plus de détails sur la section demandée:\n{section_description}"
|
185 |
+
|
186 |
+
return refined_response
|
187 |
+
|
188 |
+
def get_section_description(self, section_name: str) -> str:
|
189 |
+
# Normalize the section name to lowercase
|
190 |
+
section_name = section_name.lower()
|
191 |
+
|
192 |
+
# Retrieve the specific section from the database
|
193 |
+
self.memory.cursor.execute("SELECT description FROM semantic_memory WHERE concept LIKE ?", (f"{section_name}%",))
|
194 |
+
result = self.memory.cursor.fetchone()
|
195 |
+
if result:
|
196 |
+
logging.info(f"Found section: {section_name}")
|
197 |
+
return result[0]
|
198 |
+
else:
|
199 |
+
logging.warning(f"Section not found: {section_name}")
|
200 |
+
return ""
|
201 |
+
|
202 |
+
def enhance_response(self, query: str, initial_response: str) -> str:
|
203 |
+
# Ask the LLM to enhance the response
|
204 |
+
try:
|
205 |
+
enhanced_query = f"{initial_response} Comment pourriez-vous améliorer cette réponse pour qu'elle soit entièrement conforme à la requête de l'utilisateur ? {query}. Notez que vous êtes l'assistant IA de Michel Thomas, le consultant qui a écrit tout ce contenu sur son site web, y compris toutes les Chroniques, Flash Infos et Chronique-FAQ sur https://mtc-qc.ca. Votre tâche est de formuler une réponse claire et concise pour les utilisateurs du site web. - Utilisez uniquement le contenu de la base de données. Ne jamais utiliser des connaissances externes ou des suppositions. Si le contenu de la base de données ne fournit pas suffisamment d'informations, indiquez clairement que vous ne disposez pas de suffisamment d'informations pour répondre à la question. - Ne jamais ecrire des charactere semblable a (c) car cela reprensente le charactere é . "
|
206 |
+
enhanced_response = self.llm.create_chat_completion(
|
207 |
+
messages=[{"role": "user", "content": enhanced_query}],
|
208 |
+
max_tokens=self.max_tokens,
|
209 |
+
temperature=self.temperature,
|
210 |
+
top_p=self.top_p
|
211 |
+
)['choices'][0]['message']['content']
|
212 |
+
logging.info(f"Enhanced response: {enhanced_response}")
|
213 |
+
except Exception as e:
|
214 |
+
enhanced_response = f"Erreur lors de l'amélioration de la réponse: {e}"
|
215 |
+
logging.error(f"Erreur lors de l'amélioration de la réponse: {e}")
|
216 |
+
|
217 |
+
return enhanced_response
|
218 |
+
|
219 |
+
def evaluate_response(self, response: str, query: str) -> bool:
|
220 |
+
# Evaluate the response to determine if it is sufficient
|
221 |
+
# Example: Check if the response contains all key terms from the query
|
222 |
+
key_terms = set(query.split())
|
223 |
+
response_terms = set(response.split())
|
224 |
+
return key_terms.issubset(response_terms)
|
225 |
+
|
226 |
+
def retrieve_additional_data(self, query: str, initial_response: str) -> str:
|
227 |
+
# Retrieve additional relevant data from the database
|
228 |
+
key_terms = set(query.split()) | set(initial_response.split())
|
229 |
+
relevant_memories = self.memory.retrieve_relevant_memories(" ".join(key_terms), limit=10)
|
230 |
+
additional_data = " ".join([memory['description'] for memory in relevant_memories])
|
231 |
+
return additional_data
|
232 |
+
|
233 |
+
def dynamic_query_response_prompt(self, query: str) -> str:
|
234 |
+
return f"""
|
235 |
+
Vous êtes l'assistant intelligent de Michel Thomas. Votre tâche est de répondre à la requête de l'utilisateur en utilisant uniquement le contexte fourni dans la base de données. La requête de l'utilisateur est: "{query}". Assurez-vous que vos réponses sont claires, précises et directement liées à la requête de l'utilisateur. Si possible, incluez des exemples concrets pour illustrer vos points.
|
236 |
+
|
237 |
+
- Utilisez uniquement le contenu de la base de données pour générer la réponse.
|
238 |
+
- Ne jamais utiliser des connaissances externes ou des suppositions.
|
239 |
+
- Résumez le contenu de manière concise et claire.
|
240 |
+
- Expliquez les concepts de manière détaillée et accessible, en utilisant des exemples concrets et des analogies.
|
241 |
+
- Analysez le contenu en profondeur, identifiez les thèmes principaux et les arguments clés.
|
242 |
+
- Critiquez le contenu de manière constructive, en identifiant les points forts et les points faibles.
|
243 |
+
- Générez des questions pertinentes pour encourager une réflexion plus approfondie.
|
244 |
+
- Fournissez des exemples concrets pour illustrer les concepts.
|
245 |
+
- Formulez des hypothèses basées sur les informations disponibles.
|
246 |
+
- Tirez des conclusions bien fondées et soutenues par des preuves.
|
247 |
+
- Formulez des recommandations pratiques et applicables.
|
248 |
+
- Générez des éléments d'action spécifiques, mesurables, réalisables, pertinents et temporellement définis (SMART).
|
249 |
+
- Créez un plan détaillé pour organiser les idées principales et les sous-thèmes.
|
250 |
+
- Rédigez une introduction engageante pour captiver l'attention du lecteur.
|
251 |
+
- Rédigez une conclusion forte pour résumer les points clés et laisser une impression durable.
|
252 |
+
- Paraphrasez le contenu de manière claire et concise, en préservant le sens original.
|
253 |
+
- Simplifiez le contenu pour le rendre plus accessible à un public plus large.
|
254 |
+
- Développez le contenu en ajoutant plus de détails et d'informations.
|
255 |
+
- Comparez deux concepts ou sujets en identifiant les similitudes et les différences.
|
256 |
+
- Contrastez deux concepts ou sujets en mettant en évidence les différences significatives.
|
257 |
+
- Créez des analogies pertinentes pour clarifier les concepts.
|
258 |
+
- Créez des métaphores pertinentes pour illustrer les concepts de manière créative.
|
259 |
+
- Si le contenu de la base de données ne fournit pas suffisamment d'informations, indiquez clairement que vous ne disposez pas de suffisamment d'informations pour répondre à la question.
|
260 |
+
"""
|
261 |
+
|
262 |
+
def dynamic_whole_document_prompt(self, query: str, chronique_count: int, flash_info_count: int, chronique_faq_count: int) -> str:
|
263 |
+
return f"""
|
264 |
+
Vous êtes l'assistant intelligent de Michel Thomas, consultant qui a écrit tout ce contenu sur son site web, y compris toutes les Chroniques, Flash Infos et Chronique-FAQ sur https://mtc-qc.ca. Votre tâche est de comprendre l'ensemble du document et de générer un résumé de haut niveau ou un contexte qui peut être utilisé pour répondre à la requête de l'utilisateur. La requête de l'utilisateur est: "{query}". Concentrez-vous sur les points les plus pertinents et importants. Incluez le nombre total de Chroniques ({chronique_count}), Flash Infos ({flash_info_count}), et Chronique-FAQ ({chronique_faq_count}) publiées par Michel Thomas.
|
265 |
+
|
266 |
+
- Utilisez uniquement le contenu de la base de données pour générer le résumé.
|
267 |
+
- Ne jamais utiliser des connaissances externes ou des suppositions.
|
268 |
+
- Résumez le contenu de manière concise et claire.
|
269 |
+
- Expliquez les concepts de manière détaillée et accessible, en utilisant des exemples concrets et des analogies.
|
270 |
+
- Analysez le contenu en profondeur, identifiez les thèmes principaux et les arguments clés.
|
271 |
+
- Critiquez le contenu de manière constructive, en identifiant les points forts et les points faibles.
|
272 |
+
- Générez des questions pertinentes pour encourager une réflexion plus approfondie.
|
273 |
+
- Fournissez des exemples concrets pour illustrer les concepts.
|
274 |
+
- Formulez des hypothèses basées sur les informations disponibles.
|
275 |
+
- Tirez des conclusions bien fondées et soutenues par des preuves.
|
276 |
+
- Formulez des recommandations pratiques et applicables.
|
277 |
+
- Générez des éléments d'action spécifiques, mesurables, réalisables, pertinents et temporellement définis (SMART).
|
278 |
+
- Créez un plan détaillé pour organiser les idées principales et les sous-thèmes.
|
279 |
+
- Rédigez une introduction engageante pour captiver l'attention du lecteur.
|
280 |
+
- Rédigez une conclusion forte pour résumer les points clés et laisser une impression durable.
|
281 |
+
- Paraphrasez le contenu de manière claire et concise, en préservant le sens original.
|
282 |
+
- Simplifiez le contenu pour le rendre plus accessible à un public plus large.
|
283 |
+
- Développez le contenu en ajoutant plus de détails et d'informations.
|
284 |
+
- Comparez deux concepts ou sujets en identifiant les similitudes et les différences.
|
285 |
+
- Contrastez deux concepts ou sujets en mettant en évidence les différences significatives.
|
286 |
+
- Créez des analogies pertinentes pour clarifier les concepts.
|
287 |
+
- Créez des métaphores pertinentes pour illustrer les concepts de manière créative.
|
288 |
+
- Si le contenu de la base de données ne fournit pas suffisamment d'informations, indiquez clairement que vous ne disposez pas de suffisamment d'informations pour répondre à la question.
|
289 |
+
"""
|
290 |
+
|
291 |
+
def dynamic_enhancement_prompt(self, query: str) -> str:
|
292 |
+
return f"""
|
293 |
+
Vous êtes l'assistant intelligent de Michel Thomas. Votre tâche est d'améliorer la réponse initiale en la rendant plus complète et plus conforme à la requête de l'utilisateur. La requête de l'utilisateur est: "{query}". Prenez en compte tous les détails pertinents et formulez votre réponse de manière concise et claire. Ajoutez des détails supplémentaires si nécessaire pour rendre la réponse plus informative.
|
294 |
+
|
295 |
+
- Utilisez uniquement le contenu de la base de données pour améliorer la réponse.
|
296 |
+
- Ne jamais utiliser des connaissances externes ou des suppositions.
|
297 |
+
- Résumez le contenu de manière concise et claire en maximum 8 phrases de maximum 88 mots chacun.
|
298 |
+
- Expliquez les concepts de manière détaillée et accessible, en utilisant des exemples concrets et des analogies.
|
299 |
+
- Analysez le contenu en profondeur, identifiez les thèmes principaux et les arguments clés.
|
300 |
+
- Critiquez le contenu de manière constructive, en identifiant les points forts et les points faibles.
|
301 |
+
- Générez des questions pertinentes pour encourager une réflexion plus approfondie.
|
302 |
+
- Fournissez des exemples concrets pour illustrer les concepts.
|
303 |
+
- Formulez des hypothèses basées sur les informations disponibles.
|
304 |
+
- Tirez des conclusions bien fondées et soutenues par des preuves.
|
305 |
+
- Formulez des recommandations pratiques et applicables.
|
306 |
+
- Générez des éléments d'action spécifiques, mesurables, réalisables, pertinents et temporellement définis (SMART).
|
307 |
+
- Créez un plan détaillé pour organiser les idées principales et les sous-thèmes.
|
308 |
+
- Rédigez une introduction engageante pour captiver l'attention du lecteur.
|
309 |
+
- Rédigez une conclusion forte pour résumer les points clés et laisser une impression durable.
|
310 |
+
- Paraphrasez le contenu de manière claire et concise, en préservant le sens original.
|
311 |
+
- Simplifiez le contenu pour le rendre plus accessible �� un public plus large.
|
312 |
+
- Développez le contenu en ajoutant plus de détails et d'informations.
|
313 |
+
- Comparez deux concepts ou sujets en identifiant les similitudes et les différences.
|
314 |
+
- Contrastez deux concepts ou sujets en mettant en évidence les différences significatives.
|
315 |
+
- Créez des analogies pertinentes pour clarifier les concepts.
|
316 |
+
- Créez des métaphores pertinentes pour illustrer les concepts de manière créative.
|
317 |
+
- Si le contenu de la base de données ne fournit pas suffisamment d'informations, indiquez clairement que vous ne disposez pas de suffisamment d'informations pour répondre à la question.
|
318 |
+
"""
|
319 |
+
|
320 |
+
def count_chroniques(self) -> int:
|
321 |
+
# Count the number of chroniques in the database
|
322 |
+
self.memory.cursor.execute("SELECT COUNT(*) FROM semantic_memory WHERE concept LIKE 'chronique #%'")
|
323 |
+
count = self.memory.cursor.fetchone()[0]
|
324 |
+
logging.info(f"Number of chroniques: {count}")
|
325 |
+
return count
|
326 |
+
|
327 |
+
def count_flash_infos(self) -> int:
|
328 |
+
# Count the number of flash infos in the database
|
329 |
+
self.memory.cursor.execute("SELECT COUNT(*) FROM semantic_memory WHERE concept LIKE 'flash info fl-%'")
|
330 |
+
count = self.memory.cursor.fetchone()[0]
|
331 |
+
logging.info(f"Number of flash infos: {count}")
|
332 |
+
return count
|
333 |
+
|
334 |
+
def count_chronique_faqs(self) -> int:
|
335 |
+
# Count the number of chronique-faqs in the database
|
336 |
+
self.memory.cursor.execute("SELECT COUNT(*) FROM semantic_memory WHERE concept LIKE 'chronique-faq #%'")
|
337 |
+
count = self.memory.cursor.fetchone()[0]
|
338 |
+
logging.info(f"Number of chronique-faqs: {count}")
|
339 |
+
return count
|
src/create_database.py
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/create_database.py
|
2 |
+
import os
|
3 |
+
import json
|
4 |
+
from src.memory import MemoryManager # Corrected import path
|
5 |
+
import logging
|
6 |
+
from typing import List, Dict
|
7 |
+
|
8 |
+
# Configure logging
|
9 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
10 |
+
|
11 |
+
def parse_data_update(file_path: str, keyword_dir: str) -> List[Dict[str, str]]:
|
12 |
+
if not os.path.exists(file_path):
|
13 |
+
logging.error(f"File not found: {file_path}")
|
14 |
+
return []
|
15 |
+
|
16 |
+
with open(file_path, 'r') as file_obj:
|
17 |
+
content = file_obj.read()
|
18 |
+
|
19 |
+
content = content.lower() # Normalize to lowercase
|
20 |
+
|
21 |
+
sections = []
|
22 |
+
lines = content.split('\n')
|
23 |
+
current_section = None
|
24 |
+
current_content = []
|
25 |
+
|
26 |
+
for line in lines:
|
27 |
+
if line.strip().startswith("chronique #") or line.strip().startswith("flash info fl-") or line.strip().startswith("chronique-faq #"):
|
28 |
+
if current_section:
|
29 |
+
sections.append({
|
30 |
+
"concept": current_section,
|
31 |
+
"description": "\n".join(current_content)
|
32 |
+
})
|
33 |
+
logging.info(f"Parsed section: {current_section}")
|
34 |
+
current_section = line.strip()
|
35 |
+
current_content = []
|
36 |
+
else:
|
37 |
+
current_content.append(line)
|
38 |
+
|
39 |
+
if current_section:
|
40 |
+
sections.append({
|
41 |
+
"concept": current_section,
|
42 |
+
"description": "\n".join(current_content)
|
43 |
+
})
|
44 |
+
logging.info(f"Parsed section: {current_section}")
|
45 |
+
|
46 |
+
return sections
|
47 |
+
|
48 |
+
def get_keywords(number: str, keyword_dir: str) -> List[str]:
|
49 |
+
keyword_file = os.path.join(keyword_dir, f"FL-{number}-KEYWORD.txt")
|
50 |
+
if not os.path.exists(keyword_file):
|
51 |
+
keyword_file = os.path.join(keyword_dir, f"INFO-{number}-KEYWORD.txt")
|
52 |
+
if not os.path.exists(keyword_file):
|
53 |
+
keyword_file = os.path.join(keyword_dir, f"CHRONIQUE{number}-KEYWORD.txt")
|
54 |
+
if not os.path.exists(keyword_file):
|
55 |
+
logging.warning(f"Keyword file not found: {keyword_file}")
|
56 |
+
return []
|
57 |
+
|
58 |
+
with open(keyword_file, 'r') as file_obj:
|
59 |
+
content = file_obj.read()
|
60 |
+
if 'KEYWORD = ' in content:
|
61 |
+
content = content.split('KEYWORD = ')[1]
|
62 |
+
tags = content.split(', ')
|
63 |
+
tags = [tag.strip() for tag in tags if tag.strip()] # Remove empty tags
|
64 |
+
logging.info(f"Keywords for {number}: {tags}")
|
65 |
+
return tags
|
66 |
+
|
67 |
+
def load_and_process_dataset(data_update_path: str, keyword_dir: str, db_path: str):
|
68 |
+
memory_manager = MemoryManager(db_path)
|
69 |
+
|
70 |
+
sections = parse_data_update(data_update_path, keyword_dir)
|
71 |
+
for section in sections:
|
72 |
+
concept = section['concept']
|
73 |
+
description = section['description']
|
74 |
+
number = concept.split('#')[1].split()[0] # Extract the number from the concept
|
75 |
+
tags = get_keywords(number, keyword_dir)
|
76 |
+
|
77 |
+
# Check if the section already exists in the database
|
78 |
+
if not memory_manager.section_exists(concept):
|
79 |
+
memory_manager.add_semantic_memory(concept, description, tags=tags)
|
80 |
+
logging.info(f"Added section: {concept}")
|
81 |
+
else:
|
82 |
+
logging.info(f"Section already exists: {concept}")
|
83 |
+
|
84 |
+
if __name__ == "__main__":
|
85 |
+
data_update_path = "data-update.txt"
|
86 |
+
keyword_dir = "keyword" # Updated keyword directory
|
87 |
+
db_path = "agent.db"
|
88 |
+
load_and_process_dataset(data_update_path, keyword_dir, db_path)
|
src/gradio_app.py
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/gradio_app.py
|
2 |
+
import gradio as gr
|
3 |
+
from agent import Agent
|
4 |
+
from create_database import load_and_process_dataset # Import from create_database.py
|
5 |
+
import os
|
6 |
+
import uuid
|
7 |
+
import urllib.request
|
8 |
+
import logging
|
9 |
+
|
10 |
+
# Configure logging
|
11 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
12 |
+
|
13 |
+
def download_model():
|
14 |
+
model_url = "https://path/to/your/model.bin"
|
15 |
+
model_path = "model.bin"
|
16 |
+
|
17 |
+
if not os.path.exists(model_path):
|
18 |
+
print("Downloading model...")
|
19 |
+
urllib.request.urlretrieve(model_url, model_path)
|
20 |
+
print("Model downloaded successfully.")
|
21 |
+
|
22 |
+
def respond(
|
23 |
+
message,
|
24 |
+
history: list[tuple[str, str]],
|
25 |
+
system_message,
|
26 |
+
):
|
27 |
+
model_path = "model.bin" # Path to the downloaded model
|
28 |
+
db_path = "agent.db"
|
29 |
+
system_prompt = system_message
|
30 |
+
|
31 |
+
# Check if the model is downloaded
|
32 |
+
if not os.path.exists(model_path):
|
33 |
+
download_model()
|
34 |
+
|
35 |
+
# Check if the database exists, if not, initialize it
|
36 |
+
if not os.path.exists(db_path):
|
37 |
+
data_update_path = "data-update.txt"
|
38 |
+
keyword_dir = "keyword" # Updated keyword directory
|
39 |
+
load_and_process_dataset(data_update_path, keyword_dir, db_path)
|
40 |
+
|
41 |
+
agent = Agent(model_path, db_path, system_prompt)
|
42 |
+
user_id = str(uuid.uuid4()) # Generate a unique user ID for each session
|
43 |
+
|
44 |
+
response = agent.process_query(user_id, message)
|
45 |
+
return response
|
46 |
+
|
47 |
+
"""
|
48 |
+
For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
|
49 |
+
"""
|
50 |
+
demo = gr.ChatInterface(
|
51 |
+
respond,
|
52 |
+
additional_inputs=[
|
53 |
+
gr.Textbox(value="Vous êtes l'assistant intelligent de Les Chronique MTC. Votre rôle est d'aider les visiteurs en expliquant le contenu des Chroniques, Flash Infos et Chronique-FAQ de Michel Thomas. Utilisez le contexte fourni pour améliorer vos réponses et veillez à ce qu'elles soient précises et pertinentes.", label="System message"),
|
54 |
+
],
|
55 |
+
)
|
56 |
+
|
57 |
+
if __name__ == "__main__":
|
58 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
src/llm_interface.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/llm_interface.py
|
2 |
+
import llama_cpp
|
3 |
+
import logging
|
4 |
+
|
5 |
+
# Configure logging
|
6 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
7 |
+
|
8 |
+
class LLMInterface:
|
9 |
+
def __init__(self, model_path: str, default_system_prompt: str = ""):
|
10 |
+
self.model = llama_cpp.Llama(model_path=model_path)
|
11 |
+
self.default_system_prompt = default_system_prompt
|
12 |
+
|
13 |
+
def send_message(self, message: str, system_prompt: str = None, max_tokens: int = 512, temperature: float = 0.7, top_p: float = 0.95) -> str:
|
14 |
+
if system_prompt is None:
|
15 |
+
system_prompt = self.default_system_prompt
|
16 |
+
|
17 |
+
prompt = f"{system_prompt}\nUser: {message}\nAssistant: "
|
18 |
+
response = self.model(prompt, max_tokens=max_tokens, temperature=temperature, top_p=top_p)
|
19 |
+
return response['choices'][0]['text'].strip()
|
src/main.py
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/main.py
|
2 |
+
from src.agent import Agent
|
3 |
+
from src.create_database import load_and_process_dataset # Import from create_database.py
|
4 |
+
import os
|
5 |
+
import uuid
|
6 |
+
import requests
|
7 |
+
import logging
|
8 |
+
from llama_cpp import Llama
|
9 |
+
|
10 |
+
# Configure logging
|
11 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
12 |
+
|
13 |
+
# Create the directory if it doesn't exist
|
14 |
+
local_dir = "models"
|
15 |
+
os.makedirs(local_dir, exist_ok=True)
|
16 |
+
|
17 |
+
# Specify the filename for the model
|
18 |
+
filename = "unsloth.Q4_K_M.gguf"
|
19 |
+
model_path = os.path.join(local_dir, filename)
|
20 |
+
|
21 |
+
# Function to download the model file
|
22 |
+
def download_model(repo_id, filename, save_path):
|
23 |
+
# Construct the URL for the model file
|
24 |
+
url = f"https://huggingface.co/{repo_id}/resolve/main/{filename}"
|
25 |
+
|
26 |
+
# Download the model file
|
27 |
+
response = requests.get(url)
|
28 |
+
if response.status_code == 200:
|
29 |
+
with open(save_path, 'wb') as f:
|
30 |
+
f.write(response.content)
|
31 |
+
print(f"Model downloaded to {save_path}")
|
32 |
+
else:
|
33 |
+
print(f"Failed to download model: {response.status_code}")
|
34 |
+
|
35 |
+
# Download the model if it doesn't exist
|
36 |
+
if not os.path.exists(model_path):
|
37 |
+
download_model("PurpleAILAB/Llama3.2-3B-uncensored-SQLi-Q4_K_M-GGUF", filename, model_path)
|
38 |
+
|
39 |
+
def main():
|
40 |
+
model_path = "models/unsloth.Q4_K_M.gguf" # Path to the downloaded model
|
41 |
+
db_path = "agent.db"
|
42 |
+
system_prompt = "Vous êtes l'assistant intelligent de Les Chronique MTC. Votre rôle est d'aider les visiteurs en expliquant le contenu des Chroniques, Flash Infos et Chronique-FAQ de Michel Thomas. Utilisez le contexte fourni pour améliorer vos réponses et veillez à ce qu'elles soient précises et pertinentes."
|
43 |
+
max_tokens = 512
|
44 |
+
temperature = 0.7
|
45 |
+
top_p = 0.95
|
46 |
+
|
47 |
+
# Check if the database exists, if not, initialize it
|
48 |
+
if not os.path.exists(db_path):
|
49 |
+
data_update_path = "data-update.txt"
|
50 |
+
keyword_dir = "keyword" # Updated keyword directory
|
51 |
+
load_and_process_dataset(data_update_path, keyword_dir, db_path)
|
52 |
+
|
53 |
+
# Load the model
|
54 |
+
llm = Llama(
|
55 |
+
model_path=model_path,
|
56 |
+
n_ctx=5072, # Set the maximum context length
|
57 |
+
max_tokens=max_tokens # Control the maximum number of tokens generated in the response
|
58 |
+
)
|
59 |
+
|
60 |
+
agent = Agent(llm, db_path, system_prompt, max_tokens, temperature, top_p)
|
61 |
+
|
62 |
+
while True:
|
63 |
+
user_id = str(uuid.uuid4()) # Generate a unique user ID for each session
|
64 |
+
user_query = input("Entrez votre requête: ")
|
65 |
+
if user_query.lower() == 'exit':
|
66 |
+
break
|
67 |
+
|
68 |
+
try:
|
69 |
+
response = agent.process_query(user_id, user_query)
|
70 |
+
print("Réponse:", response)
|
71 |
+
except Exception as e:
|
72 |
+
print(f"Erreur lors du traitement de la requête: {e}")
|
73 |
+
|
74 |
+
# Clean up expired interactions
|
75 |
+
agent.memory.cleanup_expired_interactions()
|
76 |
+
|
77 |
+
if __name__ == "__main__":
|
78 |
+
main()
|
src/memory.py
ADDED
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/memory.py
|
2 |
+
import sqlite3
|
3 |
+
from datetime import datetime, timedelta
|
4 |
+
import json
|
5 |
+
from typing import List, Dict, Any, Tuple
|
6 |
+
import numpy as np
|
7 |
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
8 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
9 |
+
import logging
|
10 |
+
|
11 |
+
# Configure logging
|
12 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
13 |
+
|
14 |
+
class MemoryManager:
|
15 |
+
def __init__(self, db_path: str):
|
16 |
+
self.conn = sqlite3.connect(db_path)
|
17 |
+
self.cursor = self.conn.cursor()
|
18 |
+
self.create_tables()
|
19 |
+
self.vectorizer = TfidfVectorizer(stop_words='english')
|
20 |
+
logging.info("MemoryManager initialized and tables created.")
|
21 |
+
|
22 |
+
def create_tables(self):
|
23 |
+
# Create tables if they don't exist
|
24 |
+
self.cursor.execute('''CREATE TABLE IF NOT EXISTS semantic_memory
|
25 |
+
(id INTEGER PRIMARY KEY, concept TEXT, description TEXT, last_accessed DATETIME, tags TEXT, importance REAL DEFAULT 0.5)''')
|
26 |
+
|
27 |
+
# Add tags and importance columns if they don't exist
|
28 |
+
self.cursor.execute("PRAGMA table_info(semantic_memory)")
|
29 |
+
columns = [column[1] for column in self.cursor.fetchall()]
|
30 |
+
if 'tags' not in columns:
|
31 |
+
self.cursor.execute("ALTER TABLE semantic_memory ADD COLUMN tags TEXT")
|
32 |
+
if 'importance' not in columns:
|
33 |
+
self.cursor.execute("ALTER TABLE semantic_memory ADD COLUMN importance REAL DEFAULT 0.5")
|
34 |
+
|
35 |
+
self.cursor.execute('''CREATE INDEX IF NOT EXISTS idx_semantic_concept ON semantic_memory (concept)''')
|
36 |
+
self.cursor.execute('''CREATE INDEX IF NOT EXISTS idx_semantic_last_accessed ON semantic_memory (last_accessed)''')
|
37 |
+
self.cursor.execute('''CREATE INDEX IF NOT EXISTS idx_semantic_tags ON semantic_memory (tags)''')
|
38 |
+
|
39 |
+
# Create table for user interactions
|
40 |
+
self.cursor.execute('''CREATE TABLE IF NOT EXISTS user_interactions
|
41 |
+
(user_id TEXT, query TEXT, response TEXT, timestamp DATETIME)''')
|
42 |
+
|
43 |
+
self.cursor.execute('''CREATE INDEX IF NOT EXISTS idx_user_interactions_timestamp ON user_interactions (timestamp)''')
|
44 |
+
self.conn.commit()
|
45 |
+
logging.info("Tables and indexes created successfully.")
|
46 |
+
|
47 |
+
def add_semantic_memory(self, concept: str, description: str, tags: List[str] = None):
|
48 |
+
if tags is None:
|
49 |
+
tags = []
|
50 |
+
tags_str = json.dumps(tags)
|
51 |
+
self.cursor.execute("INSERT INTO semantic_memory (concept, description, last_accessed, tags) VALUES (?, ?, ?, ?)",
|
52 |
+
(concept, description, datetime.now().isoformat(), tags_str))
|
53 |
+
self.conn.commit()
|
54 |
+
logging.info("Semantic memory added.")
|
55 |
+
|
56 |
+
def retrieve_relevant_memories(self, query: str, limit: int = 30) -> List[Dict[str, Any]]:
|
57 |
+
all_memories = self._get_all_memories()
|
58 |
+
|
59 |
+
# Handle empty or stop-word-only query
|
60 |
+
if not query.strip() or self.vectorizer.stop_words and all(word in self.vectorizer.stop_words for word in query.split()):
|
61 |
+
return []
|
62 |
+
|
63 |
+
scored_memories = self._score_memories(query, all_memories)
|
64 |
+
return [memory for memory, score in sorted(scored_memories, key=lambda x: x[1], reverse=True)[:limit]]
|
65 |
+
|
66 |
+
def _get_all_memories(self) -> List[Tuple[Dict[str, Any], datetime]]:
|
67 |
+
self.cursor.execute("SELECT concept, description, importance, last_accessed, tags FROM semantic_memory ORDER BY importance DESC, last_accessed DESC")
|
68 |
+
semantic_memories = self.cursor.fetchall()
|
69 |
+
|
70 |
+
all_memories = [({"concept": concept, "description": description, "importance": importance},
|
71 |
+
datetime.fromisoformat(last_accessed), json.loads(tags) if tags else None) for concept, description, importance, last_accessed, tags in semantic_memories]
|
72 |
+
|
73 |
+
return all_memories
|
74 |
+
|
75 |
+
def _score_memories(self, query: str, memories: List[Tuple[Dict[str, Any], datetime, List[str]]]) -> List[Tuple[Dict[str, Any], float]]:
|
76 |
+
query_vector = self.vectorizer.fit_transform([query])
|
77 |
+
|
78 |
+
scored_memories = []
|
79 |
+
for memory, timestamp, tags in memories:
|
80 |
+
text = f"{memory['concept']} {memory['description']}"
|
81 |
+
importance = memory.get('importance', 0.5)
|
82 |
+
|
83 |
+
memory_vector = self.vectorizer.transform([text])
|
84 |
+
similarity = cosine_similarity(query_vector, memory_vector)[0][0]
|
85 |
+
|
86 |
+
if timestamp:
|
87 |
+
recency = 1 / (1 + (datetime.now() - timestamp).total_seconds() / 60) # Favor recent memories
|
88 |
+
else:
|
89 |
+
recency = 0.5 # Neutral recency for semantic memories
|
90 |
+
|
91 |
+
score = (similarity + importance + recency) / 3
|
92 |
+
scored_memories.append((memory, score))
|
93 |
+
|
94 |
+
return scored_memories
|
95 |
+
|
96 |
+
def section_exists(self, concept: str) -> bool:
|
97 |
+
# Normalize the concept to lowercase
|
98 |
+
concept = concept.lower()
|
99 |
+
self.cursor.execute("SELECT COUNT(*) FROM semantic_memory WHERE concept LIKE ?", (f"{concept}%",))
|
100 |
+
count = self.cursor.fetchone()[0]
|
101 |
+
return count > 0
|
102 |
+
|
103 |
+
def add_user_interaction(self, user_id: str, query: str, response: str):
|
104 |
+
self.cursor.execute("INSERT INTO user_interactions (user_id, query, response, timestamp) VALUES (?, ?, ?, ?)",
|
105 |
+
(user_id, query, response, datetime.now().isoformat()))
|
106 |
+
self.conn.commit()
|
107 |
+
logging.info(f"User interaction added: User ID: {user_id}, Query: {query}, Response: {response}")
|
108 |
+
|
109 |
+
def get_user_interactions(self, user_id: str) -> List[Dict[str, Any]]:
|
110 |
+
self.cursor.execute("SELECT query, response, timestamp FROM user_interactions WHERE user_id = ?", (user_id,))
|
111 |
+
interactions = self.cursor.fetchall()
|
112 |
+
return [{"query": query, "response": response, "timestamp": timestamp} for query, response, timestamp in interactions]
|
113 |
+
|
114 |
+
def cleanup_expired_interactions(self):
|
115 |
+
cutoff_time = datetime.now() - timedelta(minutes=5)
|
116 |
+
self.cursor.execute("DELETE FROM user_interactions WHERE timestamp < ?", (cutoff_time.isoformat(),))
|
117 |
+
self.conn.commit()
|
118 |
+
logging.info(f"Expired user interactions cleaned up. Cutoff time: {cutoff_time}")
|
119 |
+
|
120 |
+
def get_section_description(self, section_name: str) -> str:
|
121 |
+
# Normalize the section name to lowercase
|
122 |
+
section_name = section_name.lower()
|
123 |
+
|
124 |
+
# Retrieve the specific section from the database
|
125 |
+
self.cursor.execute("SELECT description FROM semantic_memory WHERE concept LIKE ?", (f"{section_name}%",))
|
126 |
+
result = self.cursor.fetchone()
|
127 |
+
if result:
|
128 |
+
logging.info(f"Found section: {section_name}")
|
129 |
+
return result[0]
|
130 |
+
else:
|
131 |
+
logging.warning(f"Section not found: {section_name}")
|
132 |
+
return ""
|
133 |
+
|
134 |
+
def count_chroniques(self) -> int:
|
135 |
+
# Count the number of chroniques in the database
|
136 |
+
self.cursor.execute("SELECT COUNT(*) FROM semantic_memory WHERE concept LIKE 'chronique #%'")
|
137 |
+
count = self.cursor.fetchone()[0]
|
138 |
+
logging.info(f"Number of chroniques: {count}")
|
139 |
+
return count
|
140 |
+
|
141 |
+
def count_flash_infos(self) -> int:
|
142 |
+
# Count the number of flash infos in the database
|
143 |
+
self.cursor.execute("SELECT COUNT(*) FROM semantic_memory WHERE concept LIKE 'flash info fl-%'")
|
144 |
+
count = self.cursor.fetchone()[0]
|
145 |
+
logging.info(f"Number of flash infos: {count}")
|
146 |
+
return count
|
147 |
+
|
148 |
+
def count_chronique_faqs(self) -> int:
|
149 |
+
# Count the number of chronique-faqs in the database
|
150 |
+
self.cursor.execute("SELECT COUNT(*) FROM semantic_memory WHERE concept LIKE 'chronique-faq #%'")
|
151 |
+
count = self.cursor.fetchone()[0]
|
152 |
+
logging.info(f"Number of chronique-faqs: {count}")
|
153 |
+
return count
|
154 |
+
|
155 |
+
if __name__ == "__main__":
|
156 |
+
db_path = "agent.db"
|
157 |
+
memory_manager = MemoryManager(db_path)
|
158 |
+
memory_manager.cleanup_expired_interactions()
|
src/prompts.py
ADDED
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/prompts.py
|
2 |
+
|
3 |
+
class Prompts:
|
4 |
+
GENERAL_SYSTEM_PROMPT = """
|
5 |
+
Vous êtes l'assistant intelligent de Les Chronique MTC. Votre rôle est d'aider les visiteurs en expliquant le contenu des Chroniques, Flash Infos et Chronique-FAQ de Michel Thomas. Utilisez uniquement le contexte fourni dans la base de données pour améliorer vos réponses et veillez à ce qu'elles soient précises, pertinentes et bien structurées.
|
6 |
+
|
7 |
+
- Utilisez uniquement le contenu de la base de données pour générer la réponse.
|
8 |
+
- Ne jamais utiliser des connaissances externes ou des suppositions.
|
9 |
+
- Résumez le contenu de manière concise et claire.
|
10 |
+
- Expliquez les concepts de manière détaillée et accessible, en utilisant des exemples concrets et des analogies.
|
11 |
+
- Analysez le contenu en profondeur, identifiez les thèmes principaux et les arguments clés.
|
12 |
+
- Critiquez le contenu de manière constructive, en identifiant les points forts et les points faibles.
|
13 |
+
- Générez des questions pertinentes pour encourager une réflexion plus approfondie.
|
14 |
+
- Fournissez des exemples concrets pour illustrer les concepts.
|
15 |
+
- Formulez des hypothèses basées sur les informations disponibles.
|
16 |
+
- Tirez des conclusions bien fondées et soutenues par des preuves.
|
17 |
+
- Formulez des recommandations pratiques et applicables.
|
18 |
+
- Générez des éléments d'action spécifiques, mesurables, réalisables, pertinents et temporellement définis (SMART).
|
19 |
+
- Créez un plan détaillé pour organiser les idées principales et les sous-thèmes.
|
20 |
+
- Rédigez une introduction engageante pour captiver l'attention du lecteur.
|
21 |
+
- Rédigez une conclusion forte pour résumer les points clés et laisser une impression durable.
|
22 |
+
- Paraphrasez le contenu de manière claire et concise, en préservant le sens original.
|
23 |
+
- Simplifiez le contenu pour le rendre plus accessible à un public plus large.
|
24 |
+
- Développez le contenu en ajoutant plus de détails et d'informations.
|
25 |
+
- Comparez deux concepts ou sujets en identifiant les similitudes et les différences.
|
26 |
+
- Contrastez deux concepts ou sujets en mettant en évidence les différences significatives.
|
27 |
+
- Créez des analogies pertinentes pour clarifier les concepts.
|
28 |
+
- Créez des métaphores pertinentes pour illustrer les concepts de manière créative.
|
29 |
+
- Si le contenu de la base de données ne fournit pas suffisamment d'informations, indiquez clairement que vous ne disposez pas de suffisamment d'informations pour répondre à la question.
|
30 |
+
"""
|
31 |
+
|
32 |
+
WHOLE_DOCUMENT_SYSTEM_PROMPT = """
|
33 |
+
Vous êtes l'assistant intelligent de Michel Thomas, consultant qui a écrit tout ce contenu sur son site web, y compris toutes les Chroniques, Flash Infos et Chronique-FAQ sur https://mtc-qc.ca. Votre tâche est de comprendre l'ensemble du document et de générer un résumé de haut niveau ou un contexte qui peut être utilisé pour répondre à la requête de l'utilisateur. La requête de l'utilisateur est: "{query}". Concentrez-vous sur les points les plus pertinents et importants. Incluez le nombre total de Chroniques, Flash Infos, et Chronique-FAQ publiées par Michel Thomas.
|
34 |
+
|
35 |
+
- Utilisez uniquement le contenu de la base de données pour générer le résumé.
|
36 |
+
- Ne jamais utiliser des connaissances externes ou des suppositions.
|
37 |
+
- Résumez le contenu de manière concise et claire.
|
38 |
+
- Expliquez les concepts de manière détaillée et accessible, en utilisant des exemples concrets et des analogies.
|
39 |
+
- Analysez le contenu en profondeur, identifiez les thèmes principaux et les arguments clés.
|
40 |
+
- Critiquez le contenu de manière constructive, en identifiant les points forts et les points faibles.
|
41 |
+
- Générez des questions pertinentes pour encourager une réflexion plus approfondie.
|
42 |
+
- Fournissez des exemples concrets pour illustrer les concepts.
|
43 |
+
- Formulez des hypothèses basées sur les informations disponibles.
|
44 |
+
- Tirez des conclusions bien fondées et soutenues par des preuves.
|
45 |
+
- Formulez des recommandations pratiques et applicables.
|
46 |
+
- Générez des éléments d'action spécifiques, mesurables, réalisables, pertinents et temporellement définis (SMART).
|
47 |
+
- Créez un plan détaillé pour organiser les idées principales et les sous-thèmes.
|
48 |
+
- Rédigez une introduction engageante pour captiver l'attention du lecteur.
|
49 |
+
- Rédigez une conclusion forte pour résumer les points clés et laisser une impression durable.
|
50 |
+
- Paraphrasez le contenu de manière claire et concise, en préservant le sens original.
|
51 |
+
- Simplifiez le contenu pour le rendre plus accessible à un public plus large.
|
52 |
+
- Développez le contenu en ajoutant plus de détails et d'informations.
|
53 |
+
- Comparez deux concepts ou sujets en identifiant les similitudes et les différences.
|
54 |
+
- Contrastez deux concepts ou sujets en mettant en évidence les différences significatives.
|
55 |
+
- Créez des analogies pertinentes pour clarifier les concepts.
|
56 |
+
- Créez des métaphores pertinentes pour illustrer les concepts de manière créative.
|
57 |
+
- Si le contenu de la base de données ne fournit pas suffisamment d'informations, indiquez clairement que vous ne disposez pas de suffisamment d'informations pour répondre à la question.
|
58 |
+
"""
|
59 |
+
|
60 |
+
QUERY_RESPONSE_SYSTEM_PROMPT = """
|
61 |
+
Vous êtes l'assistant intelligent de Michel Thomas. Votre tâche est de répondre à la requête de l'utilisateur en utilisant uniquement le contexte fourni dans la base de données. La requête de l'utilisateur est: "{query}". Assurez-vous que vos réponses sont claires, précises et directement liées à la requête de l'utilisateur. Si possible, incluez des exemples concrets pour illustrer vos points.
|
62 |
+
|
63 |
+
- Utilisez uniquement le contenu de la base de données pour générer la réponse.
|
64 |
+
- Ne jamais utiliser des connaissances externes ou des suppositions.
|
65 |
+
- Résumez le contenu de manière concise et claire.
|
66 |
+
- Expliquez les concepts de manière détaillée et accessible, en utilisant des exemples concrets et des analogies.
|
67 |
+
- Analysez le contenu en profondeur, identifiez les thèmes principaux et les arguments clés.
|
68 |
+
- Critiquez le contenu de manière constructive, en identifiant les points forts et les points faibles.
|
69 |
+
- Générez des questions pertinentes pour encourager une réflexion plus approfondie.
|
70 |
+
- Fournissez des exemples concrets pour illustrer les concepts.
|
71 |
+
- Formulez des hypothèses basées sur les informations disponibles.
|
72 |
+
- Tirez des conclusions bien fondées et soutenues par des preuves.
|
73 |
+
- Formulez des recommandations pratiques et applicables.
|
74 |
+
- Générez des éléments d'action spécifiques, mesurables, réalisables, pertinents et temporellement définis (SMART).
|
75 |
+
- Créez un plan détaillé pour organiser les idées principales et les sous-thèmes.
|
76 |
+
- Rédigez une introduction engageante pour captiver l'attention du lecteur.
|
77 |
+
- Rédigez une conclusion forte pour résumer les points clés et laisser une impression durable.
|
78 |
+
- Paraphrasez le contenu de manière claire et concise, en préservant le sens original.
|
79 |
+
- Simplifiez le contenu pour le rendre plus accessible à un public plus large.
|
80 |
+
- Développez le contenu en ajoutant plus de détails et d'informations.
|
81 |
+
- Comparez deux concepts ou sujets en identifiant les similitudes et les différences.
|
82 |
+
- Contrastez deux concepts ou sujets en mettant en évidence les différences significatives.
|
83 |
+
- Créez des analogies pertinentes pour clarifier les concepts.
|
84 |
+
- Créez des métaphores pertinentes pour illustrer les concepts de manière créative.
|
85 |
+
- Si le contenu de la base de données ne fournit pas suffisamment d'informations, indiquez clairement que vous ne disposez pas de suffisamment d'informations pour répondre à la question.
|
86 |
+
"""
|
87 |
+
|
88 |
+
ENHANCEMENT_SYSTEM_PROMPT = """
|
89 |
+
Vous êtes l'assistant intelligent de Michel Thomas. Votre tâche est d'améliorer la réponse initiale en la rendant plus complète et plus conforme à la requête de l'utilisateur. La requête de l'utilisateur est: "{query}". Prenez en compte tous les détails pertinents et formulez votre réponse de manière concise et claire. Ajoutez des détails supplémentaires si nécessaire pour rendre la réponse plus informative.
|
90 |
+
|
91 |
+
- Utilisez uniquement le contenu de la base de données pour améliorer la réponse.
|
92 |
+
- Ne jamais utiliser des connaissances externes ou des suppositions.
|
93 |
+
- Résumez le contenu de manière concise et claire en maximum 8 phrases de maximum 88 mots chacun.
|
94 |
+
- Expliquez les concepts de manière détaillée et accessible, en utilisant des exemples concrets et des analogies.
|
95 |
+
- Analysez le contenu en profondeur, identifiez les thèmes principaux et les arguments clés.
|
96 |
+
- Critiquez le contenu de manière constructive, en identifiant les points forts et les points faibles.
|
97 |
+
- Générez des questions pertinentes pour encourager une réflexion plus approfondie.
|
98 |
+
- Fournissez des exemples concrets pour illustrer les concepts.
|
99 |
+
- Formulez des hypothèses basées sur les informations disponibles.
|
100 |
+
- Tirez des conclusions bien fondées et soutenues par des preuves.
|
101 |
+
- Formulez des recommandations pratiques et applicables.
|
102 |
+
- Générez des éléments d'action spécifiques, mesurables, réalisables, pertinents et temporellement définis (SMART).
|
103 |
+
- Créez un plan détaillé pour organiser les idées principales et les sous-thèmes.
|
104 |
+
- Rédigez une introduction engageante pour captiver l'attention du lecteur.
|
105 |
+
- Rédigez une conclusion forte pour résumer les points clés et laisser une impression durable.
|
106 |
+
- Paraphrasez le contenu de manière claire et concise, en préservant le sens original.
|
107 |
+
- Simplifiez le contenu pour le rendre plus accessible à un public plus large.
|
108 |
+
- Développez le contenu en ajoutant plus de détails et d'informations.
|
109 |
+
- Comparez deux concepts ou sujets en identifiant les similitudes et les différences.
|
110 |
+
- Contrastez deux concepts ou sujets en mettant en évidence les diff��rences significatives.
|
111 |
+
- Créez des analogies pertinentes pour clarifier les concepts.
|
112 |
+
- Créez des métaphores pertinentes pour illustrer les concepts de manière créative.
|
113 |
+
- Si le contenu de la base de données ne fournit pas suffisamment d'informations, indiquez clairement que vous ne disposez pas de suffisamment d'informations pour répondre à la question.
|
114 |
+
"""
|
src/requirements.txt
ADDED
File without changes
|
src/utils.py
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/utils.py
|
2 |
+
import os
|
3 |
+
import json
|
4 |
+
from src.memory import MemoryManager # Corrected import path
|
5 |
+
from src.llm_interface import LLMInterface # Import LLMInterface
|
6 |
+
import logging
|
7 |
+
import spacy
|
8 |
+
from sklearn.cluster import KMeans
|
9 |
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
10 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
11 |
+
|
12 |
+
# Configure logging
|
13 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
14 |
+
|
15 |
+
def chunk_text(text, chunk_size=1000, overlap=100):
|
16 |
+
chunks = []
|
17 |
+
start = 0
|
18 |
+
while start < len(text):
|
19 |
+
end = start + chunk_size
|
20 |
+
chunks.append(text[start:end])
|
21 |
+
start = end - overlap
|
22 |
+
return chunks
|
23 |
+
|
24 |
+
def extract_and_summarize(query: str, memory_manager: MemoryManager, llm_interface: LLMInterface, system_prompt: str = "", max_tokens: int = 512, temperature: float = 0.7, top_p: float = 0.95) -> str:
|
25 |
+
# Retrieve relevant memories from the database
|
26 |
+
relevant_memories = memory_manager.retrieve_relevant_memories(query, limit=30)
|
27 |
+
logging.info(f"Retrieved {len(relevant_memories)} relevant memories for query: {query}")
|
28 |
+
|
29 |
+
# Combine relevant memories into a single context
|
30 |
+
context = " ".join([memory['description'] for memory in relevant_memories])
|
31 |
+
logging.info(f"Built context: {context}")
|
32 |
+
|
33 |
+
# Truncate the context if it exceeds the token limit
|
34 |
+
max_context_length = 30000 # Adjust this based on your LLM's token limit
|
35 |
+
if len(context) > max_context_length:
|
36 |
+
context = context[:max_context_length]
|
37 |
+
logging.info(f"Truncated context to {max_context_length} characters.")
|
38 |
+
|
39 |
+
# Use spaCy to generate sentence embeddings
|
40 |
+
nlp = spacy.load('en_core_web_lg')
|
41 |
+
sentences = context.split('.')
|
42 |
+
sentence_embeddings = [nlp(sent).vector for sent in sentences]
|
43 |
+
|
44 |
+
# Cluster sentences
|
45 |
+
num_clusters = min(len(sentences), 10) # Adjust the number of clusters
|
46 |
+
kmeans = KMeans(n_clusters=num_clusters)
|
47 |
+
kmeans.fit(sentence_embeddings)
|
48 |
+
labels = kmeans.labels_
|
49 |
+
|
50 |
+
# Select representative sentences from each cluster
|
51 |
+
representative_sentences = []
|
52 |
+
for i in range(num_clusters):
|
53 |
+
cluster_sentences = [sentences[j] for j in range(len(sentences)) if labels[j] == i]
|
54 |
+
if cluster_sentences:
|
55 |
+
representative_sentences.append(max(cluster_sentences, key=len)) # Select the longest sentence as representative
|
56 |
+
|
57 |
+
# Combine representative sentences to form a summary
|
58 |
+
summary = " ".join(representative_sentences)
|
59 |
+
logging.info(f"Generated summary: {summary}")
|
60 |
+
|
61 |
+
# Use LLM to refine the summary
|
62 |
+
try:
|
63 |
+
refined_summary = llm_interface.send_message(f"Context: {summary}\nQuestion: {query}", system_prompt=system_prompt, max_tokens=max_tokens, temperature=temperature, top_p=top_p)
|
64 |
+
logging.info(f"Refined summary: {refined_summary}")
|
65 |
+
except Exception as e:
|
66 |
+
refined_summary = f"Error refining summary: {e}"
|
67 |
+
logging.error(f"Error refining summary: {e}")
|
68 |
+
|
69 |
+
return refined_summary
|