Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .cursor/rules/rules-md.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
description:
globs:
alwaysApply: true
---

# RULES.md

- use [RULES.md](mdc:8-z-open-router/RULES.md)
114 changes: 64 additions & 50 deletions .rspec_status
Original file line number Diff line number Diff line change
@@ -1,50 +1,64 @@
example_id | status | run_time |
-------------------------------------------------- | ------ | --------------- |
./spec/ruby_llm/active_record/acts_as_spec.rb[1:1] | passed | 3.38 seconds |
./spec/ruby_llm/active_record/acts_as_spec.rb[1:2] | passed | 2.48 seconds |
./spec/ruby_llm/chat_content_spec.rb[1:1:1] | passed | 2.74 seconds |
./spec/ruby_llm/chat_content_spec.rb[1:1:2] | passed | 1.29 seconds |
./spec/ruby_llm/chat_content_spec.rb[1:1:3] | passed | 2.54 seconds |
./spec/ruby_llm/chat_content_spec.rb[1:2:1] | passed | 2.77 seconds |
./spec/ruby_llm/chat_content_spec.rb[1:2:2] | passed | 2.1 seconds |
./spec/ruby_llm/chat_pdf_spec.rb[1:1:1] | passed | 7.75 seconds |
./spec/ruby_llm/chat_pdf_spec.rb[1:1:2] | passed | 13.88 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:1:1] | passed | 1.02 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:1:2] | passed | 3.95 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:2:1] | passed | 0.4854 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:2:2] | passed | 1.37 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:3:1] | passed | 7.34 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:3:2] | passed | 19.22 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:4:1] | passed | 3.15 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:4:2] | passed | 2.51 seconds |
./spec/ruby_llm/chat_streaming_spec.rb[1:1:1] | passed | 0.65115 seconds |
./spec/ruby_llm/chat_streaming_spec.rb[1:1:2] | passed | 0.50907 seconds |
./spec/ruby_llm/chat_streaming_spec.rb[1:1:3] | passed | 6.69 seconds |
./spec/ruby_llm/chat_streaming_spec.rb[1:1:4] | passed | 0.70777 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:1] | passed | 4.23 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:2] | passed | 8.45 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:3] | passed | 8.22 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:4] | passed | 1.16 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:5] | passed | 2.73 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:6] | passed | 3.33 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:7] | passed | 1.76 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:8] | passed | 3 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:9] | passed | 4.47 seconds |
./spec/ruby_llm/embeddings_spec.rb[1:1:1:1] | passed | 0.33357 seconds |
./spec/ruby_llm/embeddings_spec.rb[1:1:1:2] | passed | 0.43632 seconds |
./spec/ruby_llm/embeddings_spec.rb[1:1:2:1] | passed | 0.65614 seconds |
./spec/ruby_llm/embeddings_spec.rb[1:1:2:2] | passed | 2.16 seconds |
./spec/ruby_llm/error_handling_spec.rb[1:1] | passed | 0.29366 seconds |
./spec/ruby_llm/image_generation_spec.rb[1:1:1] | passed | 14.16 seconds |
./spec/ruby_llm/image_generation_spec.rb[1:1:2] | passed | 16.22 seconds |
./spec/ruby_llm/image_generation_spec.rb[1:1:3] | passed | 9.1 seconds |
./spec/ruby_llm/image_generation_spec.rb[1:1:4] | passed | 0.00138 seconds |
./spec/ruby_llm/models_spec.rb[1:1:1] | passed | 0.01071 seconds |
./spec/ruby_llm/models_spec.rb[1:1:2] | passed | 0.00056 seconds |
./spec/ruby_llm/models_spec.rb[1:1:3] | passed | 0.00336 seconds |
./spec/ruby_llm/models_spec.rb[1:2:1] | passed | 0.00016 seconds |
./spec/ruby_llm/models_spec.rb[1:2:2] | passed | 0.00085 seconds |
./spec/ruby_llm/models_spec.rb[1:3:1] | passed | 1.44 seconds |
./spec/ruby_llm/models_spec.rb[1:3:2] | passed | 1.23 seconds |
./spec/ruby_llm/models_spec.rb[1:4:1] | passed | 0.0003 seconds |
./spec/ruby_llm/models_spec.rb[1:4:2] | passed | 0.00175 seconds |
example_id | status | run_time |
-------------------------------------------------------------- | ------ | --------------- |
./spec/ruby_llm/active_record/acts_as_spec.rb[1:1] | passed | 3.38 seconds |
./spec/ruby_llm/active_record/acts_as_spec.rb[1:2] | passed | 2.48 seconds |
./spec/ruby_llm/chat_content_spec.rb[1:1:1] | passed | 2.74 seconds |
./spec/ruby_llm/chat_content_spec.rb[1:1:2] | passed | 1.29 seconds |
./spec/ruby_llm/chat_content_spec.rb[1:1:3] | passed | 2.54 seconds |
./spec/ruby_llm/chat_content_spec.rb[1:2:1] | passed | 2.77 seconds |
./spec/ruby_llm/chat_content_spec.rb[1:2:2] | passed | 2.1 seconds |
./spec/ruby_llm/chat_pdf_spec.rb[1:1:1] | passed | 7.75 seconds |
./spec/ruby_llm/chat_pdf_spec.rb[1:1:2] | passed | 13.88 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:1:1] | passed | 1.02 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:1:2] | passed | 3.95 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:2:1] | passed | 0.4854 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:2:2] | passed | 1.37 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:3:1] | passed | 7.34 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:3:2] | passed | 19.22 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:4:1] | passed | 3.15 seconds |
./spec/ruby_llm/chat_spec.rb[1:1:4:2] | passed | 2.51 seconds |
./spec/ruby_llm/chat_streaming_spec.rb[1:1:1] | passed | 0.65115 seconds |
./spec/ruby_llm/chat_streaming_spec.rb[1:1:2] | passed | 0.50907 seconds |
./spec/ruby_llm/chat_streaming_spec.rb[1:1:3] | passed | 6.69 seconds |
./spec/ruby_llm/chat_streaming_spec.rb[1:1:4] | passed | 0.70777 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:1] | passed | 4.23 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:2] | passed | 8.45 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:3] | passed | 8.22 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:4] | passed | 1.16 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:5] | passed | 2.73 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:6] | passed | 3.33 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:7] | passed | 1.76 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:8] | passed | 3 seconds |
./spec/ruby_llm/chat_tools_spec.rb[1:1:9] | passed | 4.47 seconds |
./spec/ruby_llm/embeddings_spec.rb[1:1:1:1] | passed | 0.33357 seconds |
./spec/ruby_llm/embeddings_spec.rb[1:1:1:2] | passed | 0.43632 seconds |
./spec/ruby_llm/embeddings_spec.rb[1:1:2:1] | passed | 0.65614 seconds |
./spec/ruby_llm/embeddings_spec.rb[1:1:2:2] | passed | 2.16 seconds |
./spec/ruby_llm/error_handling_spec.rb[1:1] | passed | 0.29366 seconds |
./spec/ruby_llm/image_generation_spec.rb[1:1:1] | passed | 14.16 seconds |
./spec/ruby_llm/image_generation_spec.rb[1:1:2] | passed | 16.22 seconds |
./spec/ruby_llm/image_generation_spec.rb[1:1:3] | passed | 9.1 seconds |
./spec/ruby_llm/image_generation_spec.rb[1:1:4] | passed | 0.00138 seconds |
./spec/ruby_llm/models_spec.rb[1:1:1] | passed | 0.00352 seconds |
./spec/ruby_llm/models_spec.rb[1:1:2] | passed | 0.0003 seconds |
./spec/ruby_llm/models_spec.rb[1:1:3] | passed | 0.00154 seconds |
./spec/ruby_llm/models_spec.rb[1:2:1] | passed | 0.0001 seconds |
./spec/ruby_llm/models_spec.rb[1:2:2] | passed | 0.00053 seconds |
./spec/ruby_llm/models_spec.rb[1:3:1] | passed | 0.01088 seconds |
./spec/ruby_llm/models_spec.rb[1:3:2] | passed | 0.00072 seconds |
./spec/ruby_llm/providers/open_router/chat_spec.rb[1:2:1] | failed | 0.0002 seconds |
./spec/ruby_llm/providers/open_router/chat_spec.rb[1:2:2] | failed | 0.00876 seconds |
./spec/ruby_llm/providers/open_router/chat_spec.rb[1:3:1] | passed | 0.00173 seconds |
./spec/ruby_llm/providers/open_router/chat_spec.rb[1:3:2] | failed | 0.00041 seconds |
./spec/ruby_llm/providers/open_router/models_spec.rb[1:1:1] | failed | 0.00043 seconds |
./spec/ruby_llm/providers/open_router/models_spec.rb[1:1:2] | failed | 0.00005 seconds |
./spec/ruby_llm/providers/open_router/models_spec.rb[1:1:3] | failed | 0.00004 seconds |
./spec/ruby_llm/providers/open_router/models_spec.rb[1:2:1] | failed | 0.00005 seconds |
./spec/ruby_llm/providers/open_router/models_spec.rb[1:2:2] | failed | 0.00008 seconds |
./spec/ruby_llm/providers/open_router/models_spec.rb[1:2:3] | failed | 0.00003 seconds |
./spec/ruby_llm/providers/open_router/models_spec.rb[1:2:4] | failed | 0.00003 seconds |
./spec/ruby_llm/providers/open_router/models_spec.rb[1:3:1] | passed | 0.00124 seconds |
./spec/ruby_llm/providers/open_router/models_spec.rb[1:4:1] | passed | 0.00016 seconds |
./spec/ruby_llm/providers/open_router/models_spec.rb[1:4:2] | failed | 0.00037 seconds |
./spec/ruby_llm/providers/open_router/streaming_spec.rb[1:2:1] | failed | 0.00008 seconds |
./spec/ruby_llm/providers/open_router/streaming_spec.rb[1:2:2] | failed | 0.00003 seconds |
51 changes: 51 additions & 0 deletions 8-z-open-router/RULES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# RubyLLM Development Rules and Guidelines

* prefer a TDD/BDD approach
* avoid mocks, use real objects instead, consider VCR
* Follow the design patterns of this existing repo
* Maintain consistent code organization and structure
* Follow Ruby best practices and conventions

## Core Architecture

1. **Provider Implementation**
- Inherit from base Provider class
- Implement required interface methods
- Handle errors consistently
- Follow established retry patterns

2. **Testing**
- Write comprehensive RSpec tests
- Use VCR for HTTP interactions
- Maintain high test coverage
- Test error scenarios

3. **Code Style**
- Use Standard Ruby
- Write clear documentation
- Keep methods focused
- Follow Ruby naming conventions

4. **Error Handling**
- Use custom error classes
- Implement proper retries
- Provide meaningful messages
- Log appropriately

5. **Security**
- Never commit API keys
- Use environment variables
- Follow least privilege
- Handle sensitive data properly

6. **Performance**
- Implement caching where needed
- Monitor resource usage
- Handle timeouts properly
- Clean up resources

7. **Documentation**
- Document public interfaces
- Provide usage examples
- Keep docs up to date
- Include type information
55 changes: 55 additions & 0 deletions 8-z-open-router/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# OpenRouter Integration

## Context
I'm hitting Anthropic rate limits constantly with my own usage, and several community members have requested OpenRouter integration. This would allow access to a wider range of models through a single API key while maintaining RubyLLM's unified interface.

## Benefits
- Single API key to access models across providers
- Potential cost savings through OpenRouter's pricing
- Simplified rate limit management
- Access to exclusive models not available through direct provider integrations
- Fallback capabilities when primary providers are at capacity

## Implementation Considerations
- OpenRouter largely follows OpenAI's API structure, so we can likely adapt our existing OpenAI provider implementation
- Need to handle model ID mapping/translation to keep the model selection experience consistent
- Should implement proper error handling for OpenRouter-specific cases
- Will need to update the Models registry to include OpenRouter-accessible models

## Scope
Initial implementation should focus on:
- Chat completion support (highest priority)
- Embeddings support
- Model listing
- Full streaming support
- Tool use
- Image generation through DALL-E can be a second phase.

## Progress

### Completed
- ✅ Set up initial OpenRouter provider module structure
- ✅ Implemented basic OpenRouter API integration
- ✅ Fixed tests to run without requiring API keys for all providers
- ✅ Added proper test structure for OpenRouter tests
- ✅ Configured environment variables for OpenRouter API key

### In Progress
- 🔄 Implement model discovery and capabilities for OpenRouter
- 🔄 Implement chat completion support
- 🔄 Implement streaming support
- 🔄 Implement tool use support

### Next Steps
- Register OpenRouter provider in the main RubyLLM module
- Implement proper error handling for OpenRouter-specific cases
- Test with complex conversations, tools, and streaming
- Add documentation for OpenRouter integration
- Create examples for using OpenRouter with RubyLLM

## Technical Approach
- Follow the existing provider pattern used for OpenAI and Anthropic
- Adapt the OpenAI provider implementation where possible since OpenRouter follows a similar API structure
- Ensure proper model ID mapping/translation to maintain consistent model selection experience
- Implement comprehensive tests for all OpenRouter functionality
- Document the OpenRouter integration in the README and API documentation
2 changes: 2 additions & 0 deletions lib/ruby_llm/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ module RubyLLM
# RubyLLM.configure do |config|
# config.openai_api_key = ENV['OPENAI_API_KEY']
# config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
# config.open_router_api_key = ENV['OPENROUTER_API_KEY']
# end
class Configuration
attr_accessor :openai_api_key,
:anthropic_api_key,
:gemini_api_key,
:deepseek_api_key,
:open_router_api_key,
:default_model,
:default_embedding_model,
:default_image_model,
Expand Down
49 changes: 49 additions & 0 deletions lib/ruby_llm/providers/open_router.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require_relative 'open_router/capabilities'
require_relative 'open_router/chat'
require_relative 'open_router/streaming'
require_relative 'open_router/models'

module RubyLLM
module Providers
# OpenRouter API integration. Provides access to multiple LLM providers through a single API.
# Supports models from various providers including Anthropic, OpenAI, and others.
# Documentation: https://openrouter.ai/docs
module OpenRouter
extend Provider
extend OpenRouter::Chat
extend OpenRouter::Streaming
extend OpenRouter::Models

def self.extended(base)
base.extend(Provider)
base.extend(OpenRouter::Chat)
base.extend(OpenRouter::Streaming)
base.extend(OpenRouter::Models)
end

module_function

def api_base
'https://api.openrouter.ai/api/v1'
end

def headers
{
'Authorization' => "Bearer #{RubyLLM.config.open_router_api_key}",
'HTTP-Referer' => 'https://github.com/crmne/ruby_llm', # Required by OpenRouter
'X-Title' => 'RubyLLM' # Required by OpenRouter
}
end

def capabilities
OpenRouter::Capabilities
end

def slug
'open_router'
end
end
end
end
36 changes: 36 additions & 0 deletions lib/ruby_llm/providers/open_router/capabilities.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

module RubyLLM
module Providers
module OpenRouter
# Defines the capabilities of the OpenRouter API integration
module Capabilities
module_function

def chat?
true
end

def embeddings?
true
end

def images?
false # Will be implemented in phase 2
end

def streaming?
true
end

def tools?
true
end

def function_calling?
true
end
end
end
end
end
Loading