rails_ai_kit 0.1.6 → 0.1.8

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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +179 -158
  4. data/lib/generators/rails_ai_kit/dashboard/templates/index.html.erb +38 -0
  5. data/lib/generators/rails_ai_kit/dashboard/templates/traces_controller.rb +25 -0
  6. data/lib/generators/rails_ai_kit/dashboard_generator.rb +25 -0
  7. data/lib/generators/rails_ai_kit/install/templates/create_rails_ai_kit_tables.rb +30 -0
  8. data/lib/generators/rails_ai_kit/install/templates/create_rails_ai_traces.rb +21 -0
  9. data/lib/generators/rails_ai_kit/install/templates/rails_ai_kit.rb +20 -4
  10. data/lib/generators/rails_ai_kit/install_generator.rb +4 -4
  11. data/lib/rails_ai_kit/ai_generate.rb +56 -0
  12. data/lib/rails_ai_kit/configuration.rb +19 -1
  13. data/lib/rails_ai_kit/eval.rb +83 -0
  14. data/lib/rails_ai_kit/guard_result.rb +28 -0
  15. data/lib/rails_ai_kit/guardrails.rb +159 -0
  16. data/lib/rails_ai_kit/guards/base.rb +50 -0
  17. data/lib/rails_ai_kit/guards/hallucination.rb +32 -0
  18. data/lib/rails_ai_kit/guards/pii.rb +30 -0
  19. data/lib/rails_ai_kit/guards/prompt_injection.rb +29 -0
  20. data/lib/rails_ai_kit/guards/toxicity.rb +37 -0
  21. data/lib/rails_ai_kit/llm_client.rb +45 -0
  22. data/lib/rails_ai_kit/llm_providers/anthropic.rb +60 -0
  23. data/lib/rails_ai_kit/llm_providers/base.rb +32 -0
  24. data/lib/rails_ai_kit/llm_providers/custom.rb +59 -0
  25. data/lib/rails_ai_kit/llm_providers/google.rb +59 -0
  26. data/lib/rails_ai_kit/llm_providers/groq.rb +64 -0
  27. data/lib/rails_ai_kit/llm_providers/openai.rb +63 -0
  28. data/lib/rails_ai_kit/trace.rb +26 -0
  29. data/lib/rails_ai_kit/version.rb +1 -1
  30. data/lib/rails_ai_kit.rb +39 -7
  31. metadata +23 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0ed50b48e8ada5fcbdee2169fbcb5e3bd427a66cc609a3b2794a0202372c9a3
4
- data.tar.gz: ef0472f6a40a0f31768a814e5115d0da43c07623f194f2d9ff58c004aab7571c
3
+ metadata.gz: be63d7b9ec9ef2a7c943cdb80fded8941102df5dd5c410698eda80f56fc65cc5
4
+ data.tar.gz: 6848aba88723727d242aa35b156757d860e91fefb6f9e28d92902d8d6c2dc587
5
5
  SHA512:
6
- metadata.gz: 1d34a020030a1d8391b18d32c68374af6b918ab03f5edb51d56acc205e3a0c4f05a4bddbcb158b5bd9c0ac027bc8a5e60d5007f11fcf0135b6695716f25e3c96
7
- data.tar.gz: b9a3e6a229716e9a28cee18ba85febb98c5ade412077c5e215cb4823a9bf0bb53bc495ba8f10109370368ab944571eab9ef50007023e232190dd189bf6ad204b
6
+ metadata.gz: a3b21a2cb175fc0ad89248bda65d812fe458ee31b68b3083d704ada7cea7b8adb2fe3b75781ee8c29c7a925966f4185dcb870db80f2753518fda7bc5f4dfc266
7
+ data.tar.gz: c3aa3961a3e430ba1ffdbd63b2390d2515c6eb8b896e32b1e26c5797bd492f859aa6882728c9037b97968bb762566e8fcf138ff8f4c87a268283e240e6a7c37a
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ### Added
6
+
7
+ - **Guardrails (LLM-as-judge)** – Generate then validate with safety checks (no vectors). `RailsAiKit.guard(prompt:, model:, checks: [:toxicity, :pii, :hallucination], threshold: 0.8)` returns `GuardResult` with `output`, `safe?`, `scores`, `action`.
8
+ - **Guards** – Toxicity, PII, Hallucination, Prompt injection (each uses LLM to score 0–1).
9
+ - **LLM providers** – OpenAI, Anthropic (Claude), Google (Gemini), Groq, and custom URL + key (OpenAI-compatible). Single config: `llm_provider`, `llm_api_keys`, optional `llm_url`/`llm_key` for custom.
10
+ - **ai_generate macro** – `ai_generate :summary, from: :content, guards: [...], threshold: 0.85` → `record.ai_summary` (generate + guardrails).
11
+ - **Eval** – `RailsAiKit.eval(dataset: "path.json", model:)` returns accuracy, hallucination rate, toxicity failures, and per-item details.
12
+ - **AI Traces** – Table `rails_ai_traces` (prompt, response, model, tokens, guard_scores, latency, action). Optional dashboard at `/ai_traces` via `rails g rails_ai_kit:dashboard`.
13
+ - Install generator now creates both `rails_ai_kit_labels` and `rails_ai_traces` in one migration.
14
+
5
15
  ## [0.1.3] - 2026-03-07
6
16
 
7
17
  ### Fixed
data/README.md CHANGED
@@ -4,219 +4,236 @@
4
4
 
5
5
  # Rails AI Kit
6
6
 
7
- **AI-first toolkit for Rails** — embeddings, vector search, and classification without running your own ML or calling LLMs on every request.
8
-
9
- > Rails gem for vector-based text classification (pgvector), embeddings (OpenAI, Cohere), similarity search, and generators. Train labels with examples; classify on save or in batch.
7
+ **AI-first toolkit for Rails** — two core features today, more on the way.
10
8
 
11
9
  [![Ruby](https://img.shields.io/badge/Ruby-3.0+-red.svg)](https://rubygems.org/gems/rails_ai_kit)
12
10
  [![Rails](https://img.shields.io/badge/Rails-6.0+-blue.svg)](https://rubyonrails.org/)
13
11
  [![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
14
12
 
13
+ **[Source code](https://github.com/imrrohitt/rails_ai_kit)** · **[RubyGems](https://rubygems.org/gems/rails_ai_kit)**
14
+
15
15
  ---
16
16
 
17
- ### Overview
17
+ ## Two main features
18
18
 
19
- | | |
20
- |:---|:---|
21
- | **Rails-native** | Generators, ActiveRecord integration, single config file |
22
- | **Vector-ready** | [pgvector](https://github.com/pgvector/pgvector) with OpenAI or Cohere embeddings |
23
- | **Modular** | Use only the features you need; add more as the gem evolves |
19
+ Rails AI Kit currently provides **two independent feature sets**. Use one, both, or add more as the gem evolves.
24
20
 
25
- **[Source code](https://github.com/imrrohitt/rails_ai_kit)** · **[RubyGems](https://rubygems.org/gems/rails_ai_kit)**
21
+ | Feature | What it does | Use when |
22
+ |--------|----------------|----------|
23
+ | **1. Guardrails** | Run LLM generation through safety checks (toxicity, PII, hallucination, prompt injection). Generate → validate → score → allow/block. | You call an LLM and want to block unsafe outputs or toxic user inputs. |
24
+ | **2. Vector classification** | Classify text using embeddings + pgvector. Train labels with examples; auto-classify on save or in batch. No ML training, no LLM per request. | You need categories (e.g. support tickets, content tags, document routing). |
25
+
26
+ *More features (evals, tool use, etc.) are planned — the gem is actively developed.*
26
27
 
27
28
  ---
28
29
 
29
- ## Features
30
+ ## Feature 1: Guardrails
30
31
 
31
- Rails AI Kit is organized around **features**. Each is optional and self-contained.
32
+ Guardrails wrap LLM calls with **safety checks**. You get a generated response plus scores; you decide allow/block by threshold. Checks run on both the **model output** and the **user prompt** (so toxic or injection-style inputs are detected even if the model refuses to comply).
32
33
 
33
- ### Classifier
34
+ ### What you get
34
35
 
35
- Vector-based text classification: route support tickets, moderate content, tag articles, or categorize documents. No ML training you provide example texts per label; classification is nearest-neighbor in PostgreSQL.
36
+ - **`RailsAiKit.guard(prompt:, model:, checks:, threshold:)`** One call: generate + run checks. Returns `GuardResult` with `output`, `safe?`, `scores`, `action` (`:allow` / `:block`).
37
+ - **Guards:** toxicity, PII, hallucination, prompt injection (each uses an LLM-as-judge or keyword rules).
38
+ - **Prompt + output:** Toxicity and prompt-injection also evaluate the **user prompt**; final toxicity = max(prompt_toxicity, output_toxicity). Keyword safety net for obvious offensive language.
39
+ - **Rails macro:** `ai_generate :summary, from: :content, guards: [...]` → `article.ai_summary` (generate then guard).
40
+ - **Eval:** `RailsAiKit.eval(dataset: "eval.json", model:)` → accuracy, hallucination rate, toxicity failures.
41
+ - **AI Traces:** Optional `rails_ai_traces` table + `/ai_traces` dashboard to inspect prompts, responses, scores, and latency.
36
42
 
37
- - Train labels with examples → one vector per label
38
- - Auto-classify on save (embed + compare → `label`, `confidence_score`)
39
- - Similarity search: `Model.similar_to("query", limit: 5)`
40
- - Batch classify for backfills or background jobs
43
+ ### LLM providers
41
44
 
42
- *Requires pgvector, an embedding API (OpenAI/Cohere), and the install + vector_columns generators.*
45
+ One config, same API: **OpenAI**, **Anthropic (Claude)**, **Google (Gemini)**, **Groq**, or **custom** (any OpenAI-compatible endpoint via `llm_url` + `llm_key`).
43
46
 
44
- ---
47
+ ### Quick example (Guardrails)
45
48
 
46
- ## Requirements
49
+ ```ruby
50
+ result = RailsAiKit.guard(
51
+ prompt: "What is the capital of France?",
52
+ model: "gpt-4o-mini",
53
+ checks: [:toxicity, :hallucination],
54
+ threshold: 0.8
55
+ )
47
56
 
48
- - Rails 6+
49
- - PostgreSQL with [pgvector](https://github.com/pgvector/pgvector) (for Classifier and vector features)
50
- - An embedding API key (OpenAI or Cohere) for features that use embeddings
57
+ result.output # => "The capital of France is Paris."
58
+ result.safe? # => true
59
+ result.scores # => { "toxicity" => 0.0, "hallucination" => 0.0, "label" => "factual" }
60
+ result.action # => :allow
61
+ ```
62
+
63
+ Toxic or injection-style prompts are scored (including a keyword safety net); if any score exceeds `threshold`, `action` is `:block`.
51
64
 
52
65
  ---
53
66
 
54
- ## Installation
67
+ ## Feature 2: Vector classification
55
68
 
56
- Add to your Gemfile:
69
+ Vector classification uses **embeddings + pgvector** to assign labels to text. You train each label with example phrases; the gem embeds them, stores one vector per label, and classifies new content by nearest-neighbor (cosine distance). No ML training and no LLM call per classification — only an embedding API (OpenAI or Cohere).
70
+
71
+ ### What you get
72
+
73
+ - **Train labels:** `RailsAiKit.classifier("Article").train("sports", examples: ["football match", "cricket"])` → one vector per label in `rails_ai_kit_labels`.
74
+ - **Auto-classify on save:** Add `vector_classify :content, labels: ["sports", "politics", "tech"]` to your model. On save, the gem embeds `content`, stores it, compares to label vectors, and sets `label` and `confidence_score`.
75
+ - **Classify without saving:** `RailsAiKit.classifier("Article").classify("India won the cricket match")` → `{ label: "sports", confidence: 0.91 }`.
76
+ - **Batch classify:** `RailsAiKit.classifier("Article").batch_classify(records, text_attribute: :content)`.
77
+ - **Similarity search:** `Article.similar_to("new iPhone launch", limit: 5)`.
78
+ - **Filtering:** `Article.where(label: "sports")`, `Article.where("confidence_score >= ?", 0.8)`.
79
+
80
+ ### Quick example (Vector classification)
57
81
 
58
82
  ```ruby
59
- gem "rails_ai_kit"
60
- ```
83
+ # 1. Train labels once
84
+ c = RailsAiKit.classifier("Article")
85
+ c.train("sports", examples: ["football match", "cricket tournament"])
86
+ c.train("technology", examples: ["new iPhone launch", "AI update"])
61
87
 
62
- Then:
88
+ # 2. Model
89
+ class Article < ApplicationRecord
90
+ vector_classify :content, labels: ["sports", "politics", "technology"]
91
+ end
63
92
 
64
- ```bash
65
- bundle install
93
+ # 3. Create record → auto-classified
94
+ article = Article.create!(content: "Apple released a new iPhone")
95
+ article.label # => "technology"
96
+ article.confidence_score # => 0.91
66
97
  ```
67
98
 
68
- ### 1. Install (pgvector + labels table)
99
+ ---
100
+
101
+ ## Requirements
102
+
103
+ - **Rails 6+**
104
+ - **PostgreSQL** with [pgvector](https://github.com/pgvector/pgvector) (for vector classification only)
105
+ - **Embedding API** (OpenAI or Cohere) for vector classification
106
+ - **LLM API** (OpenAI, Anthropic, Google, Groq, or custom) for Guardrails / `ai_generate` / Eval
107
+
108
+ ---
109
+
110
+ ## Installation
69
111
 
70
- For the **Classifier** feature, the gem needs pgvector and one internal table for label embeddings:
112
+ ```ruby
113
+ # Gemfile
114
+ gem "rails_ai_kit"
115
+ ```
71
116
 
72
117
  ```bash
118
+ bundle install
73
119
  rails g rails_ai_kit:install
74
120
  rails db:migrate
75
121
  ```
76
122
 
77
- ### 2. Configure
123
+ The install generator adds:
124
+
125
+ - **pgvector** and tables: `rails_ai_kit_labels` (for vector classification), `rails_ai_traces` (for Guardrails tracing)
126
+ - **Initializer:** `config/initializers/rails_ai_kit.rb`
127
+
128
+ ### Configuration
78
129
 
79
- In `config/initializers/rails_ai_kit.rb` (create the file):
130
+ Edit `config/initializers/rails_ai_kit.rb`:
80
131
 
81
132
  ```ruby
82
133
  RailsAiKit.configure do |config|
83
- config.embedding_provider = :openai # or :cohere
84
- config.embedding_dimensions = 1536 # 1536 for OpenAI text-embedding-3-small
85
-
134
+ # ----- Vector classification (embeddings) -----
135
+ config.embedding_provider = :openai
136
+ config.embedding_dimensions = 1536
86
137
  config.api_keys = {
87
138
  openai: ENV["OPENAI_API_KEY"],
88
139
  cohere: ENV["COHERE_API_KEY"]
89
140
  }
90
-
91
141
  config.default_classifier_name = "default"
142
+
143
+ # ----- Guardrails (LLM) -----
144
+ config.llm_provider = :openai
145
+ config.default_llm_model = "gpt-4o-mini"
146
+ config.llm_api_keys = {
147
+ openai: ENV["OPENAI_API_KEY"],
148
+ anthropic: ENV["ANTHROPIC_API_KEY"],
149
+ google: ENV["GOOGLE_AI_API_KEY"],
150
+ groq: ENV["GROQ_API_KEY"]
151
+ }
152
+ # Custom endpoint:
153
+ # config.llm_provider = :custom
154
+ # config.llm_url = "https://your-llm.example.com/v1"
155
+ # config.llm_key = ENV["CUSTOM_LLM_KEY"]
156
+
157
+ config.trace_llm_calls = true
92
158
  end
93
159
  ```
94
160
 
95
- Use environment variables or Rails credentials; do not commit raw API keys.
161
+ Use env vars or Rails credentials; do not commit API keys.
96
162
 
97
- ### 3. Add vector columns (for Classifier)
163
+ ### Add vector columns (for vector classification only)
98
164
 
99
- For models you want to classify (e.g. `articles` with a `content` column):
165
+ For models you want to classify:
100
166
 
101
167
  ```bash
102
168
  rails g rails_ai_kit:vector_columns Article content
103
169
  rails db:migrate
104
170
  ```
105
171
 
106
- This adds `embedding` (vector), `label` (string), and `confidence_score` (float).
172
+ Adds `embedding`, `label`, `confidence_score` to the table.
107
173
 
108
174
  ---
109
175
 
110
- ## Usage
176
+ ## Usage in detail
111
177
 
112
- ### Embeddings (general)
178
+ ### Guardrails
113
179
 
114
- Use embeddings anywhere in your app:
115
-
116
- ```ruby
117
- RailsAiKit.embed("some text") # => array of floats
118
- RailsAiKit.embedding.embed("text") # same
119
- RailsAiKit.embedding.embed_batch(["a", "b"]) # batch
120
- ```
180
+ | Method | Description |
181
+ |--------|-------------|
182
+ | `RailsAiKit.guard(prompt:, model:, checks: [:toxicity, :hallucination], threshold: 0.8)` | Generate + run checks; returns `GuardResult` |
183
+ | `result.output`, `result.safe?`, `result.scores`, `result.action` | Response text, safe flag, scores hash, `:allow` / `:block` |
184
+ | `RailsAiKit.eval(dataset: "path.json", model:)` | Run eval dataset; returns accuracy, hallucination rate, toxicity failures |
185
+ | `rails g rails_ai_kit:dashboard` | Add `/ai_traces` dashboard |
121
186
 
122
- ### Classifier feature
123
-
124
- #### Declare classification on a model
187
+ **ai_generate macro (Guardrails):**
125
188
 
126
189
  ```ruby
127
190
  class Article < ApplicationRecord
128
- vector_classify :content,
129
- labels: ["sports", "politics", "technology"]
191
+ ai_generate :summary,
192
+ from: :content,
193
+ prompt_template: "Summarize in 2–3 sentences:\n\n%{text}",
194
+ guards: [:toxicity, :hallucination],
195
+ threshold: 0.85
130
196
  end
197
+ article.ai_summary # => generated summary or nil if blocked
131
198
  ```
132
199
 
133
- On save, the gem will:
134
-
135
- 1. Generate an embedding for `content`
136
- 2. Store it in `embedding`
137
- 3. Compare to trained label vectors and set `label` and `confidence_score`
138
-
139
- #### Train labels first
140
-
141
- Before classifying, train each label with example texts:
142
-
143
- ```ruby
144
- c = RailsAiKit.classifier("Article")
145
-
146
- c.train("sports", examples: [
147
- "football match",
148
- "cricket tournament",
149
- "Olympic gold medal"
150
- ])
151
- c.train("politics", examples: ["election results", "parliament debate"])
152
- c.train("technology", examples: ["new iPhone launch", "AI software update"])
153
- ```
154
-
155
- #### Create records
156
-
157
- ```ruby
158
- article = Article.create!(content: "Apple released a new iPhone")
159
- article.label # => "technology"
160
- article.confidence_score # => 0.91
161
- ```
162
-
163
- #### Classify without saving
200
+ ### Vector classification
164
201
 
165
- ```ruby
166
- RailsAiKit.classifier("Article").classify("India won the cricket match")
167
- # => { label: "sports", confidence: 0.91, distance: 0.09 }
168
-
169
- RailsAiKit.classifier("Article").classify_by_embedding(article.embedding)
170
- # => { label: "technology", confidence: 0.91, distance: 0.09 }
171
- ```
202
+ | Method | Description |
203
+ |--------|-------------|
204
+ | `RailsAiKit.classifier("Article").train("label", examples: ["...", "..."])` | Train a label with example texts |
205
+ | `RailsAiKit.classifier("Article").classify("some text")` | Classify without saving → `{ label:, confidence:, distance: }` |
206
+ | `RailsAiKit.classifier("Article").batch_classify(records, text_attribute: :content)` | Batch classify, set `label` and `confidence_score` on records |
207
+ | `Article.similar_to("query", limit: 5)` | Similarity search (nearest neighbors by embedding) |
208
+ | `Article.where(label: "sports")` | Filter by predicted label |
172
209
 
173
- #### Batch classification
210
+ **vector_classify macro:**
174
211
 
175
212
  ```ruby
176
- records = Article.where(label: nil).limit(100)
177
- RailsAiKit.classifier("Article").batch_classify(records,
178
- text_attribute: :content,
179
- label_attribute: :label,
180
- confidence_attribute: :confidence_score
181
- )
182
- # Optionally: records.each(&:save!)
183
- ```
184
-
185
- #### Similarity search
186
-
187
- ```ruby
188
- Article.similar_to("new iPhone launch", limit: 5)
213
+ class Article < ApplicationRecord
214
+ vector_classify :content,
215
+ labels: ["sports", "politics", "technology"],
216
+ classifier_name: "Article"
217
+ end
218
+ # On save: embed content → store embedding → set label & confidence_score
189
219
  ```
190
220
 
191
- #### Filtering
221
+ ### Embeddings (shared)
192
222
 
193
223
  ```ruby
194
- Article.where(label: "sports")
195
- Article.where("confidence_score >= ?", 0.8)
224
+ RailsAiKit.embed("some text")
225
+ RailsAiKit.embedding.embed_batch(["a", "b"])
196
226
  ```
197
227
 
198
228
  ---
199
229
 
200
- ## Example: Support ticket routing
201
-
202
- ```ruby
203
- # app/models/support_ticket.rb
204
- class SupportTicket < ApplicationRecord
205
- vector_classify :message,
206
- labels: ["billing", "technical", "account"],
207
- classifier_name: "SupportTicket"
208
- end
209
-
210
- # Train once
211
- c = RailsAiKit.classifier("SupportTicket")
212
- c.train("billing", examples: ["My payment failed", "Refund request", "Invoice issue"])
213
- c.train("technical", examples: ["App crashed", "Login not working", "Error message"])
214
- c.train("account", examples: ["Change email", "Close my account", "Password reset"])
230
+ ## Generators
215
231
 
216
- # Incoming ticket
217
- ticket = SupportTicket.create!(message: "My payment failed last night")
218
- ticket.label # => "billing" route to billing queue
219
- ```
232
+ | Generator | Purpose |
233
+ |-----------|---------|
234
+ | `rails g rails_ai_kit:install` | pgvector + `rails_ai_kit_labels` + `rails_ai_traces` + initializer |
235
+ | `rails g rails_ai_kit:vector_columns ModelName content_column` | Add embedding, label, confidence_score to a table (vector classification) |
236
+ | `rails g rails_ai_kit:dashboard` | Add AI Traces dashboard at `/ai_traces` (Guardrails) |
220
237
 
221
238
  ---
222
239
 
@@ -224,34 +241,43 @@ ticket.label # => "billing" → route to billing queue
224
241
 
225
242
  | Option | Description | Default |
226
243
  |--------|-------------|---------|
244
+ | **Embeddings (vector classification)** | | |
227
245
  | `embedding_provider` | `:openai` or `:cohere` | `:openai` |
228
- | `embedding_dimensions` | Vector size (must match provider) | `1536` |
229
- | `api_keys` | Hash of provider => API key | `{}` |
230
- | `default_classifier_name` | Classifier name when none given | `"default"` |
246
+ | `embedding_dimensions` | Vector size | `1536` |
247
+ | `api_keys` | Embedding API keys | `{}` |
248
+ | `default_classifier_name` | Classifier name when not specified | `"default"` |
249
+ | **LLM (Guardrails)** | | |
250
+ | `llm_provider` | `:openai`, `:anthropic`, `:google`, `:groq`, `:custom` | `:openai` |
251
+ | `llm_api_keys` | LLM API keys | `{}` |
252
+ | `llm_url`, `llm_key` | Custom LLM endpoint | `nil` |
253
+ | `default_llm_model` | Default model for guard / ai_generate | `nil` |
254
+ | `trace_llm_calls` | Log guard calls to `rails_ai_traces` | `true` |
231
255
 
232
256
  ---
233
257
 
234
- ## Generators
258
+ ## How it works
235
259
 
236
- | Generator | Purpose |
237
- |-----------|---------|
238
- | `rails g rails_ai_kit:install` | Enable pgvector + create `rails_ai_kit_labels` (for Classifier) |
239
- | `rails g rails_ai_kit:vector_columns ModelName content_column` | Add `embedding`, `label`, `confidence_score` to a table |
260
+ ### Guardrails
240
261
 
241
- ---
262
+ 1. You call `RailsAiKit.guard(prompt:, model:, checks:, threshold:)`.
263
+ 2. The gem generates a response with the configured LLM.
264
+ 3. It runs the requested checks (toxicity, PII, hallucination, prompt injection) on the **output** and, for toxicity/prompt_injection, on the **user prompt** (in parallel where possible). Keyword rules backstop obvious offensive prompts.
265
+ 4. It merges scores and sets `action` to `:block` if any score exceeds `threshold`.
266
+ 5. Optionally it records the run in `rails_ai_traces`.
242
267
 
243
- ## How Classifier works
268
+ ### Vector classification
244
269
 
245
- 1. **Label vectors** Each label is a vector (average of example embeddings), stored in `rails_ai_kit_labels`.
246
- 2. **Classification** New content is embedded and compared to label vectors (cosine distance). Nearest label wins; confidence = `1 - distance`.
247
- 3. **Storage** Your table holds content, embedding, predicted label, and confidence. The gem adds one table for label vectors only.
270
+ 1. You train labels with example texts; the gem embeds them and stores one vector per label in `rails_ai_kit_labels`.
271
+ 2. When you save a record with `vector_classify`, the gem embeds the source column, stores the vector, and finds the nearest label (cosine distance). It sets `label` and `confidence_score`.
272
+ 3. Similarity search uses the same embedding column and pgvector nearest-neighbor.
248
273
 
249
274
  ---
250
275
 
251
276
  ## Roadmap
252
277
 
253
- - **Classifier:** hierarchical labels, confidence threshold, hybrid search, incremental learning
254
- - **More features** additional AI capabilities as the gem evolves
278
+ - **Guardrails:** More guards, configurable keyword lists, retry/fallback behavior.
279
+ - **Vector classification:** Hierarchical labels, confidence threshold, hybrid search, incremental learning.
280
+ - **More features:** Additional AI capabilities as the gem evolves.
255
281
 
256
282
  ---
257
283
 
@@ -262,8 +288,6 @@ bundle install
262
288
  bundle exec rake install
263
289
  ```
264
290
 
265
- Run tests (when added) with `bundle exec rspec` or `bundle exec rake test`.
266
-
267
291
  ---
268
292
 
269
293
  ## License
@@ -272,18 +296,15 @@ MIT.
272
296
 
273
297
  ---
274
298
 
275
- ## How it’s built
299
+ ## Related
276
300
 
277
- - **Configuration**Embedding provider, dimensions, API keys.
278
- - **Embedding providers** Base + OpenAI and Cohere (`embed`, `embed_batch`).
279
- - **EmbeddingService** – `RailsAiKit.embedding` / `RailsAiKit.embed(text)`.
280
- - **Classifier** – Label training, `classify`, `classify_by_embedding`, `batch_classify`; uses `rails_ai_kit_labels` + Neighbor for similarity.
281
- - **VectorClassify** – ActiveRecord concern: `vector_classify` macro, `similar_to` scope.
282
- - **Generators** – Install (labels table), vector_columns (embedding/label/confidence on a model).
301
+ - [pgvector](https://github.com/pgvector/pgvector)Vector similarity search for Postgres
302
+ - [Neighbor](https://github.com/ankane/neighbor)Nearest neighbor search for Rails (used for vector classification)
283
303
 
284
304
  ---
285
305
 
286
- ## Related
306
+ ## Creator
287
307
 
288
- - [pgvector](https://github.com/pgvector/pgvector) Vector similarity search for Postgres
289
- - [Neighbor](https://github.com/ankane/neighbor) – Nearest neighbor search for Rails (used by this gem)
308
+ **Rohit Kushwaha** *AI Engineer*
309
+
310
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-Rohit%20Kushwaha-0A66C2?style=flat&logo=linkedin)](https://www.linkedin.com/in/rohit-kushwaha-6915b7197/)
@@ -0,0 +1,38 @@
1
+ <div class="rails_ai_kit traces">
2
+ <h1>AI Traces</h1>
3
+
4
+ <% if defined?(@stats) && @stats %>
5
+ <div class="stats">
6
+ <span>Total: <%= @stats[:total] %></span>
7
+ <span>Blocked: <%= @stats[:blocked] %></span>
8
+ <span>Models: <%= @stats[:models]&.join(", ") %></span>
9
+ </div>
10
+ <% end %>
11
+
12
+ <table>
13
+ <thead>
14
+ <tr>
15
+ <th>Time</th>
16
+ <th>Model</th>
17
+ <th>Prompt</th>
18
+ <th>Response</th>
19
+ <th>Guard scores</th>
20
+ <th>Latency (ms)</th>
21
+ <th>Action</th>
22
+ </tr>
23
+ </thead>
24
+ <tbody>
25
+ <% @traces.each do |trace| %>
26
+ <tr class="<%= trace.action %>">
27
+ <td><%= trace.created_at&.strftime("%Y-%m-%d %H:%M") %></td>
28
+ <td><%= trace.model %></td>
29
+ <td><%= truncate(trace.prompt, length: 80) %></td>
30
+ <td><%= truncate(trace.response, length: 80) %></td>
31
+ <td><%= trace.guard_scores.is_a?(Hash) ? trace.guard_scores.to_json : trace.guard_scores %></td>
32
+ <td><%= trace.latency_ms %></td>
33
+ <td><%= trace.action %></td>
34
+ </tr>
35
+ <% end %>
36
+ </tbody>
37
+ </table>
38
+ </div>
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAiKit
4
+ class TracesController < ActionController::Base
5
+ layout "application"
6
+ before_action :set_traces
7
+
8
+ def index
9
+ @traces = @traces.recent.limit(100)
10
+ @traces = @traces.by_model(params[:model]) if params[:model].present?
11
+ @traces = @traces.blocked if params[:blocked] == "1"
12
+ @stats = {
13
+ total: RailsAiKit::Trace.count,
14
+ blocked: RailsAiKit::Trace.blocked.count,
15
+ models: RailsAiKit::Trace.distinct.pluck(:model).compact
16
+ }
17
+ end
18
+
19
+ private
20
+
21
+ def set_traces
22
+ @traces = RailsAiKit::Trace.all
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RailsAiKit
6
+ module Generators
7
+ class DashboardGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Adds AI Traces dashboard (controller + view + route) at /ai_traces"
11
+
12
+ def create_controller
13
+ copy_file "traces_controller.rb", "app/controllers/rails_ai_kit/traces_controller.rb"
14
+ end
15
+
16
+ def create_view
17
+ copy_file "index.html.erb", "app/views/rails_ai_kit/traces/index.html.erb"
18
+ end
19
+
20
+ def add_route
21
+ route 'get "/ai_traces", to: "rails_ai_kit/traces#index", as: :ai_traces'
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateRailsAiKitTables < ActiveRecord::Migration[<%= Rails::VERSION::MAJOR %>.<%= Rails::VERSION::MINOR %>]
4
+ def change
5
+ enable_extension "vector" unless extension_enabled?("vector")
6
+
7
+ create_table :rails_ai_kit_labels do |t|
8
+ t.string :classifier_name, null: false
9
+ t.string :label_name, null: false
10
+ t.vector :embedding, limit: <%= (defined?(RailsAiKit) && RailsAiKit.configuration.embedding_dimensions) || 1536 %>, null: false
11
+ t.timestamps
12
+ end
13
+ add_index :rails_ai_kit_labels, [:classifier_name, :label_name], unique: true
14
+
15
+ create_table :rails_ai_traces do |t|
16
+ t.text :prompt, null: false
17
+ t.text :response
18
+ t.string :model
19
+ t.integer :prompt_tokens
20
+ t.integer :completion_tokens
21
+ t.jsonb :guard_scores, default: {}
22
+ t.decimal :latency_ms, precision: 10, scale: 2
23
+ t.string :action, default: "allow"
24
+ t.timestamps
25
+ end
26
+ add_index :rails_ai_traces, :created_at
27
+ add_index :rails_ai_traces, :model
28
+ add_index :rails_ai_traces, :action
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateRailsAiTraces < ActiveRecord::Migration[<%= Rails::VERSION::MAJOR %>.<%= Rails::VERSION::MINOR %>]
4
+ def change
5
+ create_table :rails_ai_traces do |t|
6
+ t.text :prompt, null: false
7
+ t.text :response
8
+ t.string :model
9
+ t.integer :prompt_tokens
10
+ t.integer :completion_tokens
11
+ t.jsonb :guard_scores, default: {}
12
+ t.decimal :latency_ms, precision: 10, scale: 2
13
+ t.string :action, default: "allow"
14
+ t.timestamps
15
+ end
16
+
17
+ add_index :rails_ai_traces, :created_at
18
+ add_index :rails_ai_traces, :model
19
+ add_index :rails_ai_traces, :action
20
+ end
21
+ end