Deepti-AI commited on
Commit
83f8481
·
verified ·
1 Parent(s): c0cca9e

Upload 4 files

Browse files
Files changed (5) hide show
  1. .gitattributes +2 -0
  2. Database.xlsx +3 -0
  3. main.py +326 -0
  4. requirements.txt +15 -0
  5. robot.gif +3 -0
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ Database.xlsx filter=lfs diff=lfs merge=lfs -text
37
+ robot.gif filter=lfs diff=lfs merge=lfs -text
Database.xlsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b7c2f293c5ae6f596ac129e67d6bc264709a070e15ee5121f8272cd900114bbd
3
+ size 1292016
main.py ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile
2
+ from fastapi.responses import StreamingResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ import os
5
+ import openai
6
+ from io import BytesIO
7
+ from gtts import gTTS
8
+ import tempfile
9
+ from dotenv import load_dotenv
10
+ from sentence_transformers import SentenceTransformer
11
+ import math
12
+ from collections import Counter
13
+ import json
14
+ import pandas as pd
15
+ import asyncio
16
+ import numpy as np
17
+ from deepgram import Deepgram # NEW
18
+
19
+ load_dotenv()
20
+ DEEPGRAM_API_KEY = os.getenv("DEEPGRAM_API_KEY") # Add this to your .env
21
+ dg_client = Deepgram(DEEPGRAM_API_KEY) # Initialize Deepgram client
22
+ openai.api_key = os.getenv("OPENAI_API_KEY")
23
+
24
+ app = FastAPI()
25
+
26
+ app.add_middleware(
27
+ CORSMiddleware,
28
+ allow_origins=["*"],
29
+ allow_credentials=True,
30
+ allow_methods=["*"],
31
+ allow_headers=["*"],
32
+ )
33
+
34
+
35
+ chat_messages = [{"role": "system", "content": '''
36
+ You are kammi, a friendly, human-like voice assistant built by Facile AI Solutions, headed by Deepti.You assist customers specifically with knee replacement surgery queries and you are the assistant of Dr.Sandeep who is a highly experienced knee replacement surgeon.
37
+
38
+ Rules for your responses:
39
+
40
+ 1. **Context-driven answers only**: Answer strictly based on the provided context and previous conversation history. Do not use external knowledge.
41
+
42
+ 2. **General conversation**: Engage in greetings and casual conversation. If the user mentions their name, greet them personally and continue using their name.
43
+
44
+ 3. **Technical/medical queries**:
45
+ - If the question is **relevant to knee replacement surgery** and the answer is in the context or chat history, provide the answer.
46
+ - If the question is **relevant but not present in the context**, respond: "please connect with Dr.Sandeep or Reception for this details."
47
+
48
+ 4. **Irrelevant queries**:
49
+ - If the question is completely unrelated to knee replacement surgery, politely decline and respond: "I am here to assist only with knee replacement surgery related queries."
50
+
51
+ 5. **Drive conversation**:
52
+ - After answering the user’s question, suggest a follow-up question from the context that you can answer.
53
+ - Make the follow-up natural and conversational. The follow up question must be relevant to the current question or response
54
+ - If the user responds with confirmation like “yes”, “okay” give the answer for the previous follow-up question from the context.
55
+
56
+ 6. **Readable voice output for gTTS**:
57
+ - Break sentences at natural punctuation: `, . ? ! : ;`.
58
+ - Do not use `#`, `**`, or other markdown symbols.
59
+ - Numbers and points must be spelled out: e.g., `2.5 lakh` → `two point five lakh`. Similarly Dr, Mr, Mrs, etc. must be written as Doctor, Mister, Misses etc.
60
+ 7. **Concise and human-like**:
61
+ - Keep answers short, conversational, and natural.
62
+ - Maximum 40 words / ~20 seconds of speech.
63
+
64
+ 8. **Tone and style**:
65
+ - Helpful, friendly, approachable, and human-like.
66
+ - Maintain professionalism while being conversational.
67
+
68
+ 9. **About Dr.Sandeep**:
69
+ - He has over 15 years of experience in orthopedic and joint replacement surgery.
70
+ - He specializes in total and partial knee replacement procedures.
71
+ - Known for a patient-friendly approach, focusing on pre-surgery preparation, post-surgery rehabilitation, and pain management.
72
+ - Actively keeps up-to-date with the latest techniques and technologies in knee replacement surgery.
73
+ - Highly approachable and prefers that patients are well-informed about their treatment options and recovery process.
74
+
75
+ Always provide readable, streaming-friendly sentences so gTTS can read smoothly. Drive conversation forward while staying strictly on knee replacement surgery topics, and suggest follow-up questions for which you have context-based answers.
76
+ '''}]
77
+
78
+ class BM25:
79
+ def __init__(self, corpus, k1=1.2, b=0.75):
80
+ self.corpus = [doc.split() if isinstance(doc, str) else doc for doc in corpus]
81
+ self.k1 = k1
82
+ self.b = b
83
+ self.N = len(self.corpus)
84
+ self.avgdl = sum(len(doc) for doc in self.corpus) / self.N
85
+ self.doc_freqs = self._compute_doc_frequencies()
86
+ self.idf = self._compute_idf()
87
+
88
+ def _compute_doc_frequencies(self):
89
+ """Count how many documents contain each term"""
90
+ df = {}
91
+ for doc in self.corpus:
92
+ unique_terms = set(doc)
93
+ for term in unique_terms:
94
+ df[term] = df.get(term, 0) + 1
95
+ return df
96
+
97
+ def _compute_idf(self):
98
+ """Compute the IDF for each term in the corpus"""
99
+ idf = {}
100
+ for term, df in self.doc_freqs.items():
101
+ idf[term] = math.log((self.N - df + 0.5) / (df + 0.5) + 1)
102
+ return idf
103
+
104
+ def score(self, query, document):
105
+ """Compute the BM25 score for one document and one query"""
106
+ query_terms = query.split() if isinstance(query, str) else query
107
+ doc_terms = document.split() if isinstance(document, str) else document
108
+ score = 0.0
109
+ freqs = Counter(doc_terms)
110
+ doc_len = len(doc_terms)
111
+
112
+ for term in query_terms:
113
+ if term not in freqs:
114
+ continue
115
+ f = freqs[term]
116
+ idf = self.idf.get(term, 0)
117
+ denom = f + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)
118
+ score += idf * (f * (self.k1 + 1)) / denom
119
+ return score
120
+
121
+ def rank(self, query):
122
+ """Rank all documents for a given query"""
123
+ return [(i, self.score(query, doc)) for i, doc in enumerate(self.corpus)]
124
+
125
+
126
+ def sigmoid_scaled(x, midpoint=3.0):
127
+ """
128
+ Sigmoid function with shifting.
129
+ `midpoint` controls where the output is 0.5.
130
+ """
131
+ return 1 / (1 + math.exp(-(x - midpoint)))
132
+
133
+ def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
134
+
135
+ return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
136
+
137
+ async def compute_similarity(query: str, query_embedding: np.ndarray, chunk_text: str, chunk_embedding: np.ndarray, sem_weight: float,syn_weight:float,bm25) -> float:
138
+
139
+ semantic_score = cosine_similarity(query_embedding, chunk_embedding)
140
+
141
+ # syntactic_score = fuzz.ratio(query, chunk_text) / 100.0
142
+ syntactic_score = bm25.score(query,chunk_text)
143
+ final_syntactic_score = sigmoid_scaled(syntactic_score)
144
+
145
+ combined_score = sem_weight * semantic_score + syn_weight * final_syntactic_score
146
+
147
+ return combined_score
148
+
149
+ async def retrieve_top_k_hybrid(query, k, sem_weight,syn_weight,bm25):
150
+
151
+ query_embedding = model.encode(query)
152
+
153
+ tasks = [
154
+
155
+ compute_similarity(query, query_embedding, row["Chunks"], row["Embeddings"] , sem_weight,syn_weight,bm25)
156
+
157
+ for _, row in df_expanded.iterrows()
158
+
159
+ ]
160
+
161
+ similarities = await asyncio.gather(*tasks)
162
+
163
+ df_expanded["similarity"] = similarities
164
+
165
+ top_results = df_expanded.sort_values(by="similarity", ascending=False).head(k)
166
+
167
+ return top_results["Chunks"].to_list()
168
+
169
+ model = SentenceTransformer("abhinand/MedEmbed-large-v0.1")
170
+ df_expanded = pd.read_excel("Database.xlsx") # Replace with your filename
171
+ df_expanded["Embeddings"] = df_expanded["Embeddings"].map(lambda x: json.loads(x))
172
+ corpus = df_expanded['Chunks'].to_list()
173
+ bm25 = BM25(corpus)
174
+
175
+
176
+ # --- gTTS helper: stream raw audio file in small chunks ---
177
+ def tts_chunk_stream(text_chunk: str, lang: str = "en"):
178
+ if not text_chunk.strip():
179
+ return []
180
+
181
+ tts = gTTS(text=text_chunk, lang=lang)
182
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3")
183
+ tts.save(temp_file.name)
184
+
185
+ def audio_stream():
186
+ try:
187
+ with open(temp_file.name, "rb") as f:
188
+ chunk = f.read(1024)
189
+ while chunk:
190
+ yield chunk
191
+ chunk = f.read(1024)
192
+ finally:
193
+ try:
194
+ os.remove(temp_file.name)
195
+ except Exception:
196
+ pass
197
+
198
+ return audio_stream()
199
+
200
+ async def get_rag_response(user_message: str):
201
+ global chat_messages
202
+ Chunks = await retrieve_top_k_hybrid(user_message,15, 0.9, 0.1,bm25)
203
+ context = "======================================================================================================\n".join(Chunks)
204
+ chat_messages.append({"role": "user", "content": f'''
205
+ Context : {context}
206
+ User Query: {user_message}'''})
207
+ # print("chat_messages",chat_messages)
208
+ # response = get_response(query, context, chat_messages)
209
+ # chat_messages.append({"role": "assistant", "content": response})
210
+ return chat_messages
211
+
212
+
213
+ # --- GPT + TTS async generator with smaller buffer like second code ---
214
+ async def gpt_tts_stream(prompt: str):
215
+ print("started gpt_tts_stream",prompt)
216
+ global chat_messages
217
+ chat_messages = await get_rag_response(prompt)
218
+ # print(chat_messages,"chat_messages after getting RAG response")
219
+ response = openai.ChatCompletion.create(
220
+ model="gpt-4o",
221
+ messages= chat_messages,
222
+ stream=True
223
+ )
224
+ buffer = ""
225
+ BUFFER_SIZE = 20 # smaller buffer like second code
226
+ bot_response = ""
227
+
228
+ for chunk in response:
229
+ choices = chunk.get("choices", [])
230
+ if not choices:
231
+ continue
232
+
233
+ delta = choices[0]["delta"].get("content", "")
234
+ finish_reason = choices[0].get("finish_reason")
235
+ if delta:
236
+ bot_response = bot_response + delta
237
+ buffer += delta
238
+ if len(buffer) >= BUFFER_SIZE and buffer.endswith((".", "!",",", "?", "\n", ";", ":")):
239
+ for audio_chunk in tts_chunk_stream(buffer):
240
+ print("chunk",buffer)
241
+ yield audio_chunk
242
+ buffer = ""
243
+
244
+ if finish_reason is not None:
245
+ break
246
+
247
+ bot_response = bot_response.strip()
248
+ chat_messages.append({"role": "assistant", "content": bot_response})
249
+
250
+ if buffer.strip():
251
+ for audio_chunk in tts_chunk_stream(buffer):
252
+ yield audio_chunk
253
+
254
+ @app.post("/chat_stream")
255
+ async def chat_stream(file: UploadFile = File(...)):
256
+ audio_bytes = await file.read()
257
+
258
+ # Transcribe using Deepgram
259
+ response = await dg_client.transcription.prerecorded(
260
+ {
261
+ "buffer": audio_bytes,
262
+ "mimetype": "audio/webm"
263
+ },
264
+ {
265
+ "model": "nova-3",
266
+ "language": "en",
267
+ "punctuate": True,
268
+ "smart_format": True
269
+ }
270
+ )
271
+
272
+ transcript_text = response["results"]["channels"][0]["alternatives"][0]["transcript"].strip()
273
+
274
+ return StreamingResponse(gpt_tts_stream(transcript_text), media_type="audio/mpeg")
275
+
276
+
277
+ @app.post("/reset_chat")
278
+ async def reset_chat():
279
+ global chat_messages
280
+ chat_messages = [{
281
+ "role": "system",
282
+ "content": '''
283
+ You are kammi, a friendly, human-like voice assistant built by Facile AI Solutions, headed by Deepti.You assist customers specifically with knee replacement surgery queries and you are the assistant of Dr.Sandeep who is a highly experienced knee replacement surgeon.
284
+
285
+ Rules for your responses:
286
+
287
+ 1. **Context-driven answers only**: Answer strictly based on the provided context and previous conversation history. Do not use external knowledge.
288
+
289
+ 2. **General conversation**: Engage in greetings and casual conversation. If the user mentions their name, greet them personally and continue using their name.
290
+
291
+ 3. **Technical/medical queries**:
292
+ - If the question is **relevant to knee replacement surgery** and the answer is in the context or chat history, provide the answer.
293
+ - If the question is **relevant but not present in the context**, respond: "please connect with Dr.Sandeep or Reception for this details."
294
+
295
+ 4. **Irrelevant queries**:
296
+ - If the question is completely unrelated to knee replacement surgery, politely decline and respond: "I am here to assist only with knee replacement surgery related queries."
297
+
298
+ 5. **Drive conversation**:
299
+ - After answering the user’s question, suggest a follow-up question from the context that you can answer.
300
+ - Make the follow-up natural and conversational. The follow up question must be relevant to the current question or response
301
+ - If the user responds with confirmation like “yes”, “okay” give the answer for the previous follow-up question from the context.
302
+
303
+ 6. **Readable voice output for gTTS**:
304
+ - Break sentences at natural punctuation: `, . ? ! : ;`.
305
+ - Do not use `#`, `**`, or other markdown symbols.
306
+ - Numbers and points must be spelled out: e.g., `2.5 lakh` → `two point five lakh`. Similarly Dr, Mr, Mrs, etc. must be written as Doctor, Mister, Misses etc.
307
+ 7. **Concise and human-like**:
308
+ - Keep answers short, conversational, and natural.
309
+ - Maximum 40 words / ~20 seconds of speech.
310
+
311
+ 8. **Tone and style**:
312
+ - Helpful, friendly, approachable, and human-like.
313
+ - Maintain professionalism while being conversational.
314
+
315
+ 9. **About Dr.Sandeep**:
316
+ - He has over 15 years of experience in orthopedic and joint replacement surgery.
317
+ - He specializes in total and partial knee replacement procedures.
318
+ - Known for a patient-friendly approach, focusing on pre-surgery preparation, post-surgery rehabilitation, and pain management.
319
+ - Actively keeps up-to-date with the latest techniques and technologies in knee replacement surgery.
320
+ - Highly approachable and prefers that patients are well-informed about their treatment options and recovery process.
321
+
322
+ Always provide readable, streaming-friendly sentences so gTTS can read smoothly. Drive conversation forward while staying strictly on knee replacement surgery topics, and suggest follow-up questions for which you have context-based answers.
323
+ '''
324
+ }]
325
+ return {"message": "Chat history reset successfully."}
326
+
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ uvicorn
2
+ openai==0.28
3
+ python-dotenv
4
+ fastapi
5
+ uvicorn[standard]
6
+ python-multipart
7
+ gTTS
8
+ pydub
9
+ aiofiles
10
+ sentence-transformers
11
+ pandas
12
+ sentence-transformers
13
+ openpyxl
14
+ deepgram-sdk==2.12.0
15
+
robot.gif ADDED

Git LFS Details

  • SHA256: 0b4f7a07ff399ab418854bbfd68329c06699abacced0ff6b5df451e01a375bab
  • Pointer size: 132 Bytes
  • Size of remote file: 1.02 MB