vero_template / utils /gemini_client.py
omgy's picture
Update utils/gemini_client.py
cfefc09 verified
"""
Gemini AI Client
Handles all interactions with Google's Gemini 2.5 Flash API
"""
import json
import os
import sys
from typing import Any, Dict, List, Optional
import google.generativeai as genai
class GeminiClient:
"""Client for Google Gemini AI API"""
def __init__(self, api_key: Optional[str] = None):
"""
Initialize Gemini client
Args:
api_key: Google Gemini API key (optional, reads from env if not provided)
"""
self.api_key = api_key or os.getenv("GEMINI_API_KEY")
if not self.api_key:
raise ValueError("GEMINI_API_KEY not found in environment variables")
# Configure Gemini
genai.configure(api_key=self.api_key)
# Initialize model (Gemini 2.5 Flash)
self.model = genai.GenerativeModel("gemini-2.5-flash")
# Safety settings
self.safety_settings = [
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE",
},
{
"category": "HARM_CATEGORY_HATE_SPEECH",
"threshold": "BLOCK_MEDIUM_AND_ABOVE",
},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE",
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE",
},
]
print(f"✓ Gemini AI client initialized successfully", file=sys.stderr)
def generate_text(
self, prompt: str, temperature: float = 0.7, max_tokens: int = 2048
) -> str:
"""
Generate text using Gemini
Args:
prompt: Input prompt
temperature: Creativity level (0.0 to 1.0)
max_tokens: Maximum response length
Returns:
Generated text
"""
try:
generation_config = {
"temperature": temperature,
"max_output_tokens": max_tokens,
"top_p": 0.95,
"top_k": 40,
}
response = self.model.generate_content(
prompt,
generation_config=generation_config,
safety_settings=self.safety_settings,
)
# Handle multi-part responses
if hasattr(response, "text"):
try:
return response.text
except ValueError:
# Fallback to parts if simple text access fails
pass
# Extract text from parts
if response.candidates and len(response.candidates) > 0:
candidate = response.candidates[0]
if candidate.content and candidate.content.parts:
text_parts = []
for part in candidate.content.parts:
if hasattr(part, "text"):
text_parts.append(part.text)
return "".join(text_parts)
# If all else fails, return empty string
print(f"Warning: Could not extract text from response", file=sys.stderr)
return ""
except Exception as e:
print(f"Error generating text: {str(e)}", file=sys.stderr)
raise
def enhance_resume_description(self, description: str, role: str = "") -> str:
"""
Enhance a resume job description
Args:
description: Original description
role: Job role/title for context
Returns:
Enhanced description
"""
prompt = f"""You are a professional resume writer. Enhance the following job responsibility for a resume.
Role: {role}
Original Description: {description}
CRITICAL INSTRUCTIONS:
1. Write ONLY the enhanced description - NO options, NO choices, NO explanations
2. Do NOT include phrases like "Here are options" or "Choose one"
3. Write ONE final bullet point or sentence
4. Use strong action verbs (led, developed, implemented, optimized, etc.)
5. Focus on impact and measurable results when information allows
6. Keep it concise (1-2 sentences maximum)
7. Professional tone
8. Do NOT fabricate numbers or achievements not in the original
9. Write in past tense for completed roles
Write the enhanced description now (ONLY the text, nothing else):"""
enhanced = self.generate_text(prompt, temperature=0.4)
# Clean up response
enhanced = enhanced.strip()
enhanced = enhanced.replace("**", "").replace("*", "")
enhanced = enhanced.replace("Enhanced Description:", "").strip()
enhanced = enhanced.lstrip("- ").lstrip("• ")
return enhanced
def generate_cover_letter(self, data: Dict[str, Any]) -> str:
"""
Generate a personalized cover letter
Args:
data: Dictionary containing:
- name: Applicant name
- company: Company name
- position: Job position
- skills: List of relevant skills
- experience: Brief experience summary
- tone: Tone (formal, creative, technical)
Returns:
Complete cover letter text
"""
tone_guides = {
"formal": "Professional and formal business style",
"creative": "Engaging and creative while remaining professional",
"technical": "Technical and detail-oriented style",
}
tone = data.get("tone", "formal")
tone_guide = tone_guides.get(tone, tone_guides["formal"])
prompt = f"""Write a compelling cover letter with the following details:
Applicant Name: {data.get("name", "Applicant")}
Company: {data.get("company", "the company")}
Position: {data.get("position", "the position")}
Relevant Skills: {", ".join(data.get("skills", []))}
Experience Summary: {data.get("experience", "No experience provided")}
Tone: {tone_guide}
Structure:
1. Opening paragraph: Show enthusiasm and mention how you learned about the position
2. Body paragraphs (2-3): Highlight relevant skills and experiences
3. Closing paragraph: Express interest in an interview and thank them
Requirements:
- Personalized and specific to the role
- Highlight relevant achievements
- Professional formatting
- 3-4 paragraphs
- Do not include [Date] or address placeholders
Cover Letter:"""
return self.generate_text(prompt, temperature=0.7, max_tokens=1500)
def generate_proposal(self, data: Dict[str, Any]) -> str:
"""
Generate a business proposal
Args:
data: Dictionary containing:
- client_name: Client name
- project_title: Project title
- scope: Project scope
- timeline: Expected timeline
- budget: Budget estimate (optional)
- deliverables: List of deliverables
Returns:
Complete proposal text
"""
prompt = f"""Create a professional business proposal with the following details:
Client: {data.get("client_name", "Client")}
Project: {data.get("project_title", "Project")}
Scope: {data.get("scope", "Not specified")}
Timeline: {data.get("timeline", "To be determined")}
Budget: {data.get("budget", "To be discussed")}
Deliverables: {", ".join(data.get("deliverables", []))}
Structure:
1. Executive Summary
2. Project Overview
3. Scope of Work
4. Deliverables
5. Timeline
6. Investment (if budget provided)
7. Next Steps
Requirements:
- Professional and persuasive
- Clear and specific
- Well-structured with sections
- Professional tone
Proposal:"""
return self.generate_text(prompt, temperature=0.6, max_tokens=2048)
def enhance_contract_terms(self, contract_type: str, custom_terms: str = "") -> str:
"""
Generate or enhance contract terms
Args:
contract_type: Type of contract (freelance, service, nda, etc.)
custom_terms: Custom requirements or terms
Returns:
Contract terms text
"""
prompt = f"""Generate professional contract terms for a {contract_type} agreement.
Custom Requirements: {custom_terms if custom_terms else "Standard terms"}
Include:
1. Scope of Services
2. Payment Terms
3. Timeline and Deadlines
4. Intellectual Property Rights
5. Confidentiality
6. Termination Clause
7. Liability and Warranties
Requirements:
- Professional legal language
- Clear and specific
- Balanced for both parties
- Industry-standard terms
- Add disclaimer that this should be reviewed by legal counsel
Contract Terms:"""
return self.generate_text(prompt, temperature=0.4, max_tokens=2048)
def enhance_portfolio_description(self, project_data: Dict[str, Any]) -> str:
"""
Enhance portfolio project description
Args:
project_data: Dictionary containing:
- title/name: Project title
- description: Current description
- technologies/tech: List of technologies used
- role: Your role in the project
Returns:
Enhanced project description
"""
# Handle both 'name' and 'title' field names
project_name = project_data.get("name") or project_data.get("title", "Project")
# Handle both 'tech' and 'technologies' field names
technologies = project_data.get("technologies") or project_data.get("tech", [])
if not technologies:
technologies = []
# Get description
description = project_data.get("description", "")
# If description is empty, return empty to avoid generating fake content
if not description:
print(
f"Warning: No description for project '{project_name}', skipping AI enhancement",
file=sys.stderr,
)
return description
prompt = f"""You are a professional resume writer. Enhance the following project description for a resume.
Project Name: {project_name}
Current Description: {description}
Technologies Used: {", ".join(technologies) if technologies else "Not specified"}
Your Role: {project_data.get("role", "Developer")}
CRITICAL INSTRUCTIONS:
1. Respond with ONLY the enhanced description text - NO options, NO choices, NO explanations
2. Do NOT include phrases like "Here are options" or "Choose one"
3. Do NOT include numbered lists or multiple versions
4. Write ONE final, polished description in 2-4 sentences
5. Use strong action verbs (developed, engineered, implemented, built)
6. Quantify impact where the original description allows
7. Highlight technical skills and problem-solving
8. Keep it professional and concise
9. Base it ONLY on information provided - do NOT invent features
10. Write in past tense if project is complete, present tense if ongoing
Write the enhanced description now (ONLY the description, nothing else):"""
enhanced = self.generate_text(prompt, temperature=0.4)
# Clean up the response - remove any unwanted formatting
enhanced = enhanced.strip()
# Remove common AI response patterns
unwanted_patterns = [
"Here are",
"Here's",
"Option 1",
"Option 2",
"Option 3",
"**Option",
"Choose one",
"Enhanced Description:",
"Final Description:",
]
# If response contains multiple options, take only the first paragraph
if any(pattern.lower() in enhanced.lower() for pattern in unwanted_patterns):
print(
f"Warning: AI returned multiple options, extracting first valid description",
file=sys.stderr,
)
# Split by double newline or numbered list patterns
paragraphs = enhanced.split("\n\n")
for para in paragraphs:
# Skip headers and option labels
if not any(p.lower() in para.lower() for p in unwanted_patterns):
if len(para.strip()) > 50: # Must be substantial
enhanced = para.strip()
break
# Remove markdown formatting
enhanced = enhanced.replace("**", "").replace("*", "")
enhanced = enhanced.replace("> ", "")
# Remove option prefixes
import re
enhanced = re.sub(
r"^\*\*Option \d+.*?\*\*\s*", "", enhanced, flags=re.MULTILINE
)
enhanced = re.sub(r"^Option \d+.*?:\s*", "", enhanced, flags=re.MULTILINE)
enhanced = re.sub(r"^\d+\.\s*", "", enhanced)
return enhanced.strip()
def generate_skills_summary(
self, skills: List[str], experience_years: int = 0
) -> str:
"""
Generate a professional skills summary
Args:
skills: List of skills
experience_years: Years of experience
Returns:
Skills summary paragraph
"""
prompt = f"""You are a professional resume writer. Create a compelling professional summary.
Skills: {", ".join(skills)}
Years of Experience: {experience_years if experience_years > 0 else "Entry-level"}
CRITICAL INSTRUCTIONS:
1. Write ONLY the professional summary - NO options, NO choices, NO explanations
2. Do NOT include phrases like "Here are options" or "Choose one"
3. Write ONE final professional summary in 2-3 sentences
4. Highlight key technical strengths
5. Focus on value proposition
6. Use professional tone
7. Do NOT exaggerate or fabricate experience
8. Write in third person or first person as appropriate for resume
Write the professional summary now (ONLY the summary text, nothing else):"""
summary = self.generate_text(prompt, temperature=0.5, max_tokens=300)
# Clean up response
summary = summary.strip()
summary = summary.replace("**", "").replace("*", "")
summary = summary.replace("Professional Summary:", "").strip()
summary = summary.replace("Summary:", "").strip()
return summary
def improve_text_quality(self, text: str, style: str = "professional") -> str:
"""
General purpose text improvement
Args:
text: Text to improve
style: Desired style (professional, casual, technical, creative)
Returns:
Improved text
"""
prompt = f"""Improve the following text in a {style} style:
Original: {text}
Requirements:
- Fix grammar and spelling
- Improve clarity and flow
- Maintain original meaning
- Use appropriate vocabulary
- Keep similar length
Improved Text:"""
return self.generate_text(prompt, temperature=0.5)
def generate_json_structured(
self, prompt: str, schema: Dict[str, Any]
) -> Dict[str, Any]:
"""
Generate structured JSON response
Args:
prompt: Prompt for generation
schema: Expected JSON schema
Returns:
Dictionary with generated data
"""
full_prompt = f"""{prompt}
Respond with valid JSON matching this structure:
{json.dumps(schema, indent=2)}
JSON Response:"""
response_text = self.generate_text(full_prompt, temperature=0.5)
try:
# Extract JSON from response
if "```json" in response_text:
json_str = response_text.split("```json")[1].split("```")[0].strip()
elif "```" in response_text:
json_str = response_text.split("```")[1].split("```")[0].strip()
else:
json_str = response_text.strip()
return json.loads(json_str)
except Exception as e:
print(f"Error parsing JSON: {str(e)}", file=sys.stderr)
return {}
# Singleton instance
_gemini_client = None
def get_gemini_client() -> GeminiClient:
"""Get or create Gemini client singleton"""
global _gemini_client
if _gemini_client is None:
_gemini_client = GeminiClient()
return _gemini_client