rohitsar567 commited on
Commit
e02c535
·
verified ·
1 Parent(s): 6f9473f

Deploy v1 — single-Docker FastAPI + Next.js + RAG + voice + faithfulness

Browse files
Files changed (2) hide show
  1. backend/orchestrator.py +37 -18
  2. backend/translator.py +92 -0
backend/orchestrator.py CHANGED
@@ -78,23 +78,15 @@ class BrainPick:
78
 
79
 
80
  def pick_brain(intent: str, language: str) -> BrainPick:
81
- """Route to the right brain per Doc decisions.md D-016 (revised 2026-05-13).
82
-
83
- Rebalanced from eval signal:
84
- - Indic queries -> Sarvam-M (Indic + cultural context + BFSI vocab — its
85
- genuine strength; the Sarvam-first narrative belongs here)
86
- - Comparison/recommendation -> DeepSeek-V3 (SOTA open-source reasoning)
87
- - Simple English QA -> DeepSeek-V3 (eval showed Sarvam-M at 37.5%
88
- factual on this slice vs Llama at 100%; DeepSeek is stronger still
89
- with citation-discipline as bonus)
90
- - Llama-3.3-70B reserved as grader AND cross-check rescue brain
91
  """
92
- if language == "indic":
93
- return BrainPick(SarvamLLM(), "indic-query")
94
- if intent in ("comparison", "recommendation"):
95
- return BrainPick(OpenRouterLLM(), f"complex-{intent}")
96
- # English simple QA → DeepSeek-V3 (was Sarvam-M; rebalanced from eval data)
97
- return BrainPick(OpenRouterLLM(), "simple-qa")
98
 
99
 
100
  # ---------- main entrypoint ----------
@@ -127,7 +119,20 @@ async def handle_turn(
127
  intent = classify_intent(user_text)
128
  language = detect_language(user_text)
129
 
130
- # 1a. Fact-find branchconversational openers / advice-seeking queries
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  # bypass retrieval + faithfulness; we ask the next discovery question.
132
  if intent == "fact_find":
133
  from backend.needs_finder import Profile, next_question
@@ -257,11 +262,25 @@ async def handle_turn(
257
  for c in chunks
258
  ]
259
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  return TurnResult(
261
  reply_text=reply,
262
  citations=citations,
263
  retrieved_chunk_ids=[c.chunk_id for c in chunks],
264
- brain_used=f"{pick.provider.name}::{pick.reason}",
265
  intent=intent,
266
  language=language,
267
  latency_ms=int((time.time() - t0) * 1000),
 
78
 
79
 
80
  def pick_brain(intent: str, language: str) -> BrainPick:
81
+ """Route to the right brain per Doc decisions.md D-016 (rev 2026-05-13).
82
+
83
+ English: DeepSeek-V3 always (reasoning quality > Sarvam-M for English Q&A).
84
+ Indic: handled in handle_turn via translation cascade (Sarvam translates
85
+ in, DeepSeek reasons, Sarvam translates back). This function returns the
86
+ REASONING brain in both cases; the Indic in/out translation is done
87
+ separately by translator.py.
 
 
 
88
  """
89
+ return BrainPick(OpenRouterLLM(), f"reasoning-{intent}")
 
 
 
 
 
90
 
91
 
92
  # ---------- main entrypoint ----------
 
119
  intent = classify_intent(user_text)
120
  language = detect_language(user_text)
121
 
122
+ # 1a. INDIC CASCADEtranslate Indic query English, reason in DeepSeek,
123
+ # translate answer back. Capture original-language user_text for logging.
124
+ original_user_text = user_text
125
+ translated_query = None
126
+ if language == "indic":
127
+ try:
128
+ from backend.translator import translate_to_english
129
+ translated_query = await translate_to_english(user_text)
130
+ if translated_query and translated_query.strip() and translated_query != user_text:
131
+ user_text = translated_query # use English for retrieval + reasoning
132
+ except Exception:
133
+ pass # fall through with original; if Sarvam translator fails, DeepSeek can still try
134
+
135
+ # 1b. Fact-find branch — conversational openers / advice-seeking queries
136
  # bypass retrieval + faithfulness; we ask the next discovery question.
137
  if intent == "fact_find":
138
  from backend.needs_finder import Profile, next_question
 
262
  for c in chunks
263
  ]
264
 
265
+ # 7. INDIC CASCADE — translate the English reply back into Hinglish/Hindi
266
+ # so the user hears it in their language. Citations stay intact (the
267
+ # translation prompt preserves them).
268
+ final_brain_tag = f"{pick.provider.name}::{pick.reason}"
269
+ if language == "indic" and not blocked and reply:
270
+ try:
271
+ from backend.translator import translate_to_indic
272
+ reply_indic = await translate_to_indic(reply, target_lang="hi-IN")
273
+ if reply_indic and reply_indic.strip():
274
+ reply = reply_indic
275
+ final_brain_tag = f"cascade::sarvam-trans+{pick.provider.name}+sarvam-trans"
276
+ except Exception:
277
+ pass # if translation fails, return English; better than nothing
278
+
279
  return TurnResult(
280
  reply_text=reply,
281
  citations=citations,
282
  retrieved_chunk_ids=[c.chunk_id for c in chunks],
283
+ brain_used=final_brain_tag,
284
  intent=intent,
285
  language=language,
286
  latency_ms=int((time.time() - t0) * 1000),
backend/translator.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Translation helpers — Sarvam-M as the Indic specialist for query/answer
2
+ translation in the cascade routing pattern.
3
+
4
+ The cascade pattern for Hindi/Hinglish queries:
5
+ 1. Sarvam-M translates the user's Hinglish query → clean English
6
+ 2. DeepSeek-V3 reasons over the retrieved policy chunks → English answer
7
+ (with full citation grammar preserved)
8
+ 3. Sarvam-M translates the English answer → natural Hinglish
9
+
10
+ Why this is better than either model alone:
11
+ - Sarvam-M has the best Indic comprehension + cultural context but mid-tier
12
+ English reasoning. Don't rely on it for the reasoning step.
13
+ - DeepSeek-V3 has SOTA open-source reasoning but is English-trained.
14
+ Don't rely on it for the Indic understanding/generation step.
15
+ - The cascade gets the best of both — at a +3s latency cost.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from backend.providers.base import ChatMessage
21
+ from backend.providers.sarvam_llm import SarvamLLM
22
+
23
+
24
+ _TRANSLATE_TO_EN_SYSTEM = """You are a precise translator from Hindi / Hinglish / code-switched Indian English to clean standard English.
25
+
26
+ RULES:
27
+ 1. Output ONLY the translated English sentence. No preamble, no quotes, no explanation.
28
+ 2. Preserve insurance/finance/medical terms exactly as in the source.
29
+ 3. Names of insurers (Star Health, HDFC ERGO, Niva Bupa, Care, ICICI Lombard, Bajaj Allianz, Aditya Birla, etc.) and policy product names must remain unchanged.
30
+ 4. Numbers, currencies (₹, lakh, crore), durations (din/months/years) stay numeric — convert "do saal" → "2 years"; "tees din" → "30 days".
31
+ 5. If the source is already English, return it as-is unchanged."""
32
+
33
+
34
+ _TRANSLATE_TO_INDIC_SYSTEM = """You are a precise translator from English to natural conversational Hindi / Hinglish (code-switched Indian English) — the way a buyer in urban India actually speaks.
35
+
36
+ RULES:
37
+ 1. Output ONLY the translated text. No preamble, no quotes.
38
+ 2. Use Devanagari for Hindi words, English for English words that are commonly kept in English in spoken Hindi (insurance, policy, coverage, premium, hospital, claim, network, copay, etc.).
39
+ 3. Names of insurers and policies stay unchanged.
40
+ 4. Numbers stay numeric.
41
+ 5. Keep the tone conversational + warm, like an experienced advisor speaking to a friend.
42
+ 6. Maximum 60 words; if the source is longer, condense to the key facts. Do not invent details.
43
+
44
+ Example:
45
+ English: "Yes, HDFC ERGO Optima Secure covers Ayurveda treatment at recognized AYUSH hospitals. The waiting period is 30 days from policy start."
46
+ Hinglish: "Haan, HDFC ERGO Optima Secure mein Ayurveda treatment cover hai — but sirf recognized AYUSH hospitals mein. Policy shuru hone se 30 din ka waiting period rahega."
47
+ """
48
+
49
+
50
+ async def translate_to_english(text: str, sarvam: SarvamLLM | None = None) -> str:
51
+ """Translate a Hinglish/Hindi query into clean English for the reasoning brain."""
52
+ if not text.strip():
53
+ return text
54
+ sarvam = sarvam or SarvamLLM()
55
+ res = await sarvam.chat(
56
+ messages=[
57
+ ChatMessage(role="system", content=_TRANSLATE_TO_EN_SYSTEM),
58
+ ChatMessage(role="user", content=text),
59
+ ],
60
+ temperature=0.0,
61
+ max_tokens=400,
62
+ )
63
+ out = res.text.strip()
64
+ # Strip <think> tags if Sarvam-M went into reasoning mode
65
+ from backend.persona import strip_think_tags
66
+ return strip_think_tags(out) or text
67
+
68
+
69
+ async def translate_to_indic(
70
+ english: str,
71
+ target_lang: str = "hi-IN",
72
+ sarvam: SarvamLLM | None = None,
73
+ ) -> str:
74
+ """Translate an English answer back into natural Hinglish for the user.
75
+
76
+ Preserves any [Source: ...] citation tags so the faithfulness gate can
77
+ still verify the citation chain after translation.
78
+ """
79
+ if not english.strip():
80
+ return english
81
+ sarvam = sarvam or SarvamLLM()
82
+ res = await sarvam.chat(
83
+ messages=[
84
+ ChatMessage(role="system", content=_TRANSLATE_TO_INDIC_SYSTEM),
85
+ ChatMessage(role="user", content=english),
86
+ ],
87
+ temperature=0.2,
88
+ max_tokens=600,
89
+ )
90
+ out = res.text.strip()
91
+ from backend.persona import strip_think_tags
92
+ return strip_think_tags(out) or english