voice-rag commited on
Commit
87d6ce2
·
1 Parent(s): 37523d3

added agent gender

Browse files
plan.md DELETED
@@ -1,91 +0,0 @@
1
- # Project Scan & Improvement Plan
2
-
3
- ## 1. Overview
4
- A static analysis of the **`c:/voice/the-brain/python-services`** package was performed. The goal is to surface:
5
- - Critical bugs and runtime errors.
6
- - Security concerns (PII leakage, missing environment validation).
7
- - Performance bottlenecks.
8
- - Code‑quality and maintainability issues.
9
- - Recommendations for testing, documentation, and CI.
10
-
11
- All file references are clickable for easy navigation.
12
-
13
- ---
14
-
15
- ## 2. Critical Bugs & Security Issues
16
- | Severity | File | Line(s) | Issue | Recommendation |
17
- |---|---|---|---|---|
18
-
19
-
20
- | **High** | [caller_info_extractor.py](file:///c:/voice/the-brain/python-services/caller_info_extractor.py) | 46‑47 | Phone numbers are logged at `INFO` level, exposing PII. | Remove raw numbers from logs or mask them (e.g., `****1234`) and downgrade to `DEBUG`. |
21
- | **High** | [utils.py](file:///c:/voice/the-brain/python-services/utils.py) | 135‑141 | `strip_formatting` removes all parenthetical text, potentially discarding spoken content. | Refine regex to only strip markdown parentheses (`[text](url)`) while preserving plain language parentheses. |
22
- | **Medium** | [conversation_manager.py](file:///c:/voice/the-brain/python-services/conversation_manager.py) | 84‑86 | Hindi (`hi`) is forced to Urdu (`ur`), which may mis‑route genuine Hindi callers. | Document the rationale; make the mapping configurable. |
23
- | **Medium** | [llm_server.py](file:///c:/voice/the-brain/python-services/llm_server.py) | 174‑176 | Token‑governor uses `req.sessionStart` which can be `None`, leading to misleading duration calculations. | Guard against `None` and log a warning when the timestamp is missing. |
24
-
25
- ---
26
-
27
- ## 3. High‑Priority Improvement Roadmap
28
-
29
- 1. **PII‑Safe Logging**
30
- - Add a helper `mask_phone(phone: str) -> str` returning a masked version (e.g., `****1234`).
31
- - Use it wherever phone numbers are logged and lower the log level to `DEBUG`.
32
- 2. **Typed Code & Static Checks**
33
- - Add missing type hints across the code base.
34
- - Integrate `mypy --strict` and `ruff`/`flake8` into CI.
35
- 3. **Test Suite**
36
- - Add `tests/` with pytest covering:
37
- - Regex extraction (`extract_phone_from_text`, `extract_name_from_text`).
38
- - Language detection edge cases.
39
- - Conversation session lifecycle (creation, expiration, cleanup).
40
- - API endpoint sanity using FastAPI’s `TestClient`.
41
- 4. **Dependency Pinning**
42
- - Update `requirements.txt` with explicit version numbers.
43
- 5. **Documentation Upgrade**
44
- - Expand `README.md` with setup, run instructions, API reference, and contribution guide.
45
- 6. **Docker / Deploy Hygiene**
46
- - Add a health‑check in the `Dockerfile` that hits `/health`.
47
- - Externalise hard‑coded thresholds and model names to env vars or a config file.
48
-
49
- ---
50
-
51
- ## 4. Medium‑Priority Enhancements
52
- - Lazy‑load Groq/Fireworks clients after configuration validation.
53
- - Use a module‑level `ThreadPoolExecutor` for all `run_in_executor` calls to avoid per‑request thread creation.
54
- - Replace duplicate `arabic_regex` definition in `utils.py` with a single compiled regex.
55
- - Introduce structured JSON logging for easier aggregation.
56
- - Implement exponential back‑off before falling back to Fireworks on 429 errors.
57
-
58
- ---
59
-
60
- ## 5. Low‑Priority / Nice‑to‑Have
61
- - Add GitHub Actions workflow running lint, type‑check, and tests on each PR.
62
- - Provide pre‑commit hooks (`black`, `ruff`).
63
- - Expose OpenAPI schema (FastAPI does this automatically; just add the URL to docs).
64
- - Add Prometheus metrics exporter for request latency, token usage, and error rates.
65
- - Store language‑specific prompt templates in separate files for easier updates.
66
-
67
- ---
68
-
69
- ## 6. Verification Plan
70
- - **Manual Review** – Walk through each changed file; confirm no raw PII appears in logs.
71
- - **Automated Tests** – Run `pytest`; ensure ≥80 % coverage for core utilities.
72
- - **Static Analysis** – Run `ruff` and `mypy --strict`; fix all reported issues.
73
- - **Runtime Smoke Test** – Start the server (`uvicorn llm_server:app --port 7860`) and hit `/health` and `/session/start` endpoints.
74
- - **Security Scan** – Simulate a request that extracts a phone number and verify logs contain only masked values.
75
-
76
- ---
77
-
78
- ## 7. Immediate Action Items (Task List)
79
- - `[ ]` Create `config.py` with env‑var validation.
80
- - `[ ]` Refactor `client.py` to lazily instantiate providers after config validation.
81
- - `[ ]` Implement `mask_phone` and replace raw phone logs.
82
- - `[ ]` Add missing type hints and run `mypy`.
83
- - `[ ]` Write unit tests for regex utilities.
84
- - `[ ]` Pin dependency versions in `requirements.txt`.
85
- - `[ ]` Expand `README.md`.
86
- - `[ ]` Add Docker health‑check.
87
- - `[ ]` Externalise thresholds and model names.
88
-
89
- ---
90
-
91
- *Prepared by Antigravity – your AI coding assistant.*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
python-services/conversation_manager.py CHANGED
@@ -34,12 +34,14 @@ class ConversationSession:
34
  session_id: str,
35
  company_name: str,
36
  agent_name: str,
 
37
  custom_rules: List[str] = None,
38
  document_context: str = "", # RAG payload vector string placeholder
39
  ):
40
  self.session_id = session_id
41
  self.company_name = company_name
42
  self.agent_name = agent_name
 
43
  self.custom_rules = custom_rules or []
44
  self.document_context = document_context
45
 
@@ -151,6 +153,7 @@ class ConversationSession:
151
  "session_id": self.session_id,
152
  "company": self.company_name,
153
  "agent": self.agent_name,
 
154
  "language": self.get_current_language(),
155
  "turns": self.turn_count,
156
  "caller_info": self.caller_info.to_dict(),
@@ -172,6 +175,7 @@ class ConversationManager:
172
  self,
173
  company_name: str,
174
  agent_name: str,
 
175
  custom_rules: List[str] = None,
176
  document_context: str = "",
177
  session_id: Optional[str] = None,
@@ -183,6 +187,7 @@ class ConversationManager:
183
  session_id=sid,
184
  company_name=company_name,
185
  agent_name=agent_name,
 
186
  custom_rules=custom_rules,
187
  document_context=document_context,
188
  )
 
34
  session_id: str,
35
  company_name: str,
36
  agent_name: str,
37
+ agent_gender: str = "female", # Added parameter
38
  custom_rules: List[str] = None,
39
  document_context: str = "", # RAG payload vector string placeholder
40
  ):
41
  self.session_id = session_id
42
  self.company_name = company_name
43
  self.agent_name = agent_name
44
+ self.agent_gender = agent_gender # Store parameter
45
  self.custom_rules = custom_rules or []
46
  self.document_context = document_context
47
 
 
153
  "session_id": self.session_id,
154
  "company": self.company_name,
155
  "agent": self.agent_name,
156
+ "agent_gender": self.agent_gender, # Added to summary dict
157
  "language": self.get_current_language(),
158
  "turns": self.turn_count,
159
  "caller_info": self.caller_info.to_dict(),
 
175
  self,
176
  company_name: str,
177
  agent_name: str,
178
+ agent_gender: str = "female", # Added parameter
179
  custom_rules: List[str] = None,
180
  document_context: str = "",
181
  session_id: Optional[str] = None,
 
187
  session_id=sid,
188
  company_name=company_name,
189
  agent_name=agent_name,
190
+ agent_gender=agent_gender, # Pass parameter
191
  custom_rules=custom_rules,
192
  document_context=document_context,
193
  )
python-services/llm_server.py CHANGED
@@ -66,6 +66,7 @@ manager = ConversationManager()
66
  class StartSessionRequest(BaseModel):
67
  company_name: str = "Our Company"
68
  agent_name: str = "Sara"
 
69
  custom_rules: List[str] = []
70
  document_context: str = "" # RAG text block container
71
  session_id: Optional[str] = None # Generated if missing
@@ -93,6 +94,7 @@ async def start_session(req: StartSessionRequest):
93
  session = manager.create_session(
94
  company_name=req.company_name,
95
  agent_name=req.agent_name,
 
96
  custom_rules=req.custom_rules,
97
  document_context=req.document_context,
98
  session_id=req.session_id,
@@ -125,6 +127,7 @@ async def chat(req: ChatRequest):
125
  session = manager.create_session(
126
  company_name=req.company_name or "Our Company",
127
  agent_name=req.agent_name or "Sara",
 
128
  session_id=req.session_id
129
  )
130
  logger.warning(f"Target memory space missing. Initialized emergency backup stack instance: {session.session_id}")
@@ -152,11 +155,12 @@ async def chat(req: ChatRequest):
152
  system_prompt = build_system_prompt(
153
  company_name=session.company_name,
154
  agent_name=session.agent_name,
 
155
  language=detected_language,
156
  document_context=session.document_context,
157
  custom_rules=session.custom_rules,
158
  collected_caller_info=session.caller_info.to_dict(),
159
- input_text=req.message # FIXED: Directly passed user input string to match scripts accurately
160
  )
161
 
162
  # Append structural input tokens into history logs
 
66
  class StartSessionRequest(BaseModel):
67
  company_name: str = "Our Company"
68
  agent_name: str = "Sara"
69
+ agent_gender: str = "female" # Added field
70
  custom_rules: List[str] = []
71
  document_context: str = "" # RAG text block container
72
  session_id: Optional[str] = None # Generated if missing
 
94
  session = manager.create_session(
95
  company_name=req.company_name,
96
  agent_name=req.agent_name,
97
+ agent_gender=req.agent_gender, # Pass parameter
98
  custom_rules=req.custom_rules,
99
  document_context=req.document_context,
100
  session_id=req.session_id,
 
127
  session = manager.create_session(
128
  company_name=req.company_name or "Our Company",
129
  agent_name=req.agent_name or "Sara",
130
+ agent_gender="female", # Default backup gender
131
  session_id=req.session_id
132
  )
133
  logger.warning(f"Target memory space missing. Initialized emergency backup stack instance: {session.session_id}")
 
155
  system_prompt = build_system_prompt(
156
  company_name=session.company_name,
157
  agent_name=session.agent_name,
158
+ agent_gender=session.agent_gender, # Pass parameter from session
159
  language=detected_language,
160
  document_context=session.document_context,
161
  custom_rules=session.custom_rules,
162
  collected_caller_info=session.caller_info.to_dict(),
163
+ input_text=req.message
164
  )
165
 
166
  # Append structural input tokens into history logs
python-services/prompt_builder.py CHANGED
@@ -34,6 +34,7 @@ LANGUAGE_SCRIPT_MAP = {
34
  def build_system_prompt(
35
  company_name: str,
36
  agent_name: str,
 
37
  language: str,
38
  document_context: str = "", # RAG placeholder channel
39
  custom_rules: List[str] = None, # Company-specific behavioral rules
@@ -54,6 +55,7 @@ def build_system_prompt(
54
  caller_context = _get_caller_context(collected_caller_info or {})
55
  document_section = _get_document_section(document_context)
56
  rules_section = _get_rules_section(custom_rules or [])
 
57
  current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
58
 
59
  prompt = f"""You are {agent_name}, a professional customer service representative working for {company_name}.
@@ -64,6 +66,9 @@ def build_system_prompt(
64
  - Current time: {current_time}.
65
  - You operate entirely as an elite human client coordinator on a live telephone connection — NEVER state, hint, or imply that you are an "AI", "Robot", "Bot", "Language Model", or a software script.
66
 
 
 
 
67
  ## Language & Script Enforcement Constraints
68
  {language_instructions}
69
 
@@ -78,6 +83,7 @@ def build_system_prompt(
78
  8. VOICE SYNTHESIS CRITICAL RULE: Never use bullet points, asterisks, numbered listings, dashes, or markdown typography formatting (** or __) in your output phrases. Speak purely in plain, flowing text sentences.
79
  9. If you do not have data to answer a query, state that you will verify the logs or arrange a specialist follow-up call. Never hypothesize details.
80
  10. Avoid mentioning competitors of {company_name} or drafting relative industry charts.
 
81
 
82
  ## Caller Information Collection
83
  Collect the caller's name and contact number naturally throughout the dialogue.
@@ -100,7 +106,7 @@ Collect the caller's name and contact number naturally throughout the dialogue.
100
  return prompt.strip()
101
 
102
 
103
- def build_system_prompts(lang_code: str, user_name_inner: str) -> List[Dict[str, str]]:
104
  """
105
  Compiles system configuration parameters into an array format compatible
106
  with standard OpenAI and Groq chat execution completions.
@@ -109,6 +115,7 @@ def build_system_prompts(lang_code: str, user_name_inner: str) -> List[Dict[str,
109
  raw_prompt = build_system_prompt(
110
  company_name="our world-class organization",
111
  agent_name="the customer support team coordinator",
 
112
  language=lang_code,
113
  collected_caller_info={"name": user_name_inner} if user_name_inner else {}
114
  )
@@ -116,6 +123,33 @@ def build_system_prompts(lang_code: str, user_name_inner: str) -> List[Dict[str,
116
  return [{"role": "system", "content": raw_prompt}]
117
 
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  def _get_language_instructions(language: str, input_text: str) -> str:
120
  """Generates explicit orthographical rule blocks based on targeted language codes."""
121
  script_info = LANGUAGE_SCRIPT_MAP.get(language)
 
34
  def build_system_prompt(
35
  company_name: str,
36
  agent_name: str,
37
+ agent_gender: str, # Added parameter
38
  language: str,
39
  document_context: str = "", # RAG placeholder channel
40
  custom_rules: List[str] = None, # Company-specific behavioral rules
 
55
  caller_context = _get_caller_context(collected_caller_info or {})
56
  document_section = _get_document_section(document_context)
57
  rules_section = _get_rules_section(custom_rules or [])
58
+ agent_gender_instruction = _get_agent_gender_instruction(agent_gender, lang)
59
  current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
60
 
61
  prompt = f"""You are {agent_name}, a professional customer service representative working for {company_name}.
 
66
  - Current time: {current_time}.
67
  - You operate entirely as an elite human client coordinator on a live telephone connection — NEVER state, hint, or imply that you are an "AI", "Robot", "Bot", "Language Model", or a software script.
68
 
69
+ ## Agent Gender & Grammatical Self-Reference Rules
70
+ {agent_gender_instruction}
71
+
72
  ## Language & Script Enforcement Constraints
73
  {language_instructions}
74
 
 
83
  8. VOICE SYNTHESIS CRITICAL RULE: Never use bullet points, asterisks, numbered listings, dashes, or markdown typography formatting (** or __) in your output phrases. Speak purely in plain, flowing text sentences.
84
  9. If you do not have data to answer a query, state that you will verify the logs or arrange a specialist follow-up call. Never hypothesize details.
85
  10. Avoid mentioning competitors of {company_name} or drafting relative industry charts.
86
+ 11. Avoid referencing your training, software design parameters, or guidelines explicitly during runtime. Maintain the human coordinator persona at all times.
87
 
88
  ## Caller Information Collection
89
  Collect the caller's name and contact number naturally throughout the dialogue.
 
106
  return prompt.strip()
107
 
108
 
109
+ def build_system_prompts(lang_code: str, user_name_inner: str, agent_gender: str = "female") -> List[Dict[str, str]]:
110
  """
111
  Compiles system configuration parameters into an array format compatible
112
  with standard OpenAI and Groq chat execution completions.
 
115
  raw_prompt = build_system_prompt(
116
  company_name="our world-class organization",
117
  agent_name="the customer support team coordinator",
118
+ agent_gender=agent_gender,
119
  language=lang_code,
120
  collected_caller_info={"name": user_name_inner} if user_name_inner else {}
121
  )
 
123
  return [{"role": "system", "content": raw_prompt}]
124
 
125
 
126
+ def _get_agent_gender_instruction(agent_gender: str, language: str) -> str:
127
+ """Generates standalone gender constraints section string based on agent gender and active language."""
128
+ lang = (language or "en").lower().strip()
129
+
130
+ if agent_gender == "female":
131
+ return """- Your gender is FEMALE. You MUST speak and refer to yourself using proper feminine grammatical forms, verbs, adjectives, and pronouns.
132
+ - In Urdu (اردو): Always use feminine first-person verb endings and adjectives (e.g., use 'کرتی ہوں', 'رہی ہوں', 'ہوں گی', 'تیار ہوں' [f] and NEVER 'کرتا ہوں', 'رہا ہوں', 'ہوں گا', 'تیار ہوں' [m]).
133
+ - In Arabic (العربية): Always use feminine adjectives, nouns, and participial forms (e.g., use 'أنا سعيدة', 'أنا مسؤولة', 'أنا متحدثة', 'مستعدة' [f] and NEVER 'سعيد', 'مسؤول', 'متحدث', 'مستعد' [m]). This rule applies across all Arabic dialects (including Gulf, Egyptian, Levantine); mirror the caller's dialect using correct feminine forms.
134
+ - In Persian (فارسی): Verbs and adjectives are grammatically genderless in the first person, but use feminine self-identifiers if speaking of your role (e.g., using 'خانم' [Ms./Lady/Coordinator] as a self-identifier if applicable).
135
+ - In English (en): Refer to yourself using female pronouns (she/her) if the context requires pronouns, and standard first-person pronouns (I/me) otherwise."""
136
+
137
+ elif agent_gender == "male":
138
+ return """- Your gender is MALE. You MUST speak and refer to yourself using proper masculine grammatical forms, verbs, adjectives, and pronouns.
139
+ - In Urdu (اردو): Always use masculine first-person verb endings and adjectives (e.g., use 'کرتا ہوں', 'رہا ہوں', 'ہوں گا', 'تیار ہوں' [m] and NEVER 'کرتی ہوں', 'رہی ہوں', 'ہوں گی', 'تیار ہوں' [f]).
140
+ - In Arabic (العربية): Always use masculine adjectives, nouns, and participial forms (e.g., use 'أنا سعيد', 'أنا مسؤول', 'أنا متحدث', 'مستعد' [m] and NEVER 'سعيدة', 'مسؤولة', 'متحدثة', 'مستعدة' [f]). This rule applies across all Arabic dialects (including Gulf, Egyptian, Levantine); mirror the caller's dialect using correct masculine forms.
141
+ - In Persian (فارسی): Verbs and adjectives are grammatically genderless in the first person, but use masculine self-identifiers if speaking of your role (e.g., using 'آقای' [Mr./Coordinator] as a self-identifier if applicable).
142
+ - In English (en): Refer to yourself using male pronouns (he/him) if the context requires pronouns, and standard first-person pronouns (I/me) otherwise."""
143
+
144
+ else: # unspecified / neutral
145
+ return """- Your gender is UNSPECIFIED/NEUTRAL. You MUST use gender-neutral self-referencing language and honorifics where possible.
146
+ - Avoid committing to strict male or female verb endings or adjectives.
147
+ - In Urdu (اردو): Urdu grammar strictly requires gendered verb endings for first-person references. You MUST restructure sentences to avoid first-person gendered verb endings where possible. When unavoidable, default to formal masculine verb endings (e.g., 'کرتے ہیں') as the standard formal professional default in Pakistani contexts.
148
+ - In Arabic (العربية): Use formal neutral phrasing and avoid adjectives, nouns, or participial forms that require gender agreement about yourself. Default to plural or balanced structures if gender is unavoidable. Dialect rendering should also maintain this neutral tone.
149
+ - In Persian (فارسی): Verbs and adjectives are naturally gender-neutral, so communicate normally without gendered role titles.
150
+ - In English (en): Use standard gender-neutral pronouns (they/them) or restructure sentences to avoid third-person self-references."""
151
+
152
+
153
  def _get_language_instructions(language: str, input_text: str) -> str:
154
  """Generates explicit orthographical rule blocks based on targeted language codes."""
155
  script_info = LANGUAGE_SCRIPT_MAP.get(language)