Jayashree Sridhar commited on
Commit
c3ffbb9
·
1 Parent(s): f49168f

modifed validation tools as basetool instances and modified the calling portion in crew_config.py

Browse files
Files changed (2) hide show
  1. agents/tools/validation_tools.py +74 -700
  2. crew_config.py +1 -9
agents/tools/validation_tools.py CHANGED
@@ -1,444 +1,32 @@
1
- """
2
- Response validation tools for ensuring safe and appropriate responses
3
- """
4
-
5
  import re
6
- from typing import Dict, List, Tuple, Optional, Any
7
- from dataclasses import dataclass
8
- import json
9
  from transformers import pipeline
10
  import torch
11
- from pydantic import PrivateAttr
12
  from crewai.tools import BaseTool
13
- #from .base_tool import BaseTool
14
-
15
-
16
-
17
- # @dataclass
18
- class ValidationResult:
19
- """Result of validation check"""
20
- is_valid: bool
21
- issues: List[str]
22
- warnings: List[str]
23
- suggestions: List[str]
24
- confidence: float
25
- refined_text: Optional[str] = None
26
-
27
- # class ValidationTools:
28
- # """Tools for validating responses and ensuring safety"""
29
-
30
- # def __init__(self, config):
31
- # self.config = config
32
-
33
- # # Initialize sentiment analyzer for tone checking
34
- # self.sentiment_analyzer = pipeline(
35
- # "sentiment-analysis",
36
- # model="nlptown/bert-base-multilingual-uncased-sentiment",
37
- # device=0 if torch.cuda.is_available() else -1
38
- # )
39
-
40
- # # Prohibited patterns for different categories
41
- # self.prohibited_patterns = {
42
- # 'medical': [
43
- # r'\b(?:diagnos|prescrib|medicat|cure|treat|therap)\w*\b',
44
- # r'\b(?:disease|illness|disorder|syndrome)\s+(?:is|are|can be)\b',
45
- # r'\b(?:take|consume|dose|dosage)\s+\d+\s*(?:mg|ml|pill|tablet)',
46
- # r'\b(?:medical|clinical|physician|doctor)\s+(?:advice|consultation|opinion)',
47
- # ],
48
- # 'legal': [
49
- # r'\b(?:legal advice|lawsuit|sue|court|litigation)\b',
50
- # r'\b(?:illegal|unlawful|crime|criminal|prosecut)\w*\b',
51
- # r'\b(?:you should|must|have to)\s+(?:sign|agree|consent|contract)',
52
- # r'\b(?:rights|obligations|liability|damages)\s+(?:are|include)\b',
53
- # ],
54
- # 'financial': [
55
- # r'\b(?:invest|buy|sell|trade)\s+(?:stock|crypto|bitcoin|forex)\b',
56
- # r'\b(?:guaranteed|promise)\s+(?:return|profit|income|earnings)\b',
57
- # r'\b(?:financial advisor|investment advice|trading strategy)\b',
58
- # r'\b(?:tax|accounting|financial planning)\s+(?:advice|consultation)',
59
- # ],
60
- # 'harmful': [
61
- # r'\b(?:suicide|suicidal|kill\s+(?:your|my)self|end\s+(?:it|life))\b',
62
- # r'\b(?:self[\-\s]?harm|hurt\s+(?:your|my)self|cutting)\b',
63
- # r'\b(?:violence|violent|weapon|attack|assault)\b',
64
- # r'\b(?:hate|discriminat|racist|sexist|homophobic)\b',
65
- # ],
66
- # 'absolute': [
67
- # r'\b(?:always|never|every|all|none|no one|everyone)\s+(?:will|must|should|is|are)\b',
68
- # r'\b(?:definitely|certainly|guaranteed|assured|promise)\b',
69
- # r'\b(?:only way|only solution|must do|have to)\b',
70
- # ]
71
- # }
72
-
73
- # # Required elements for supportive responses
74
- # self.supportive_elements = {
75
- # 'empathy': [
76
- # 'understand', 'hear', 'feel', 'acknowledge', 'recognize',
77
- # 'appreciate', 'empathize', 'relate', 'comprehend'
78
- # ],
79
- # 'validation': [
80
- # 'valid', 'normal', 'understandable', 'natural', 'okay',
81
- # 'reasonable', 'makes sense', 'legitimate'
82
- # ],
83
- # 'support': [
84
- # 'support', 'help', 'here for you', 'together', 'alongside',
85
- # 'assist', 'guide', 'accompany', 'with you'
86
- # ],
87
- # 'hope': [
88
- # 'can', 'possible', 'able', 'capable', 'potential',
89
- # 'opportunity', 'growth', 'improve', 'better', 'progress'
90
- # ],
91
- # 'empowerment': [
92
- # 'choice', 'decide', 'control', 'power', 'strength',
93
- # 'agency', 'capable', 'resource', 'ability'
94
- # ]
95
- # }
96
-
97
- # # Crisis indicators
98
- # self.crisis_indicators = [
99
- # r'\b(?:want|going|plan)\s+to\s+(?:die|kill|end)\b',
100
- # r'\b(?:no reason|point|hope)\s+(?:to|in)\s+(?:live|living|life)\b',
101
- # r'\b(?:better off|world)\s+without\s+me\b',
102
- # r'\bsuicide\s+(?:plan|method|attempt)\b',
103
- # r'\b(?:final|last)\s+(?:goodbye|letter|message)\b'
104
- # ]
105
-
106
- # # Tone indicators
107
- # self.negative_tone_words = [
108
- # 'stupid', 'idiot', 'dumb', 'pathetic', 'worthless',
109
- # 'loser', 'failure', 'weak', 'incompetent', 'useless'
110
- # ]
111
-
112
- # self.dismissive_phrases = [
113
- # 'just get over it', 'stop complaining', 'not a big deal',
114
- # 'being dramatic', 'overreacting', 'too sensitive'
115
- # ]
116
-
117
- # def validate_response(self, response: str, context: Dict[str, Any] = None) -> ValidationResult:
118
- # """Comprehensive validation of response"""
119
- # issues = []
120
- # warnings = []
121
- # suggestions = []
122
-
123
- # # Check for prohibited content
124
- # prohibited_check = self._check_prohibited_content(response)
125
- # if prohibited_check["found"]:
126
- # issues.extend(prohibited_check["violations"])
127
- # suggestions.extend(prohibited_check["suggestions"])
128
-
129
- # # Check tone and sentiment
130
- # tone_check = self._check_tone(response)
131
- # if not tone_check["appropriate"]:
132
- # warnings.extend(tone_check["issues"])
133
- # suggestions.extend(tone_check["suggestions"])
134
-
135
- # # Check for supportive elements
136
- # support_check = self._check_supportive_elements(response)
137
- # if support_check["missing"]:
138
- # warnings.append(f"Missing supportive elements: {', '.join(support_check['missing'])}")
139
- # suggestions.extend(support_check["suggestions"])
140
-
141
- # # Check for crisis content in context
142
- # if context and context.get("user_input"):
143
- # crisis_check = self._check_crisis_indicators(context["user_input"])
144
- # if crisis_check["is_crisis"] and "crisis" not in response.lower():
145
- # warnings.append("User may be in crisis but response doesn't address this")
146
- # suggestions.append("Include crisis resources and immediate support options")
147
-
148
- # # Calculate overall confidence
149
- # confidence = self._calculate_confidence(issues, warnings)
150
-
151
- # # Generate refined response if needed
152
- # refined_text = None
153
- # if issues or (warnings and confidence < 0.7):
154
- # refined_text = self._refine_response(response, issues, warnings, suggestions)
155
-
156
- # return ValidationResult(
157
- # is_valid=len(issues) == 0,
158
- # issues=issues,
159
- # warnings=warnings,
160
- # suggestions=suggestions,
161
- # confidence=confidence,
162
- # refined_text=refined_text
163
- # )
164
-
165
- # def _check_prohibited_content(self, text: str) -> Dict[str, Any]:
166
- # """Check for prohibited content patterns"""
167
- # found_violations = []
168
- # suggestions = []
169
-
170
- # for category, patterns in self.prohibited_patterns.items():
171
- # for pattern in patterns:
172
- # if re.search(pattern, text, re.IGNORECASE):
173
- # found_violations.append(f"Contains {category} advice/content")
174
-
175
- # # Add specific suggestions
176
- # if category == "medical":
177
- # suggestions.append("Replace with: 'Consider speaking with a healthcare professional'")
178
- # elif category == "legal":
179
- # suggestions.append("Replace with: 'For legal matters, consult with a qualified attorney'")
180
- # elif category == "financial":
181
- # suggestions.append("Replace with: 'For financial decisions, consider consulting a financial advisor'")
182
- # elif category == "harmful":
183
- # suggestions.append("Include crisis resources and express immediate concern for safety")
184
- # elif category == "absolute":
185
- # suggestions.append("Use qualifying language like 'often', 'might', 'could' instead of absolutes")
186
- # break
187
-
188
- # return {
189
- # "found": len(found_violations) > 0,
190
- # "violations": found_violations,
191
- # "suggestions": suggestions
192
- # }
193
-
194
- # def _check_tone(self, text: str) -> Dict[str, Any]:
195
- # """Check the tone and sentiment of the response"""
196
- # issues = []
197
- # suggestions = []
198
-
199
- # # Check sentiment
200
- # try:
201
- # sentiment_result = self.sentiment_analyzer(text[:512])[0] # Limit length for model
202
- # sentiment_score = sentiment_result['score']
203
- # sentiment_label = sentiment_result['label']
204
-
205
- # # Check if too negative
206
- # if '1' in sentiment_label or '2' in sentiment_label: # 1-2 stars = negative
207
- # issues.append("Response tone is too negative")
208
- # suggestions.append("Add more supportive and hopeful language")
209
- # except:
210
- # pass
211
-
212
- # # Check for negative words
213
- # text_lower = text.lower()
214
- # found_negative = [word for word in self.negative_tone_words if word in text_lower]
215
- # if found_negative:
216
- # issues.append(f"Contains negative/judgmental language: {', '.join(found_negative)}")
217
- # suggestions.append("Replace judgmental terms with supportive language")
218
-
219
- # # Check for dismissive phrases
220
- # found_dismissive = [phrase for phrase in self.dismissive_phrases if phrase in text_lower]
221
- # if found_dismissive:
222
- # issues.append("Contains dismissive language")
223
- # suggestions.append("Acknowledge and validate the person's feelings instead")
224
-
225
- # return {
226
- # "appropriate": len(issues) == 0,
227
- # "issues": issues,
228
- # "suggestions": suggestions
229
- # }
230
-
231
- # def _check_supportive_elements(self, text: str) -> Dict[str, Any]:
232
- # """Check for presence of supportive elements"""
233
- # text_lower = text.lower()
234
- # missing_elements = []
235
- # suggestions = []
236
-
237
- # element_scores = {}
238
- # for element, keywords in self.supportive_elements.items():
239
- # found = any(keyword in text_lower for keyword in keywords)
240
- # element_scores[element] = found
241
- # if not found:
242
- # missing_elements.append(element)
243
-
244
- # # Generate suggestions for missing elements
245
- # if 'empathy' in missing_elements:
246
- # suggestions.append("Add empathetic language like 'I understand how difficult this must be'")
247
- # if 'validation' in missing_elements:
248
- # suggestions.append("Validate their feelings with phrases like 'Your feelings are completely valid'")
249
- # if 'support' in missing_elements:
250
- # suggestions.append("Express support with 'I'm here to support you through this'")
251
- # if 'hope' in missing_elements:
252
- # suggestions.append("Include hopeful elements about growth and positive change")
253
- # if 'empowerment' in missing_elements:
254
- # suggestions.append("Emphasize their agency and ability to make choices")
255
-
256
- # return {
257
- # "missing": missing_elements,
258
- # "present": [k for k, v in element_scores.items() if v],
259
- # "suggestions": suggestions
260
- # }
261
-
262
- # def _check_crisis_indicators(self, text: str) -> Dict[str, Any]:
263
- # """Check for crisis indicators in text"""
264
- # for pattern in self.crisis_indicators:
265
- # if re.search(pattern, text, re.IGNORECASE):
266
- # return {
267
- # "is_crisis": True,
268
- # "pattern_matched": pattern,
269
- # "action": "Immediate crisis response needed"
270
- # }
271
-
272
- # return {"is_crisis": False}
273
-
274
- # def _calculate_confidence(self, issues: List[str], warnings: List[str]) -> float:
275
- # """Calculate confidence score for validation"""
276
- # if issues:
277
- # return 0.3 - (0.1 * len(issues)) # Major issues severely impact confidence
278
-
279
- # confidence = 1.0
280
- # confidence -= 0.1 * len(warnings) # Each warning reduces confidence
281
-
282
- # return max(0.0, confidence)
283
-
284
- # def _refine_response(self, response: str, issues: List[str], warnings: List[str], suggestions: List[str]) -> str:
285
- # """Attempt to refine the response based on issues found"""
286
- # refined = response
287
-
288
- # # Add disclaimer for professional advice
289
- # if any('advice' in issue for issue in issues):
290
- # disclaimer = "\n\n*Please note: I'm here to provide support and guidance, but for specific professional matters, it's important to consult with qualified professionals.*"
291
- # if disclaimer not in refined:
292
- # refined += disclaimer
293
-
294
- # # Add crisis resources if needed
295
- # if any('crisis' in warning for warning in warnings):
296
- # crisis_text = "\n\n**If you're in crisis, please reach out for immediate help:**\n- Crisis Hotline: 988 (US)\n- Crisis Text Line: Text HOME to 741741\n- International: findahelpline.com"
297
- # if crisis_text not in refined:
298
- # refined += crisis_text
299
-
300
- # # Add supportive closing if missing hope
301
- # if any('hope' in warning for warning in warnings):
302
- # hopeful_closing = "\n\nRemember, you have the strength to navigate this challenge, and positive change is possible. I'm here to support you on this journey."
303
- # if not any(phrase in refined.lower() for phrase in ['journey', 'strength', 'possible']):
304
- # refined += hopeful_closing
305
-
306
- # return refined
307
-
308
- # def validate_user_input(self, text: str) -> ValidationResult:
309
- # """Validate user input for safety and process-ability"""
310
- # issues = []
311
- # warnings = []
312
- # suggestions = []
313
-
314
- # # Check if empty
315
- # if not text or not text.strip():
316
- # issues.append("Empty input received")
317
- # suggestions.append("Please share what's on your mind")
318
- # return ValidationResult(False, issues, warnings, suggestions, 0.0)
319
-
320
- # # Check length
321
- # if len(text) > 5000:
322
- # warnings.append("Input is very long")
323
- # suggestions.append("Consider breaking this into smaller parts")
324
-
325
- # # Check for crisis indicators
326
- # crisis_check = self._check_crisis_indicators(text)
327
- # if crisis_check["is_crisis"]:
328
- # warnings.append("Crisis indicators detected")
329
- # suggestions.append("Prioritize safety and provide crisis resources")
330
-
331
- # # Check for spam/repetition
332
- # if self._is_spam(text):
333
- # issues.append("Input appears to be spam or repetitive")
334
- # suggestions.append("Please share genuine thoughts or concerns")
335
-
336
- # confidence = self._calculate_confidence(issues, warnings)
337
-
338
- # return ValidationResult(
339
- # is_valid=len(issues) == 0,
340
- # issues=issues,
341
- # warnings=warnings,
342
- # suggestions=suggestions,
343
- # confidence=confidence
344
- # )
345
-
346
- # def _is_spam(self, text: str) -> bool:
347
- # """Simple spam detection"""
348
- # # Check for excessive repetition
349
- # words = text.lower().split()
350
- # if len(words) > 10:
351
- # unique_ratio = len(set(words)) / len(words)
352
- # if unique_ratio < 0.3: # Less than 30% unique words
353
- # return True
354
-
355
- # # Check for common spam patterns
356
- # spam_patterns = [
357
- # r'(?:buy|sell|click|visit)\s+(?:now|here|this)',
358
- # r'(?:congratulations|winner|prize|lottery)',
359
- # r'(?:viagra|pills|drugs|pharmacy)',
360
- # r'(?:$$|money\s+back|guarantee)'
361
- # ]
362
-
363
- # for pattern in spam_patterns:
364
- # if re.search(pattern, text, re.IGNORECASE):
365
- # return True
366
-
367
- # return False
368
-
369
- # def get_crisis_resources(self, location: str = "global") -> Dict[str, Any]:
370
- # """Get crisis resources based on location"""
371
- # resources = {
372
- # "global": {
373
- # "name": "International Association for Suicide Prevention",
374
- # "url": "https://www.iasp.info/resources/Crisis_Centres/",
375
- # "text": "Find crisis centers worldwide"
376
- # },
377
- # "us": {
378
- # "name": "988 Suicide & Crisis Lifeline",
379
- # "phone": "988",
380
- # "text": "Text HOME to 741741",
381
- # "url": "https://988lifeline.org/"
382
- # },
383
- # "uk": {
384
- # "name": "Samaritans",
385
- # "phone": "116 123",
386
- # "email": "jo@samaritans.org",
387
- # "url": "https://www.samaritans.org/"
388
- # },
389
- # "india": {
390
- # "name": "National Suicide Prevention Helpline",
391
- # "phone": "91-9820466726",
392
- # "additional": "Vandrevala Foundation: 9999666555"
393
- # },
394
- # "australia": {
395
- # "name": "Lifeline",
396
- # "phone": "13 11 14",
397
- # "text": "Text 0477 13 11 14",
398
- # "url": "https://www.lifeline.org.au/"
399
- # }
400
- # }
401
-
402
- # return resources.get(location.lower(), resources["global"])
403
-
404
- #from .base_tool import BaseTool
405
- #from crewai_tools import BaseTool
406
 
 
407
  class ValidateResponseTool(BaseTool):
408
  name: str = "validate_response"
409
- description: str = "Validates safety and helpfulness."
410
  model_config = {"arbitrary_types_allowed": True}
411
- _config: object = PrivateAttr()
 
 
 
 
 
 
 
412
  def __init__(self, config=None, **data):
413
  super().__init__(**data)
414
- self._config = config
415
- # ... any required initialization ...
416
- def _run(self, response: str, context: dict = None):
417
- # Place your actual validation logic here, include dummy for illustration
418
- # For full validation logic, use your own code!
419
- # """Result of validation check"""
420
- is_valid: bool
421
- issues: List[str]
422
- warnings: List[str]
423
- suggestions: List[str]
424
- confidence: float
425
- refined_text: Optional[str] = None
426
- return {"is_valid", "issues", "warnings", "suggestions","confidence","refined_text"}
427
-
428
- class ValidationTools:
429
- #_model: ValidateResponseTool = PrivateAttr()
430
- def __init__(self, config=None):
431
- self._validate_response = ValidateResponseTool(config)
432
- # Add more tools as needed (check_safety, refine_response, etc.)
433
- # # Initialize sentiment analyzer for tone checking
434
- self.sentiment_analyzer = pipeline(
435
  "sentiment-analysis",
436
  model="nlptown/bert-base-multilingual-uncased-sentiment",
437
  device=0 if torch.cuda.is_available() else -1
438
  )
439
-
440
- # Prohibited patterns for different categories
441
- self.prohibited_patterns = {
442
  'medical': [
443
  r'\b(?:diagnos|prescrib|medicat|cure|treat|therap)\w*\b',
444
  r'\b(?:disease|illness|disorder|syndrome)\s+(?:is|are|can be)\b',
@@ -469,9 +57,7 @@ class ValidationTools:
469
  r'\b(?:only way|only solution|must do|have to)\b',
470
  ]
471
  }
472
-
473
- # Required elements for supportive responses
474
- self.supportive_elements = {
475
  'empathy': [
476
  'understand', 'hear', 'feel', 'acknowledge', 'recognize',
477
  'appreciate', 'empathize', 'relate', 'comprehend'
@@ -493,310 +79,98 @@ class ValidationTools:
493
  'agency', 'capable', 'resource', 'ability'
494
  ]
495
  }
496
-
497
- # Crisis indicators
498
- self.crisis_indicators = [
499
  r'\b(?:want|going|plan)\s+to\s+(?:die|kill|end)\b',
500
  r'\b(?:no reason|point|hope)\s+(?:to|in)\s+(?:live|living|life)\b',
501
  r'\b(?:better off|world)\s+without\s+me\b',
502
  r'\bsuicide\s+(?:plan|method|attempt)\b',
503
  r'\b(?:final|last)\s+(?:goodbye|letter|message)\b'
504
  ]
505
-
506
- # Tone indicators
507
- self.negative_tone_words = [
508
  'stupid', 'idiot', 'dumb', 'pathetic', 'worthless',
509
  'loser', 'failure', 'weak', 'incompetent', 'useless'
510
  ]
511
-
512
- self.dismissive_phrases = [
513
  'just get over it', 'stop complaining', 'not a big deal',
514
  'being dramatic', 'overreacting', 'too sensitive'
515
  ]
516
-
517
- def validate_response(self, response: str, context: Dict[str, Any] = None) -> ValidationResult:
518
- """Comprehensive validation of response"""
 
 
 
 
519
  issues = []
520
  warnings = []
521
  suggestions = []
522
 
523
- # Check for prohibited content
524
- prohibited_check = self._check_prohibited_content(response)
525
- if prohibited_check["found"]:
526
- issues.extend(prohibited_check["violations"])
527
- suggestions.extend(prohibited_check["suggestions"])
528
-
529
- # Check tone and sentiment
530
- tone_check = self._check_tone(response)
531
- if not tone_check["appropriate"]:
532
- warnings.extend(tone_check["issues"])
533
- suggestions.extend(tone_check["suggestions"])
534
-
535
- # Check for supportive elements
536
- support_check = self._check_supportive_elements(response)
537
- if support_check["missing"]:
538
- warnings.append(f"Missing supportive elements: {', '.join(support_check['missing'])}")
539
- suggestions.extend(support_check["suggestions"])
540
-
541
- # Check for crisis content in context
542
- if context and context.get("user_input"):
543
- crisis_check = self._check_crisis_indicators(context["user_input"])
544
- if crisis_check["is_crisis"] and "crisis" not in response.lower():
545
- warnings.append("User may be in crisis but response doesn't address this")
546
- suggestions.append("Include crisis resources and immediate support options")
547
-
548
- # Calculate overall confidence
549
- confidence = self._calculate_confidence(issues, warnings)
550
-
551
- # Generate refined response if needed
552
- refined_text = None
553
- if issues or (warnings and confidence < 0.7):
554
- refined_text = self._refine_response(response, issues, warnings, suggestions)
555
-
556
- return ValidationResult(
557
- is_valid=len(issues) == 0,
558
- issues=issues,
559
- warnings=warnings,
560
- suggestions=suggestions,
561
- confidence=confidence,
562
- refined_text=refined_text
563
- )
564
-
565
- def _check_prohibited_content(self, text: str) -> Dict[str, Any]:
566
- """Check for prohibited content patterns"""
567
- found_violations = []
568
- suggestions = []
569
-
570
- for category, patterns in self.prohibited_patterns.items():
571
  for pattern in patterns:
572
- if re.search(pattern, text, re.IGNORECASE):
573
- found_violations.append(f"Contains {category} advice/content")
574
-
575
- # Add specific suggestions
576
  if category == "medical":
577
  suggestions.append("Replace with: 'Consider speaking with a healthcare professional'")
578
  elif category == "legal":
579
- suggestions.append("Replace with: 'For legal matters, consult with a qualified attorney'")
580
  elif category == "financial":
581
- suggestions.append("Replace with: 'For financial decisions, consider consulting a financial advisor'")
582
  elif category == "harmful":
583
  suggestions.append("Include crisis resources and express immediate concern for safety")
584
  elif category == "absolute":
585
  suggestions.append("Use qualifying language like 'often', 'might', 'could' instead of absolutes")
586
  break
587
-
588
- return {
589
- "found": len(found_violations) > 0,
590
- "violations": found_violations,
591
- "suggestions": suggestions
592
- }
593
-
594
- def _check_tone(self, text: str) -> Dict[str, Any]:
595
- """Check the tone and sentiment of the response"""
596
- issues = []
597
- suggestions = []
598
-
599
- # Check sentiment
600
  try:
601
- sentiment_result = self.sentiment_analyzer(text[:512])[0] # Limit length for model
602
- sentiment_score = sentiment_result['score']
603
  sentiment_label = sentiment_result['label']
604
-
605
- # Check if too negative
606
- if '1' in sentiment_label or '2' in sentiment_label: # 1-2 stars = negative
607
- issues.append("Response tone is too negative")
608
  suggestions.append("Add more supportive and hopeful language")
609
- except:
610
  pass
611
-
612
- # Check for negative words
613
- text_lower = text.lower()
614
- found_negative = [word for word in self.negative_tone_words if word in text_lower]
615
  if found_negative:
616
- issues.append(f"Contains negative/judgmental language: {', '.join(found_negative)}")
617
  suggestions.append("Replace judgmental terms with supportive language")
618
-
619
- # Check for dismissive phrases
620
- found_dismissive = [phrase for phrase in self.dismissive_phrases if phrase in text_lower]
621
  if found_dismissive:
622
- issues.append("Contains dismissive language")
623
  suggestions.append("Acknowledge and validate the person's feelings instead")
624
-
625
- return {
626
- "appropriate": len(issues) == 0,
627
- "issues": issues,
628
- "suggestions": suggestions
629
- }
630
-
631
- def _check_supportive_elements(self, text: str) -> Dict[str, Any]:
632
- """Check for presence of supportive elements"""
633
- text_lower = text.lower()
634
  missing_elements = []
635
- suggestions = []
636
-
637
- element_scores = {}
638
- for element, keywords in self.supportive_elements.items():
639
- found = any(keyword in text_lower for keyword in keywords)
640
- element_scores[element] = found
641
- if not found:
642
  missing_elements.append(element)
643
-
644
- # Generate suggestions for missing elements
645
- if 'empathy' in missing_elements:
646
- suggestions.append("Add empathetic language like 'I understand how difficult this must be'")
647
- if 'validation' in missing_elements:
648
- suggestions.append("Validate their feelings with phrases like 'Your feelings are completely valid'")
649
- if 'support' in missing_elements:
650
- suggestions.append("Express support with 'I'm here to support you through this'")
651
- if 'hope' in missing_elements:
652
- suggestions.append("Include hopeful elements about growth and positive change")
653
- if 'empowerment' in missing_elements:
654
- suggestions.append("Emphasize their agency and ability to make choices")
655
-
656
- return {
657
- "missing": missing_elements,
658
- "present": [k for k, v in element_scores.items() if v],
659
- "suggestions": suggestions
660
- }
661
-
662
- def _check_crisis_indicators(self, text: str) -> Dict[str, Any]:
663
- """Check for crisis indicators in text"""
664
- for pattern in self.crisis_indicators:
665
- if re.search(pattern, text, re.IGNORECASE):
666
- return {
667
- "is_crisis": True,
668
- "pattern_matched": pattern,
669
- "action": "Immediate crisis response needed"
670
- }
671
-
672
- return {"is_crisis": False}
673
-
674
- def _calculate_confidence(self, issues: List[str], warnings: List[str]) -> float:
675
- """Calculate confidence score for validation"""
676
- if issues:
677
- return 0.3 - (0.1 * len(issues)) # Major issues severely impact confidence
678
-
679
  confidence = 1.0
680
- confidence -= 0.1 * len(warnings) # Each warning reduces confidence
681
-
682
- return max(0.0, confidence)
683
-
684
- def _refine_response(self, response: str, issues: List[str], warnings: List[str], suggestions: List[str]) -> str:
685
- """Attempt to refine the response based on issues found"""
686
- refined = response
687
-
688
- # Add disclaimer for professional advice
689
- if any('advice' in issue for issue in issues):
690
- disclaimer = "\n\n*Please note: I'm here to provide support and guidance, but for specific professional matters, it's important to consult with qualified professionals.*"
691
- if disclaimer not in refined:
692
- refined += disclaimer
693
-
694
- # Add crisis resources if needed
695
- if any('crisis' in warning for warning in warnings):
696
- crisis_text = "\n\n**If you're in crisis, please reach out for immediate help:**\n- Crisis Hotline: 988 (US)\n- Crisis Text Line: Text HOME to 741741\n- International: findahelpline.com"
697
- if crisis_text not in refined:
698
- refined += crisis_text
699
-
700
- # Add supportive closing if missing hope
701
- if any('hope' in warning for warning in warnings):
702
- hopeful_closing = "\n\nRemember, you have the strength to navigate this challenge, and positive change is possible. I'm here to support you on this journey."
703
- if not any(phrase in refined.lower() for phrase in ['journey', 'strength', 'possible']):
704
- refined += hopeful_closing
705
-
706
- return refined
707
-
708
- def validate_user_input(self, text: str) -> ValidationResult:
709
- """Validate user input for safety and process-ability"""
710
- issues = []
711
- warnings = []
712
- suggestions = []
713
-
714
- # Check if empty
715
- if not text or not text.strip():
716
- issues.append("Empty input received")
717
- suggestions.append("Please share what's on your mind")
718
- return ValidationResult(False, issues, warnings, suggestions, 0.0)
719
-
720
- # Check length
721
- if len(text) > 5000:
722
- warnings.append("Input is very long")
723
- suggestions.append("Consider breaking this into smaller parts")
724
-
725
- # Check for crisis indicators
726
- crisis_check = self._check_crisis_indicators(text)
727
- if crisis_check["is_crisis"]:
728
- warnings.append("Crisis indicators detected")
729
- suggestions.append("Prioritize safety and provide crisis resources")
730
-
731
- # Check for spam/repetition
732
- if self._is_spam(text):
733
- issues.append("Input appears to be spam or repetitive")
734
- suggestions.append("Please share genuine thoughts or concerns")
735
-
736
- confidence = self._calculate_confidence(issues, warnings)
737
-
738
- return ValidationResult(
739
- is_valid=len(issues) == 0,
740
- issues=issues,
741
- warnings=warnings,
742
- suggestions=suggestions,
743
- confidence=confidence
744
- )
745
-
746
- def _is_spam(self, text: str) -> bool:
747
- """Simple spam detection"""
748
- # Check for excessive repetition
749
- words = text.lower().split()
750
- if len(words) > 10:
751
- unique_ratio = len(set(words)) / len(words)
752
- if unique_ratio < 0.3: # Less than 30% unique words
753
- return True
754
-
755
- # Check for common spam patterns
756
- spam_patterns = [
757
- r'(?:buy|sell|click|visit)\s+(?:now|here|this)',
758
- r'(?:congratulations|winner|prize|lottery)',
759
- r'(?:viagra|pills|drugs|pharmacy)',
760
- r'(?:$$|money\s+back|guarantee)'
761
- ]
762
-
763
- for pattern in spam_patterns:
764
- if re.search(pattern, text, re.IGNORECASE):
765
- return True
766
-
767
- return False
768
-
769
- def get_crisis_resources(self, location: str = "global") -> Dict[str, Any]:
770
- """Get crisis resources based on location"""
771
- resources = {
772
- "global": {
773
- "name": "International Association for Suicide Prevention",
774
- "url": "https://www.iasp.info/resources/Crisis_Centres/",
775
- "text": "Find crisis centers worldwide"
776
- },
777
- "us": {
778
- "name": "988 Suicide & Crisis Lifeline",
779
- "phone": "988",
780
- "text": "Text HOME to 741741",
781
- "url": "https://988lifeline.org/"
782
- },
783
- "uk": {
784
- "name": "Samaritans",
785
- "phone": "116 123",
786
- "email": "jo@samaritans.org",
787
- "url": "https://www.samaritans.org/"
788
- },
789
- "india": {
790
- "name": "National Suicide Prevention Helpline",
791
- "phone": "91-9820466726",
792
- "additional": "Vandrevala Foundation: 9999666555"
793
- },
794
- "australia": {
795
- "name": "Lifeline",
796
- "phone": "13 11 14",
797
- "text": "Text 0477 13 11 14",
798
- "url": "https://www.lifeline.org.au/"
799
- }
800
- }
801
-
802
- return resources.get(location.lower(), resources["global"])
 
 
 
 
 
1
  import re
2
+ from typing import List, Optional, Dict, Any
 
 
3
  from transformers import pipeline
4
  import torch
5
+ from pydantic import PrivateAttr
6
  from crewai.tools import BaseTool
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ # --- BaseTool for Response Validation ---
9
  class ValidateResponseTool(BaseTool):
10
  name: str = "validate_response"
11
+ description: str = "Validates safety and helpfulness of an AI response"
12
  model_config = {"arbitrary_types_allowed": True}
13
+
14
+ _prohibited_patterns: dict = PrivateAttr()
15
+ _supportive_elements: dict = PrivateAttr()
16
+ _crisis_indicators: List[str] = PrivateAttr()
17
+ _negative_tone_words: List[str] = PrivateAttr()
18
+ _dismissive_phrases: List[str] = PrivateAttr()
19
+ _sentiment_analyzer: object = PrivateAttr()
20
+
21
  def __init__(self, config=None, **data):
22
  super().__init__(**data)
23
+ # === Paste your initialization data here as PrivateAttr ===
24
+ self._sentiment_analyzer = pipeline(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  "sentiment-analysis",
26
  model="nlptown/bert-base-multilingual-uncased-sentiment",
27
  device=0 if torch.cuda.is_available() else -1
28
  )
29
+ self._prohibited_patterns = {
 
 
30
  'medical': [
31
  r'\b(?:diagnos|prescrib|medicat|cure|treat|therap)\w*\b',
32
  r'\b(?:disease|illness|disorder|syndrome)\s+(?:is|are|can be)\b',
 
57
  r'\b(?:only way|only solution|must do|have to)\b',
58
  ]
59
  }
60
+ self._supportive_elements = {
 
 
61
  'empathy': [
62
  'understand', 'hear', 'feel', 'acknowledge', 'recognize',
63
  'appreciate', 'empathize', 'relate', 'comprehend'
 
79
  'agency', 'capable', 'resource', 'ability'
80
  ]
81
  }
82
+ self._crisis_indicators = [
 
 
83
  r'\b(?:want|going|plan)\s+to\s+(?:die|kill|end)\b',
84
  r'\b(?:no reason|point|hope)\s+(?:to|in)\s+(?:live|living|life)\b',
85
  r'\b(?:better off|world)\s+without\s+me\b',
86
  r'\bsuicide\s+(?:plan|method|attempt)\b',
87
  r'\b(?:final|last)\s+(?:goodbye|letter|message)\b'
88
  ]
89
+ self._negative_tone_words = [
 
 
90
  'stupid', 'idiot', 'dumb', 'pathetic', 'worthless',
91
  'loser', 'failure', 'weak', 'incompetent', 'useless'
92
  ]
93
+ self._dismissive_phrases = [
 
94
  'just get over it', 'stop complaining', 'not a big deal',
95
  'being dramatic', 'overreacting', 'too sensitive'
96
  ]
97
+
98
+ def _run(self, response: str, context: Optional[dict] = None):
99
+ """
100
+ Pydantic and CrewAI-compatible single-tool version.
101
+ Returns a dictionary directly.
102
+ """
103
+ # Issues, warnings, suggestions collections
104
  issues = []
105
  warnings = []
106
  suggestions = []
107
 
108
+ # --- Prohibited Content ---
109
+ for category, patterns in self._prohibited_patterns.items():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  for pattern in patterns:
111
+ if re.search(pattern, response, re.IGNORECASE):
112
+ issues.append(f"Contains {category} advice/content")
 
 
113
  if category == "medical":
114
  suggestions.append("Replace with: 'Consider speaking with a healthcare professional'")
115
  elif category == "legal":
116
+ suggestions.append("For legal matters, consult with a qualified attorney")
117
  elif category == "financial":
118
+ suggestions.append("For financial decisions, consider consulting a financial advisor")
119
  elif category == "harmful":
120
  suggestions.append("Include crisis resources and express immediate concern for safety")
121
  elif category == "absolute":
122
  suggestions.append("Use qualifying language like 'often', 'might', 'could' instead of absolutes")
123
  break
124
+ # --- Sentiment/Tone ---
 
 
 
 
 
 
 
 
 
 
 
 
125
  try:
126
+ sentiment_result = self._sentiment_analyzer(response[:512])[0]
 
127
  sentiment_label = sentiment_result['label']
128
+ if '1' in sentiment_label or '2' in sentiment_label:
129
+ warnings.append("Response tone is too negative")
 
 
130
  suggestions.append("Add more supportive and hopeful language")
131
+ except Exception:
132
  pass
133
+ # --- Negative words ---
134
+ found_negative = [word for word in self._negative_tone_words if word in response.lower()]
 
 
135
  if found_negative:
136
+ warnings.append(f"Contains negative/judgmental language: {', '.join(found_negative)}")
137
  suggestions.append("Replace judgmental terms with supportive language")
138
+ # --- Dismissive ---
139
+ found_dismissive = [phrase for phrase in self._dismissive_phrases if phrase in response.lower()]
 
140
  if found_dismissive:
141
+ warnings.append("Contains dismissive language")
142
  suggestions.append("Acknowledge and validate the person's feelings instead")
143
+ # --- Supportive Elements ---
144
+ text_lower = response.lower()
 
 
 
 
 
 
 
 
145
  missing_elements = []
146
+ for element, keywords in self._supportive_elements.items():
147
+ if not any(keyword in text_lower for keyword in keywords):
 
 
 
 
 
148
  missing_elements.append(element)
149
+ if missing_elements:
150
+ warnings.append(f"Missing supportive elements: {', '.join(missing_elements)}")
151
+ for miss in missing_elements:
152
+ if miss == 'empathy':
153
+ suggestions.append("Add empathetic language like 'I understand how difficult this must be'")
154
+ elif miss == 'validation':
155
+ suggestions.append("Validate their feelings with phrases like 'Your feelings are completely valid'")
156
+ elif miss == 'support':
157
+ suggestions.append("Express support with 'I'm here to support you through this'")
158
+ elif miss == 'hope':
159
+ suggestions.append("Include hopeful elements about growth and positive change")
160
+ elif miss == 'empowerment':
161
+ suggestions.append("Emphasize their agency and ability to make choices")
162
+ # --- Crisis detection from context ---
163
+ if context and context.get("user_input"):
164
+ for pattern in self._crisis_indicators:
165
+ if re.search(pattern, context["user_input"], re.IGNORECASE):
166
+ if "crisis" not in response.lower():
167
+ warnings.append("User may be in crisis but response doesn't address this")
168
+ suggestions.append("Include crisis resources and immediate support options")
169
+ # --- Confidence ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  confidence = 1.0
171
+ if issues:
172
+ confidence = 0.3 - (0.1 * len(issues))
173
+ confidence = max(0.0, confidence - 0.1 * len(warnings))
174
+ class ValidationTools:
175
+ def __init__(self, config=None):
176
+ self.validate_response_tool = ValidateResponseTool(config)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crew_config.py CHANGED
@@ -55,15 +55,7 @@ class PersonalCoachCrew:
55
  verbose=self.config.crew.verbose,
56
  allow_delegation=False,
57
  tools=[
58
- self.validation_tools.validate_user_input,
59
- self.validation_tools.validate_response,
60
- self.validation_tools._validate_response,
61
- self.validation_tools._check_prohibited_content,
62
- self.validation_tools._check_crisis_indicators,
63
- self.validation_tools._calculate_confidence,
64
- self.validation_tools._check_supportive_elements,
65
- self.validation_tools._check_tone,
66
- self.validation_tools._is_spam,
67
 
68
 
69
  ]
 
55
  verbose=self.config.crew.verbose,
56
  allow_delegation=False,
57
  tools=[
58
+ self.validation_tools.validate_response_tool
 
 
 
 
 
 
 
 
59
 
60
 
61
  ]