Download backend/intelligence/llm_client.py from Tremick/PIOE: direct link, hf CLI and curl.
- Browser
- Download file 14.2 kB
-
https://huggingface.co/spaces/Tremick/PIOE/resolve/main/backend/intelligence/llm_client.py
- Command line
-
hf download hf://spaces/Tremick/PIOE/backend/intelligence/llm_client.py
-
curl -L -o llm_client.py https://huggingface.co/spaces/Tremick/PIOE/resolve/main/backend/intelligence/llm_client.py
14.2 kB
| """ | |
| PIOE LLM Client Abstraction Layer | |
| Supports Gemini (default) and OpenAI as providers. | |
| """ | |
| from abc import ABC, abstractmethod | |
| from typing import Optional | |
| import json | |
| from ..config import get_settings | |
| class BaseLLMClient(ABC): | |
| """Abstract base class for LLM providers.""" | |
| def classify(self, text: str) -> dict: | |
| """Classify opportunity text into category and domain.""" | |
| pass | |
| def summarize(self, text: str, max_length: int = 150) -> str: | |
| """Generate concise summary of opportunity.""" | |
| pass | |
| def recommend_action(self, opportunity: dict) -> dict: | |
| """Recommend action based on opportunity context.""" | |
| pass | |
| def extract_metadata(self, text: str) -> dict: | |
| """Extract structured metadata (deadline, location, reward, etc.).""" | |
| pass | |
| class GeminiClient(BaseLLMClient): | |
| """Google Gemini implementation.""" | |
| def __init__(self, api_key: str): | |
| import google.generativeai as genai | |
| genai.configure(api_key=api_key) | |
| self.model = genai.GenerativeModel('gemini-2.5-flash') | |
| def _generate(self, prompt: str, as_json: bool = False) -> str: | |
| """Generate response from Gemini.""" | |
| response = self.model.generate_content(prompt) | |
| return response.text | |
| def classify(self, text: str) -> dict: | |
| """Classify opportunity into category and domain.""" | |
| prompt = f"""Analyze this opportunity and classify it. Return JSON only. | |
| TEXT: {text[:2000]} | |
| Return this exact JSON structure: | |
| {{ | |
| "category": "one of: scholarship, fellowship, internship, job, research, hackathon, competition, grant, conference, open_source, investment, weak_signal, other", | |
| "domain": "one of: ai, computer_vision, robotics, finance, crypto, academia, mixed", | |
| "confidence": 0.0 to 1.0 | |
| }}""" | |
| try: | |
| result = self._generate(prompt) | |
| # Extract JSON from response | |
| start = result.find('{') | |
| end = result.rfind('}') + 1 | |
| if start != -1 and end > start: | |
| return json.loads(result[start:end]) | |
| except Exception as e: | |
| print(f"Classification error: {e}") | |
| return {"category": "other", "domain": "mixed", "confidence": 0.0} | |
| def summarize(self, text: str, max_length: int = 150) -> str: | |
| """Generate concise summary.""" | |
| prompt = f"""Summarize this opportunity in {max_length} characters or less. | |
| Focus on: what it is, who it's for, and deadline if any. | |
| TEXT: {text[:2000]} | |
| Return only the summary, no quotes or labels.""" | |
| try: | |
| return self._generate(prompt).strip()[:max_length] | |
| except Exception as e: | |
| print(f"Summary error: {e}") | |
| return text[:max_length] | |
| def recommend_action(self, opportunity: dict) -> dict: | |
| """ | |
| PIOE 2.0 Enhanced Action Guidance. | |
| Returns comprehensive recommendations for how to approach the opportunity. | |
| """ | |
| prompt = f"""You are an expert career and opportunity advisor. Analyze this opportunity and provide detailed action guidance. | |
| OPPORTUNITY DETAILS: | |
| - Title: {opportunity.get('title', '')} | |
| - Category: {opportunity.get('category', '')} | |
| - Domain: {opportunity.get('domain', '')} | |
| - Deadline: {opportunity.get('deadline', 'No deadline specified')} | |
| - Description: {opportunity.get('raw_text', '')[:1500]} | |
| - ROI Score: {opportunity.get('roi_score', 'N/A')} | |
| - Competition Level: {opportunity.get('competition_level', 'N/A')} | |
| - Region: {opportunity.get('region', 'global')} | |
| USER CONTEXT: | |
| - Location: Nigeria, Africa | |
| - Interests: AI, Computer Vision, Robotics, Web3 | |
| - Status: Student/Early Career | |
| Provide strategic action guidance. Return JSON only: | |
| {{ | |
| "primary_action": "one of: apply_now, apply_prepared, track, save_for_later, deep_research, network_first, skip", | |
| "urgency": "one of: immediate, this_week, this_month, whenever, expired", | |
| "timing_status": "one of: early, optimal, late, unknown", | |
| "skills_to_highlight": ["skill1", "skill2", "skill3"], | |
| "portfolio_pieces": ["project type 1", "project type 2"], | |
| "preparation_steps": [ | |
| "step 1", | |
| "step 2", | |
| "step 3" | |
| ], | |
| "networking_tips": "who to contact or how to stand out (1 sentence)", | |
| "differentiation_angle": "what unique angle to take (1 sentence)", | |
| "success_probability": 0.0 to 1.0, | |
| "time_investment_hours": estimated hours to apply well, | |
| "risk_level": "low, medium, or high", | |
| "why": "brief strategic reasoning (max 100 chars)", | |
| "red_flags": ["any concerns"] or [] | |
| }}""" | |
| try: | |
| result = self._generate(prompt) | |
| start = result.find('{') | |
| end = result.rfind('}') + 1 | |
| if start != -1 and end > start: | |
| parsed = json.loads(result[start:end]) | |
| # Ensure required fields exist | |
| return { | |
| "primary_action": parsed.get("primary_action", "save_for_later"), | |
| "urgency": parsed.get("urgency", "whenever"), | |
| "timing_status": parsed.get("timing_status", "unknown"), | |
| "skills_to_highlight": parsed.get("skills_to_highlight", []), | |
| "portfolio_pieces": parsed.get("portfolio_pieces", []), | |
| "preparation_steps": parsed.get("preparation_steps", []), | |
| "networking_tips": parsed.get("networking_tips", ""), | |
| "differentiation_angle": parsed.get("differentiation_angle", ""), | |
| "success_probability": parsed.get("success_probability", 0.3), | |
| "time_investment_hours": parsed.get("time_investment_hours", 10), | |
| "risk_level": parsed.get("risk_level", "medium"), | |
| "why": parsed.get("why", "Review and decide"), | |
| "red_flags": parsed.get("red_flags", []), | |
| } | |
| except Exception as e: | |
| print(f"Action guidance error: {e}") | |
| # Fallback response | |
| return { | |
| "primary_action": "save_for_later", | |
| "urgency": "whenever", | |
| "timing_status": "unknown", | |
| "skills_to_highlight": [], | |
| "portfolio_pieces": [], | |
| "preparation_steps": ["Review the opportunity details", "Assess fit with your goals"], | |
| "networking_tips": "", | |
| "differentiation_angle": "", | |
| "success_probability": 0.3, | |
| "time_investment_hours": 10, | |
| "risk_level": "medium", | |
| "why": "Needs manual review", | |
| "red_flags": [], | |
| } | |
| def extract_metadata(self, text: str) -> dict: | |
| """Extract structured metadata from text.""" | |
| prompt = f"""Extract metadata from this opportunity text. Return JSON only. | |
| TEXT: {text[:2000]} | |
| Return this structure (use null for missing info): | |
| {{ | |
| "deadline": "YYYY-MM-DD or null", | |
| "location": "location or 'remote' or null", | |
| "reward": "amount or null", | |
| "organization": "org name or null", | |
| "requirements": ["skill1", "skill2"] or [], | |
| "url": "application url or null" | |
| }}""" | |
| try: | |
| result = self._generate(prompt) | |
| start = result.find('{') | |
| end = result.rfind('}') + 1 | |
| if start != -1 and end > start: | |
| return json.loads(result[start:end]) | |
| except Exception as e: | |
| print(f"Metadata extraction error: {e}") | |
| return {} | |
| class OpenAIClient(BaseLLMClient): | |
| """OpenAI implementation (fallback).""" | |
| def __init__(self, api_key: str): | |
| from openai import OpenAI | |
| self.client = OpenAI(api_key=api_key) | |
| self.model = "gpt-3.5-turbo" | |
| def _generate(self, prompt: str) -> str: | |
| """Generate response from OpenAI.""" | |
| response = self.client.chat.completions.create( | |
| model=self.model, | |
| messages=[{"role": "user", "content": prompt}], | |
| temperature=0.3 | |
| ) | |
| return response.choices[0].message.content | |
| def classify(self, text: str) -> dict: | |
| """Classify opportunity - same logic as Gemini.""" | |
| prompt = f"""Classify this opportunity. Return JSON only with keys: category, domain, confidence. | |
| Categories: scholarship, fellowship, internship, job, research, hackathon, competition, grant, conference, open_source, investment, weak_signal, other | |
| Domains: ai, computer_vision, robotics, finance, crypto, academia, mixed | |
| TEXT: {text[:2000]}""" | |
| try: | |
| result = self._generate(prompt) | |
| start = result.find('{') | |
| end = result.rfind('}') + 1 | |
| if start != -1 and end > start: | |
| return json.loads(result[start:end]) | |
| except Exception: | |
| pass | |
| return {"category": "other", "domain": "mixed", "confidence": 0.0} | |
| def summarize(self, text: str, max_length: int = 150) -> str: | |
| prompt = f"Summarize in {max_length} chars: {text[:2000]}" | |
| try: | |
| return self._generate(prompt).strip()[:max_length] | |
| except Exception: | |
| return text[:max_length] | |
| def recommend_action(self, opportunity: dict) -> dict: | |
| return {"action": "save", "reason": "Review later", "urgency": "low"} | |
| def extract_metadata(self, text: str) -> dict: | |
| return {} | |
| class LLMClient: | |
| """ | |
| Factory class that provides the configured LLM client. | |
| Uses Gemini by default, falls back to OpenAI if configured. | |
| """ | |
| _instance: Optional[BaseLLMClient] = None | |
| def get_client(cls) -> BaseLLMClient: | |
| """Get or create the LLM client instance.""" | |
| if cls._instance is None: | |
| settings = get_settings() | |
| if settings.ai_provider == "gemini" and settings.gemini_api_key: | |
| cls._instance = GeminiClient(settings.gemini_api_key) | |
| elif settings.openai_api_key: | |
| cls._instance = OpenAIClient(settings.openai_api_key) | |
| else: | |
| # Return a mock client if no API keys configured | |
| cls._instance = MockLLMClient() | |
| return cls._instance | |
| class MockLLMClient(BaseLLMClient): | |
| """Mock client for development without API keys. PIOE 2.0 compatible.""" | |
| def classify(self, text: str) -> dict: | |
| # Basic rule-based classification | |
| text_lower = text.lower() | |
| if any(kw in text_lower for kw in ["scholarship", "fellowship", "grant"]): | |
| return {"category": "scholarship", "domain": "academia", "confidence": 0.7} | |
| elif any(kw in text_lower for kw in ["hackathon", "competition", "challenge"]): | |
| return {"category": "hackathon", "domain": "ai", "confidence": 0.7} | |
| elif any(kw in text_lower for kw in ["internship", "intern"]): | |
| return {"category": "internship", "domain": "mixed", "confidence": 0.7} | |
| elif any(kw in text_lower for kw in ["job", "hiring", "position"]): | |
| return {"category": "job", "domain": "mixed", "confidence": 0.7} | |
| elif any(kw in text_lower for kw in ["bounty", "ecosystem", "solana", "ethereum"]): | |
| return {"category": "bounty", "domain": "crypto", "confidence": 0.7} | |
| elif any(kw in text_lower for kw in ["pitch", "demo day", "accelerator"]): | |
| return {"category": "pitch_event", "domain": "mixed", "confidence": 0.7} | |
| elif any(kw in text_lower for kw in ["collaborat", "partner", "looking for"]): | |
| return {"category": "collaboration", "domain": "mixed", "confidence": 0.6} | |
| return {"category": "other", "domain": "mixed", "confidence": 0.3} | |
| def summarize(self, text: str, max_length: int = 150) -> str: | |
| return text[:max_length] | |
| def recommend_action(self, opportunity: dict) -> dict: | |
| """PIOE 2.0 action guidance - rule-based fallback.""" | |
| category = opportunity.get("category", "other") | |
| # Category-based action mapping | |
| action_map = { | |
| "hackathon": ("apply_now", "this_week", ["Python", "ML/AI"], ["Previous hackathon project"]), | |
| "grant": ("apply_prepared", "this_month", ["Technical writing", "Project planning"], ["Open source contributions"]), | |
| "ecosystem_grant": ("apply_prepared", "this_month", ["Solidity/Rust", "Web3"], ["DApp or smart contract"]), | |
| "internship": ("apply_now", "this_week", ["Relevant coursework", "Projects"], ["GitHub portfolio"]), | |
| "scholarship": ("apply_prepared", "this_month", ["Academic excellence", "Leadership"], ["Research paper or thesis"]), | |
| "bounty": ("apply_now", "immediate", ["Specific tech stack"], ["Related code samples"]), | |
| "pitch_event": ("apply_prepared", "this_month", ["Presentation", "Business model"], ["Pitch deck", "Demo video"]), | |
| "collaboration": ("network_first", "whenever", ["Domain expertise"], ["Relevant projects"]), | |
| } | |
| action, urgency, skills, portfolio = action_map.get( | |
| category, | |
| ("save_for_later", "whenever", [], []) | |
| ) | |
| return { | |
| "primary_action": action, | |
| "urgency": urgency, | |
| "timing_status": "unknown", | |
| "skills_to_highlight": skills, | |
| "portfolio_pieces": portfolio, | |
| "preparation_steps": [ | |
| "Review the opportunity requirements", | |
| "Prepare relevant materials", | |
| "Submit before deadline" | |
| ], | |
| "networking_tips": "Research the organization and connect with past participants", | |
| "differentiation_angle": "Highlight unique projects and Africa/Nigeria perspective", | |
| "success_probability": 0.3, | |
| "time_investment_hours": 10, | |
| "risk_level": "medium", | |
| "why": f"Standard approach for {category}", | |
| "red_flags": [], | |
| } | |
| def extract_metadata(self, text: str) -> dict: | |
| return {} | |