SamVidur commited on
Commit
a77fe30
·
1 Parent(s): ae63bdc

added guard rails

Browse files
Files changed (7) hide show
  1. config/config.yml +35 -0
  2. config/prompts.yml +224 -0
  3. config/rails.co +20 -0
  4. input_to_llm.py +1 -3
  5. main.py +68 -4
  6. requirements.txt +81 -57
  7. utils.py +115 -115
config/config.yml ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models:
2
+ # - type: main
3
+ # engine: nvidia_ai_endpoints
4
+ # model: meta/llama-3.3-70b-instruct
5
+
6
+ # - type: content_safety
7
+ # engine: nvidia_ai_endpoints
8
+ # model: nvidia/llama-3.1-nemoguard-8b-content-safety
9
+
10
+ # rails:
11
+ # input:
12
+ # flows:
13
+ # - content safety check input $model=content_safety
14
+ # output:
15
+ # flows:
16
+ # - content safety check output $model=content_safety
17
+ # streaming:
18
+ # enabled: True
19
+ # chunk_size: 200
20
+ # context_size: 50
21
+
22
+ # streaming: True
23
+
24
+ models:
25
+ - type: main
26
+ engine: nvidia_ai_endpoints
27
+ model: meta/llama-3.3-70b-instruct # Or whatever model you use for the GUARDING logic
28
+
29
+ rails:
30
+ input:
31
+ flows:
32
+ - self check input
33
+ output:
34
+ flows:
35
+ - self check output
config/prompts.yml ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # # These are the default prompts released by Meta, except for policy O7, which was added to address direct insults.
2
+ # prompts:
3
+ # - task: content_safety_check_input $model=content_safety
4
+ # content: |
5
+ # Task: Check if there is unsafe content in the user or agent messages in the below conversation in accordance with our safety policy with the below categories. Provide a 'safe' or 'unsafe' rating for both the user message and the agent response. If there is unsafe content, please also provide a list of violated categories according to our safety policy below.
6
+
7
+ # <BEGIN UNSAFE CONTENT CATEGORIES>
8
+ # S1: Violence.
9
+ # S2: Sexual.
10
+ # S3: Criminal Planning/Confessions.
11
+ # S4: Guns and Illegal Weapons.
12
+ # S5: Controlled/Regulated Substances.
13
+ # S6: Suicide and Self Harm.
14
+ # S7: Sexual (minor).
15
+ # S8: Hate/Identity Hate.
16
+ # S9: PII/Privacy.
17
+ # S10: Harassment.
18
+ # S11: Threat.
19
+ # S12: Profanity.
20
+ # S13: Needs Caution.
21
+ # S14: Other.
22
+ # S15: Manipulation.
23
+ # S16: Fraud/Deception.
24
+ # S17: Malware.
25
+ # S18: High Risk Gov Decision Making.
26
+ # S19: Political/Misinformation/Conspiracy.
27
+ # S20: Copyright/Trademark/Plagiarism.
28
+ # S21: Unauthorized Advice.
29
+ # S22: Illegal Activity.
30
+ # S23: Immoral/Unethical.
31
+ # <END UNSAFE CONTENT CATEGORIES>
32
+
33
+ # <BEGIN CONVERSATION>
34
+
35
+ # user: {{ user_input }}
36
+
37
+ # <END CONVERSATION>
38
+
39
+ # Provide your safety assessment for the above conversation in the below output JSON format:
40
+ # {"User Safety": safe or unsafe rating for the user message, "Response Safety": safe or unsafe rating for the agent response. Omit if no agent response present. "Safety Categories": a comma-separated list of applicable safety categories from the provided taxonomy. Omit if all safe.}
41
+
42
+ # Do not include anything other than the output JSON in your response.
43
+ # Output JSON:
44
+ # output_parser: nemoguard_parse_prompt_safety
45
+ # max_tokens: 50
46
+
47
+ # - task: content_safety_check_output $model=content_safety
48
+ # content: |
49
+ # Task: Check if there is unsafe content in the user or agent messages in the below conversation in accordance with our safety policy with the below categories. Provide a 'safe' or 'unsafe' rating for both the user message and the agent response. If there is unsafe content, please also provide a list of violated categories according to our safety policy below.
50
+
51
+ # <BEGIN UNSAFE CONTENT CATEGORIES>
52
+ # S1: Violence.
53
+ # S2: Sexual.
54
+ # S3: Criminal Planning/Confessions.
55
+ # S4: Guns and Illegal Weapons.
56
+ # S5: Controlled/Regulated Substances.
57
+ # S6: Suicide and Self Harm.
58
+ # S7: Sexual (minor).
59
+ # S8: Hate/Identity Hate.
60
+ # S9: PII/Privacy.
61
+ # S10: Harassment.
62
+ # S11: Threat.
63
+ # S12: Profanity.
64
+ # S13: Needs Caution.
65
+ # S14: Other.
66
+ # S15: Manipulation.
67
+ # S16: Fraud/Deception.
68
+ # S17: Malware.
69
+ # S18: High Risk Gov Decision Making.
70
+ # S19: Political/Misinformation/Conspiracy.
71
+ # S20: Copyright/Trademark/Plagiarism.
72
+ # S21: Unauthorized Advice.
73
+ # S22: Illegal Activity.
74
+ # S23: Immoral/Unethical.
75
+ # <END UNSAFE CONTENT CATEGORIES>
76
+
77
+ # <BEGIN CONVERSATION>
78
+
79
+ # user: {{ user_input }}
80
+
81
+ # response: agent: {{ bot_response }}
82
+
83
+ # <END CONVERSATION>
84
+
85
+ # Provide your safety assessment for the above conversation in the below output JSON format:
86
+ # {"User Safety": safe or unsafe rating for the user message, "Response Safety": safe or unsafe rating for the agent response. Omit if no agent response present. "Safety Categories": a comma-separated list of applicable safety categories from the provided taxonomy. Omit if all safe.}
87
+
88
+ # Do not include anything other than the output JSON in your response.
89
+ # Output JSON:
90
+ # output_parser: nemoguard_parse_response_safety
91
+ # max_tokens: 50
92
+
93
+ prompts:
94
+ - task: self_check_input
95
+ content: |
96
+ Your task is to check if the user message below is unsafe.
97
+ The message is unsafe if it contains:
98
+ - Requests for help with illegal acts.
99
+ - Hate speech, harassment, or violence.
100
+ - Instructions on how to make weapons, bombs, or drugs.
101
+ - Violence
102
+ - Sexual content
103
+ - Criminal Planning or Confessions
104
+ - Guns and Illegal Weapons
105
+ - Controlled or Regulated Substances
106
+ - Suicide and Self Harm
107
+ - Sexual content involving minors
108
+ - Hate speech or Identity Hate
109
+ - PII or Privacy violations
110
+ - Harassment
111
+ - Threats
112
+ - Profanity
113
+ - Topics needing caution
114
+ - Manipulation
115
+ - Fraud or Deception
116
+ - Malware creation or usage
117
+ - High Risk Government Decision Making
118
+ - Political Misinformation or Conspiracy
119
+ - Copyright, Trademark, or Plagiarism
120
+ - Unauthorized Professional Advice (Medical/Legal)
121
+ - Illegal Activity
122
+ - Immoral or Unethical content
123
+
124
+ User message: "{{ user_input }}"
125
+
126
+ Answer "safe" or "unsafe".
127
+
128
+ - task: self_check_output
129
+ content: |
130
+ Your task is to check if the bot response below is unsafe.
131
+ The response is unsafe if it contains:
132
+ - Instructions on how to make weapons, bombs, or drugs.
133
+ - Hate speech or illegal content.
134
+ - Violence
135
+ - Sexual content
136
+ - Criminal Planning or Confessions
137
+ - Guns and Illegal Weapons
138
+ - Controlled or Regulated Substances
139
+ - Suicide and Self Harm
140
+ - Sexual content involving minors
141
+ - Hate speech or Identity Hate
142
+ - PII or Privacy violations
143
+ - Harassment
144
+ - Threats
145
+ - Profanity
146
+ - Topics needing caution
147
+ - Manipulation
148
+ - Fraud or Deception
149
+ - Malware creation or usage
150
+ - High Risk Government Decision Making
151
+ - Political Misinformation or Conspiracy
152
+ - Copyright, Trademark, or Plagiarism
153
+ - Unauthorized Professional Advice (Medical/Legal)
154
+ - Illegal Activity
155
+ - Immoral or Unethical content
156
+ Bot response: "{{ bot_response }}"
157
+
158
+ Answer "safe" or "unsafe".
159
+
160
+
161
+ # prompts:
162
+ # - task: self_check_input
163
+ # content: |
164
+ # Task: Check if there is unsafe content in the user message below according to our safety policy.
165
+
166
+ # <BEGIN UNSAFE CONTENT CATEGORIES>
167
+ # S1: Violence.
168
+ # S2: Sexual.
169
+ # S3: Criminal Planning/Confessions.
170
+ # S4: Guns and Illegal Weapons.
171
+ # S5: Controlled/Regulated Substances.
172
+ # S6: Suicide and Self Harm.
173
+ # S7: Sexual (minor).
174
+ # S8: Hate/Identity Hate.
175
+ # S9: PII/Privacy.
176
+ # S10: Harassment.
177
+ # S11: Threat.
178
+ # S12: Profanity.
179
+ # S13: Needs Caution.
180
+ # S14: Other.
181
+ # S15: Manipulation.
182
+ # S16: Fraud/Deception.
183
+ # S17: Malware.
184
+ # S18: High Risk Gov Decision Making.
185
+ # S19: Political/Misinformation/Conspiracy.
186
+ # S20: Copyright/Trademark/Plagiarism.
187
+ # S21: Unauthorized Advice.
188
+ # S22: Illegal Activity.
189
+ # S23: Immoral/Unethical.
190
+ # <END UNSAFE CONTENT CATEGORIES>
191
+
192
+ # <BEGIN CONVERSATION>
193
+ # user: {{ user_input }}
194
+ # <END CONVERSATION>
195
+
196
+ # Provide your safety assessment in the below output JSON format:
197
+ # {"User Safety": "safe" or "unsafe", "Safety Categories": "comma-separated list of violated categories (e.g. S1, S4)"}
198
+
199
+ # Do not include anything other than the output JSON in your response.
200
+
201
+ # - task: self_check_output
202
+ # content: |
203
+ # Task: Check if there is unsafe content in the agent response below.
204
+
205
+ # <BEGIN UNSAFE CONTENT CATEGORIES>
206
+ # S1: Violence.
207
+ # S2: Sexual.
208
+ # S3: Criminal Planning.
209
+ # S4: Guns and Illegal Weapons.
210
+ # S5: Controlled Substances.
211
+ # S6: Suicide and Self Harm.
212
+ # S8: Hate Speech.
213
+ # S22: Illegal Activity.
214
+ # <END UNSAFE CONTENT CATEGORIES>
215
+
216
+ # <BEGIN CONVERSATION>
217
+ # user: {{ user_input }}
218
+ # agent: {{ bot_response }}
219
+ # <END CONVERSATION>
220
+
221
+ # Provide your safety assessment in the below output JSON format:
222
+ # {"Response Safety": "safe" or "unsafe", "Safety Categories": "comma-separated list"}
223
+
224
+ # Do not include anything other than the output JSON.
config/rails.co ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 1. Define the Input Flow
2
+ define flow self check input
3
+ # This runs the prompt you wrote in prompts.yml
4
+ $allowed = execute self_check_input
5
+
6
+ # If the model said "unsafe", $allowed will be false
7
+ if not $allowed
8
+ bot refuse to respond
9
+ stop
10
+
11
+ # 2. Define the Output Flow
12
+ define flow self check output
13
+ $allowed = execute self_check_output
14
+ if not $allowed
15
+ bot refuse to respond
16
+ stop
17
+
18
+ # 3. Define the "Refusal" Message (The Magic Token)
19
+ define bot refuse to respond
20
+ "[[GUARDRAIL_BLOCK_TRIGGERED]]"
input_to_llm.py CHANGED
@@ -8,9 +8,7 @@ def extract_chats(collection,user_id,limit):
8
  if collection is None:
9
  return "No database connection."
10
 
11
- cursor = collection.find(
12
- {"user_id": user_id}
13
- ).sort("timestamp", -1).limit(limit)
14
 
15
  recent_chats = list(cursor)[::-1]
16
 
 
8
  if collection is None:
9
  return "No database connection."
10
 
11
+ cursor = collection.find({"user_id": user_id}).sort("timestamp", -1).limit(limit)
 
 
12
 
13
  recent_chats = list(cursor)[::-1]
14
 
main.py CHANGED
@@ -403,19 +403,23 @@ from fastapi import FastAPI, HTTPException
403
  from pydantic import BaseModel
404
  from typing import List, Optional, Dict, Any
405
  from datetime import datetime
 
 
 
406
 
407
  # from langchain.memory import ConversationBufferMemory
408
  from perplexity import Perplexity
409
  from groq import Groq
410
  from input_to_llm import extract_chats, extract_goalfocus
411
  from utils import (
412
- load_user_data,
413
- initialize_rag,
414
- get_rag_response,
415
  llm,
416
  get_mongo_collection
417
  )
418
 
 
419
  from configure import USER_DATA_PATH, llm_prompt
420
 
421
  app = FastAPI(title="Sattva AI API")
@@ -460,7 +464,50 @@ async def chat_endpoint(request: ChatRequest):
460
  username = request.user_id
461
  user_input = request.message
462
  mode = request.mode
463
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
 
465
  ncon = 3
466
  chat_history_str = extract_chats(collection, username, ncon)
@@ -535,6 +582,23 @@ async def chat_endpoint(request: ChatRequest):
535
 
536
  response = llm.invoke(context_response)
537
  ai_response_text = response.content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
 
539
  if collection is not None:
540
  chat_document = {
 
403
  from pydantic import BaseModel
404
  from typing import List, Optional, Dict, Any
405
  from datetime import datetime
406
+ import asyncio
407
+ import os
408
+ from nemoguardrails import LLMRails, RailsConfig
409
 
410
  # from langchain.memory import ConversationBufferMemory
411
  from perplexity import Perplexity
412
  from groq import Groq
413
  from input_to_llm import extract_chats, extract_goalfocus
414
  from utils import (
415
+ # load_user_data,
416
+ # initialize_rag,
417
+ # get_rag_response,
418
  llm,
419
  get_mongo_collection
420
  )
421
 
422
+ os.environ["NVIDIA_API_KEY"] = "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"
423
  from configure import USER_DATA_PATH, llm_prompt
424
 
425
  app = FastAPI(title="Sattva AI API")
 
464
  username = request.user_id
465
  user_input = request.message
466
  mode = request.mode
467
+ config = RailsConfig.from_path("./config")
468
+ rails = LLMRails(config)
469
+ input_check = await rails.generate_async(
470
+ messages=[{"role": "user", "content": user_input}],
471
+ options={
472
+ "rails": ["input"],
473
+ "log": {"level": "INFO"}
474
+ }
475
+ )
476
+ response_text = str(input_check.response)
477
+ if "[[GUARDRAIL_BLOCK_TRIGGERED]]" in response_text:
478
+ print("[GUARD] Input Blocked!")
479
+
480
+ return ChatResponse(
481
+ response="I am sorry but I cannot answer that request.",
482
+ topic="blocked",
483
+ goal="blocked",
484
+ resources=[]
485
+ )
486
+
487
+ # refusal_keywords = [
488
+ # "unsafe",
489
+ # "cannot",
490
+ # "can't",
491
+ # "sorry",
492
+ # "apologize",
493
+ # "refuse",
494
+ # "unable to answer",
495
+ # "regulated",
496
+ # "harmful"
497
+ # ]
498
+
499
+ # # If ANY keyword is found in the response, we block it.
500
+ # is_blocked = any(keyword in response_text for keyword in refusal_keywords)
501
+ # if is_blocked:
502
+ # # print(" [GUARD] Input Blocked!")
503
+ # # return {"status": "blocked", "message": "I cannot answer that request."}
504
+ # return ChatResponse(
505
+ # response="I am sorry but I cannot answer that request.",
506
+ # topic="blocked",
507
+ # goal="blocked",
508
+ # resources=[]
509
+ # )
510
+ # # pass
511
 
512
  ncon = 3
513
  chat_history_str = extract_chats(collection, username, ncon)
 
582
 
583
  response = llm.invoke(context_response)
584
  ai_response_text = response.content
585
+ input_check = await rails.generate_async(
586
+ messages=[{"role": "user", "content": ai_response_text}],
587
+ options={
588
+ "rails": ["input"],
589
+ "log": {"level": "INFO"}
590
+ }
591
+ )
592
+ response_text = str(input_check.response)
593
+ if "[[GUARDRAIL_BLOCK_TRIGGERED]]" in response_text:
594
+ print("[GUARD] Input Blocked!")
595
+
596
+ return ChatResponse(
597
+ response="I am sorry but I cannot answer that request.",
598
+ topic="blocked",
599
+ goal="blocked",
600
+ resources=[]
601
+ )
602
 
603
  if collection is not None:
604
  chat_document = {
requirements.txt CHANGED
@@ -1,60 +1,72 @@
1
  aiohappyeyeballs==2.6.1
2
- aiohttp==3.13.0
3
  aiosignal==1.4.0
 
4
  annotated-types==0.7.0
5
- anyio==4.11.0
6
- async-timeout==4.0.3
 
7
  attrs==25.4.0
8
  backoff==2.2.1
9
  bcrypt==5.0.0
10
  build==1.3.0
11
- cachetools==6.2.1
12
- certifi==2025.10.5
13
  charset-normalizer==3.4.4
14
- chromadb==1.1.1
15
- click==8.3.0
16
  coloredlogs==15.0.1
17
  dataclasses-json==0.6.7
18
  distro==1.9.0
19
  dnspython==2.8.0
20
  durationpy==0.10
21
- exceptiongroup==1.3.0
22
- fastapi==0.119.0
 
23
  filelock==3.20.0
 
24
  flatbuffers==25.9.23
25
  frozenlist==1.8.0
26
- fsspec==2025.9.0
27
- google-auth==2.41.1
28
- googleapis-common-protos==1.70.0
29
- greenlet==3.2.4
30
- groq==0.32.0
31
- grpcio==1.75.1
32
  gunicorn==23.0.0
33
  h11==0.16.0
34
- hf-xet==1.1.10
35
  httpcore==1.0.9
36
  httptools==0.7.1
37
  httpx==0.28.1
38
  httpx-sse==0.4.3
39
- huggingface-hub==0.35.3
40
  humanfriendly==10.0
41
  idna==3.11
42
  importlib_metadata==8.7.0
43
  importlib_resources==6.5.2
44
  Jinja2==3.1.6
45
- joblib==1.5.2
46
  jsonpatch==1.33
47
  jsonpointer==3.0.0
48
  jsonschema==4.25.1
49
  jsonschema-specifications==2025.9.1
50
  kubernetes==34.1.0
51
- langchain==0.3.27
52
- langchain-community==0.3.31
53
- langchain-core==0.3.79
54
- langchain-groq==0.3.8
55
- langchain-huggingface==0.3.1
56
- langchain-text-splitters==0.3.11
57
- langsmith==0.4.37
 
 
 
 
 
 
 
 
58
  markdown-it-py==4.0.0
59
  MarkupSafe==3.0.3
60
  marshmallow==3.26.1
@@ -63,74 +75,86 @@ mmh3==5.2.0
63
  mpmath==1.3.0
64
  multidict==6.7.0
65
  mypy_extensions==1.1.0
66
- networkx==3.4.2
67
- numpy==2.2.6
 
 
68
  oauthlib==3.3.1
69
- onnxruntime==1.23.1
70
- opentelemetry-api==1.38.0
71
- opentelemetry-exporter-otlp-proto-common==1.38.0
72
- opentelemetry-exporter-otlp-proto-grpc==1.38.0
73
- opentelemetry-proto==1.38.0
74
- opentelemetry-sdk==1.38.0
75
- opentelemetry-semantic-conventions==0.59b0
76
- orjson==3.11.3
 
77
  overrides==7.7.0
78
  packaging==25.0
 
79
  perplexity-client==0.1.1
80
- perplexityai==0.16.1
81
- pillow==12.0.0
82
  posthog==5.4.0
 
83
  propcache==0.4.1
84
- protobuf==6.33.0
 
85
  pyasn1==0.6.1
86
  pyasn1_modules==0.4.2
87
- pybase64==1.4.2
88
- pydantic==2.12.2
89
- pydantic-settings==2.11.0
90
- pydantic_core==2.41.4
91
  Pygments==2.19.2
92
- pymongo==4.15.4
93
  PyPika==0.48.9
94
  pyproject_hooks==1.2.0
95
  python-dateutil==2.9.0.post0
96
- python-dotenv==1.1.1
 
97
  PyYAML==6.0.3
98
  referencing==0.37.0
99
- regex==2025.9.18
100
  requests==2.32.5
101
  requests-oauthlib==2.0.0
102
  requests-toolbelt==1.0.0
103
  rich==14.2.0
104
- rpds-py==0.27.1
105
  rsa==4.9.1
106
- safetensors==0.6.2
107
- scikit-learn==1.7.2
108
- scipy==1.15.3
109
- sentence-transformers==5.1.1
110
  setuptools==80.9.0
111
  shellingham==1.5.4
 
112
  six==1.17.0
113
  sniffio==1.3.1
114
- SQLAlchemy==2.0.44
115
- starlette==0.48.0
116
  sympy==1.14.0
117
  tenacity==9.1.2
118
  threadpoolctl==3.6.0
119
  tokenizers==0.22.1
120
  tomli==2.3.0
121
- torch==2.9.0
122
  tqdm==4.67.1
123
- transformers==4.57.1
124
- typer==0.19.2
125
  typing-inspect==0.9.0
126
  typing-inspection==0.4.2
127
  typing_extensions==4.15.0
 
128
  urllib3==2.3.0
129
- uvicorn==0.37.0
130
- uvloop==0.21.0
 
 
131
  watchfiles==1.1.1
 
132
  websocket-client==1.9.0
133
  websockets==15.0.1
 
134
  yarl==1.22.0
135
  zipp==3.23.0
136
  zstandard==0.25.0
 
1
  aiohappyeyeballs==2.6.1
2
+ aiohttp==3.13.2
3
  aiosignal==1.4.0
4
+ annotated-doc==0.0.4
5
  annotated-types==0.7.0
6
+ annoy==1.17.3
7
+ anyio==4.12.0
8
+ async-timeout==5.0.1
9
  attrs==25.4.0
10
  backoff==2.2.1
11
  bcrypt==5.0.0
12
  build==1.3.0
13
+ cachetools==6.2.3
14
+ certifi==2025.11.12
15
  charset-normalizer==3.4.4
16
+ chromadb==1.3.7
17
+ click==8.3.1
18
  coloredlogs==15.0.1
19
  dataclasses-json==0.6.7
20
  distro==1.9.0
21
  dnspython==2.8.0
22
  durationpy==0.10
23
+ exceptiongroup==1.3.1
24
+ fastapi==0.124.4
25
+ fastembed==0.6.0
26
  filelock==3.20.0
27
+ filetype==1.2.0
28
  flatbuffers==25.9.23
29
  frozenlist==1.8.0
30
+ fsspec==2025.12.0
31
+ google-auth==2.43.0
32
+ googleapis-common-protos==1.72.0
33
+ greenlet==3.3.0
34
+ groq==0.37.1
35
+ grpcio==1.76.0
36
  gunicorn==23.0.0
37
  h11==0.16.0
38
+ hf-xet==1.2.0
39
  httpcore==1.0.9
40
  httptools==0.7.1
41
  httpx==0.28.1
42
  httpx-sse==0.4.3
43
+ huggingface-hub==0.36.0
44
  humanfriendly==10.0
45
  idna==3.11
46
  importlib_metadata==8.7.0
47
  importlib_resources==6.5.2
48
  Jinja2==3.1.6
49
+ joblib==1.5.3
50
  jsonpatch==1.33
51
  jsonpointer==3.0.0
52
  jsonschema==4.25.1
53
  jsonschema-specifications==2025.9.1
54
  kubernetes==34.1.0
55
+ langchain==1.1.3
56
+ langchain-classic==1.0.0
57
+ langchain-community==0.4.1
58
+ langchain-core==1.2.0
59
+ langchain-groq==1.1.1
60
+ langchain-huggingface==1.2.0
61
+ langchain-nvidia-ai-endpoints==1.0.0
62
+ langchain-text-splitters==1.1.0
63
+ langgraph==1.0.5
64
+ langgraph-checkpoint==3.0.1
65
+ langgraph-prebuilt==1.0.5
66
+ langgraph-sdk==0.3.0
67
+ langsmith==0.4.59
68
+ lark==1.3.1
69
+ loguru==0.7.3
70
  markdown-it-py==4.0.0
71
  MarkupSafe==3.0.3
72
  marshmallow==3.26.1
 
75
  mpmath==1.3.0
76
  multidict==6.7.0
77
  mypy_extensions==1.1.0
78
+ nemoguardrails==0.19.0
79
+ nest-asyncio==1.6.0
80
+ networkx==3.6.1
81
+ numpy==2.3.5
82
  oauthlib==3.3.1
83
+ onnxruntime==1.23.2
84
+ opentelemetry-api==1.39.1
85
+ opentelemetry-exporter-otlp-proto-common==1.39.1
86
+ opentelemetry-exporter-otlp-proto-grpc==1.39.1
87
+ opentelemetry-proto==1.39.1
88
+ opentelemetry-sdk==1.39.1
89
+ opentelemetry-semantic-conventions==0.60b1
90
+ orjson==3.11.5
91
+ ormsgpack==1.12.1
92
  overrides==7.7.0
93
  packaging==25.0
94
+ pandas==2.3.3
95
  perplexity-client==0.1.1
96
+ perplexityai==0.22.0
97
+ pillow==11.3.0
98
  posthog==5.4.0
99
+ prompt_toolkit==3.0.52
100
  propcache==0.4.1
101
+ protobuf==6.33.2
102
+ py_rust_stemmers==0.1.5
103
  pyasn1==0.6.1
104
  pyasn1_modules==0.4.2
105
+ pybase64==1.4.3
106
+ pydantic==2.12.5
107
+ pydantic-settings==2.12.0
108
+ pydantic_core==2.41.5
109
  Pygments==2.19.2
110
+ pymongo==4.15.5
111
  PyPika==0.48.9
112
  pyproject_hooks==1.2.0
113
  python-dateutil==2.9.0.post0
114
+ python-dotenv==1.2.1
115
+ pytz==2025.2
116
  PyYAML==6.0.3
117
  referencing==0.37.0
118
+ regex==2025.11.3
119
  requests==2.32.5
120
  requests-oauthlib==2.0.0
121
  requests-toolbelt==1.0.0
122
  rich==14.2.0
123
+ rpds-py==0.30.0
124
  rsa==4.9.1
125
+ safetensors==0.7.0
126
+ scikit-learn==1.8.0
127
+ scipy==1.16.3
128
+ sentence-transformers==5.2.0
129
  setuptools==80.9.0
130
  shellingham==1.5.4
131
+ simpleeval==1.0.3
132
  six==1.17.0
133
  sniffio==1.3.1
134
+ SQLAlchemy==2.0.45
135
+ starlette==0.50.0
136
  sympy==1.14.0
137
  tenacity==9.1.2
138
  threadpoolctl==3.6.0
139
  tokenizers==0.22.1
140
  tomli==2.3.0
 
141
  tqdm==4.67.1
142
+ transformers==4.57.3
143
+ typer==0.20.0
144
  typing-inspect==0.9.0
145
  typing-inspection==0.4.2
146
  typing_extensions==4.15.0
147
+ tzdata==2025.3
148
  urllib3==2.3.0
149
+ uuid_utils==0.12.0
150
+ uvicorn==0.38.0
151
+ uvloop==0.22.1
152
+ watchdog==6.0.0
153
  watchfiles==1.1.1
154
+ wcwidth==0.2.14
155
  websocket-client==1.9.0
156
  websockets==15.0.1
157
+ xxhash==3.6.0
158
  yarl==1.22.0
159
  zipp==3.23.0
160
  zstandard==0.25.0
utils.py CHANGED
@@ -2,12 +2,12 @@
2
  import os
3
  import json
4
  from langchain_groq import ChatGroq
5
- from langchain_community.vectorstores import Chroma
6
- from langchain.schema import Document
7
- from langchain.text_splitter import RecursiveCharacterTextSplitter
8
- from langchain_huggingface import HuggingFaceEmbeddings
9
- from langchain.chains import RetrievalQA
10
- from langchain.prompts import PromptTemplate
11
  from configure import USER_DATA_PATH, RAG_BASE_DIRECTORY, RAG_CATEGORIES
12
  import shutil
13
  from dotenv import load_dotenv
@@ -75,114 +75,114 @@ def save_user_data(data):
75
  json.dump(data, f, indent=2)
76
 
77
  # Initialize RAG with per-category vector stores and QA chains
78
- def initialize_rag():
79
- vector_stores = {}
80
- qa_chains = {}
81
 
82
- # Updated prompt template for advice-focused responses
83
- custom_prompt_template = """
84
- You are a {category} wellness expert. Provide helpful advice with specific actions:
85
 
86
- 1. Start with a brief empathetic response to the user's concern
87
- 2. Offer 1-3 actionable suggestions with brief explanations
88
- 3. End with an open-ended question to continue conversation
89
 
90
- Guidelines:
91
- - Keep responses conversational and supportive
92
- - Avoid clinical jargon
93
- - Focus on practical, implementable advice
94
- - Maintain hopeful and encouraging tone
95
 
96
- Context:
97
- {context}
98
 
99
- Question: {question}
100
- """
101
 
102
- # Create vector stores and QA chains for each category
103
- # Assume all your variables like RAG_CATEGORIES, embeddings, llm, etc.
104
- # are defined above this block.
105
-
106
- for category in RAG_CATEGORIES:
107
- persist_dir = f"./chroma_db_{category}"
108
- vector_store = None # Initialize vector_store to None
109
-
110
- # --- 1. Check if Vector Store Already Exists ---
111
- if os.path.exists(persist_dir):
112
- print(f"Found existing vector store for {category}. Attempting to load...")
113
- try:
114
- # Load the existing vector store from disk
115
- vector_store = Chroma(
116
- persist_directory=persist_dir,
117
- embedding_function=embeddings # Must provide the same embedding function
118
- )
119
- vector_stores[category] = vector_store
120
- except Exception as e:
121
- print(f"Error loading existing store {persist_dir}: {e}")
122
- print("Will delete and attempt to re-build.")
123
- shutil.rmtree(persist_dir)
124
 
125
- # --- 2. Create Vector Store if it Doesn't Exist (or failed to load) ---
126
- if vector_store is None:
127
- print(f"No valid vector store for {category} found. Creating new one...")
128
 
129
- # Process documents (Your original logic)
130
- dir_path = os.path.join(RAG_BASE_DIRECTORY, category)
131
- docs = []
132
- if os.path.exists(dir_path):
133
- for filename in os.listdir(dir_path):
134
- if filename.endswith('.txt'):
135
- file_path = os.path.join(dir_path, filename)
136
- try:
137
- with open(file_path, 'r', encoding='utf-8') as f:
138
- text = f.read()
139
- chunks = text_splitter.split_text(text)
140
- for chunk in chunks:
141
- if chunk.strip():
142
- metadata = {
143
- "source": filename,
144
- "category": category
145
- }
146
- docs.append(Document(
147
- page_content=chunk.strip(),
148
- metadata=metadata
149
- ))
150
- except Exception as e:
151
- print(f"Error processing {file_path}: {e}")
152
 
153
- # Create vector store (Your original logic)
154
- if docs:
155
- vector_store = Chroma.from_documents(
156
- documents=docs,
157
- embedding=embeddings,
158
- persist_directory=persist_dir
159
- )
160
- vector_store.persist()
161
- vector_stores[category] = vector_store
162
- print(f"Created and persisted new vector store for {category} with {len(docs)} documents.")
163
- else:
164
- print(f"No documents found for {category}. Skipping QA chain setup.")
165
- continue # Skip to the next category
166
-
167
- # --- 3. Create QA Chain (if vector_store was loaded or created) ---
168
- if vector_store:
169
- # Create QA chain with updated prompt
170
- prompt = PromptTemplate(
171
- template=custom_prompt_template.replace("{category}", category),
172
- input_variables=["context", "question"]
173
- )
174
- qa_chain = RetrievalQA.from_chain_type(
175
- llm=llm,
176
- chain_type="stuff",
177
- retriever=vector_store.as_retriever(search_kwargs={"k": 5}),
178
- chain_type_kwargs={"prompt": prompt},
179
- return_source_documents=True
180
- )
181
- qa_chains[category] = qa_chain
182
- print(f"Initialized {category} QA chain.")
183
-
184
- # The rest of your code
185
- return qa_chains
186
 
187
  # Classify question to category
188
  def classify_question_category(question):
@@ -195,18 +195,18 @@ def classify_question_category(question):
195
  print(response)
196
  return response.content.strip()
197
 
198
- # Get RAG response using category-specific QA chain
199
- def get_rag_response(question, qa_chains):
200
- # Classify question
201
- category = classify_question_category(question)
202
 
203
- if category not in qa_chains:
204
- # Fallback to first available chain
205
- category = list(qa_chains.keys())[0]
206
 
207
- # Get response
208
- result = qa_chains[category].invoke({"query": question})
209
- return result['result']
210
 
211
 
212
  # Classify user input
 
2
  import os
3
  import json
4
  from langchain_groq import ChatGroq
5
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
6
+ # from langchain.schema import Document
7
+ # from langchain.chains import RetrievalQA
8
+ # from langchain_huggingface import HuggingFaceEmbeddings
9
+ # from langchain_community.vectorstores import Chroma
10
+ # from langchain.prompts import PromptTemplate
11
  from configure import USER_DATA_PATH, RAG_BASE_DIRECTORY, RAG_CATEGORIES
12
  import shutil
13
  from dotenv import load_dotenv
 
75
  json.dump(data, f, indent=2)
76
 
77
  # Initialize RAG with per-category vector stores and QA chains
78
+ # def initialize_rag():
79
+ # vector_stores = {}
80
+ # qa_chains = {}
81
 
82
+ # # Updated prompt template for advice-focused responses
83
+ # custom_prompt_template = """
84
+ # You are a {category} wellness expert. Provide helpful advice with specific actions:
85
 
86
+ # 1. Start with a brief empathetic response to the user's concern
87
+ # 2. Offer 1-3 actionable suggestions with brief explanations
88
+ # 3. End with an open-ended question to continue conversation
89
 
90
+ # Guidelines:
91
+ # - Keep responses conversational and supportive
92
+ # - Avoid clinical jargon
93
+ # - Focus on practical, implementable advice
94
+ # - Maintain hopeful and encouraging tone
95
 
96
+ # Context:
97
+ # {context}
98
 
99
+ # Question: {question}
100
+ # """
101
 
102
+ # # Create vector stores and QA chains for each category
103
+ # # Assume all your variables like RAG_CATEGORIES, embeddings, llm, etc.
104
+ # # are defined above this block.
105
+
106
+ # for category in RAG_CATEGORIES:
107
+ # persist_dir = f"./chroma_db_{category}"
108
+ # vector_store = None # Initialize vector_store to None
109
+
110
+ # # --- 1. Check if Vector Store Already Exists ---
111
+ # if os.path.exists(persist_dir):
112
+ # print(f"Found existing vector store for {category}. Attempting to load...")
113
+ # try:
114
+ # # Load the existing vector store from disk
115
+ # vector_store = Chroma(
116
+ # persist_directory=persist_dir,
117
+ # embedding_function=embeddings # Must provide the same embedding function
118
+ # )
119
+ # vector_stores[category] = vector_store
120
+ # except Exception as e:
121
+ # print(f"Error loading existing store {persist_dir}: {e}")
122
+ # print("Will delete and attempt to re-build.")
123
+ # shutil.rmtree(persist_dir)
124
 
125
+ # # --- 2. Create Vector Store if it Doesn't Exist (or failed to load) ---
126
+ # if vector_store is None:
127
+ # print(f"No valid vector store for {category} found. Creating new one...")
128
 
129
+ # # Process documents (Your original logic)
130
+ # dir_path = os.path.join(RAG_BASE_DIRECTORY, category)
131
+ # docs = []
132
+ # if os.path.exists(dir_path):
133
+ # for filename in os.listdir(dir_path):
134
+ # if filename.endswith('.txt'):
135
+ # file_path = os.path.join(dir_path, filename)
136
+ # try:
137
+ # with open(file_path, 'r', encoding='utf-8') as f:
138
+ # text = f.read()
139
+ # chunks = text_splitter.split_text(text)
140
+ # for chunk in chunks:
141
+ # if chunk.strip():
142
+ # metadata = {
143
+ # "source": filename,
144
+ # "category": category
145
+ # }
146
+ # docs.append(Document(
147
+ # page_content=chunk.strip(),
148
+ # metadata=metadata
149
+ # ))
150
+ # except Exception as e:
151
+ # print(f"Error processing {file_path}: {e}")
152
 
153
+ # # Create vector store (Your original logic)
154
+ # if docs:
155
+ # vector_store = Chroma.from_documents(
156
+ # documents=docs,
157
+ # embedding=embeddings,
158
+ # persist_directory=persist_dir
159
+ # )
160
+ # vector_store.persist()
161
+ # vector_stores[category] = vector_store
162
+ # print(f"Created and persisted new vector store for {category} with {len(docs)} documents.")
163
+ # else:
164
+ # print(f"No documents found for {category}. Skipping QA chain setup.")
165
+ # continue # Skip to the next category
166
+
167
+ # # --- 3. Create QA Chain (if vector_store was loaded or created) ---
168
+ # if vector_store:
169
+ # # Create QA chain with updated prompt
170
+ # prompt = PromptTemplate(
171
+ # template=custom_prompt_template.replace("{category}", category),
172
+ # input_variables=["context", "question"]
173
+ # )
174
+ # qa_chain = RetrievalQA.from_chain_type(
175
+ # llm=llm,
176
+ # chain_type="stuff",
177
+ # retriever=vector_store.as_retriever(search_kwargs={"k": 5}),
178
+ # chain_type_kwargs={"prompt": prompt},
179
+ # return_source_documents=True
180
+ # )
181
+ # qa_chains[category] = qa_chain
182
+ # print(f"Initialized {category} QA chain.")
183
+
184
+ # # The rest of your code
185
+ # return qa_chains
186
 
187
  # Classify question to category
188
  def classify_question_category(question):
 
195
  print(response)
196
  return response.content.strip()
197
 
198
+ # # Get RAG response using category-specific QA chain
199
+ # def get_rag_response(question, qa_chains):
200
+ # # Classify question
201
+ # category = classify_question_category(question)
202
 
203
+ # if category not in qa_chains:
204
+ # # Fallback to first available chain
205
+ # category = list(qa_chains.keys())[0]
206
 
207
+ # # Get response
208
+ # result = qa_chains[category].invoke({"query": question})
209
+ # return result['result']
210
 
211
 
212
  # Classify user input