completion-kit 0.5.28 → 0.5.30
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/README.md +55 -5
- data/app/assets/javascripts/completion_kit/application.js +6 -0
- data/app/assets/stylesheets/completion_kit/application.css +88 -0
- data/app/controllers/completion_kit/api/v1/base_controller.rb +2 -0
- data/app/controllers/completion_kit/application_controller.rb +2 -0
- data/app/views/layouts/completion_kit/application.html.erb +7 -7
- data/lib/completion_kit/version.rb +1 -1
- data/lib/completion_kit.rb +4 -0
- data/lib/generators/completion_kit/templates/initializer.rb +6 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1bc6277357aee1869602b6c290d958c794788d14efc01d0c4caf1529d3567d69
|
|
4
|
+
data.tar.gz: a1e60f9278e2bc42c4e1bb487e850f7568a3b134b46e8dbc6b6b3d0bea79c469
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb4ffbbdb89589e0eca37f995ebc342bd6a6063160ca00cffa25abc956532a2b34aa7e6ce7b7c852802bcf5ed74cc4c49cb7f817ab36dbe07608bf18dd52dc1d
|
|
7
|
+
data.tar.gz: 61ef079b27c73bb749778e182052ac9c1e6824f1d432635210b647f716f10c5364f6ff8ec9b45cbc274d00b3d93d58b37613c61953596fd7ccf6fe89df1b88f0
|
data/README.md
CHANGED
|
@@ -18,19 +18,19 @@ It's the difference between "this prompt seems to work" and "this prompt scores
|
|
|
18
18
|
|
|
19
19
|
> **Just want to use it?** [CompletionKit Cloud](https://completionkit.com) is the same engine, fully hosted — zero install, no Rails ops, plans at [completionkit.com/pricing](https://completionkit.com/pricing).
|
|
20
20
|
|
|
21
|
-

|
|
22
22
|
|
|
23
23
|
## Three ways to run it
|
|
24
24
|
|
|
25
|
-
Same engine, same UI, same REST API and MCP server — pick the deployment that fits.
|
|
25
|
+
Same engine, same UI, same REST API and MCP server — pick the deployment that fits. The first two are stack-agnostic: you run CompletionKit as a product and talk to it over HTTP and MCP, whatever language your own app is written in. The third is for teams already building on Rails.
|
|
26
26
|
|
|
27
27
|
### 1. Hosted — [completionkit.com](https://completionkit.com) (recommended)
|
|
28
28
|
|
|
29
29
|
The fastest path. Sign up and you're running on the same engine you'd self-host, without touching a Rails app. No `db:migrate`, no Puma, no Solid Queue, no provider key management — multi-tenant workspaces, your team logs in, you go. Plans at [completionkit.com/pricing](https://completionkit.com/pricing).
|
|
30
30
|
|
|
31
|
-
### 2. Self-hosted — the bundled standalone
|
|
31
|
+
### 2. Self-hosted — the bundled standalone app
|
|
32
32
|
|
|
33
|
-
Run it on your own infra.
|
|
33
|
+
Run it on your own infra as a self-contained product. There's nothing to integrate and no Ruby to write — once it's up, you drive everything through the web UI, the REST API, and the MCP server, from whatever stack your own app is built in. It needs Postgres and a host that can run the app (Fly, Render, Heroku, Docker, …).
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
git clone https://github.com/homemade-software-inc/completion-kit.git
|
|
@@ -111,7 +111,7 @@ Or add them to `config/credentials.yml.enc` under `active_record_encryption`. In
|
|
|
111
111
|
|
|
112
112
|
## Authentication
|
|
113
113
|
|
|
114
|
-
CompletionKit requires authentication in
|
|
114
|
+
CompletionKit requires authentication in any deployed environment. In development and test, routes are open by default (with a log warning); every other environment returns 403 until auth is configured.
|
|
115
115
|
|
|
116
116
|
### Basic Auth (recommended for simple setups)
|
|
117
117
|
|
|
@@ -132,8 +132,27 @@ end
|
|
|
132
132
|
|
|
133
133
|
Only one mode can be active.
|
|
134
134
|
|
|
135
|
+
## Rate limiting
|
|
136
|
+
|
|
137
|
+
The REST API, the MCP endpoint, and the web UI are rate limited per IP, per minute. The defaults are generous; tune them in the initializer:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
CompletionKit.configure do |c|
|
|
141
|
+
c.api_rate_limit = 120 # REST API + MCP, requests per minute (default 120)
|
|
142
|
+
c.web_rate_limit = 300 # web UI, requests per minute (default 300)
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Limiting uses `Rails.cache`. A shared cache store (Solid Cache, Redis) throttles accurately across multiple app instances; a per-process store still throttles each instance independently.
|
|
147
|
+
|
|
135
148
|
## How it works
|
|
136
149
|
|
|
150
|
+
<p align="center">
|
|
151
|
+
<img src="https://raw.githubusercontent.com/homemade-software-inc/completion-kit/main/docs/diagrams/workflow.png" alt="CompletionKit workflow: a prompt and a dataset feed a run against a model, an LLM judge scores each output on your rubric, low scores drive an AI-suggested rewrite, and the new prompt version re-runs so you can compare" width="820" />
|
|
152
|
+
</p>
|
|
153
|
+
|
|
154
|
+
It's a loop. Each pass leaves you with a score you can compare against the last one.
|
|
155
|
+
|
|
137
156
|
1. **Create a prompt** with `{{variable}}` placeholders
|
|
138
157
|
2. **Upload a dataset.** A CSV where column headers match the variable names.
|
|
139
158
|
3. **Run it** against a model and score outputs with an LLM judge against criteria you define.
|
|
@@ -204,6 +223,37 @@ bin/rails db:migrate
|
|
|
204
223
|
git add db/migrate/ && git commit -m "install new engine migration"
|
|
205
224
|
```
|
|
206
225
|
|
|
226
|
+
### Docker
|
|
227
|
+
|
|
228
|
+
The standalone app ships a `Dockerfile`, so you can self-host it without a Ruby toolchain on the host. Build with the **repository root** as the context — the app depends on the engine source alongside it:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
docker build -f standalone/Dockerfile -t completion-kit .
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
CompletionKit needs a Rails secret (`SECRET_KEY_BASE`) and three Active Record encryption keys. With Docker there's no Rails toolchain on the host to run `bin/rails db:encryption:init`, so generate them with `openssl`. Generate them **once** and keep them stable — if the encryption keys change, provider credentials already stored in the database can no longer be decrypted. Write everything to an env file:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
cat > completion-kit.env <<EOF
|
|
238
|
+
DATABASE_URL=postgres://user:pass@host/completionkit
|
|
239
|
+
SECRET_KEY_BASE=$(openssl rand -hex 64)
|
|
240
|
+
COMPLETION_KIT_ENCRYPTION_PRIMARY_KEY=$(openssl rand -hex 32)
|
|
241
|
+
COMPLETION_KIT_ENCRYPTION_DETERMINISTIC_KEY=$(openssl rand -hex 32)
|
|
242
|
+
COMPLETION_KIT_ENCRYPTION_KEY_DERIVATION_SALT=$(openssl rand -hex 32)
|
|
243
|
+
EOF
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`openssl rand` runs as the file is written, so each line gets a real random value. Keep `completion-kit.env` out of version control and back it up somewhere safe.
|
|
247
|
+
|
|
248
|
+
Run the web process and a job worker from the same image, both pointed at that file:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
docker run -d -p 3000:3000 --env-file completion-kit.env completion-kit
|
|
252
|
+
docker run -d --env-file completion-kit.env completion-kit ./bin/jobs
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Both processes must share the same `SECRET_KEY_BASE` and encryption keys — the single env file guarantees that. The web container runs `db:prepare` on boot, so migrations apply on first start and on every deploy.
|
|
256
|
+
|
|
207
257
|
## Multi-tenant host apps (advanced)
|
|
208
258
|
|
|
209
259
|
For hosts that mount CompletionKit in a multi-tenant app, two optional hooks scope engine records per tenant without forking the engine:
|
|
@@ -158,3 +158,9 @@ document.addEventListener("turbo:before-stream-render", function(event) {
|
|
|
158
158
|
if (status) { status.textContent = 'Models updated.'; setTimeout(function() { status.textContent = ' '; }, 3000); }
|
|
159
159
|
}
|
|
160
160
|
});
|
|
161
|
+
|
|
162
|
+
document.addEventListener("click", function(e) {
|
|
163
|
+
document.querySelectorAll("details.ck-nav-menu[open], details.ck-settings-menu[open], details.ck-flyout[open]").forEach(function(menu) {
|
|
164
|
+
if (!menu.contains(e.target)) menu.removeAttribute("open");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -4345,6 +4345,16 @@ a.tag-mark {
|
|
|
4345
4345
|
.ck-settings-menu__trigger::-webkit-details-marker {
|
|
4346
4346
|
display: none;
|
|
4347
4347
|
}
|
|
4348
|
+
.ck-settings-menu__trigger svg {
|
|
4349
|
+
display: block;
|
|
4350
|
+
width: 18px;
|
|
4351
|
+
height: 18px;
|
|
4352
|
+
}
|
|
4353
|
+
.ck-settings-menu__trigger:focus-visible {
|
|
4354
|
+
outline: none;
|
|
4355
|
+
border-color: var(--ck-accent);
|
|
4356
|
+
color: var(--ck-accent);
|
|
4357
|
+
}
|
|
4348
4358
|
.ck-settings-menu__panel {
|
|
4349
4359
|
position: absolute;
|
|
4350
4360
|
right: 0;
|
|
@@ -4374,6 +4384,16 @@ a.tag-mark {
|
|
|
4374
4384
|
.ck-settings-menu__item:hover {
|
|
4375
4385
|
background: var(--ck-surface-hover);
|
|
4376
4386
|
}
|
|
4387
|
+
.ck-settings-menu__panel form {
|
|
4388
|
+
margin: 0;
|
|
4389
|
+
}
|
|
4390
|
+
.ck-settings-menu__panel button.ck-settings-menu__item {
|
|
4391
|
+
width: 100%;
|
|
4392
|
+
text-align: left;
|
|
4393
|
+
background: none;
|
|
4394
|
+
border: 0;
|
|
4395
|
+
cursor: pointer;
|
|
4396
|
+
}
|
|
4377
4397
|
|
|
4378
4398
|
/* Settings page kicker — small label above the page title on settings pages */
|
|
4379
4399
|
.ck-settings-kicker {
|
|
@@ -4961,3 +4981,71 @@ a.tag-mark {
|
|
|
4961
4981
|
max-height: 60vh;
|
|
4962
4982
|
}
|
|
4963
4983
|
}
|
|
4984
|
+
|
|
4985
|
+
.ck-concept {
|
|
4986
|
+
position: relative;
|
|
4987
|
+
display: inline;
|
|
4988
|
+
}
|
|
4989
|
+
.ck-concept__toggle {
|
|
4990
|
+
margin: 0 0 0 0.15rem;
|
|
4991
|
+
padding: 0;
|
|
4992
|
+
border: 0;
|
|
4993
|
+
background: none;
|
|
4994
|
+
font: inherit;
|
|
4995
|
+
line-height: 0;
|
|
4996
|
+
color: var(--ck-dim);
|
|
4997
|
+
cursor: pointer;
|
|
4998
|
+
}
|
|
4999
|
+
.ck-concept__toggle:hover,
|
|
5000
|
+
.ck-concept__toggle:focus {
|
|
5001
|
+
color: var(--ck-accent);
|
|
5002
|
+
}
|
|
5003
|
+
.ck-concept__icon {
|
|
5004
|
+
width: 0.92em;
|
|
5005
|
+
height: 0.92em;
|
|
5006
|
+
vertical-align: -0.12em;
|
|
5007
|
+
}
|
|
5008
|
+
.ck-concept__pop {
|
|
5009
|
+
display: none;
|
|
5010
|
+
position: absolute;
|
|
5011
|
+
top: calc(100% + 0.4rem);
|
|
5012
|
+
left: 0;
|
|
5013
|
+
z-index: 40;
|
|
5014
|
+
width: 19rem;
|
|
5015
|
+
max-width: calc(100vw - 1.5rem);
|
|
5016
|
+
padding: 0.7rem 0.8rem;
|
|
5017
|
+
background: var(--ck-bg-strong);
|
|
5018
|
+
border: 1px solid var(--ck-line-strong);
|
|
5019
|
+
border-radius: var(--ck-radius);
|
|
5020
|
+
box-shadow: 0 16px 34px rgba(0, 0, 0, 0.5);
|
|
5021
|
+
white-space: normal;
|
|
5022
|
+
}
|
|
5023
|
+
.ck-concept__toggle:hover + .ck-concept__pop,
|
|
5024
|
+
.ck-concept__toggle:focus + .ck-concept__pop,
|
|
5025
|
+
.ck-concept__pop:hover {
|
|
5026
|
+
display: block;
|
|
5027
|
+
}
|
|
5028
|
+
.ck-concept__name {
|
|
5029
|
+
display: block;
|
|
5030
|
+
margin-bottom: 0.3rem;
|
|
5031
|
+
font-family: var(--ck-mono);
|
|
5032
|
+
font-size: 0.72rem;
|
|
5033
|
+
font-weight: 700;
|
|
5034
|
+
letter-spacing: 0.06em;
|
|
5035
|
+
text-transform: uppercase;
|
|
5036
|
+
color: var(--ck-text);
|
|
5037
|
+
}
|
|
5038
|
+
.ck-concept__body {
|
|
5039
|
+
display: block;
|
|
5040
|
+
font-size: 0.8rem;
|
|
5041
|
+
line-height: 1.5;
|
|
5042
|
+
color: var(--ck-muted);
|
|
5043
|
+
}
|
|
5044
|
+
@media (max-width: 640px) {
|
|
5045
|
+
.ck-concept__pop {
|
|
5046
|
+
position: fixed;
|
|
5047
|
+
inset: auto 0.75rem 0.75rem 0.75rem;
|
|
5048
|
+
width: auto;
|
|
5049
|
+
max-width: none;
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
@@ -2,6 +2,8 @@ module CompletionKit
|
|
|
2
2
|
module Api
|
|
3
3
|
module V1
|
|
4
4
|
class BaseController < ActionController::API
|
|
5
|
+
rate_limit to: CompletionKit.config.api_rate_limit, within: 1.minute,
|
|
6
|
+
with: -> { render json: {error: "Rate limit exceeded"}, status: :too_many_requests }
|
|
5
7
|
before_action :authenticate_api!
|
|
6
8
|
|
|
7
9
|
private
|
|
@@ -3,6 +3,8 @@ module CompletionKit
|
|
|
3
3
|
helper Heroicons::IconsHelper
|
|
4
4
|
layout "completion_kit/application"
|
|
5
5
|
|
|
6
|
+
rate_limit to: CompletionKit.config.web_rate_limit, within: 1.minute,
|
|
7
|
+
with: -> { render plain: "Rate limit exceeded. Please slow down.", status: :too_many_requests }
|
|
6
8
|
before_action :authenticate_completion_kit!
|
|
7
9
|
|
|
8
10
|
private
|
|
@@ -27,21 +27,21 @@
|
|
|
27
27
|
<%= link_to "Metrics", metrics_path, class: request.path.start_with?(metrics_path) || request.path.start_with?(metric_groups_path) ? ck_button_classes(:dark) : ck_button_classes(:light, variant: :outline) %>
|
|
28
28
|
<%= link_to "Datasets", datasets_path, class: active.(datasets_path) %>
|
|
29
29
|
<%= link_to "Runs", runs_path, class: active.(runs_path) %>
|
|
30
|
-
<% settings_active = request.path.start_with?(provider_credentials_path) || request.path.start_with?(tags_path) || request.path.start_with?(onboarding_path) %>
|
|
30
|
+
<% settings_active = request.path.start_with?(provider_credentials_path) || request.path.start_with?(tags_path) || request.path.start_with?(onboarding_path) || request.path.start_with?(api_reference_path) %>
|
|
31
31
|
<details class="ck-settings-menu">
|
|
32
32
|
<summary class="<%= settings_active ? ck_button_classes(:dark) : ck_button_classes(:light, variant: :outline) %> ck-settings-menu__trigger" aria-label="Settings">
|
|
33
|
-
|
|
33
|
+
<%= heroicon_tag "cog-6-tooth", variant: :outline, size: 18, "aria-hidden": "true" %>
|
|
34
34
|
</summary>
|
|
35
35
|
<div class="ck-settings-menu__panel" role="menu">
|
|
36
|
+
<%= link_to "Getting started", onboarding_path(reset: 1), class: "ck-settings-menu__item" %>
|
|
37
|
+
<%= link_to "API", api_reference_path, class: "ck-settings-menu__item" %>
|
|
36
38
|
<%= link_to "Providers", provider_credentials_path, class: "ck-settings-menu__item" %>
|
|
37
39
|
<%= link_to "Tags", tags_path, class: "ck-settings-menu__item" %>
|
|
38
|
-
|
|
40
|
+
<% if main_app.respond_to?(:logout_path) %>
|
|
41
|
+
<%= button_to "Sign out", main_app.logout_path, method: :delete, class: "ck-settings-menu__item" %>
|
|
42
|
+
<% end %>
|
|
39
43
|
</div>
|
|
40
44
|
</details>
|
|
41
|
-
<%= link_to "API", api_reference_path, class: active.(api_reference_path) %>
|
|
42
|
-
<% if main_app.respond_to?(:logout_path) %>
|
|
43
|
-
<%= button_to "Log out", main_app.logout_path, method: :delete, class: ck_button_classes(:light, variant: :outline) %>
|
|
44
|
-
<% end %>
|
|
45
45
|
</nav>
|
|
46
46
|
</details>
|
|
47
47
|
</div>
|
data/lib/completion_kit.rb
CHANGED
|
@@ -10,6 +10,7 @@ module CompletionKit
|
|
|
10
10
|
attr_accessor :username, :password, :auth_strategy, :api_token
|
|
11
11
|
attr_accessor :tenant_scope, :tenant_scope_columns
|
|
12
12
|
attr_accessor :api_reference_authentication_partial
|
|
13
|
+
attr_accessor :api_rate_limit, :web_rate_limit
|
|
13
14
|
|
|
14
15
|
def initialize
|
|
15
16
|
@openai_api_key = ENV['OPENAI_API_KEY']
|
|
@@ -21,6 +22,9 @@ module CompletionKit
|
|
|
21
22
|
@high_quality_threshold = 4
|
|
22
23
|
@medium_quality_threshold = 3
|
|
23
24
|
|
|
25
|
+
@api_rate_limit = 120
|
|
26
|
+
@web_rate_limit = 300
|
|
27
|
+
|
|
24
28
|
@api_reference_authentication_partial = "completion_kit/api_reference/authentication"
|
|
25
29
|
end
|
|
26
30
|
|
|
@@ -44,4 +44,10 @@ CompletionKit.configure do |config|
|
|
|
44
44
|
# Web UI Authentication
|
|
45
45
|
# config.username = "admin"
|
|
46
46
|
# config.password = ENV['COMPLETION_KIT_PASSWORD']
|
|
47
|
+
|
|
48
|
+
# Rate limiting (requests per minute, per IP)
|
|
49
|
+
# The REST API and MCP endpoint share api_rate_limit; the web UI uses
|
|
50
|
+
# web_rate_limit. Both are enforced through Rails.cache.
|
|
51
|
+
# config.api_rate_limit = 120
|
|
52
|
+
# config.web_rate_limit = 300
|
|
47
53
|
end
|