CoolFace
Apppublic

bitcloud2/Final_Assignment_Template

sourceHugging Faceupdated 1y agoView on Hugging Face
1likes
gemini_model.py183 linesDownload Raw Back to root
1import os2import time3 4import httpx5import warnings6from typing import List, Dict, Optional7from smolagents import ApiModel, ChatMessage8 9 10class GeminiApiModel(ApiModel):11    """12    ApiModel implementation using the Google Gemini API via direct HTTP requests.13    """14 15    def __init__(16        self,17        model_id: str = "gemini-pro",18        api_key: Optional[str] = None,19        **kwargs,20    ):21        """22        Initializes the GeminiApiModel.23 24        Args:25            model_id (str): The Gemini model ID to use (e.g., "gemini-pro").26            api_key (str, optional): Google AI Studio API key. Defaults to GEMINI_API_KEY environment variable.27            **kwargs: Additional keyword arguments passed to the parent ApiModel.28        """29        self.model_id = model_id30        # Prefer explicitly passed key, fallback to environment variable31        self.api_key = api_key if api_key else os.environ.get("GEMINI_API_KEY")32        if not self.api_key:33            warnings.warn(34                "GEMINI_API_KEY not provided via argument or environment variable. API calls will likely fail.",35                UserWarning,36            )37        # Gemini API doesn't inherently support complex role structures or function calling like OpenAI.38        # We'll flatten messages for simplicity.39        super().__init__(40            model_id=model_id,41            flatten_messages_as_text=True,  # Flatten messages to a single text prompt42            **kwargs,43        )44 45    def create_client(self):46        """No dedicated client needed as we use httpx directly."""47        return None  # Or potentially return httpx client if reused48 49    def __call__(50        self,51        messages: List[Dict[str, str]],52        stop_sequences: Optional[53            List[str]54        ] = None,  # Note: Gemini API might not support stop sequences directly here55        grammar: Optional[56            str57        ] = None,  # Note: Gemini API doesn't support grammar directly58        tools_to_call_from: Optional[59            List["Tool"]60        ] = None,  # Note: Basic Gemini API doesn't support tools61        **kwargs,62    ) -> ChatMessage:63        """64        Calls the Google Gemini API with the provided messages.65 66        Args:67            messages: A list of message dictionaries (e.g., [{'role': 'user', 'content': '...'}]).68            stop_sequences: Optional stop sequences (may not be supported).69            grammar: Optional grammar constraint (not supported).70            tools_to_call_from: Optional list of tools (not supported).71            **kwargs: Additional keyword arguments.72 73        Returns:74            A ChatMessage object containing the response.75        """76        if not self.api_key:77            raise ValueError("GEMINI_API_KEY is not set.")78 79        # Prepare the prompt by concatenating message content80        # The Gemini Pro basic API expects a simple text prompt.81        prompt = self._messages_to_prompt(messages)82        prompt += (83            "\n\n"84            + "If you have a result from a web search that looks helpful, please use httpx to get the HTML from the URL listed."85            + "You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise. If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string."86        )87        # print(f"--- Gemini API prompt: ---\n{prompt}\n--- End of prompt ---")88 89        url = f"https://generativelanguage.googleapis.com/v1beta/models/{self.model_id}:generateContent?key={self.api_key}"90        headers = {"Content-Type": "application/json"}91        # Construct the payload according to Gemini API requirements92        data = {"contents": [{"parts": [{"text": prompt}]}]}93 94        # Add generation config if provided via kwargs (optional)95        generation_config = {}96        if "temperature" in kwargs:97            generation_config["temperature"] = kwargs["temperature"]98        if "max_output_tokens" in kwargs:99            generation_config["maxOutputTokens"] = kwargs["max_output_tokens"]100        # Add other relevant config parameters here if needed101 102        if generation_config:103            data["generationConfig"] = generation_config104 105        # Handle stop sequences if provided (basic support)106        # Note: This is a best-effort addition, check Gemini API docs for formal support107        if stop_sequences:108            if "generationConfig" not in data:109                data["generationConfig"] = {}110            # Assuming Gemini API might support 'stopSequences' in generationConfig111            data["generationConfig"]["stopSequences"] = stop_sequences112 113        raw_response = None114        try:115            response = httpx.post(116                url, headers=headers, json=data, timeout=120.0117            )  # Increased timeout118            time.sleep(6)  # Add delay to respect rate limits119            response.raise_for_status()120            response_json = response.json()121            raw_response = response_json  # Store raw response122 123            # Parse the response - adjust based on actual Gemini API structure124            if "candidates" in response_json and response_json["candidates"]:125                part = response_json["candidates"][0]["content"]["parts"][0]126                if "text" in part:127                    content = part["text"]128                    # Check for "FINAL ANSWER: " and extract the rest of the string129                    final_answer_marker = "FINAL ANSWER: "130                    if final_answer_marker in content:131                        content = content.split(final_answer_marker)[-1].strip()132 133                    # Simulate token counts if available, otherwise default to 0134                    # The basic generateContent API might not return usage directly in the main response135                    # It might be in safetyRatings or other metadata if enabled/available.136                    # Setting to 0 for now as it's not reliably present in the simplest call.137                    self.last_input_token_count = 0138                    self.last_output_token_count = 0139                    # If usage data becomes available in response_json, parse it here:140                    # e.g., if response_json.get("usageMetadata"):141                    #    self.last_input_token_count = response_json["usageMetadata"].get("promptTokenCount", 0)142                    #    self.last_output_token_count = response_json["usageMetadata"].get("candidatesTokenCount", 0)143 144                    return ChatMessage(145                        role="assistant", content=content, raw=raw_response146                    )147 148            # Handle cases where the expected response structure isn't found149            error_content = f"Error or unexpected response format: {response_json}"150            return ChatMessage(151                role="assistant", content=error_content, raw=raw_response152            )153 154        except httpx.RequestError as exc:155            error_content = (156                f"An error occurred while requesting {exc.request.url!r}: {exc}"157            )158            return ChatMessage(159                role="assistant", content=error_content, raw={"error": str(exc)}160            )161        except httpx.HTTPStatusError as exc:162            error_content = f"Error response {exc.response.status_code} while requesting {exc.request.url!r}: {exc.response.text}"163            return ChatMessage(164                role="assistant",165                content=error_content,166                raw={167                    "error": str(exc),168                    "status_code": exc.response.status_code,169                    "response_text": exc.response.text,170                },171            )172        except Exception as e:173            error_content = f"An unexpected error occurred: {e}"174            return ChatMessage(175                role="assistant", content=error_content, raw={"error": str(e)}176            )177 178    def _messages_to_prompt(self, messages: List[Dict[str, str]]) -> str:179        """Converts a list of messages into a single string prompt."""180        # Simple concatenation, could be more sophisticated based on roles if needed181        # Ensure we handle cases where 'content' might not be a string (though it should be)182        return "\n".join([str(msg.get("content", "")) for msg in messages])183