Spaces:
Running on Zero
Running on Zero
File size: 10,314 Bytes
0828c2c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 | """Response enhancement module for improving RAG outputs."""
import logging
import re
from .config_loader import get_config
logger = logging.getLogger(__name__)
class ResponseEnhancer:
"""Enhance and polish RAG responses for better user experience."""
def __init__(self):
"""Initialize response enhancer with configuration."""
self.config = get_config()
self.name = self.config.get("profile.name", "the candidate")
# Negative phrases to detect and rewrite
self.negative_patterns = [
(
r"(?:However|Unfortunately|I apologize),?\s*(?:I\s+)?don'?t\s+have\s+(?:any\s+)?(?:more\s+)?(?:specific\s+)?(?:information|details)\s+(?:about|on|regarding)\s+([^.!?]+)",
self._rewrite_no_info,
),
(
r"(?:I\s+)?(?:don'?t|do\s+not)\s+have\s+(?:any\s+)?(?:more\s+)?(?:information|details|data)\s+(?:about|on|regarding)\s+([^.!?]+)",
self._rewrite_no_info,
),
(
r"(?:I'm|I am)\s+(?:not\s+)?(?:un)?(?:sure|certain|aware)\s+(?:about|of)\s+([^.!?]+)",
self._rewrite_uncertain,
),
(
r"(?:I\s+)?(?:cannot|can'?t|unable to)\s+(?:provide|share|give)\s+(?:more\s+)?(?:information|details)\s+(?:about|on)\s+([^.!?]+)",
self._rewrite_cannot_provide,
),
]
# Closing enhancements (more modest)
self.positive_closings = [
"For more details, connecting directly would be helpful.",
"Additional information can be discussed in a direct conversation.",
"Feel free to reach out for more specific information.",
"Direct contact would provide more comprehensive details.",
]
def _rewrite_no_info(self, match: re.Match) -> str:
"""Rewrite 'don't have information' statements.
Args:
match: Regex match object
Returns:
Rewritten positive statement
"""
topic = match.group(1).strip()
rewrites = [
f"For more specific details about {topic}, it would be best to connect directly.",
"The available profile focuses on other aspects of the background.",
f"Additional information about {topic} can be discussed in a direct conversation.",
"The documented profile covers the key highlights.",
]
# Use hash to consistently select same rewrite for same topic
idx = hash(topic) % len(rewrites)
return rewrites[idx]
def _rewrite_uncertain(self, match: re.Match) -> str:
"""Rewrite uncertain statements.
Args:
match: Regex match object
Returns:
Rewritten confident statement
"""
topic = match.group(1).strip()
rewrites = [
f"Specific details about {topic} would be best discussed directly.",
f"For more information about {topic}, connecting directly would be helpful.",
f"Details about {topic} may be available through direct conversation.",
]
idx = hash(topic) % len(rewrites)
return rewrites[idx]
def _rewrite_cannot_provide(self, match: re.Match) -> str:
"""Rewrite 'cannot provide' statements.
Args:
match: Regex match object
Returns:
Rewritten forward-looking statement
"""
topic = match.group(1).strip()
rewrites = [
f"More detailed information about {topic} can be discussed directly.",
f"For additional context about {topic}, direct conversation would be helpful.",
f"Further details about {topic} are available through direct contact.",
]
idx = hash(topic) % len(rewrites)
return rewrites[idx]
def _add_positive_closing(self, text: str) -> str:
"""Add a modest closing statement if appropriate.
Args:
text: Response text
Returns:
Text with closing added if needed (only for very negative endings)
"""
# Check if response already mentions connecting or reaching out
forward_looking_patterns = [
r"(?:connect|contact|reach out|discuss|conversation|directly)",
r"(?:feel free|available|happy|welcome)\s+to",
]
for pattern in forward_looking_patterns:
if re.search(pattern, text, re.IGNORECASE):
return text # Already has some form of closing or contact suggestion
# Only add closing for clearly negative endings
very_negative_ending_patterns = [
r"(?:however|unfortunately).*(?:don'?t|cannot|can'?t|no)[^.!?]*[.!?]\s*$",
r"(?:not\s+available|not\s+found|no\s+information)[^.!?]*[.!?]\s*$",
]
for pattern in very_negative_ending_patterns:
if re.search(pattern, text, re.IGNORECASE):
# Add a modest closing
closing = self.positive_closings[hash(text) % len(self.positive_closings)]
return f"{text.rstrip()} {closing}" # Single space, not double newline
return text
def _fix_markdown_formatting(self, text: str) -> str:
"""Fix markdown formatting issues, particularly inline numbered lists.
Args:
text: Response text with potential formatting issues
Returns:
Text with properly formatted markdown
"""
# Simple but effective approach: Replace inline list patterns with line-broken versions
# Step 1: Fix inline numbered lists
# Pattern: Find any text that has "1. text 2. text" (not already on separate lines)
# We'll use a simple replace: put newline before each " N. " where N is a digit
# But first, protect already well-formatted lists (those at start of line)
lines = text.split("\n")
protected_lines = []
for line in lines:
stripped = line.strip()
# Check if line starts with a number (already formatted list)
if re.match(r"^\d+\.\s+", stripped):
# Protect by temporarily marking it
protected_lines.append("___LISTITEM___" + line)
else:
protected_lines.append(line)
text = "\n".join(protected_lines)
# Now fix inline lists: add line break before each " 1.", " 2.", etc.
# But only if it's in the middle of text (has non-whitespace before it)
text = re.sub(r"([^\n])\s+(\d+)\.\s+([A-Z])", r"\1\n\n\2. \3", text)
# Remove protection markers
text = text.replace("___LISTITEM___", "")
# Step 2: Ensure proper blank lines between elements
lines = text.split("\n")
formatted_lines = []
prev_was_list = False
for line in lines:
stripped = line.strip()
if not stripped:
# Don't add multiple blank lines in a row
if formatted_lines and formatted_lines[-1] != "":
formatted_lines.append("")
continue
is_list = bool(re.match(r"^\d+\.|^-\s+|^\*\s+", stripped))
# Add blank line transitions
if formatted_lines:
last_line = formatted_lines[-1]
# Add blank before first list item
if (
is_list
and not prev_was_list
and last_line != ""
or not is_list
and prev_was_list
and last_line != ""
):
formatted_lines.append("")
formatted_lines.append(line)
prev_was_list = is_list
return "\n".join(formatted_lines)
def enhance(self, response: str) -> str:
"""Enhance response by removing negative language and adding positive tone.
Args:
response: Original response text
Returns:
Enhanced response text
"""
if not response or len(response.strip()) == 0:
return response
enhanced = response
# Apply negative pattern rewrites
for pattern, rewrite_func in self.negative_patterns:
matches = list(re.finditer(pattern, enhanced, re.IGNORECASE))
# Process matches in reverse to maintain string positions
for match in reversed(matches):
replacement = rewrite_func(match)
enhanced = enhanced[: match.start()] + replacement + enhanced[match.end() :]
logger.debug(f"Rewrote negative phrase: '{match.group(0)}' -> '{replacement}'")
# Add positive closing if needed
enhanced = self._add_positive_closing(enhanced)
# Fix markdown formatting
enhanced = self._fix_markdown_formatting(enhanced)
# Clean up any double spaces or awkward punctuation
enhanced = re.sub(r" +", " ", enhanced) # Multiple spaces to single space
enhanced = re.sub(r"\s+([.,!?])", r"\1", enhanced)
enhanced = re.sub(r"([.!?])\s*([.!?])", r"\1", enhanced)
return enhanced.strip()
def enhance_with_context(self, response: str, question: str) -> str:
"""Enhance response with awareness of the question context.
Args:
response: Original response text
question: Original question
Returns:
Context-aware enhanced response
"""
enhanced = self.enhance(response)
# If the question is about job search/roles and response seems incomplete
# Add a modest closing about opportunities (not overly enthusiastic)
if re.search(
r"(?:job|role|position|opportunity|looking for|seeking)", question, re.IGNORECASE
) and not re.search(
r"(?:opportunity|interview|connect|discuss|reach out|contact)", enhanced, re.IGNORECASE
):
enhanced += (
" For current opportunities and detailed discussions, direct contact would be best."
)
return enhanced
def get_response_enhancer() -> ResponseEnhancer:
"""Get response enhancer instance.
Returns:
ResponseEnhancer instance
"""
return ResponseEnhancer()
|