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()