From 5c42d1b3f2dda952b8fb55bf5cb16593e1f1f0eb Mon Sep 17 00:00:00 2001 From: Cyrus Gracias Date: Fri, 29 Aug 2025 16:48:18 +0530 Subject: [PATCH] Add support for open router without tests --- DOCUMENTATION.md | 33 +++++++++- README.md | 15 ++++- .../ai/providers/simple_unified.py | 64 ++++++++++++++++++- git_smart_squash/cli.py | 2 +- git_smart_squash/simple_config.py | 1 + 5 files changed, 109 insertions(+), 6 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 717532f..8a42452 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -144,7 +144,7 @@ git-smart-squash [OPTIONS] | Option | Short | Description | Default | |--------|-------|-------------|---------| | `--base BASE` | | Base branch to compare against | Config file or `main` | -| `--ai-provider PROVIDER` | | AI provider to use (`local`, `openai`, `anthropic`, `gemini`) | Config file or `local` | +| `--ai-provider PROVIDER` | | AI provider to use (`local`, `openai`, `anthropic`, `gemini`, `openrouter`) | Config file or `local` | | `--model MODEL` | | Specific model to use | Provider default | | `--config PATH` | | Path to configuration file | Auto-detected | | `--auto-apply` | | Apply changes without confirmation prompt | Config file or `false` | @@ -269,6 +269,29 @@ export GEMINI_API_KEY="..." **Pricing:** ~$0.01 per typical use +### OpenRouter + +**Advantages:** +- Access to 100+ models from multiple providers through a single API +- Competitive pricing with flexible model selection +- Unified interface for various AI providers +- Fast response times with global infrastructure +- No vendor lock-in - switch models easily + +**Setup:** +```bash +export OPENROUTER_API_KEY="sk-or-v1-..." +``` + +**Models:** +- `openai/gpt-5` (default) - Latest GPT-5 model with excellent performance +- `anthropic/claude-3.5-sonnet` - Claude Sonnet through OpenRouter +- `google/gemini-pro-1.5` - Gemini Pro through OpenRouter +- `meta-llama/llama-3.1-405b-instruct` - Llama 3.1 through OpenRouter +- Any other model available on OpenRouter - Specify with `--model` flag + +**Pricing:** Variable based on selected model (~$0.005 - $0.03 per typical use) + ## Configuration ### Configuration Hierarchy @@ -284,7 +307,7 @@ Git Smart Squash looks for configuration in this order: ```yaml # AI Provider Settings ai: - provider: local # or openai, anthropic, gemini + provider: local # or openai, anthropic, gemini, openrouter model: devstral # optional, uses provider default if not set # You can specify ANY model supported by the provider instructions: | # Optional custom instructions for AI @@ -316,6 +339,7 @@ auto_apply: false # Set to true to apply commits without confirmation | `OPENAI_API_KEY` | OpenAI API key | | `ANTHROPIC_API_KEY` | Anthropic API key | | `GEMINI_API_KEY` | Google Gemini API key | +| `OPENROUTER_API_KEY` | OpenRouter API key | | `OLLAMA_HOST` | Ollama server URL (default: http://localhost:11434) | ## How It Works @@ -405,6 +429,11 @@ git-smart-squash --ai-provider anthropic --model claude-3-haiku-20240307 git-smart-squash --ai-provider gemini --model gemini-1.5-flash git-smart-squash --ai-provider gemini --model gemini-ultra +# OpenRouter examples +git-smart-squash --ai-provider openrouter --model openai/gpt-5 +git-smart-squash --ai-provider openrouter --model anthropic/claude-3.5-sonnet +git-smart-squash --ai-provider openrouter --model google/gemini-pro-1.5 + # Local Ollama examples git-smart-squash --model codellama:13b git-smart-squash --model mistral:latest diff --git a/README.md b/README.md index 81d99ec..cd2865c 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,12 @@ export ANTHROPIC_API_KEY="your-key" export GEMINI_API_KEY="your-key" ``` +**Option C: OpenRouter (100+ models)** +```bash +export OPENROUTER_API_KEY="sk-or-v1-your-key" +# Then run with: git-smart-squash --ai-provider openrouter +``` + ### 3. Run ```bash @@ -68,7 +74,7 @@ That's it. Review the plan and approve. | Parameter | Description | Default | |-----------|-------------|---------| | `--base` | Base branch to compare against | Config file or `main` | -| `--ai-provider` | AI provider to use (openai, anthropic, local, gemini) | Configured in settings | +| `--ai-provider` | AI provider to use (openai, anthropic, local, gemini, openrouter) | Configured in settings | | `--model` | Specific AI model to use (see recommended models below) | Provider default | | `--config` | Path to custom configuration file | `.git-smart-squash.yml` or `~/.git-smart-squash.yml` | | `--auto-apply` | Apply commit plan without confirmation prompt | `false` | @@ -82,6 +88,7 @@ That's it. Review the plan and approve. - **OpenAI**: `gpt-4.1` (default) - **Anthropic**: `claude-sonnet-4-20250514` (default) - **Gemini**: `gemini-2.5-pro` (default) +- **OpenRouter**: `openai/gpt-5` (default) - **Local/Ollama**: `devstral` (default) ### Model Selection @@ -91,6 +98,10 @@ git-smart-squash --ai-provider openai --model gpt-4.1-mini # For local Ollama git-smart-squash --ai-provider local --model llama-3.1 + +# For OpenRouter (access to 100+ models) +git-smart-squash --ai-provider openrouter --model openai/gpt-5 +git-smart-squash --ai-provider openrouter --model anthropic/claude-3.5-sonnet ``` ## Custom Instructions @@ -130,6 +141,7 @@ git-smart-squash --base develop ### "I want to use a specific AI provider" ```bash git-smart-squash --ai-provider openai +git-smart-squash --ai-provider openrouter # Access to 100+ models ``` ## Safety @@ -157,6 +169,7 @@ git reset --hard your-branch-backup-[timestamp] | **OpenAI** | ~$0.01 | Cloud | | **Anthropic** | ~$0.01 | Cloud | | **Gemini** | ~$0.01 | Cloud | +| **OpenRouter** | ~$0.005-$0.03 | Cloud | ## Advanced Configuration (Optional) diff --git a/git_smart_squash/ai/providers/simple_unified.py b/git_smart_squash/ai/providers/simple_unified.py index cef416e..24ec131 100644 --- a/git_smart_squash/ai/providers/simple_unified.py +++ b/git_smart_squash/ai/providers/simple_unified.py @@ -17,7 +17,8 @@ class UnifiedAIProvider: 'local': 32000, # Ollama hard maximum 'openai': 1000000, # OpenAI hard maximum (1M tokens) 'gemini': 1000000, # Gemini hard maximum (1M tokens) - 'anthropic': 200000 # Anthropic hard maximum (200k tokens) + 'anthropic': 200000, # Anthropic hard maximum (200k tokens) + 'openrouter': 1000000 # OpenRouter hard maximum (1M tokens) } # Conservative defaults @@ -177,6 +178,8 @@ def generate(self, prompt: str) -> str: return self._generate_anthropic(prompt) elif self.provider_type == "gemini": return self._generate_gemini(prompt) + elif self.provider_type == "openrouter": + return self._generate_openrouter(prompt) else: raise ValueError(f"Unsupported provider: {self.provider_type}") @@ -439,4 +442,61 @@ def _generate_gemini(self, prompt: str) -> str: except ImportError: raise Exception("Google Generative AI library not installed. Run: pip install google-generativeai") except Exception as e: - raise Exception(f"Google Gemini generation failed: {e}") \ No newline at end of file + raise Exception(f"Google Gemini generation failed: {e}") + + + def _generate_openrouter(self, prompt: str) -> str: + """Generate using OpenRouter API with structured output enforcement.""" + try: + import openai + + api_key = os.getenv('OPENROUTER_API_KEY') + if not api_key: + raise Exception("OPENROUTER_API_KEY environment variable not set") + + # Calculate dynamic parameters + params = self._calculate_dynamic_params(prompt) + + # Use provider-level context limit + model_context_limit = self.PROVIDER_MAX_CONTEXT_TOKENS.get('openrouter', 1000000) + + # Check if prompt exceeds model context limit + if params["prompt_tokens"] > model_context_limit - 1000: # Reserve 1000 for response + raise Exception(f"Prompt ({params['prompt_tokens']} tokens) exceeds {self.config.ai.model} context limit ({model_context_limit}). Consider reducing diff size.") + + # Warn if prompt is large but manageable + if params["prompt_tokens"] > model_context_limit * 0.7: + logger.warning(f"Large prompt ({params['prompt_tokens']} tokens) approaching {self.config.ai.model} context limit.") + + client = openai.OpenAI( + base_url="https://openrouter.ai/api/v1", + api_key=api_key + ) + + response = client.chat.completions.create( + model=self.config.ai.model, + messages=[{"role": "user", "content": prompt}], + max_tokens=self.MAX_PREDICT_TOKENS, + temperature=0.7, + response_format={ + "type": "json_schema", + "json_schema": { + "name": "commit_plan", + "schema": self.COMMIT_SCHEMA, + "strict": True + } + } + ) + + # Check if response was truncated + if response.choices[0].finish_reason == "length": + logger.warning(f"OpenAI response truncated at {self.MAX_PREDICT_TOKENS} tokens. Consider reducing diff size.") + + # Return the full structured response + content = response.choices[0].message.content + return content # OpenAI already returns the correct format with json_schema + + except ImportError: + raise Exception("OpenAI library not installed. Run: pip install openai") + except Exception as e: + raise Exception(f"OpenAI generation failed: {e}") \ No newline at end of file diff --git a/git_smart_squash/cli.py b/git_smart_squash/cli.py index 929d73d..d6008c7 100644 --- a/git_smart_squash/cli.py +++ b/git_smart_squash/cli.py @@ -79,7 +79,7 @@ def create_parser(self) -> argparse.ArgumentParser: parser.add_argument( '--ai-provider', - choices=['openai', 'anthropic', 'local', 'gemini'], + choices=['openai', 'anthropic', 'local', 'gemini', 'openrouter'], help='AI provider to use' ) diff --git a/git_smart_squash/simple_config.py b/git_smart_squash/simple_config.py index 51d5225..be46422 100644 --- a/git_smart_squash/simple_config.py +++ b/git_smart_squash/simple_config.py @@ -52,6 +52,7 @@ def _get_default_model(self, provider: str) -> str: 'openai': 'gpt-4.1', 'anthropic': 'claude-sonnet-4-20250514', # Claude Sonnet 4 model 'gemini': 'gemini-2.5-pro' # Gemini 2.5 Pro model + 'openrouter': 'openai/gpt-5' # OpenAI GPT-5 model } return defaults.get(provider, 'devstral')