ALBERT Clement commited on
Commit
e7e3aa3
·
1 Parent(s): 2d2a393

Voice assistant improvements

Browse files
Files changed (7) hide show
  1. .gitignore +1 -0
  2. README.md +0 -1
  3. app.py +220 -25
  4. app_back.py +98 -0
  5. requirements.txt +5 -3
  6. src/voice/assistant.py +30 -0
  7. src/voice/speech_to_text.py +42 -0
.gitignore CHANGED
@@ -47,5 +47,6 @@ src/main.py
47
  src/chat.py
48
  src/llm/prompt2.py
49
  src/loaders
 
50
 
51
  ###
 
47
  src/chat.py
48
  src/llm/prompt2.py
49
  src/loaders
50
+ /notebook/
51
 
52
  ###
README.md CHANGED
@@ -9,7 +9,6 @@ app_file: app.py
9
  pinned: false
10
  ---
11
 
12
-
13
  # RAG Assistant
14
 
15
  Assistant conversationnel basé sur une architecture **Retrieval Augmented Generation (RAG)**.
 
9
  pinned: false
10
  ---
11
 
 
12
  # RAG Assistant
13
 
14
  Assistant conversationnel basé sur une architecture **Retrieval Augmented Generation (RAG)**.
app.py CHANGED
@@ -1,7 +1,8 @@
1
  import spaces
2
-
3
  import gradio as gr
4
 
 
 
5
  from src.vectorstore.faiss_index import load_index
6
  from src.vectorstore.metadata_store import load_metadata
7
 
@@ -13,35 +14,63 @@ from src.llm.postprocessing import clean_answer
13
 
14
  from src.data_processing.txt_embeddings import compute_embeddings
15
 
16
- # Load once
17
- from pathlib import Path
 
 
 
 
 
18
 
19
  BASE_DIR = Path(__file__).parent
20
 
21
  INDEX_PATH = str(BASE_DIR / "data/vectorstore/faiss.index")
22
  METADATA_PATH = str(BASE_DIR / "data/metadata/metadata.json")
23
 
 
24
  index = load_index(INDEX_PATH)
25
  metadata = load_metadata(METADATA_PATH)
26
 
27
- @spaces.GPU
28
 
 
 
 
 
29
 
 
30
  def chat_fn(message, history):
31
 
32
- # 1. Embedding de la question
33
  query_emb = compute_embeddings([message])
34
 
 
35
  # 2. Retrieval FAISS
36
- docs = retrieve(query_emb, index, metadata, top_k=10)
 
 
 
 
 
 
37
 
38
- # 3. Reranking (IMPORTANT: retourne (doc, score))
39
- docs = rerank(message, docs, top_k=10)
 
 
 
 
40
 
41
- # 4. Construction du contexte LLM
42
- context = "\n\n".join([doc["text"] for doc, score in docs])
43
 
44
- # 5. Prompt
 
 
 
 
 
 
 
 
 
45
  prompt = f"""
46
  Context:
47
  {context}
@@ -51,14 +80,20 @@ Question:
51
 
52
  Answer:
53
  """
54
-
55
- # 6. Génération réponse LLM
56
  answer = generate_answer(prompt)
57
 
58
- # 6.1. Postprocessing
59
  answer = clean_answer(answer)
60
 
61
- # 7. Debug chunks avec scores
 
 
 
 
 
 
 
62
  debug = "\n\n".join(
63
  [
64
  f"📄 Chunk {i+1} (Score : {score:.3f})\n{doc['text']}"
@@ -66,19 +101,179 @@ Answer:
66
  ]
67
  )
68
 
69
- # 8. Output final
70
- final_output = f"{answer}\n\n---\n\n🔍 Retrieved chunks:\n{debug}"
71
 
72
- return final_output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
- ###########################################################################
75
- # Interface Gradio type ChatGPT
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
- demo = gr.ChatInterface(
78
- fn=chat_fn,
79
- title="💬 RAG Assistant",
80
- description="Chat avec ton système RAG local"
81
- )
82
 
83
  if __name__ == "__main__":
84
  demo.launch()
 
1
  import spaces
 
2
  import gradio as gr
3
 
4
+ from pathlib import Path
5
+
6
  from src.vectorstore.faiss_index import load_index
7
  from src.vectorstore.metadata_store import load_metadata
8
 
 
14
 
15
  from src.data_processing.txt_embeddings import compute_embeddings
16
 
17
+ from src.voice.assistant import text_to_audio
18
+ from src.voice.speech_to_text import transcribe_audio
19
+
20
+
21
+ # ============================================================
22
+ # Chargement des ressources
23
+ # ============================================================
24
 
25
  BASE_DIR = Path(__file__).parent
26
 
27
  INDEX_PATH = str(BASE_DIR / "data/vectorstore/faiss.index")
28
  METADATA_PATH = str(BASE_DIR / "data/metadata/metadata.json")
29
 
30
+
31
  index = load_index(INDEX_PATH)
32
  metadata = load_metadata(METADATA_PATH)
33
 
 
34
 
35
+ # ============================================================
36
+ # Fonction RAG + TTS
37
+ # ============================================================
38
+ @spaces.GPU
39
 
40
+ #
41
  def chat_fn(message, history):
42
 
43
+ # 1. Embedding question
44
  query_emb = compute_embeddings([message])
45
 
46
+
47
  # 2. Retrieval FAISS
48
+ docs = retrieve(
49
+ query_emb,
50
+ index,
51
+ metadata,
52
+ top_k=10
53
+ )
54
+
55
 
56
+ # 3. Reranking
57
+ docs = rerank(
58
+ message,
59
+ docs,
60
+ top_k=4
61
+ )
62
 
 
 
63
 
64
+ # 4. Construction contexte
65
+ context = "\n\n".join(
66
+ [
67
+ doc["text"]
68
+ for doc, score in docs
69
+ ]
70
+ )
71
+
72
+
73
+ # 5. Prompt LLM
74
  prompt = f"""
75
  Context:
76
  {context}
 
80
 
81
  Answer:
82
  """
83
+ # 6. Génération réponse
 
84
  answer = generate_answer(prompt)
85
 
86
+ # 7. Nettoyage
87
  answer = clean_answer(answer)
88
 
89
+ # 8. Génération audio Kokoro
90
+
91
+ try:
92
+ audio = text_to_audio(answer)
93
+ except Exception:
94
+ audio = None
95
+
96
+ # Debug sources
97
  debug = "\n\n".join(
98
  [
99
  f"📄 Chunk {i+1} (Score : {score:.3f})\n{doc['text']}"
 
101
  ]
102
  )
103
 
 
 
104
 
105
+ final_output = (
106
+ f"{answer}\n\n"
107
+ "---\n\n"
108
+ f"🔍 Retrieved chunks:\n{debug}"
109
+ )
110
+
111
+ return final_output, audio
112
+
113
+ #
114
+ def respond_audio(audio, history):
115
+
116
+ print("respond_audio appelée")
117
+ print(audio)
118
+
119
+ if audio is None:
120
+ return history, None
121
+
122
+ if history is None:
123
+ history = []
124
+
125
+ message = transcribe_audio(audio)
126
+
127
+ answer, output_audio = chat_fn(
128
+ message,
129
+ history
130
+ )
131
+
132
+ history.append(
133
+ {
134
+ "role": "user",
135
+ "content": message
136
+ }
137
+ )
138
+
139
+ history.append(
140
+ {
141
+ "role": "assistant",
142
+ "content": answer
143
+ }
144
+ )
145
+
146
+ return history, output_audio
147
+
148
+ # ============================================================
149
+ # Interface Gradio
150
+ # ============================================================
151
+
152
+ with gr.Blocks() as demo:
153
 
154
+ gr.Markdown(
155
+ "# 💬 RAG Assistant vocal"
156
+ )
157
+
158
+
159
+ # ========================================================
160
+ # Ligne 1 : Chatbot
161
+ # ========================================================
162
+
163
+ chatbot = gr.Chatbot(
164
+ label="Conversation",
165
+ height=450
166
+ )
167
+
168
+
169
+ # ========================================================
170
+ # Ligne 2 : Question texte
171
+ # ========================================================
172
+
173
+ msg = gr.Textbox(
174
+ placeholder="Pose ta question...",
175
+ label="✍️ Question texte"
176
+ )
177
+
178
+
179
+ # ========================================================
180
+ # Ligne 3 : Audio entrée / sortie
181
+ # ========================================================
182
+
183
+ with gr.Row():
184
+
185
+ with gr.Column():
186
+
187
+ audio_input = gr.Audio(
188
+ sources=[
189
+ "microphone",
190
+ "upload"
191
+ ],
192
+ type="filepath",
193
+ waveform_options=gr.WaveformOptions(
194
+ show_recording_waveform=True
195
+ ),
196
+ label="🎤 Parlez ou déposez un fichier audio"
197
+ )
198
+
199
+
200
+ with gr.Column():
201
+
202
+ audio_output = gr.Audio(
203
+ label="🔊 Réponse audio"
204
+ )
205
+
206
+
207
+ # ========================================================
208
+ # Réponse texte
209
+ # ========================================================
210
+
211
+ def respond(message, history):
212
+
213
+ if history is None:
214
+ history = []
215
+
216
+ answer, audio = chat_fn(
217
+ message,
218
+ history
219
+ )
220
+
221
+
222
+ history.append(
223
+ {
224
+ "role": "user",
225
+ "content": message
226
+ }
227
+ )
228
+
229
+
230
+ history.append(
231
+ {
232
+ "role": "assistant",
233
+ "content": answer
234
+ }
235
+ )
236
+
237
+
238
+ return (
239
+ "",
240
+ history,
241
+ audio
242
+ )
243
+
244
+ # ========================================================
245
+ # Events
246
+ # ========================================================
247
+
248
+ msg.submit(
249
+ respond,
250
+ inputs=[
251
+ msg,
252
+ chatbot
253
+ ],
254
+ outputs=[
255
+ msg,
256
+ chatbot,
257
+ audio_output
258
+ ]
259
+ )
260
+
261
+
262
+ audio_input.change(
263
+ respond_audio,
264
+ inputs=[
265
+ audio_input,
266
+ chatbot
267
+ ],
268
+ outputs=[
269
+ chatbot,
270
+ audio_output
271
+ ]
272
+ )
273
 
274
+ # ============================================================
275
+ # Run
276
+ # ============================================================
 
 
277
 
278
  if __name__ == "__main__":
279
  demo.launch()
app_back.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import spaces
2
+
3
+ import gradio as gr
4
+
5
+ from src.vectorstore.faiss_index import load_index
6
+ from src.vectorstore.metadata_store import load_metadata
7
+
8
+ from src.retrieval.retriever import retrieve
9
+ from src.retrieval.reranker import rerank
10
+
11
+ from src.llm.generator import generate_answer
12
+ from src.llm.postprocessing import clean_answer
13
+
14
+ from src.data_processing.txt_embeddings import compute_embeddings
15
+
16
+ from src.voice.assistant import text_to_audio
17
+
18
+ # Load once
19
+ from pathlib import Path
20
+
21
+ BASE_DIR = Path(__file__).parent
22
+
23
+ INDEX_PATH = str(BASE_DIR / "data/vectorstore/faiss.index")
24
+ METADATA_PATH = str(BASE_DIR / "data/metadata/metadata.json")
25
+
26
+ index = load_index(INDEX_PATH)
27
+ metadata = load_metadata(METADATA_PATH)
28
+
29
+ @spaces.GPU
30
+
31
+
32
+ def chat_fn(message, history):
33
+
34
+ # 1. Embedding de la question
35
+ query_emb = compute_embeddings([message])
36
+
37
+ # 2. Retrieval FAISS
38
+ docs = retrieve(query_emb, index, metadata, top_k=10)
39
+
40
+ # 3. Reranking (IMPORTANT: retourne (doc, score))
41
+ docs = rerank(message, docs, top_k=10)
42
+
43
+ # 4. Construction du contexte LLM
44
+ context = "\n\n".join([doc["text"] for doc, score in docs])
45
+
46
+ # 5. Prompt
47
+ prompt = f"""
48
+ Context:
49
+ {context}
50
+
51
+ Question:
52
+ {message}
53
+
54
+ Answer:
55
+ """
56
+
57
+ # 6. Génération réponse LLM
58
+ answer = generate_answer(prompt)
59
+
60
+ # 6.1. Postprocessing
61
+ answer = clean_answer(answer)
62
+
63
+ # 6.2. Audio answer
64
+ audio = text_to_audio(answer)
65
+
66
+ # 7. Debug chunks avec scores
67
+ debug = "\n\n".join(
68
+ [
69
+ f"📄 Chunk {i+1} (Score : {score:.3f})\n{doc['text']}"
70
+ for i, (doc, score) in enumerate(docs)
71
+ ]
72
+ )
73
+
74
+ # 8. Output final
75
+ final_output = f"{answer}\n\n---\n\n🔍 Retrieved chunks:\n{debug}"
76
+
77
+
78
+ return final_output, audio
79
+
80
+ ###########################################################################
81
+ # Interface Gradio type ChatGPT
82
+
83
+ """
84
+
85
+ audio_output = gr.Audio(
86
+ label="🔊 Audio"
87
+ )
88
+
89
+ demo = gr.ChatInterface(
90
+ fn=chat_fn,
91
+ title="💬 RAG Assistant",
92
+ description="Chat avec ton système RAG local",
93
+ additional_outputs=[audio_output]
94
+ )
95
+
96
+ if __name__ == "__main__":
97
+ demo.launch()
98
+ """
requirements.txt CHANGED
@@ -1,7 +1,7 @@
1
- torch==2.12.1
2
  transformers==5.12.1
3
  sentence-transformers==5.6.0
4
- numpy==2.4.6
5
  nltk==3.9.4
6
  python-docx==1.2.0
7
  PyMuPDF==1.28.0
@@ -10,4 +10,6 @@ faiss-cpu==1.14.3
10
  accelerate==1.14.0
11
  safetensors==0.8.0
12
  gradio==6.19.0
13
- spaces==0.50.4
 
 
 
1
+ torch
2
  transformers==5.12.1
3
  sentence-transformers==5.6.0
4
+ numpy
5
  nltk==3.9.4
6
  python-docx==1.2.0
7
  PyMuPDF==1.28.0
 
10
  accelerate==1.14.0
11
  safetensors==0.8.0
12
  gradio==6.19.0
13
+ spaces==0.50.4
14
+ kokoro==0.9.4
15
+ faster_whisper==1.2.1
src/voice/assistant.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from kokoro import KPipeline
2
+ import numpy as np
3
+
4
+
5
+ # Chargement unique du modèle
6
+ pipeline = KPipeline(lang_code="f")
7
+
8
+ VOICE = "ff_siwis"
9
+ SAMPLE_RATE = 24000
10
+
11
+
12
+ def text_to_audio(text):
13
+ """
14
+ Convertit un texte en audio.
15
+ Retourne un tuple compatible avec gr.Audio
16
+ """
17
+
18
+ generator = pipeline(
19
+ text,
20
+ voice=VOICE
21
+ )
22
+
23
+ chunks = []
24
+
25
+ for _, _, audio in generator:
26
+ chunks.append(audio)
27
+
28
+ audio = np.concatenate(chunks)
29
+
30
+ return SAMPLE_RATE, audio
src/voice/speech_to_text.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from faster_whisper import WhisperModel
2
+ import torch
3
+
4
+
5
+ print("CUDA disponible :", torch.cuda.is_available())
6
+ print("Nombre de GPU :", torch.cuda.device_count())
7
+
8
+
9
+ if torch.cuda.is_available():
10
+ print("GPU :", torch.cuda.get_device_name(0))
11
+ device = "cuda"
12
+ compute_type = "float16"
13
+
14
+ else:
15
+ device = "cpu"
16
+ compute_type = "int8"
17
+
18
+
19
+ print("DEVICE :", device)
20
+ print("COMPUTE :", compute_type)
21
+
22
+
23
+ model = WhisperModel(
24
+ "base",
25
+ device=device,
26
+ compute_type=compute_type
27
+ )
28
+
29
+
30
+ def transcribe_audio(audio_path):
31
+
32
+ segments, info = model.transcribe(
33
+ audio_path,
34
+ language="fr"
35
+ )
36
+
37
+ text = " ".join(
38
+ segment.text
39
+ for segment in segments
40
+ )
41
+
42
+ return text.strip()