Sahil commited on
Commit
75efae8
Β·
1 Parent(s): 49e98e5

Multilingual support added (Marathi , Hindi , English)

Browse files
app/__pycache__/__init__.cpython-313.pyc CHANGED
Binary files a/app/__pycache__/__init__.cpython-313.pyc and b/app/__pycache__/__init__.cpython-313.pyc differ
 
app/__pycache__/config.cpython-313.pyc CHANGED
Binary files a/app/__pycache__/config.cpython-313.pyc and b/app/__pycache__/config.cpython-313.pyc differ
 
app/routers/assistant.py CHANGED
@@ -17,6 +17,7 @@ class MessageIn(BaseModel):
17
  class ChatRequest(BaseModel):
18
  conversation_id: Optional[str] = None
19
  message: str
 
20
 
21
 
22
  class ConversationCreate(BaseModel):
@@ -137,6 +138,7 @@ async def chat(
137
  user_message=body.message,
138
  history=history,
139
  profile_context=profile_context,
 
140
  )
141
  ai_reply = ai_result["reply"]
142
  ai_sources = ai_result.get("sources", [])
 
17
  class ChatRequest(BaseModel):
18
  conversation_id: Optional[str] = None
19
  message: str
20
+ preferred_language: str = "English"
21
 
22
 
23
  class ConversationCreate(BaseModel):
 
138
  user_message=body.message,
139
  history=history,
140
  profile_context=profile_context,
141
+ preferred_lang=body.preferred_language,
142
  )
143
  ai_reply = ai_result["reply"]
144
  ai_sources = ai_result.get("sources", [])
app/routers/buddy.py CHANGED
@@ -15,6 +15,10 @@ router = APIRouter(prefix="/buddy", tags=["emotional-buddy"])
15
  class BuddyTextRequest(BaseModel):
16
  text: str
17
  history: Optional[List[dict]] = None
 
 
 
 
18
 
19
 
20
  @router.post("/chat")
@@ -30,14 +34,15 @@ async def text_chat(
30
  raise HTTPException(status_code=422, detail="Text must not be empty")
31
 
32
  history = body.history or []
 
33
 
34
  # AI response
35
- ai_text, mood_score, emotion = await emotional_buddy_respond(body.text, history)
36
 
37
  # TTS β€” non-critical: failure returns empty audio, client can still show text
38
  audio_response = b""
39
  try:
40
- audio_response = await text_to_speech_bytes(ai_text)
41
  except Exception as tts_err:
42
  import logging
43
  logging.getLogger(__name__).warning("TTS failed: %s", tts_err)
 
15
  class BuddyTextRequest(BaseModel):
16
  text: str
17
  history: Optional[List[dict]] = None
18
+ preferred_language: str = "English"
19
+
20
+
21
+ _LANG_CODE: dict[str, str] = {"English": "en", "Hindi": "hi", "Marathi": "mr"}
22
 
23
 
24
  @router.post("/chat")
 
34
  raise HTTPException(status_code=422, detail="Text must not be empty")
35
 
36
  history = body.history or []
37
+ lang_code = _LANG_CODE.get(body.preferred_language, "en")
38
 
39
  # AI response
40
+ ai_text, mood_score, emotion = await emotional_buddy_respond(body.text, history, body.preferred_language)
41
 
42
  # TTS β€” non-critical: failure returns empty audio, client can still show text
43
  audio_response = b""
44
  try:
45
+ audio_response = await text_to_speech_bytes(ai_text, lang_code)
46
  except Exception as tts_err:
47
  import logging
48
  logging.getLogger(__name__).warning("TTS failed: %s", tts_err)
app/services/__pycache__/__init__.cpython-313.pyc CHANGED
Binary files a/app/services/__pycache__/__init__.cpython-313.pyc and b/app/services/__pycache__/__init__.cpython-313.pyc differ
 
app/services/__pycache__/ai.cpython-313.pyc CHANGED
Binary files a/app/services/__pycache__/ai.cpython-313.pyc and b/app/services/__pycache__/ai.cpython-313.pyc differ
 
app/services/__pycache__/rag_pipeline.cpython-313.pyc CHANGED
Binary files a/app/services/__pycache__/rag_pipeline.cpython-313.pyc and b/app/services/__pycache__/rag_pipeline.cpython-313.pyc differ
 
app/services/__pycache__/tts.cpython-313.pyc CHANGED
Binary files a/app/services/__pycache__/tts.cpython-313.pyc and b/app/services/__pycache__/tts.cpython-313.pyc differ
 
app/services/ai.py CHANGED
@@ -79,7 +79,10 @@ entertainment, general trivia, etc. β€” respond ONLY with:
79
  β€’ Do NOT use markdown headers (##), bold (**), or bullet-heavy formatting β€” write in clean prose
80
  β€’ End complex answers with a gentle reminder to consult a qualified healthcare provider
81
 
82
- User medical profile context will be provided when available β€” use it to personalise responses."""
 
 
 
83
 
84
  _RAG_MEDICAL_SYSTEM = """\
85
  You are Aarogyan's Medical Health Assistant β€” a supportive, evidence-based AI health companion.
@@ -108,6 +111,9 @@ Use ONLY the provided context to answer. If the context is insufficient, say so
108
  β€’ Write in clean prose β€” no markdown headers, no bold, no repeated ideas across paragraphs
109
  β€’ End with a brief recommendation to consult a healthcare provider if the topic warrants it
110
 
 
 
 
111
  --- Retrieved Medical Context ---
112
  {context}
113
  --- End of Context ---
@@ -174,7 +180,10 @@ IMPORTANT: Always respond in JSON format:
174
  "response": "your empathetic, warm reply here",
175
  "mood_score": <integer 1-10>,
176
  "emotion": "<one of: happy, sad, angry, fearful, disgusted, surprised, neutral>"
177
- }"""
 
 
 
178
 
179
 
180
  async def _call_groq(messages: list[dict], system: str, temperature: float = 0.7) -> str:
@@ -222,6 +231,7 @@ async def _chat_with_rag(
222
  history: list[dict],
223
  profile_context: str,
224
  is_complex: bool = False,
 
225
  ) -> dict:
226
  """RAG-augmented chat: retrieve context then synthesise with Groq.
227
 
@@ -240,7 +250,7 @@ async def _chat_with_rag(
240
 
241
  if not context_str:
242
  logger.warning("RAG returned no context β€” falling back to plain LLM")
243
- return await _chat_plain(user_message, history, profile_context)
244
 
245
  profile_section = ""
246
  if profile_context:
@@ -250,6 +260,7 @@ async def _chat_with_rag(
250
  context=context_str,
251
  profile_section=profile_section,
252
  )
 
253
 
254
  messages = [*history, {"role": "user", "content": user_message}]
255
  reply = await _call_groq(messages, system, temperature=0.2)
@@ -261,6 +272,7 @@ async def _chat_plain(
261
  user_message: str,
262
  history: list[dict],
263
  profile_context: str,
 
264
  ) -> dict:
265
  """Plain LLM chat without RAG (fallback when Qdrant returns nothing).
266
  Returns {"reply": str, "sources": []}
@@ -268,6 +280,7 @@ async def _chat_plain(
268
  system = MEDICAL_ASSISTANT_SYSTEM
269
  if profile_context:
270
  system += f"\n\n--- User Health Profile ---\n{profile_context}"
 
271
  messages = [*history, {"role": "user", "content": user_message}]
272
  reply = await _call_groq(messages, system)
273
  return {"reply": reply.strip(), "sources": []}
@@ -277,10 +290,11 @@ async def chat_with_ai(
277
  user_message: str,
278
  history: list[dict],
279
  profile_context: str,
 
280
  ) -> dict:
281
  """Returns {"reply": str, "sources": list[str]}."""
282
  is_complex = await _route_query(user_message)
283
- return await _chat_with_rag(user_message, history, profile_context, is_complex=is_complex)
284
 
285
 
286
  async def summarise_document(ocr_text: str) -> dict:
@@ -308,11 +322,11 @@ async def summarise_document(ocr_text: str) -> dict:
308
  import re as _re
309
 
310
 
311
- async def emotional_buddy_respond(user_text: str, history: list[dict] | None = None) -> tuple[str, int, str]:
312
  """Returns (buddy_reply_text, mood_score, emotion)."""
313
- messages = list(history or [])
314
  messages.append({"role": "user", "content": user_text})
315
- raw = await _call_groq(messages, EMOTIONAL_BUDDY_SYSTEM)
316
 
317
  reply = raw
318
  mood_score = 5
 
79
  β€’ Do NOT use markdown headers (##), bold (**), or bullet-heavy formatting β€” write in clean prose
80
  β€’ End complex answers with a gentle reminder to consult a qualified healthcare provider
81
 
82
+ User medical profile context will be provided when available β€” use it to personalise responses.
83
+
84
+ ━━━ LANGUAGE ━━━
85
+ Detect the language of the user's message (English, Hindi, or Marathi) and respond in that exact same language. Use the user's preferred language as the fallback when the language is ambiguous."""
86
 
87
  _RAG_MEDICAL_SYSTEM = """\
88
  You are Aarogyan's Medical Health Assistant β€” a supportive, evidence-based AI health companion.
 
111
  β€’ Write in clean prose β€” no markdown headers, no bold, no repeated ideas across paragraphs
112
  β€’ End with a brief recommendation to consult a healthcare provider if the topic warrants it
113
 
114
+ ━━━ LANGUAGE ━━━
115
+ Detect the language of the user's message (English, Hindi, or Marathi) and respond in that exact same language. Use the user's preferred language as the fallback when the language is ambiguous.
116
+
117
  --- Retrieved Medical Context ---
118
  {context}
119
  --- End of Context ---
 
180
  "response": "your empathetic, warm reply here",
181
  "mood_score": <integer 1-10>,
182
  "emotion": "<one of: happy, sad, angry, fearful, disgusted, surprised, neutral>"
183
+ }
184
+
185
+ ━━━ LANGUAGE ━━━
186
+ Detect the language of the user's message (English, Hindi, or Marathi) and write the "response" value in that exact same language. If the language is ambiguous, use the user's preferred language as the fallback. Always keep the JSON keys in English."""
187
 
188
 
189
  async def _call_groq(messages: list[dict], system: str, temperature: float = 0.7) -> str:
 
231
  history: list[dict],
232
  profile_context: str,
233
  is_complex: bool = False,
234
+ preferred_lang: str = "English",
235
  ) -> dict:
236
  """RAG-augmented chat: retrieve context then synthesise with Groq.
237
 
 
250
 
251
  if not context_str:
252
  logger.warning("RAG returned no context β€” falling back to plain LLM")
253
+ return await _chat_plain(user_message, history, profile_context, preferred_lang=preferred_lang)
254
 
255
  profile_section = ""
256
  if profile_context:
 
260
  context=context_str,
261
  profile_section=profile_section,
262
  )
263
+ system += f"\n\nThe user's preferred language is {preferred_lang}."
264
 
265
  messages = [*history, {"role": "user", "content": user_message}]
266
  reply = await _call_groq(messages, system, temperature=0.2)
 
272
  user_message: str,
273
  history: list[dict],
274
  profile_context: str,
275
+ preferred_lang: str = "English",
276
  ) -> dict:
277
  """Plain LLM chat without RAG (fallback when Qdrant returns nothing).
278
  Returns {"reply": str, "sources": []}
 
280
  system = MEDICAL_ASSISTANT_SYSTEM
281
  if profile_context:
282
  system += f"\n\n--- User Health Profile ---\n{profile_context}"
283
+ system += f"\n\nThe user's preferred language is {preferred_lang}."
284
  messages = [*history, {"role": "user", "content": user_message}]
285
  reply = await _call_groq(messages, system)
286
  return {"reply": reply.strip(), "sources": []}
 
290
  user_message: str,
291
  history: list[dict],
292
  profile_context: str,
293
+ preferred_lang: str = "English",
294
  ) -> dict:
295
  """Returns {"reply": str, "sources": list[str]}."""
296
  is_complex = await _route_query(user_message)
297
+ return await _chat_with_rag(user_message, history, profile_context, is_complex=is_complex, preferred_lang=preferred_lang)
298
 
299
 
300
  async def summarise_document(ocr_text: str) -> dict:
 
322
  import re as _re
323
 
324
 
325
+ async def emotional_buddy_respond(user_text: str, history: list[dict] | None = None, preferred_lang: str = "English") -> tuple[str, int, str]:
326
  """Returns (buddy_reply_text, mood_score, emotion)."""
327
+ system = EMOTIONAL_BUDDY_SYSTEM + f"\n\nThe user's preferred language is {preferred_lang}."
328
  messages.append({"role": "user", "content": user_text})
329
+ raw = await _call_groq(messages, system, temperature=0.75)
330
 
331
  reply = raw
332
  mood_score = 5
app/services/tts.py CHANGED
@@ -30,12 +30,12 @@ def _split_text(text: str, limit: int = _MAX_CHARS) -> list[str]:
30
  return chunks or [text[:limit]]
31
 
32
 
33
- async def _fetch_chunk(client: httpx.AsyncClient, chunk: str) -> bytes:
34
  url = "https://translate.google.com/translate_tts"
35
  params = {
36
  "ie": "UTF-8",
37
  "q": chunk,
38
- "tl": "en",
39
  "client": "tw-ob",
40
  "total": "1",
41
  "idx": "0",
@@ -47,7 +47,7 @@ async def _fetch_chunk(client: httpx.AsyncClient, chunk: str) -> bytes:
47
  return resp.content
48
 
49
 
50
- async def text_to_speech_bytes(text: str) -> bytes:
51
  """Convert text to speech using Google Translate TTS. Returns MP3 bytes.
52
  Splits long text into chunks and concatenates the audio.
53
  Raises on failure after 20 seconds total.
@@ -55,7 +55,7 @@ async def text_to_speech_bytes(text: str) -> bytes:
55
  chunks = _split_text(text)
56
  async with httpx.AsyncClient(timeout=15) as client:
57
  parts = await asyncio.wait_for(
58
- asyncio.gather(*[_fetch_chunk(client, c) for c in chunks]),
59
  timeout=20,
60
  )
61
  return b"".join(parts)
 
30
  return chunks or [text[:limit]]
31
 
32
 
33
+ async def _fetch_chunk(client: httpx.AsyncClient, chunk: str, lang: str = "en") -> bytes:
34
  url = "https://translate.google.com/translate_tts"
35
  params = {
36
  "ie": "UTF-8",
37
  "q": chunk,
38
+ "tl": lang,
39
  "client": "tw-ob",
40
  "total": "1",
41
  "idx": "0",
 
47
  return resp.content
48
 
49
 
50
+ async def text_to_speech_bytes(text: str, lang: str = "en") -> bytes:
51
  """Convert text to speech using Google Translate TTS. Returns MP3 bytes.
52
  Splits long text into chunks and concatenates the audio.
53
  Raises on failure after 20 seconds total.
 
55
  chunks = _split_text(text)
56
  async with httpx.AsyncClient(timeout=15) as client:
57
  parts = await asyncio.wait_for(
58
+ asyncio.gather(*[_fetch_chunk(client, c, lang) for c in chunks]),
59
  timeout=20,
60
  )
61
  return b"".join(parts)