openrouter_client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.env.example +1 -0
- data/.rubocop.yml +68 -0
- data/.ruby-version +2 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +124 -0
- data/LICENSE +22 -0
- data/README.md +378 -0
- data/Rakefile +16 -0
- data/lib/openrouter/api_key.rb +136 -0
- data/lib/openrouter/client.rb +157 -0
- data/lib/openrouter/completion.rb +293 -0
- data/lib/openrouter/credit.rb +71 -0
- data/lib/openrouter/generation.rb +107 -0
- data/lib/openrouter/model.rb +164 -0
- data/lib/openrouter/stream.rb +105 -0
- data/lib/openrouter/version.rb +5 -0
- data/lib/openrouter.rb +119 -0
- data/openrouter_client.gemspec +33 -0
- data/rbi/openrouter/api_key.rbi +85 -0
- data/rbi/openrouter/client.rbi +56 -0
- data/rbi/openrouter/completion.rbi +152 -0
- data/rbi/openrouter/credit.rbi +42 -0
- data/rbi/openrouter/generation.rbi +69 -0
- data/rbi/openrouter/model.rbi +92 -0
- data/rbi/openrouter/openrouter.rbi +77 -0
- data/rbi/openrouter/stream.rbi +39 -0
- data/rbi/openrouter/version.rbi +6 -0
- data/sorbet/config +2 -0
- data/sorbet/rbi/.gitignore +2 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 83e60f6533fc86470014efb28c27ba4f714322de55613bb1a990f5c623922f35
|
|
4
|
+
data.tar.gz: 592ff75e2be878e65655321a402148e6386499176d388de7f0a8fad76bc537d5
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c2dfb5c991c3a135601517d73b30c5eb95466033ee35c1d837ecc262fd6b42c3e21125990359d7e3ce69838395c03bc350369ffdb3861af34401f07d8a7444b0
|
|
7
|
+
data.tar.gz: 3c1335769301ebb1a617d89a6189b3fadce8b7f7b6cf55256471bf6abc351a36de0a6a96d083cd94c7fdfec52e99af414b9e336c34a8c195734a4d5d49675a9b
|
data/.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
OPENROUTER_API_KEY=
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.3.9
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
|
|
6
|
+
Style/Documentation:
|
|
7
|
+
Enabled: false
|
|
8
|
+
|
|
9
|
+
Bundler/OrderedGems:
|
|
10
|
+
Enabled: false
|
|
11
|
+
|
|
12
|
+
Style/StringLiterals:
|
|
13
|
+
EnforcedStyle: double_quotes
|
|
14
|
+
|
|
15
|
+
Style/FrozenStringLiteralComment:
|
|
16
|
+
SafeAutoCorrect: true
|
|
17
|
+
EnforcedStyle: always_true
|
|
18
|
+
|
|
19
|
+
Metrics/MethodLength:
|
|
20
|
+
Enabled: false
|
|
21
|
+
|
|
22
|
+
Style/AccessorGrouping:
|
|
23
|
+
Enabled: false
|
|
24
|
+
|
|
25
|
+
Metrics/AbcSize:
|
|
26
|
+
Enabled: false
|
|
27
|
+
|
|
28
|
+
Metrics/CyclomaticComplexity:
|
|
29
|
+
Enabled: false
|
|
30
|
+
|
|
31
|
+
Metrics/ParameterLists:
|
|
32
|
+
Enabled: false
|
|
33
|
+
|
|
34
|
+
Metrics/ClassLength:
|
|
35
|
+
Enabled: false
|
|
36
|
+
|
|
37
|
+
Metrics/PerceivedComplexity:
|
|
38
|
+
Enabled: false
|
|
39
|
+
|
|
40
|
+
Style/HashSyntax:
|
|
41
|
+
Enabled: false
|
|
42
|
+
|
|
43
|
+
Layout/LineLength:
|
|
44
|
+
Max: 150
|
|
45
|
+
|
|
46
|
+
Naming/VariableNumber:
|
|
47
|
+
Exclude:
|
|
48
|
+
- "test/**/*_test.rb"
|
|
49
|
+
|
|
50
|
+
Naming/PredicateMethod:
|
|
51
|
+
AllowedMethods:
|
|
52
|
+
- destroy!
|
|
53
|
+
|
|
54
|
+
Lint/UnusedMethodArgument:
|
|
55
|
+
Exclude:
|
|
56
|
+
- "test/**/*_test.rb"
|
|
57
|
+
|
|
58
|
+
Naming/FileName:
|
|
59
|
+
Exclude:
|
|
60
|
+
- "lib/openrouter.rb"
|
|
61
|
+
|
|
62
|
+
Style/RedundantInitialize:
|
|
63
|
+
Exclude:
|
|
64
|
+
- "rbi/**/*.rbi"
|
|
65
|
+
|
|
66
|
+
Naming/BlockForwarding:
|
|
67
|
+
Exclude:
|
|
68
|
+
- "rbi/**/*.rbi"
|
data/.ruby-version
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
openrouter_client (0.1.0)
|
|
5
|
+
faraday (>= 1)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
addressable (2.8.8)
|
|
11
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
12
|
+
ast (2.4.3)
|
|
13
|
+
benchmark (0.5.0)
|
|
14
|
+
bigdecimal (4.0.1)
|
|
15
|
+
crack (1.0.1)
|
|
16
|
+
bigdecimal
|
|
17
|
+
rexml
|
|
18
|
+
dotenv (3.2.0)
|
|
19
|
+
erubi (1.13.1)
|
|
20
|
+
faraday (2.14.0)
|
|
21
|
+
faraday-net_http (>= 2.0, < 3.5)
|
|
22
|
+
json
|
|
23
|
+
logger
|
|
24
|
+
faraday-net_http (3.4.2)
|
|
25
|
+
net-http (~> 0.5)
|
|
26
|
+
hashdiff (1.2.1)
|
|
27
|
+
json (2.18.0)
|
|
28
|
+
language_server-protocol (3.17.0.5)
|
|
29
|
+
lint_roller (1.1.0)
|
|
30
|
+
logger (1.7.0)
|
|
31
|
+
minitest (5.27.0)
|
|
32
|
+
net-http (0.9.1)
|
|
33
|
+
uri (>= 0.11.1)
|
|
34
|
+
netrc (0.11.0)
|
|
35
|
+
parallel (1.27.0)
|
|
36
|
+
parser (3.3.10.0)
|
|
37
|
+
ast (~> 2.4.1)
|
|
38
|
+
racc
|
|
39
|
+
prism (1.7.0)
|
|
40
|
+
public_suffix (7.0.2)
|
|
41
|
+
racc (1.8.1)
|
|
42
|
+
rainbow (3.1.1)
|
|
43
|
+
rake (13.3.1)
|
|
44
|
+
rbi (0.3.8)
|
|
45
|
+
prism (~> 1.0)
|
|
46
|
+
rbs (>= 3.4.4)
|
|
47
|
+
rbs (3.10.0)
|
|
48
|
+
logger
|
|
49
|
+
regexp_parser (2.11.3)
|
|
50
|
+
rexml (3.4.4)
|
|
51
|
+
rubocop (1.82.1)
|
|
52
|
+
json (~> 2.3)
|
|
53
|
+
language_server-protocol (~> 3.17.0.2)
|
|
54
|
+
lint_roller (~> 1.1.0)
|
|
55
|
+
parallel (~> 1.10)
|
|
56
|
+
parser (>= 3.3.0.2)
|
|
57
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
58
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
59
|
+
rubocop-ast (>= 1.48.0, < 2.0)
|
|
60
|
+
ruby-progressbar (~> 1.7)
|
|
61
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
62
|
+
rubocop-ast (1.49.0)
|
|
63
|
+
parser (>= 3.3.7.2)
|
|
64
|
+
prism (~> 1.7)
|
|
65
|
+
ruby-progressbar (1.13.0)
|
|
66
|
+
sorbet (0.6.12874)
|
|
67
|
+
sorbet-static (= 0.6.12874)
|
|
68
|
+
sorbet-runtime (0.6.12874)
|
|
69
|
+
sorbet-static (0.6.12874-aarch64-linux)
|
|
70
|
+
sorbet-static (0.6.12874-universal-darwin)
|
|
71
|
+
sorbet-static (0.6.12874-x86_64-linux)
|
|
72
|
+
sorbet-static-and-runtime (0.6.12874)
|
|
73
|
+
sorbet (= 0.6.12874)
|
|
74
|
+
sorbet-runtime (= 0.6.12874)
|
|
75
|
+
spoom (1.6.3)
|
|
76
|
+
erubi (>= 1.10.0)
|
|
77
|
+
prism (>= 0.28.0)
|
|
78
|
+
rbi (>= 0.3.3)
|
|
79
|
+
rexml (>= 3.2.6)
|
|
80
|
+
sorbet-static-and-runtime (>= 0.5.10187)
|
|
81
|
+
thor (>= 0.19.2)
|
|
82
|
+
tapioca (0.16.11)
|
|
83
|
+
benchmark
|
|
84
|
+
bundler (>= 2.2.25)
|
|
85
|
+
netrc (>= 0.11.0)
|
|
86
|
+
parallel (>= 1.21.0)
|
|
87
|
+
rbi (~> 0.2)
|
|
88
|
+
sorbet-static-and-runtime (>= 0.5.11087)
|
|
89
|
+
spoom (>= 1.2.0)
|
|
90
|
+
thor (>= 1.2.0)
|
|
91
|
+
yard-sorbet
|
|
92
|
+
thor (1.4.0)
|
|
93
|
+
unicode-display_width (3.2.0)
|
|
94
|
+
unicode-emoji (~> 4.1)
|
|
95
|
+
unicode-emoji (4.2.0)
|
|
96
|
+
uri (1.1.1)
|
|
97
|
+
vcr (6.4.0)
|
|
98
|
+
webmock (3.26.1)
|
|
99
|
+
addressable (>= 2.8.0)
|
|
100
|
+
crack (>= 0.3.2)
|
|
101
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
102
|
+
yard (0.9.38)
|
|
103
|
+
yard-sorbet (0.9.0)
|
|
104
|
+
sorbet-runtime
|
|
105
|
+
yard
|
|
106
|
+
|
|
107
|
+
PLATFORMS
|
|
108
|
+
aarch64-linux
|
|
109
|
+
universal-darwin
|
|
110
|
+
x86_64-linux
|
|
111
|
+
|
|
112
|
+
DEPENDENCIES
|
|
113
|
+
dotenv
|
|
114
|
+
minitest (~> 5.0)
|
|
115
|
+
openrouter_client!
|
|
116
|
+
rake (~> 13.0)
|
|
117
|
+
rubocop (~> 1.21)
|
|
118
|
+
sorbet
|
|
119
|
+
tapioca
|
|
120
|
+
vcr
|
|
121
|
+
webmock
|
|
122
|
+
|
|
123
|
+
BUNDLED WITH
|
|
124
|
+
2.7.2
|
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 851 Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
data/README.md
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# OpenRouter Client
|
|
2
|
+
|
|
3
|
+
A Ruby client for the [OpenRouter API](https://openrouter.ai), providing access to hundreds of AI models through a unified interface.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bundle add openrouter_client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
gem install openrouter_client
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Configuration
|
|
22
|
+
|
|
23
|
+
Configure the client once at boot (e.g., in Rails an initializer) using `OpenRouter.configure`.
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
OpenRouter.configure do |config|
|
|
27
|
+
config.api_key = "your-key" # Optional. Defaults to ENV["OPENROUTER_API_KEY"]
|
|
28
|
+
config.api_base = "https://openrouter.ai/api/v1" # Optional (default)
|
|
29
|
+
config.request_timeout = 120 # Optional (default: 120 seconds)
|
|
30
|
+
config.site_url = "https://myapp.com" # Optional. For app attribution
|
|
31
|
+
config.site_name = "My App" # Optional. For app attribution
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Create a Chat Completion
|
|
36
|
+
|
|
37
|
+
The simplest way to use OpenRouter is to create a chat completion:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
completion = OpenRouter::Completion.create!(
|
|
41
|
+
messages: [
|
|
42
|
+
{ role: "user", content: "What is the meaning of life?" }
|
|
43
|
+
],
|
|
44
|
+
model: "openai/gpt-4"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
puts completion.content # => "The meaning of life..."
|
|
48
|
+
puts completion.model # => "openai/gpt-4"
|
|
49
|
+
puts completion.id # => "gen-xxxxx"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### With System Messages
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
completion = OpenRouter::Completion.create!(
|
|
56
|
+
messages: [
|
|
57
|
+
{ role: "system", content: "You are a helpful assistant." },
|
|
58
|
+
{ role: "user", content: "Hello!" }
|
|
59
|
+
],
|
|
60
|
+
model: "anthropic/claude-3-opus"
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Streaming Responses
|
|
65
|
+
|
|
66
|
+
Use `stream!` for SSE streaming. It yields each chunk and returns the final completion:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
completion = OpenRouter::Completion.stream!(
|
|
70
|
+
messages: [{ role: "user", content: "Tell me a story" }],
|
|
71
|
+
model: "openai/gpt-4"
|
|
72
|
+
) do |chunk|
|
|
73
|
+
# chunk is a Hash with the streamed data
|
|
74
|
+
print chunk.dig("choices", 0, "delta", "content")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
puts "\n---"
|
|
78
|
+
puts "Final content: #{completion.content}"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Advanced Parameters
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
completion = OpenRouter::Completion.create!(
|
|
85
|
+
messages: [{ role: "user", content: "Write a haiku" }],
|
|
86
|
+
model: "openai/gpt-4",
|
|
87
|
+
temperature: 0.7,
|
|
88
|
+
max_tokens: 100,
|
|
89
|
+
top_p: 0.9,
|
|
90
|
+
frequency_penalty: 0.5,
|
|
91
|
+
presence_penalty: 0.5,
|
|
92
|
+
stop: ["\n\n"],
|
|
93
|
+
seed: 42
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Tool Calling
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
completion = OpenRouter::Completion.create!(
|
|
101
|
+
messages: [{ role: "user", content: "What's the weather in Tokyo?" }],
|
|
102
|
+
model: "openai/gpt-4",
|
|
103
|
+
tools: [
|
|
104
|
+
{
|
|
105
|
+
type: "function",
|
|
106
|
+
function: {
|
|
107
|
+
name: "get_weather",
|
|
108
|
+
description: "Get the weather for a location",
|
|
109
|
+
parameters: {
|
|
110
|
+
type: "object",
|
|
111
|
+
properties: {
|
|
112
|
+
location: { type: "string", description: "City name" }
|
|
113
|
+
},
|
|
114
|
+
required: ["location"]
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if completion.has_tool_calls?
|
|
122
|
+
completion.tool_calls.each do |tool_call|
|
|
123
|
+
puts "Function: #{tool_call['function']['name']}"
|
|
124
|
+
puts "Arguments: #{tool_call['function']['arguments']}"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Model Fallbacks
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
completion = OpenRouter::Completion.create!(
|
|
133
|
+
messages: [{ role: "user", content: "Hello" }],
|
|
134
|
+
models: ["openai/gpt-4", "anthropic/claude-3-opus", "google/gemini-pro"],
|
|
135
|
+
route: "fallback"
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Image Generation
|
|
140
|
+
|
|
141
|
+
Generate images with models that support image output:
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
completion = OpenRouter::Completion.create!(
|
|
145
|
+
model: "google/gemini-2.5-flash-image-preview",
|
|
146
|
+
messages: [{ role: "user", content: "Generate a beautiful sunset over mountains" }],
|
|
147
|
+
modalities: ["image", "text"]
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Check if images were generated
|
|
151
|
+
if completion.images?
|
|
152
|
+
completion.images.each do |image|
|
|
153
|
+
# Images are base64-encoded data URLs
|
|
154
|
+
image_url = image.dig("image_url", "url")
|
|
155
|
+
puts "Generated image: #{image_url[0..50]}..."
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
puts completion.content # Text response (if any)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### Image Configuration (Gemini models)
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
completion = OpenRouter::Completion.create!(
|
|
166
|
+
model: "google/gemini-2.5-flash-image-preview",
|
|
167
|
+
messages: [{ role: "user", content: "Create a futuristic cityscape" }],
|
|
168
|
+
modalities: ["image", "text"],
|
|
169
|
+
image_config: {
|
|
170
|
+
aspect_ratio: "16:9", # 1:1, 2:3, 3:2, 4:3, 9:16, 16:9, etc.
|
|
171
|
+
image_size: "4K" # 1K, 2K, or 4K
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Additional API Options
|
|
177
|
+
|
|
178
|
+
Pass any additional OpenRouter API parameters:
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
completion = OpenRouter::Completion.create!(
|
|
182
|
+
messages: [{ role: "user", content: "Hello" }],
|
|
183
|
+
model: "openai/gpt-4",
|
|
184
|
+
# Reasoning configuration
|
|
185
|
+
reasoning: { effort: "high" },
|
|
186
|
+
# Web search plugin
|
|
187
|
+
plugins: [{ id: "web", enabled: true }],
|
|
188
|
+
# Provider preferences
|
|
189
|
+
provider: {
|
|
190
|
+
order: ["OpenAI", "Anthropic"],
|
|
191
|
+
allow_fallbacks: true
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Models
|
|
197
|
+
|
|
198
|
+
List, search, and find models:
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
# Get all models
|
|
202
|
+
models = OpenRouter::Model.all
|
|
203
|
+
puts "Total models: #{models.count}"
|
|
204
|
+
|
|
205
|
+
# Find a specific model (returns nil if not found)
|
|
206
|
+
model = OpenRouter::Model.find_by(id: "openai/gpt-4")
|
|
207
|
+
|
|
208
|
+
# Find a model (raises NotFoundError if not found)
|
|
209
|
+
model = OpenRouter::Model.find("openai/gpt-4")
|
|
210
|
+
model = OpenRouter::Model.find_by!(id: "openai/gpt-4")
|
|
211
|
+
|
|
212
|
+
# Model attributes
|
|
213
|
+
puts model.name # => "GPT-4"
|
|
214
|
+
puts model.context_length # => 8192
|
|
215
|
+
puts model.input_price # => 0.00003 (per token)
|
|
216
|
+
puts model.output_price # => 0.00006 (per token)
|
|
217
|
+
puts model.free? # => false
|
|
218
|
+
|
|
219
|
+
# Search models
|
|
220
|
+
results = OpenRouter::Model.search(query: "claude")
|
|
221
|
+
|
|
222
|
+
# Find by provider
|
|
223
|
+
openai_models = OpenRouter::Model.by_provider(provider: "openai")
|
|
224
|
+
|
|
225
|
+
# Find free models
|
|
226
|
+
free_models = OpenRouter::Model.free
|
|
227
|
+
|
|
228
|
+
# Use a model to create a completion
|
|
229
|
+
model = OpenRouter::Model.find("openai/gpt-4")
|
|
230
|
+
completion = model.complete(messages: [{ role: "user", content: "Hello!" }])
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Generation Stats
|
|
234
|
+
|
|
235
|
+
Query detailed usage information for a completion:
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
completion = OpenRouter::Completion.create!(
|
|
239
|
+
messages: [{ role: "user", content: "Hello" }],
|
|
240
|
+
model: "openai/gpt-4"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Query generation stats (may take a moment to be available)
|
|
244
|
+
generation = OpenRouter::Generation.find_by(id: completion.id)
|
|
245
|
+
|
|
246
|
+
if generation
|
|
247
|
+
puts generation.model # => "openai/gpt-4"
|
|
248
|
+
puts generation.native_prompt_tokens # => 5
|
|
249
|
+
puts generation.native_completion_tokens # => 10
|
|
250
|
+
puts generation.total_cost # => 0.00045
|
|
251
|
+
puts generation.provider # => "openai"
|
|
252
|
+
puts generation.tokens_per_second # => 50.0
|
|
253
|
+
end
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Credits
|
|
257
|
+
|
|
258
|
+
Check your credit balance:
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
credit = OpenRouter::Credit.fetch
|
|
262
|
+
puts credit.total_credits # => 100.0
|
|
263
|
+
puts credit.total_usage # => 25.0
|
|
264
|
+
puts credit.remaining # => 75.0
|
|
265
|
+
|
|
266
|
+
# Convenience method
|
|
267
|
+
remaining = OpenRouter::Credit.remaining
|
|
268
|
+
|
|
269
|
+
# Check credit status
|
|
270
|
+
credit.low? # => true if less than 10% remaining
|
|
271
|
+
credit.exhausted? # => true if no credits left
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### API Keys
|
|
275
|
+
|
|
276
|
+
Manage API keys programmatically:
|
|
277
|
+
|
|
278
|
+
```ruby
|
|
279
|
+
# Get current key info
|
|
280
|
+
current_key = OpenRouter::ApiKey.current
|
|
281
|
+
puts current_key.name
|
|
282
|
+
puts current_key.usage
|
|
283
|
+
|
|
284
|
+
# List all keys
|
|
285
|
+
keys = OpenRouter::ApiKey.all
|
|
286
|
+
|
|
287
|
+
# Create a new key
|
|
288
|
+
new_key = OpenRouter::ApiKey.create!(name: "Production Key", limit: 100.0)
|
|
289
|
+
puts new_key.key # Only shown once!
|
|
290
|
+
|
|
291
|
+
# Update a key
|
|
292
|
+
new_key.update!(name: "Updated Name", limit: 200.0)
|
|
293
|
+
|
|
294
|
+
# Delete a key
|
|
295
|
+
new_key.destroy!
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Error Handling
|
|
299
|
+
|
|
300
|
+
The gem raises typed exceptions for different error conditions:
|
|
301
|
+
|
|
302
|
+
```ruby
|
|
303
|
+
begin
|
|
304
|
+
OpenRouter::Completion.create!(
|
|
305
|
+
messages: [{ role: "user", content: "Hello" }],
|
|
306
|
+
model: "openai/gpt-4"
|
|
307
|
+
)
|
|
308
|
+
rescue OpenRouter::UnauthorizedError
|
|
309
|
+
# Invalid or missing API key (401)
|
|
310
|
+
rescue OpenRouter::ForbiddenError
|
|
311
|
+
# Access denied (403)
|
|
312
|
+
rescue OpenRouter::NotFoundError
|
|
313
|
+
# Resource not found (404)
|
|
314
|
+
rescue OpenRouter::RateLimitError
|
|
315
|
+
# Rate limited (429)
|
|
316
|
+
rescue OpenRouter::PaymentRequiredError
|
|
317
|
+
# Insufficient credits (402)
|
|
318
|
+
rescue OpenRouter::BadRequestError
|
|
319
|
+
# Invalid request (400)
|
|
320
|
+
rescue OpenRouter::ServerError
|
|
321
|
+
# Server error (5xx)
|
|
322
|
+
end
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Response Helpers
|
|
326
|
+
|
|
327
|
+
Completions have several helper methods:
|
|
328
|
+
|
|
329
|
+
```ruby
|
|
330
|
+
completion.content # Message content
|
|
331
|
+
completion.role # "assistant"
|
|
332
|
+
completion.finish_reason # "stop", "length", "tool_calls", etc.
|
|
333
|
+
completion.prompt_tokens # Tokens in prompt
|
|
334
|
+
completion.completion_tokens # Tokens in response
|
|
335
|
+
completion.total_tokens # Total tokens
|
|
336
|
+
|
|
337
|
+
completion.stopped? # Finished due to stop sequence
|
|
338
|
+
completion.truncated? # Finished due to max_tokens
|
|
339
|
+
completion.tool_calls? # Contains tool calls
|
|
340
|
+
completion.images? # Contains generated images
|
|
341
|
+
completion.images # Array of generated images
|
|
342
|
+
completion.tool_calls # Array of tool calls
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Development
|
|
346
|
+
|
|
347
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
348
|
+
|
|
349
|
+
For local development, copy the example environment file and set your API key so `bin/console` can load it automatically:
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
cp .env.example .env
|
|
353
|
+
echo 'OPENROUTER_API_KEY=your_api_key_here' >> .env
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Available Scripts
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
bin/setup # Install dependencies
|
|
360
|
+
bin/console # Interactive Ruby console with gem loaded
|
|
361
|
+
bin/test # Run tests
|
|
362
|
+
bin/vcr # Run tests with VCR recording (creates cassettes)
|
|
363
|
+
bin/format # Run RuboCop with auto-correct
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Running Tests
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
bin/test # Run all tests
|
|
370
|
+
|
|
371
|
+
bin/vcr # Run tests and record VCR cassettes
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to rubygems.org.
|
|
375
|
+
|
|
376
|
+
## License
|
|
377
|
+
|
|
378
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << "test"
|
|
8
|
+
t.libs << "lib"
|
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require "rubocop/rake_task"
|
|
13
|
+
|
|
14
|
+
RuboCop::RakeTask.new
|
|
15
|
+
|
|
16
|
+
task default: %i[test rubocop]
|