t2m / mllm_tools /github.py
thanhkt's picture
Upload 26 files
8fb7841 verified
# filepath: d:\Theory2Manim-2\Theory2Manim\mllm_tools\github.py
import json
import re
from typing import List, Dict, Any, Union, Optional
import io
import os
import base64
from PIL import Image
import mimetypes
import litellm
from litellm import completion, completion_cost
from dotenv import load_dotenv
load_dotenv()
class GitHubModelsWrapper:
"""Wrapper for GitHub Models using LiteLLM to support multiple GitHub hosted models"""
def __init__(
self,
model_name: str = "github/gpt-4o",
temperature: float = 0.7,
print_cost: bool = False,
verbose: bool = False,
use_langfuse: bool = True,
github_token: Optional[str] = None
):
"""
Initialize the GitHub Models wrapper
Args:
model_name: Name of the GitHub model to use (e.g. "github/gpt-4o", "github/gpt-4o-mini",
"github/o1-preview", "github/claude-3-5-sonnet", "github/phi-3.5-mini-instruct")
temperature: Temperature for completion
print_cost: Whether to print the cost of the completion
verbose: Whether to print verbose output
use_langfuse: Whether to enable Langfuse logging
github_token: GitHub token for authentication (if not provided, will use GITHUB_TOKEN env var)
"""
self.model_name = model_name
self.temperature = temperature
self.print_cost = print_cost
self.verbose = verbose
self.accumulated_cost = 0
# Set up GitHub token
self.github_token = github_token or os.getenv('GITHUB_TOKEN')
if not self.github_token:
raise ValueError("GitHub token is required. Please set GITHUB_TOKEN environment variable or pass github_token parameter.")
# Set environment variable for LiteLLM
os.environ['GITHUB_TOKEN'] = self.github_token
if self.verbose:
os.environ['LITELLM_LOG'] = 'DEBUG'
# Set langfuse callback only if enabled
if use_langfuse:
litellm.success_callback = ["langfuse"]
litellm.failure_callback = ["langfuse"]
def _encode_file(self, file_path: Union[str, Image.Image]) -> str:
"""
Encode local file or PIL Image to base64 string
Args:
file_path: Path to local file or PIL Image object
Returns:
Base64 encoded file string
"""
if isinstance(file_path, Image.Image):
buffered = io.BytesIO()
file_path.save(buffered, format="PNG")
return base64.b64encode(buffered.getvalue()).decode("utf-8")
else:
with open(file_path, "rb") as file:
return base64.b64encode(file.read()).decode("utf-8")
def _get_mime_type(self, file_path: str) -> str:
"""
Get the MIME type of a file based on its extension
Args:
file_path: Path to the file
Returns:
MIME type as a string (e.g., "image/jpeg", "audio/mp3")
"""
mime_type, _ = mimetypes.guess_type(file_path)
if mime_type is None:
raise ValueError(f"Unsupported file type: {file_path}")
return mime_type
def _supports_vision(self, model_name: str) -> bool:
"""
Check if the model supports vision/image processing
Args:
model_name: Name of the model
Returns:
True if model supports vision, False otherwise
"""
vision_models = [
"gpt-4o",
"gpt-4o-mini",
"claude-3-5-sonnet",
"claude-3-haiku"
]
# Extract model name without the github/ prefix
clean_model_name = model_name.replace("github/", "")
return any(vision_model in clean_model_name for vision_model in vision_models)
def __call__(self, messages: List[Dict[str, Any]], metadata: Optional[Dict[str, Any]] = None) -> str:
"""
Process messages and return completion
Args:
messages: List of message dictionaries with 'type' and 'content' keys
metadata: Optional metadata to pass to litellm completion, e.g. for Langfuse tracking
Returns:
Generated text response
"""
if metadata is None:
metadata = {}
metadata["trace_name"] = f"github-models-completion-{self.model_name}"
# Convert messages to LiteLLM format
formatted_messages = []
for msg in messages:
if msg["type"] == "text":
formatted_messages.append({
"role": "user",
"content": [{"type": "text", "text": msg["content"]}]
})
elif msg["type"] == "image":
# Check if model supports vision
if not self._supports_vision(self.model_name):
raise ValueError(f"Model {self.model_name} does not support image processing")
# Check if content is a local file path or PIL Image
if isinstance(msg["content"], Image.Image) or os.path.isfile(msg["content"]):
try:
if isinstance(msg["content"], Image.Image):
mime_type = "image/png"
else:
mime_type = self._get_mime_type(msg["content"])
base64_data = self._encode_file(msg["content"])
data_url = f"data:{mime_type};base64,{base64_data}"
except ValueError as e:
print(f"Error processing file {msg['content']}: {e}")
continue
else:
data_url = msg["content"]
# Format for vision-capable models
formatted_messages.append({
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": data_url,
"detail": "high"
}
}
]
})
else:
raise ValueError(f"Unsupported message type: {msg['type']}. GitHub models currently support 'text' and 'image' types.")
try:
# Check if it's an o-series model (like o1-preview, o1-mini)
if (re.match(r".*o1.*", self.model_name)):
# O-series models don't support temperature and have reasoning_effort
response = completion(
model=self.model_name,
messages=formatted_messages,
reasoning_effort="medium", # Options: "low", "medium", "high"
metadata=metadata,
max_retries=3
)
else:
response = completion(
model=self.model_name,
messages=formatted_messages,
temperature=self.temperature,
metadata=metadata,
max_retries=3
)
if self.print_cost:
try:
# Note: GitHub Models may not provide cost information
cost = completion_cost(completion_response=response)
if cost is not None:
self.accumulated_cost += cost
print(f"Cost: ${float(cost):.10f}")
print(f"Accumulated Cost: ${self.accumulated_cost:.10f}")
else:
print("Cost information not available for GitHub Models")
except Exception as e:
print(f"Could not calculate cost: {e}")
content = response.choices[0].message.content
if content is None:
print(f"Got null response from GitHub model. Full response: {response}")
return ""
return content
except Exception as e:
print(f"Error in GitHub model completion: {e}")
return str(e)
def create_github_model_wrapper(model_name: str = "github/gpt-4o", **kwargs) -> GitHubModelsWrapper:
"""
Convenience function to create a GitHub Models wrapper
Args:
model_name: GitHub model name (e.g., "github/gpt-4o", "github/claude-3-5-sonnet")
**kwargs: Additional arguments passed to GitHubModelsWrapper
Returns:
Configured GitHubModelsWrapper instance
Example:
>>> # Create a wrapper for GPT-4o
>>> wrapper = create_github_model_wrapper("github/gpt-4o", temperature=0.3)
>>>
>>> # Use it for text generation
>>> response = wrapper([{"type": "text", "content": "Explain quantum computing"}])
>>>
>>> # Use it for vision (if model supports it)
>>> response = wrapper([
... {"type": "text", "content": "What's in this image?"},
... {"type": "image", "content": "path/to/image.jpg"}
... ])
"""
return GitHubModelsWrapper(model_name=model_name, **kwargs)
# Available GitHub Models (as of the documentation)
AVAILABLE_MODELS = {
# GPT Models
"gpt-4o": "github/gpt-4o",
"gpt-4o-mini": "github/gpt-4o-mini",
"o1-preview": "github/o1-preview",
"o1-mini": "github/o1-mini",
"gpt-4.1": "github/gpt-4.1",
# Phi Models
"phi-3-5-mini-instruct": "github/phi-3.5-mini-instruct",
"phi-3-5-moe-instruct": "github/phi-3.5-moe-instruct",
# Llama Models
"llama-3.1-405b-instruct": "github/llama-3.1-405b-instruct",
"llama-3.1-70b-instruct": "github/llama-3.1-70b-instruct",
"llama-3.1-8b-instruct": "github/llama-3.1-8b-instruct",
# Mistral Models
"mistral-large": "github/mistral-large",
"mistral-large-2407": "github/mistral-large-2407",
"mistral-nemo": "github/mistral-nemo",
"mistral-small": "github/mistral-small",
# Cohere Models
"cohere-command-r": "github/cohere-command-r",
"cohere-command-r-plus": "github/cohere-command-r-plus",
# AI21 Models
"ai21-jamba-1.5-large": "github/ai21-jamba-1.5-large",
"ai21-jamba-1.5-mini": "github/ai21-jamba-1.5-mini"
}
def list_available_models() -> Dict[str, str]:
"""
Get a dictionary of available GitHub models
Returns:
Dictionary mapping friendly names to full model names
"""
return AVAILABLE_MODELS.copy()
if __name__ == "__main__":
# Example usage
print("Available GitHub Models:")
for friendly_name, full_name in AVAILABLE_MODELS.items():
print(f" {friendly_name}: {full_name}")
# Example of creating a wrapper (requires GITHUB_TOKEN environment variable)
try:
wrapper = create_github_model_wrapper("github/gpt-4o-mini", temperature=0.3)
print("\nGitHub Models wrapper created successfully!")
# Test with a simple text prompt
response = wrapper([{"type": "text", "content": "Hello! Can you confirm you're working?"}])
print(f"Response: {response}")
except Exception as e:
print(f"Error creating wrapper: {e}")
print("Make sure to set GITHUB_TOKEN environment variable")