rails-ai-gateway 0.2.0 → 0.3.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 +4 -4
- data/.yardopts +6 -0
- data/CHANGELOG.md +15 -0
- data/README.md +48 -2
- data/app/assets/images/rails_ai_gateway/providers/cerebras.webp +0 -0
- data/app/assets/images/rails_ai_gateway/providers/deepseek.webp +0 -0
- data/app/assets/images/rails_ai_gateway/providers/groq.webp +0 -0
- data/app/assets/images/rails_ai_gateway/providers/mistral.webp +0 -0
- data/app/assets/images/rails_ai_gateway/providers/nebius.webp +0 -0
- data/app/assets/images/rails_ai_gateway/providers/openai.webp +0 -0
- data/app/assets/images/rails_ai_gateway/providers/openrouter.webp +0 -0
- data/app/assets/images/rails_ai_gateway/providers/perplexity.webp +0 -0
- data/app/assets/javascripts/rails_ai_gateway/admin.js +25 -0
- data/app/assets/stylesheets/rails_ai_gateway/admin.css +1111 -167
- data/app/controllers/rails_ai_gateway/admin_controller.rb +48 -3
- data/app/models/rails_ai_gateway/model_route.rb +34 -0
- data/app/models/rails_ai_gateway/provider.rb +11 -0
- data/app/views/layouts/rails_ai_gateway/admin.html.erb +70 -17
- data/app/views/rails_ai_gateway/admin/errors.html.erb +3 -3
- data/app/views/rails_ai_gateway/admin/index.html.erb +420 -90
- data/app/views/rails_ai_gateway/admin/key.html.erb +4 -4
- data/config/routes.rb +3 -0
- data/db/migrate/20260920000000_create_rails_ai_gateway.rb +1 -1
- data/db/migrate/20260921000000_add_prompts_and_token_counts_to_rails_ai_gateway.rb +9 -0
- data/db/migrate/20260921010000_add_capabilities_to_rails_ai_gateway_model_routes.rb +5 -0
- data/db/migrate/20260921020000_add_query_keywords_to_rails_ai_gateway_model_routes.rb +5 -0
- data/lib/generators/rails_ai_gateway/install_generator.rb +1 -1
- data/lib/generators/rails_ai_gateway/templates/initializer.rb +1 -1
- data/lib/rails_ai_gateway/configuration.rb +26 -3
- data/lib/rails_ai_gateway/model_helper.rb +46 -0
- data/lib/rails_ai_gateway/proxy.rb +94 -9
- data/lib/rails_ai_gateway/version.rb +1 -1
- data/lib/rails_ai_gateway.rb +56 -0
- metadata +16 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b09f7d65429d12dbd10f742e683c8a366302b465da1869f62f9c0ffb17be37a5
|
|
4
|
+
data.tar.gz: fb68e097b3437b2008f4352bc1c6905a4e57afd03be22cc6bb9ad709ea24e05d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2f7c4bce60576871c5853bc6b7becd05752ac10cfad18b0a1d697587797381d94e800bacb87e9ba4f9983c35b2cb22c4ba28155828f629b562c5ff2bfe60e5a1
|
|
7
|
+
data.tar.gz: 40c43dae901d3a2f4b431a85c2bda10d51d40c3b3f602c95ec5d8f74ef6603676c9f41a905ec695e8a1975eb757dfeb5828f205f531ca611b00ebf9663251b9f
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 - 2026-09-21
|
|
4
|
+
|
|
5
|
+
- Add durable input/output token totals grouped by public model.
|
|
6
|
+
- Add per-route system prompt injection without prompt logging.
|
|
7
|
+
- Add `RailsAIGateway::ENDPOINT`, `AI` inflection, and `RailsAIGateway` namespace alias.
|
|
8
|
+
- Add one-key templates for popular OpenAI-compatible providers.
|
|
9
|
+
- Add model capability metadata to routes, admin UI, and `/v1/models`.
|
|
10
|
+
- Add provider logos, modal connection flow, and deterministic keyword query routing.
|
|
11
|
+
- Add `models`, `model`, and `route_for` helpers for host Rails code.
|
|
12
|
+
- Use same-origin referrer policy so browser form submissions pass Rails CSRF origin checks.
|
|
13
|
+
- Add safe delete actions for providers and model routes in admin console.
|
|
14
|
+
- Use Nebius Token Factory API endpoint in provider template.
|
|
15
|
+
- Validate and forward OpenAI-compatible text, image, and audio message parts.
|
|
16
|
+
- Filter query routing by declared model capabilities.
|
|
17
|
+
|
|
3
18
|
## 0.2.0 - 2026-09-20
|
|
4
19
|
|
|
5
20
|
- Redesign admin UI as a responsive AI gateway operations console.
|
data/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/rails-ai-gateway)
|
|
4
4
|
[](LICENSE)
|
|
5
|
+
[](https://www.rubydoc.info/gems/rails-ai-gateway)
|
|
5
6
|
|
|
6
7
|
Put one OpenAI-compatible endpoint in front of your AI providers without running another
|
|
7
8
|
service. Rails AI Gateway mounts inside your Rails app, stores configuration and request
|
|
@@ -15,6 +16,11 @@ metadata with ActiveRecord, and includes a Web UI for providers, model routes, a
|
|
|
15
16
|
- Encrypted provider API keys using ActiveRecord encryption
|
|
16
17
|
- Scoped gateway keys stored as SHA-256 digests
|
|
17
18
|
- Request status, latency, attempt, and reported token-usage logs
|
|
19
|
+
- Durable input/output token totals grouped by public model
|
|
20
|
+
- Optional system prompt injection configured per model route
|
|
21
|
+
- One-key provider templates for OpenAI, OpenRouter, Groq, DeepSeek, Mistral, Cerebras,
|
|
22
|
+
Nebius AI, and Perplexity
|
|
23
|
+
- Explicit model capabilities exposed through UI and `/v1/models`
|
|
18
24
|
- Configurable timeouts, body limits, fallback attempts, and network policy
|
|
19
25
|
- SSRF protection with DNS validation and address pinning
|
|
20
26
|
- Server-rendered, responsive, accessible admin UI
|
|
@@ -54,6 +60,9 @@ Web UI guides initial setup:
|
|
|
54
60
|
2. Add public model route, such as `fast-chat` mapped to `gpt-5.6-luna`.
|
|
55
61
|
3. Create gateway key and save token when shown. Raw token cannot be displayed again.
|
|
56
62
|
|
|
63
|
+
Popular provider templates need only API key. Custom OpenAI-compatible provider form remains
|
|
64
|
+
available for self-hosted and less common endpoints.
|
|
65
|
+
|
|
57
66
|
Routes with same public model name form fallback chain. Lower priority runs first. Gateway
|
|
58
67
|
retries connection failures before upstream accepts request and HTTP `429`, `500`, `502`,
|
|
59
68
|
`503`, or `504`. Ambiguous timeouts and started streams are never retried.
|
|
@@ -63,7 +72,7 @@ retries connection failures before upstream accepts request and HTTP `429`, `500
|
|
|
63
72
|
Runtime and security policy live in `config/initializers/rails_ai_gateway.rb`:
|
|
64
73
|
|
|
65
74
|
```ruby
|
|
66
|
-
|
|
75
|
+
RailsAIGateway.configure do |config|
|
|
67
76
|
config.admin_controller = "ApplicationController"
|
|
68
77
|
config.admin_authorization = ->(controller) {
|
|
69
78
|
controller.current_user&.admin? == true
|
|
@@ -82,6 +91,16 @@ RailsAiGateway.configure do |config|
|
|
|
82
91
|
end
|
|
83
92
|
```
|
|
84
93
|
|
|
94
|
+
Rails acronym inflection exposes `RailsAIGateway` as the preferred constant while keeping
|
|
95
|
+
`RailsAiGateway` compatible. Use the endpoint constant instead of repeating a URL path:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
OpenAI::Client.new(uri_base: "#{ENV.fetch("APP_URL")}#{RailsAIGateway::ENDPOINT}")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`RailsAIGateway::ENDPOINT` is `/ai/v1`, matching the generator's default mount. If host app
|
|
102
|
+
changes mount path, build endpoint from that route instead.
|
|
103
|
+
|
|
85
104
|
Admin requests are denied until `admin_authorization` returns exactly `true`. Provider
|
|
86
105
|
definitions, model routes, gateway keys, and logs stay database-backed and editable through
|
|
87
106
|
Web UI.
|
|
@@ -89,7 +108,7 @@ Web UI.
|
|
|
89
108
|
Need another mount path? Change host route:
|
|
90
109
|
|
|
91
110
|
```ruby
|
|
92
|
-
mount
|
|
111
|
+
mount RailsAIGateway::Engine, at: "/gateway"
|
|
93
112
|
```
|
|
94
113
|
|
|
95
114
|
## Make Requests
|
|
@@ -109,6 +128,32 @@ Available endpoints:
|
|
|
109
128
|
|
|
110
129
|
OpenAI clients can use `http://localhost:3000/ai/v1` as base URL.
|
|
111
130
|
|
|
131
|
+
Each model route can prepend an optional system message to chat requests. Injected prompts
|
|
132
|
+
are stored on route and are never copied into request logs. Token dashboards use only usage
|
|
133
|
+
reported by upstream providers; missing usage remains zero rather than being estimated.
|
|
134
|
+
Routes declare capabilities from `text`, `vision`, `embedding`, `audio`, `video`, `tools`,
|
|
135
|
+
and `reasoning`; clients receive merged capabilities in each `/v1/models` entry. Capability
|
|
136
|
+
metadata does not add unsupported transport endpoints by itself.
|
|
137
|
+
|
|
138
|
+
Chat requests support OpenAI-compatible multimodal content arrays containing `text`,
|
|
139
|
+
`image_url` (HTTPS or image data URL), and `input_audio` (base64 WAV/MP3) parts. Gateway
|
|
140
|
+
selects only routes advertising every required capability and forwards accepted parts without
|
|
141
|
+
conversion. Request-size limit still applies to encoded media.
|
|
142
|
+
|
|
143
|
+
Host Rails code can inspect active models without making an HTTP request:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
RailsAIGateway.models
|
|
147
|
+
RailsAIGateway.models(capabilities: %w[vision tools])
|
|
148
|
+
RailsAIGateway.models(provider: "OpenAI")
|
|
149
|
+
RailsAIGateway.model("fast-chat")
|
|
150
|
+
RailsAIGateway.route_for(model: "fast-chat", query: "Review this Ruby code", capabilities: %w[text tools])
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Helpers return provider names and routing metadata, never provider API keys. `route_for`
|
|
154
|
+
uses same literal keyword ranking as proxy: matching specialized routes first, then generic
|
|
155
|
+
fallback routes.
|
|
156
|
+
|
|
112
157
|
## Development
|
|
113
158
|
|
|
114
159
|
```bash
|
|
@@ -117,6 +162,7 @@ cd rails-ai-gateway
|
|
|
117
162
|
bundle install
|
|
118
163
|
bundle exec ruby test/check.rb
|
|
119
164
|
gem build rails_ai_gateway.gemspec
|
|
165
|
+
rdoc --main README.md README.md lib
|
|
120
166
|
```
|
|
121
167
|
|
|
122
168
|
SQLite integration runs by default. Run same suite against PostgreSQL with an empty test
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
2
|
+
const dialog = document.querySelector("#provider-connect-dialog");
|
|
3
|
+
if (!dialog || typeof dialog.showModal !== "function") return;
|
|
4
|
+
|
|
5
|
+
document.querySelectorAll("[data-provider-connect]").forEach((button) => {
|
|
6
|
+
button.addEventListener("click", () => {
|
|
7
|
+
const name = button.dataset.providerName;
|
|
8
|
+
dialog.querySelector("[data-dialog-template]").value = button.dataset.template;
|
|
9
|
+
dialog.querySelector("[data-dialog-logo]").src = button.dataset.logo;
|
|
10
|
+
dialog.querySelector("[data-dialog-key-label]").textContent = `${name} API Key`;
|
|
11
|
+
dialog.querySelector("[data-dialog-submit]").value = `Connect ${name}`;
|
|
12
|
+
dialog.querySelector("#connect-dialog-title").textContent = `Connect ${name}`;
|
|
13
|
+
dialog.showModal();
|
|
14
|
+
dialog.querySelector("[data-dialog-api-key]").focus();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
dialog.querySelectorAll("[data-dialog-close]").forEach((button) => button.addEventListener("click", () => dialog.close()));
|
|
19
|
+
dialog.addEventListener("click", (event) => {
|
|
20
|
+
if (event.target === dialog) dialog.close();
|
|
21
|
+
});
|
|
22
|
+
dialog.addEventListener("close", () => {
|
|
23
|
+
dialog.querySelector("[data-dialog-api-key]").value = "";
|
|
24
|
+
});
|
|
25
|
+
});
|