Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |