feat(chat): Add post-processing for automatic markdown links

- Call _postprocess_links() on AI response before returning
- Ensures companies and people are linked even when AI doesn't format them
- Fixes inconsistent link generation by Gemini AI

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-01-27 14:04:53 +01:00
parent 827a4df1e2
commit 05d8e3f6ec

View File

@ -1132,11 +1132,74 @@ BŁĘDNIE (NIE RÓB - resetuje numerację):
user_id=user_id,
temperature=0.7
)
return response_text
# Post-process to ensure links are added even if AI didn't format them
return self._postprocess_links(response_text, context)
else:
# Legacy: direct API call (no centralized cost tracking)
response = self.model.generate_content(full_prompt)
return response.text
# Post-process to ensure links are added even if AI didn't format them
return self._postprocess_links(response.text, context)
def _postprocess_links(self, text: str, context: Dict) -> str:
"""
Post-process AI response to add markdown links for companies and people.
This ensures consistent linking regardless of AI behavior.
Args:
text: AI response text
context: Context dict with company_people data
Returns:
Text with names replaced by markdown links
"""
import re
# Build lookup dict: name -> url
name_to_url = {}
# Extract companies and people from company_people context
company_people = context.get('company_people', {})
for company_name, data in company_people.items():
# Add company
if data.get('profile'):
name_to_url[company_name] = data['profile']
# Add people
for person in data.get('people', []):
if person.get('name') and person.get('profile'):
name_to_url[person['name']] = person['profile']
# Also extract from companies list (context['companies'] has profile URLs)
# Companies format: list of dicts with 'name' and 'profile'
# This is populated by _company_to_compact_dict
# Sort by name length (longest first) to avoid partial replacements
sorted_names = sorted(name_to_url.keys(), key=len, reverse=True)
for name in sorted_names:
url = name_to_url[name]
if not name or not url:
continue
# Skip if already a markdown link
# Pattern: [Name](url) - already linked
already_linked = re.search(r'\[' + re.escape(name) + r'\]\([^)]+\)', text)
if already_linked:
continue
# Replace **Name** (bold) with [Name](url)
bold_pattern = r'\*\*' + re.escape(name) + r'\*\*'
if re.search(bold_pattern, text):
text = re.sub(bold_pattern, f'[{name}]({url})', text, count=1)
continue
# Replace plain "Name" at word boundaries (but not if already in link)
# Be careful not to replace inside existing markdown
plain_pattern = r'(?<!\[)(?<!\()' + re.escape(name) + r'(?!\])(?!\))'
if re.search(plain_pattern, text):
# Only replace first occurrence to avoid over-linking
text = re.sub(plain_pattern, f'[{name}]({url})', text, count=1)
return text
def _calculate_cost(self, input_tokens: int, output_tokens: int) -> float:
"""