Spaces:
Runtime error
Runtime error
| from __future__ import annotations | |
| import os | |
| from typing import Optional | |
| # Providers are optional; we import lazily | |
| class LLMClient: | |
| def __init__(self) -> None: | |
| self.provider = os.getenv("LLM_PROVIDER", "openai").lower() | |
| self.openai_key = os.getenv("OPENAI_API_KEY") | |
| self.anthropic_key = os.getenv("ANTHROPIC_API_KEY") | |
| self.gemini_key = os.getenv("GEMINI_API_KEY") | |
| self._openai_client = None | |
| self._anthropic_client = None | |
| self._gemini_model = None | |
| # Optional per-agent Gemini keys (fallback to default if missing) | |
| self._agent_keys = { | |
| "cv": os.getenv("GEMINI_API_KEY_CV") or self.gemini_key, | |
| "cover": os.getenv("GEMINI_API_KEY_COVER") or self.gemini_key, | |
| "chat": os.getenv("GEMINI_API_KEY_CHAT") or self.gemini_key, | |
| "parser": os.getenv("GEMINI_API_KEY_PARSER") or self.gemini_key, | |
| "match": os.getenv("GEMINI_API_KEY_MATCH") or self.gemini_key, | |
| "tailor": os.getenv("GEMINI_API_KEY_TAILOR") or self.gemini_key, | |
| } | |
| # Preload if configured | |
| if self.provider == "openai" and self.openai_key: | |
| try: | |
| from openai import OpenAI | |
| self._openai_client = OpenAI(api_key=self.openai_key) | |
| except Exception: | |
| self._openai_client = None | |
| elif self.provider == "anthropic" and self.anthropic_key: | |
| try: | |
| import anthropic | |
| self._anthropic_client = anthropic.Anthropic(api_key=self.anthropic_key) | |
| except Exception: | |
| self._anthropic_client = None | |
| elif self.provider == "gemini" and self.gemini_key: | |
| # We will lazily configure per-call to support per-agent keys | |
| try: | |
| import google.generativeai as genai # noqa: F401 | |
| except Exception: | |
| self._gemini_model = None | |
| def enabled(self) -> bool: | |
| if self.provider == "openai": | |
| return self._openai_client is not None | |
| if self.provider == "anthropic": | |
| return self._anthropic_client is not None | |
| if self.provider == "gemini": | |
| # If we have at least one usable key, consider enabled | |
| return any([self.gemini_key] + list(self._agent_keys.values())) | |
| return False | |
| def generate(self, system_prompt: str, user_prompt: str, model: Optional[str] = None, max_tokens: int = 1200, agent: Optional[str] = None) -> str: | |
| # Fallback behavior if no provider configured | |
| if not self.enabled: | |
| text = (system_prompt + "\n\n" + user_prompt)[: max_tokens * 4] | |
| return text | |
| provider = self.provider | |
| if provider == "openai": | |
| return self._generate_openai(system_prompt, user_prompt, model or os.getenv("LLM_MODEL", "gpt-4o-mini"), max_tokens) | |
| if provider == "anthropic": | |
| return self._generate_anthropic(system_prompt, user_prompt, model or os.getenv("LLM_MODEL", "claude-3-5-sonnet-latest"), max_tokens) | |
| if provider == "gemini": | |
| return self._generate_gemini(system_prompt, user_prompt, model or os.getenv("LLM_MODEL", "gemini-1.5-flash"), max_tokens, agent=agent) | |
| # Unknown provider fallback | |
| return (system_prompt + "\n\n" + user_prompt)[: max_tokens * 4] | |
| def _generate_openai(self, system_prompt: str, user_prompt: str, model: str, max_tokens: int) -> str: | |
| try: | |
| response = self._openai_client.chat.completions.create( | |
| model=model, | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_prompt}, | |
| ], | |
| temperature=0.4, | |
| max_tokens=max_tokens, | |
| ) | |
| return response.choices[0].message.content.strip() | |
| except Exception: | |
| return (system_prompt + "\n\n" + user_prompt)[: max_tokens * 4] | |
| def _generate_anthropic(self, system_prompt: str, user_prompt: str, model: str, max_tokens: int) -> str: | |
| try: | |
| msg = self._anthropic_client.messages.create( | |
| model=model, | |
| max_tokens=max_tokens, | |
| system=system_prompt, | |
| messages=[{"role": "user", "content": user_prompt}], | |
| temperature=0.4, | |
| ) | |
| # Anthropic returns a list of content blocks | |
| parts = [] | |
| for b in msg.content: | |
| if hasattr(b, "text"): | |
| parts.append(b.text) | |
| elif isinstance(b, dict) and b.get("type") == "text": | |
| parts.append(b.get("text", "")) | |
| return "\n".join(p for p in parts if p).strip() or (system_prompt + "\n\n" + user_prompt)[: max_tokens * 4] | |
| except Exception: | |
| return (system_prompt + "\n\n" + user_prompt)[: max_tokens * 4] | |
| def _generate_gemini(self, system_prompt: str, user_prompt: str, model: str, max_tokens: int, agent: Optional[str] = None) -> str: | |
| try: | |
| import google.generativeai as genai | |
| # Resolve API key per agent if provided | |
| api_key = self.gemini_key | |
| if agent: | |
| # Normalize agent to known keys | |
| norm = agent.lower() | |
| if norm == "general": | |
| norm = "chat" | |
| api_key = self._agent_keys.get(norm, self.gemini_key) | |
| # Configure and call | |
| genai.configure(api_key=api_key) | |
| model_instance = genai.GenerativeModel(model) | |
| prompt = system_prompt + "\n\n" + user_prompt | |
| resp = model_instance.generate_content(prompt) | |
| text = getattr(resp, "text", None) | |
| if not text and hasattr(resp, "candidates") and resp.candidates: | |
| text = resp.candidates[0].content.parts[0].text | |
| return (text or prompt)[: max_tokens * 4] | |
| except Exception: | |
| return (system_prompt + "\n\n" + user_prompt)[: max_tokens * 4] | |
| llm = LLMClient() |