voice-rag commited on
Commit
f62bf0e
·
1 Parent(s): 95a6c29

Restored Urdu grammar configuration from 90d92cd

Browse files
python-services/caller_info_extractor.py CHANGED
@@ -43,11 +43,8 @@ def extract_phone_from_text(text: str) -> Optional[str]:
43
  if match:
44
  # Strip structural characters to normalize string digits
45
  phone = re.sub(r'[\s\-\(\)\+]', '', match.group())
46
- if 7 <= len(phone) <= 15:
47
- logger.info(f"Regex pipeline intercepted telephone credentials: {phone}")
48
- return phone
49
- else:
50
- logger.warning(f"Extracted phone number '{phone}' has invalid length {len(phone)}")
51
  return None
52
 
53
 
@@ -57,42 +54,20 @@ def extract_name_from_text(text: str, language: str = "en") -> Optional[str]:
57
  Branches into script-specific regex blocks depending on runtime language tracking.
58
  """
59
  lang = (language or "en").lower().strip()
60
-
61
- # Check if the text contains Latin characters to route English/Roman Urdu patterns
62
- has_latin = bool(re.search(r'[a-zA-Z]', text))
63
-
64
- target_patterns = []
65
- if has_latin:
66
- target_patterns.extend(NAME_PATTERNS_EN)
67
- # Always allow Roman Urdu name patterns if Latin script is used
68
- NAME_PATTERNS_ROMAN_UR = [
69
- r"(?:mera\s+)?(?:naam|name)\s+(?:hai\s+|he\s+|is\s+)?([A-Za-z][a-z]+(?:\s+[A-Za-z][a-z]+)?)",
70
- r"([A-Za-z][a-z]+(?:\s+[A-Za-z][a-z]+)?)\s+(?:ye\s+)?(?:mera\s+)?(?:naam|name|number)\b",
71
- r"(?:i am|i'm|this is|call me)\s+([A-Za-z][a-z]+(?:\s+[A-Za-z][a-z]+)?)"
72
- ]
73
- target_patterns.extend(NAME_PATTERNS_ROMAN_UR)
74
-
75
- # Support native script patterns
76
- if lang == "ur" or not has_latin:
77
- target_patterns.extend(NAME_PATTERNS_UR)
78
- if lang == "ar":
79
- target_patterns.extend(NAME_PATTERNS_AR)
80
-
81
- # Use a list to check unique patterns in order
82
- seen = set()
83
- unique_patterns = []
84
- for p in target_patterns:
85
- if p not in seen:
86
- seen.add(p)
87
- unique_patterns.append(p)
88
-
89
- for pattern in unique_patterns:
90
  match = re.search(pattern, text, re.IGNORECASE)
91
  if match:
92
  name = match.group(1).strip()
93
  # Basic character validation step — filtering out short artifacts or junk blocks
94
  if 2 <= len(name) <= 40:
95
- logger.info(f"Regex pipeline intercepted identity credentials: {name}")
96
  return name
97
  return None
98
 
@@ -140,11 +115,9 @@ class CallerInfoCollector:
140
 
141
  if not self.phone and extracted.get("phone"):
142
  clean_phone = re.sub(r'[\s\-\(\)\+]', '', str(extracted["phone"]))
143
- if clean_phone and 7 <= len(clean_phone) <= 15:
144
  self.phone = clean_phone
145
  logger.info(f"LLM data backfill applied telephone configuration: {self.phone}")
146
- else:
147
- logger.warning(f"LLM extracted phone number '{clean_phone}' has invalid length")
148
 
149
  if extracted.get("issue"):
150
  self.issue = str(extracted["issue"]).strip()
 
43
  if match:
44
  # Strip structural characters to normalize string digits
45
  phone = re.sub(r'[\s\-\(\)\+]', '', match.group())
46
+ logger.info(f"Regex pipeline intercepted telephone credentials: {phone}")
47
+ return phone
 
 
 
48
  return None
49
 
50
 
 
54
  Branches into script-specific regex blocks depending on runtime language tracking.
55
  """
56
  lang = (language or "en").lower().strip()
57
+ target_patterns = NAME_PATTERNS_EN
58
+
59
+ if lang == "ur":
60
+ target_patterns = NAME_PATTERNS_UR
61
+ elif lang == "ar":
62
+ target_patterns = NAME_PATTERNS_AR
63
+
64
+ for pattern in target_patterns:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  match = re.search(pattern, text, re.IGNORECASE)
66
  if match:
67
  name = match.group(1).strip()
68
  # Basic character validation step — filtering out short artifacts or junk blocks
69
  if 2 <= len(name) <= 40:
70
+ logger.info(f"Regex pipeline intercepted identity credentials: {name} [Lang: {lang}]")
71
  return name
72
  return None
73
 
 
115
 
116
  if not self.phone and extracted.get("phone"):
117
  clean_phone = re.sub(r'[\s\-\(\)\+]', '', str(extracted["phone"]))
118
+ if clean_phone:
119
  self.phone = clean_phone
120
  logger.info(f"LLM data backfill applied telephone configuration: {self.phone}")
 
 
121
 
122
  if extracted.get("issue"):
123
  self.issue = str(extracted["issue"]).strip()
python-services/client.py CHANGED
@@ -3,14 +3,10 @@
3
 
4
  import os
5
  import logging
6
- from dotenv import load_dotenv
7
  from openai import OpenAI
8
 
9
  logger = logging.getLogger("client")
10
 
11
- # Load environment configuration
12
- load_dotenv()
13
-
14
  GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
15
  FIREWORKS_API_KEY = os.getenv("FIREWORKS_API_KEY", "")
16
 
 
3
 
4
  import os
5
  import logging
 
6
  from openai import OpenAI
7
 
8
  logger = logging.getLogger("client")
9
 
 
 
 
10
  GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
11
  FIREWORKS_API_KEY = os.getenv("FIREWORKS_API_KEY", "")
12
 
python-services/conversation_manager.py CHANGED
@@ -122,7 +122,7 @@ class ConversationSession:
122
  )
123
 
124
  # Bound the network execution cycle to match tight audio latency constraints
125
- res = await asyncio.wait_for(classify_task, timeout=0.8)
126
  out = (res.choices[0].message.content or "").strip().lower()
127
 
128
  if "quick" in out:
 
122
  )
123
 
124
  # Bound the network execution cycle to match tight audio latency constraints
125
+ res = await asyncio.wait_for(classify_task, timeout=0.25)
126
  out = (res.choices[0].message.content or "").strip().lower()
127
 
128
  if "quick" in out:
python-services/language_detector.py CHANGED
@@ -77,7 +77,7 @@ def detect_language_from_text(text: str) -> tuple[str, float]:
77
  return "ur", 0.95
78
 
79
  # If a distinct non-English unicode layout matches, commit with high confidence
80
- if script_guess in ('zh', 'bn'):
81
  mapped_lang = LANG_CODE_MAP.get(script_guess, script_guess)
82
  return mapped_lang, 0.95
83
 
@@ -112,8 +112,6 @@ def detect_language_from_text(text: str) -> tuple[str, float]:
112
  if top.lang == "hi":
113
  return "ur", top.prob
114
  lang = LANG_CODE_MAP.get(top.lang, top.lang)
115
- if lang not in ("ur", "ar", "en", "zh", "fa", "tr", "bn"):
116
- lang = "en"
117
  return lang, top.prob
118
  except Exception:
119
  return DEFAULT_LANGUAGE, 0.50
@@ -194,6 +192,4 @@ def detect_language(text: str) -> str:
194
  raw_detected = detect_language_from_content(text)
195
  if raw_detected in ('hi', 'pa'):
196
  return "ur"
197
- if raw_detected not in ["ur", "ar", "en", "zh", "fa", "tr", "bn"]:
198
- return "en"
199
  return raw_detected
 
77
  return "ur", 0.95
78
 
79
  # If a distinct non-English unicode layout matches, commit with high confidence
80
+ if script_guess in ('zh', 'bn', 'gu', 'ta', 'te', 'kn', 'ml', 'he', 'ko', 'ja'):
81
  mapped_lang = LANG_CODE_MAP.get(script_guess, script_guess)
82
  return mapped_lang, 0.95
83
 
 
112
  if top.lang == "hi":
113
  return "ur", top.prob
114
  lang = LANG_CODE_MAP.get(top.lang, top.lang)
 
 
115
  return lang, top.prob
116
  except Exception:
117
  return DEFAULT_LANGUAGE, 0.50
 
192
  raw_detected = detect_language_from_content(text)
193
  if raw_detected in ('hi', 'pa'):
194
  return "ur"
 
 
195
  return raw_detected
python-services/llm_server.py CHANGED
@@ -7,7 +7,7 @@ import time
7
  import logging
8
  import asyncio
9
  from typing import List, Optional, Dict, Any
10
- from fastapi import FastAPI, HTTPException, BackgroundTasks
11
  from fastapi.middleware.cors import CORSMiddleware
12
  from fastapi.responses import JSONResponse
13
  from pydantic import BaseModel
@@ -83,50 +83,6 @@ class EndSessionRequest(BaseModel):
83
  session_id: str
84
 
85
 
86
- async def run_background_extraction(session_id: str, messages: List[Dict[str, str]]):
87
- """
88
- Asynchronously calls Groq 8B model to extract name, phone, and issue
89
- from the session history, then updates the session's caller_info.
90
- """
91
- session = manager.get_session(session_id)
92
- if not session:
93
- return
94
-
95
- history_text = ""
96
- for msg in messages:
97
- history_text += f"{msg['role']}: {msg['content']}\n"
98
-
99
- prompt = f"""You are a data extraction assistant. Analyze the conversation history below and extract the caller's name, phone number, and the core issue they are calling about.
100
-
101
- Conversation history:
102
- {history_text}
103
-
104
- Provide your response strictly in the following JSON format:
105
- {{
106
- "name": "extracted name or null",
107
- "phone": "extracted phone number or null",
108
- "issue": "concise description of the user's issue, query, or reason for calling or null"
109
- }}
110
- Reply ONLY with the raw JSON object. Do not include markdown formatting, code block backticks (like ```json), or any other conversational text."""
111
-
112
- try:
113
- loop = asyncio.get_running_loop()
114
- response = await loop.run_in_executor(
115
- None,
116
- lambda: groq.chat.completions.create(
117
- model=MODEL_FAST,
118
- messages=[{"role": "user", "content": prompt}],
119
- temperature=0.0,
120
- response_format={"type": "json_object"}
121
- )
122
- )
123
- import json
124
- extracted_data = json.loads(response.choices[0].message.content.strip())
125
- session.caller_info.update_from_llm_extraction(extracted_data)
126
- except Exception as e:
127
- logger.error(f"Error during background LLM info extraction: {e}")
128
-
129
-
130
  # ── REST API Router Endpoints ────────────────────────────────────────────────
131
 
132
  @app.post("/session/start")
@@ -155,7 +111,7 @@ async def start_session(req: StartSessionRequest):
155
 
156
 
157
  @app.post("/chat")
158
- async def chat(req: ChatRequest, background_tasks: BackgroundTasks):
159
  """
160
  Processes voice transcript inputs, executes dynamic RAG context assembly,
161
  evaluates historical payload token depth, and executes low-latency model inference.
@@ -187,15 +143,13 @@ async def chat(req: ChatRequest, background_tasks: BackgroundTasks):
187
 
188
  # 3. Rolling window processing and continuous background text summary compilation
189
  new_summary = req.currentSummary or ""
190
- if len(session.messages) > 16:
191
- messages_to_keep = session.messages[-8:]
192
- messages_to_summarize = session.messages[:-8]
193
-
194
- # Slice messages BEFORE the await to prevent race condition deletes
195
- session.messages = messages_to_keep
196
 
197
  # Non-blocking context thread synthesis execution
198
  new_summary = await generate_summary(new_summary, messages_to_summarize)
 
199
 
200
  # 4. Synthesize structural text templates with active script sniffing indicators
201
  system_prompt = build_system_prompt(
@@ -257,7 +211,7 @@ async def chat(req: ChatRequest, background_tasks: BackgroundTasks):
257
 
258
  # 7. Model Router Intent Classification Integration
259
  intent = await session.classify_intent(req.message, detected_language)
260
- primary_model = MODEL_HEAVY if intent == "heavy" else MODEL_FAST
261
 
262
  inference_parameters: Dict[str, Any] = {
263
  "model": primary_model,
@@ -313,9 +267,6 @@ async def chat(req: ChatRequest, background_tasks: BackgroundTasks):
313
  if extracted_phone:
314
  session.caller_info.phone = extracted_phone
315
 
316
- # Trigger background LLM caller info extraction (backfill issue, phone, and name)
317
- background_tasks.add_task(run_background_extraction, session.session_id, session.get_history_for_api())
318
-
319
  elapsed_ms = int((time.time() - start_timestamp) * 1000)
320
  logger.info(f"Execution wrapped. Metrics: ID={session.session_id} | Provider={provider_tag} | Latency={elapsed_ms}ms | Language={detected_language}")
321
 
 
7
  import logging
8
  import asyncio
9
  from typing import List, Optional, Dict, Any
10
+ from fastapi import FastAPI, HTTPException
11
  from fastapi.middleware.cors import CORSMiddleware
12
  from fastapi.responses import JSONResponse
13
  from pydantic import BaseModel
 
83
  session_id: str
84
 
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  # ── REST API Router Endpoints ────────────────────────────────────────────────
87
 
88
  @app.post("/session/start")
 
111
 
112
 
113
  @app.post("/chat")
114
+ async def chat(req: ChatRequest):
115
  """
116
  Processes voice transcript inputs, executes dynamic RAG context assembly,
117
  evaluates historical payload token depth, and executes low-latency model inference.
 
143
 
144
  # 3. Rolling window processing and continuous background text summary compilation
145
  new_summary = req.currentSummary or ""
146
+ if len(session.messages) > 6:
147
+ messages_to_keep = session.messages[-6:]
148
+ messages_to_summarize = session.messages[:-6]
 
 
 
149
 
150
  # Non-blocking context thread synthesis execution
151
  new_summary = await generate_summary(new_summary, messages_to_summarize)
152
+ session.messages = messages_to_keep
153
 
154
  # 4. Synthesize structural text templates with active script sniffing indicators
155
  system_prompt = build_system_prompt(
 
211
 
212
  # 7. Model Router Intent Classification Integration
213
  intent = await session.classify_intent(req.message, detected_language)
214
+ primary_model = MODEL_HEAVY if (intent == "heavy" or needs70B(req.message, detected_language)) else MODEL_FAST
215
 
216
  inference_parameters: Dict[str, Any] = {
217
  "model": primary_model,
 
267
  if extracted_phone:
268
  session.caller_info.phone = extracted_phone
269
 
 
 
 
270
  elapsed_ms = int((time.time() - start_timestamp) * 1000)
271
  logger.info(f"Execution wrapped. Metrics: ID={session.session_id} | Provider={provider_tag} | Latency={elapsed_ms}ms | Language={detected_language}")
272
 
python-services/prompt_builder.py CHANGED
@@ -91,12 +91,7 @@ def build_system_prompt(
91
  Collect the caller's name and contact number naturally throughout the dialogue.
92
  - Do NOT ask for name or phone on the first greeting turn. Wait until the conversation has an established direction.
93
  - Weave information collection into the flow as a personal courtesy, not as a system requirement (e.g., "May I know who I am speaking with?").
94
- - CRITICAL: NEVER justify collecting name or phone by referencing administrative, tracking, record, or database reasons. This makes callers feel surveilled and tense.
95
- * English forbidden phrases include: "so I can track your record", "for our database", "for verification purposes", "to complete your file", "to check your details".
96
- * Urdu (اردو) forbidden phrases include: "تاکہ میں آپ کی معلومات کو چیک کر سکوں", "تاکہ میں آپ کی معلومات ریکارڈ کر سکوں", "تاکہ میں آپ کی فائل کو مکمل کر سکوں", "تاکہ میں ریکارڈ دیکھ سکوں", "تصدیق کے لیے", "تاکہ میں سسٹم میں دیکھ سکوں".
97
- * Arabic (العربية) forbidden phrases include: "لأتحقق من ملفك", "لتسجيل بياناتك", "لأغراض التحقق", "لقاعدة البيانات".
98
- NEVER use these or any similar expressions in any language.
99
- - CRITICAL: If the caller provides a phone number, check if it is valid (typically 7 to 15 digits depending on the region). If the number is clearly invalid (e.g. contains too many digits like "033525252525252525" or is too short), do NOT accept it. Instead, politely ask the caller to repeat or clarify their number.
100
  - If a caller declines to share their name or phone, accept it immediately and gracefully without apologizing excessively or re-asking. Transition directly to answering their question.
101
  - Currently tracked caller status profile: {caller_context}
102
 
 
91
  Collect the caller's name and contact number naturally throughout the dialogue.
92
  - Do NOT ask for name or phone on the first greeting turn. Wait until the conversation has an established direction.
93
  - Weave information collection into the flow as a personal courtesy, not as a system requirement (e.g., "May I know who I am speaking with?").
94
+ - CRITICAL: NEVER justify collecting name or phone by referencing administrative, tracking, record, or database reasons. This makes callers feel surveilled and tense. Phrases like "so I can track your record", "for our database", "for verification purposes" are FORBIDDEN.
 
 
 
 
 
95
  - If a caller declines to share their name or phone, accept it immediately and gracefully without apologizing excessively or re-asking. Transition directly to answering their question.
96
  - Currently tracked caller status profile: {caller_context}
97