completion-kit 0.26.7 → 0.27.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/app/assets/stylesheets/completion_kit/application.css +25 -8
- data/app/jobs/completion_kit/model_discovery_job.rb +6 -1
- data/app/models/completion_kit/prompt.rb +12 -0
- data/app/models/completion_kit/provider_credential.rb +4 -0
- data/app/services/completion_kit/azure_foundry_client.rb +14 -23
- data/app/services/completion_kit/model_discovery_service.rb +18 -1
- data/app/views/completion_kit/provider_credentials/_models_card.html.erb +12 -5
- data/app/views/completion_kit/responses/show.html.erb +7 -2
- data/app/views/completion_kit/runs/_response_row.html.erb +1 -1
- data/db/migrate/20260713000001_add_catalog_model_count_to_completion_kit_provider_credentials.rb +5 -0
- data/lib/completion_kit/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f0fa2b7edcc463d34ce7dbed34a522119f840778bd75fa9912124069564420dd
|
|
4
|
+
data.tar.gz: 16e1aa2310b4efd26c67fcf70ac55dedb0bd760e97201f41bdb9c73f98247070
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0572479a9194f494f34f4982c563dc5b3ffae3c1e430c0b39786ea05a6eac15359d7f51946810c8208bf60eb43274554b152d981b492d9fe9a04a01c7c8cb23b'
|
|
7
|
+
data.tar.gz: 931853452dac7e901d2e8eed066011c7f428aed7290d68d86787b0c029a9d4f7e0e1b0888ed2adefe015093a6f423487b433731e6a4826ac9dcf5ca255943e62
|
|
@@ -961,14 +961,6 @@ tr:hover .ck-chip--publish {
|
|
|
961
961
|
max-width: 36rem;
|
|
962
962
|
}
|
|
963
963
|
|
|
964
|
-
.ck-model-list__empty {
|
|
965
|
-
display: flex;
|
|
966
|
-
align-items: center;
|
|
967
|
-
gap: 1rem;
|
|
968
|
-
flex-wrap: wrap;
|
|
969
|
-
padding: 0.35rem 0.25rem;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
964
|
.ck-form-card__footer-header {
|
|
973
965
|
display: flex;
|
|
974
966
|
align-items: center;
|
|
@@ -1504,6 +1496,26 @@ tr:hover .ck-chip--publish {
|
|
|
1504
1496
|
gap: 0.6rem;
|
|
1505
1497
|
}
|
|
1506
1498
|
|
|
1499
|
+
.ck-model-list__catalog {
|
|
1500
|
+
margin-top: 0.75rem;
|
|
1501
|
+
padding-top: 0.6rem;
|
|
1502
|
+
border-top: 1px solid var(--ck-line);
|
|
1503
|
+
font-family: var(--ck-mono);
|
|
1504
|
+
font-size: 0.72rem;
|
|
1505
|
+
letter-spacing: 0.03em;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
.ck-model-list__catalog-link {
|
|
1509
|
+
color: var(--ck-accent);
|
|
1510
|
+
font-weight: 500;
|
|
1511
|
+
text-decoration: none;
|
|
1512
|
+
white-space: nowrap;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
.ck-model-list__catalog-link:hover {
|
|
1516
|
+
color: var(--ck-accent-hover);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1507
1519
|
.ck-model-list__summary-stamp {
|
|
1508
1520
|
font-family: var(--ck-mono);
|
|
1509
1521
|
font-size: 0.7rem;
|
|
@@ -1562,6 +1574,11 @@ tr:hover .ck-chip--publish {
|
|
|
1562
1574
|
color: var(--ck-accent);
|
|
1563
1575
|
}
|
|
1564
1576
|
|
|
1577
|
+
.ck-code--error {
|
|
1578
|
+
background: var(--ck-danger-soft);
|
|
1579
|
+
border-color: rgba(248, 113, 113, 0.35);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1565
1582
|
/* the main prompt template block on prompts/show — bigger padding + a
|
|
1566
1583
|
touch more line-height since this is the page's primary content */
|
|
1567
1584
|
.ck-code--prompt {
|
|
@@ -48,7 +48,12 @@ module CompletionKit
|
|
|
48
48
|
credential.broadcast_discovery_progress
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
credential.update_columns(
|
|
51
|
+
credential.update_columns(
|
|
52
|
+
discovery_status: "completed",
|
|
53
|
+
discovery_error: nil,
|
|
54
|
+
catalog_model_count: service.catalog_model_count,
|
|
55
|
+
updated_at: Time.current
|
|
56
|
+
)
|
|
52
57
|
credential.reload
|
|
53
58
|
credential.broadcast_discovery_complete
|
|
54
59
|
end
|
|
@@ -8,6 +8,7 @@ module CompletionKit
|
|
|
8
8
|
validates :name, presence: true
|
|
9
9
|
validates :template, presence: true
|
|
10
10
|
validates :llm_model, presence: true
|
|
11
|
+
validate :llm_model_usable_for_generation, if: :llm_model_changed?
|
|
11
12
|
validates :family_key, presence: true
|
|
12
13
|
validates :version_number, presence: true, numericality: { only_integer: true, greater_than: 0 }
|
|
13
14
|
|
|
@@ -86,6 +87,17 @@ module CompletionKit
|
|
|
86
87
|
|
|
87
88
|
private
|
|
88
89
|
|
|
90
|
+
def llm_model_usable_for_generation
|
|
91
|
+
return if llm_model.blank?
|
|
92
|
+
|
|
93
|
+
rows = Model.where(model_id: llm_model)
|
|
94
|
+
return if rows.empty?
|
|
95
|
+
return if rows.where(supports_generation: true, status: "active").exists?
|
|
96
|
+
return unless rows.where(supports_generation: false).exists?
|
|
97
|
+
|
|
98
|
+
errors.add(:llm_model, "is not available for generating responses")
|
|
99
|
+
end
|
|
100
|
+
|
|
89
101
|
def assign_family_key
|
|
90
102
|
self.family_key ||= SecureRandom.uuid
|
|
91
103
|
end
|
|
@@ -61,6 +61,10 @@ module CompletionKit
|
|
|
61
61
|
Model.where(provider: provider).active.count
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
def foundry_catalog_available?
|
|
65
|
+
azure_foundry? && catalog_model_count.to_i.positive?
|
|
66
|
+
end
|
|
67
|
+
|
|
64
68
|
def self.discovery_in_progress?
|
|
65
69
|
where(discovery_status: "discovering").exists?
|
|
66
70
|
end
|
|
@@ -58,16 +58,26 @@ module CompletionKit
|
|
|
58
58
|
return [] unless configured?
|
|
59
59
|
return [] unless ProviderEndpoint.safe?(api_endpoint)
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
path = foundry_project? ? "#{azure_base_url}/deployments?api-version=v1" : models_path
|
|
62
|
+
response = build_connection(azure_base_url).get(path) do |req|
|
|
62
63
|
req.headers["api-key"] = api_key
|
|
63
64
|
end
|
|
64
65
|
return [] unless response.success?
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
body = JSON.parse(response.body)
|
|
68
|
+
if foundry_project?
|
|
69
|
+
body.fetch("value", []).map { |d| { id: d["name"], name: d["name"] } }
|
|
70
|
+
else
|
|
71
|
+
body.fetch("data", []).map { |entry| { id: entry["id"], name: entry["id"] } }
|
|
72
|
+
end
|
|
67
73
|
rescue StandardError
|
|
68
74
|
[]
|
|
69
75
|
end
|
|
70
76
|
|
|
77
|
+
def foundry_project?
|
|
78
|
+
api_endpoint.to_s.include?("/api/projects/")
|
|
79
|
+
end
|
|
80
|
+
|
|
71
81
|
def configured?
|
|
72
82
|
configuration_errors.empty?
|
|
73
83
|
end
|
|
@@ -101,27 +111,8 @@ module CompletionKit
|
|
|
101
111
|
api_version.blank?
|
|
102
112
|
end
|
|
103
113
|
|
|
104
|
-
def
|
|
105
|
-
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def models_url
|
|
109
|
-
if foundry_project?
|
|
110
|
-
"#{azure_base_url}/deployments?api-version=v1"
|
|
111
|
-
elsif v1_mode?
|
|
112
|
-
"/openai/v1/models"
|
|
113
|
-
else
|
|
114
|
-
"/openai/deployments?api-version=#{api_version}"
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def parse_models(body)
|
|
119
|
-
json = JSON.parse(body)
|
|
120
|
-
if foundry_project?
|
|
121
|
-
json.fetch("value", []).map { |d| { id: d["name"], name: d["name"] } }
|
|
122
|
-
else
|
|
123
|
-
json.fetch("data", []).map { |e| { id: e["id"], name: e["id"] } }
|
|
124
|
-
end
|
|
114
|
+
def models_path
|
|
115
|
+
v1_mode? ? "/openai/v1/models" : "/openai/deployments?api-version=#{api_version}"
|
|
125
116
|
end
|
|
126
117
|
|
|
127
118
|
def post_chat(model:, prompt:, max_tokens:, temperature:, max_completion: false)
|
|
@@ -8,11 +8,14 @@ module CompletionKit
|
|
|
8
8
|
|
|
9
9
|
AZURE_HOST_SUFFIXES = [".openai.azure.com", ".services.ai.azure.com"].freeze
|
|
10
10
|
|
|
11
|
+
attr_reader :catalog_model_count
|
|
12
|
+
|
|
11
13
|
def initialize(config:)
|
|
12
14
|
@provider = config[:provider]
|
|
13
15
|
@api_key = config[:api_key]
|
|
14
16
|
@api_endpoint = config[:api_endpoint]
|
|
15
17
|
@api_version = config[:api_version]
|
|
18
|
+
@catalog_model_count = nil
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
def refresh!(force: false, &on_progress)
|
|
@@ -156,18 +159,32 @@ module CompletionKit
|
|
|
156
159
|
def fetch_azure_foundry_models
|
|
157
160
|
raise DiscoveryError, "An Azure endpoint URL is required." if @api_endpoint.blank?
|
|
158
161
|
|
|
159
|
-
|
|
162
|
+
path = azure_foundry_project? ? "#{azure_base_url}/deployments?api-version=v1" : azure_models_path
|
|
163
|
+
response = fetch_connection(azure_base_url).get(path) do |req|
|
|
160
164
|
req.headers["api-key"] = @api_key
|
|
161
165
|
end
|
|
162
166
|
raise DiscoveryError, azure_error_message(response) unless response.success?
|
|
167
|
+
|
|
163
168
|
body = JSON.parse(response.body)
|
|
164
169
|
if azure_foundry_project?
|
|
170
|
+
@catalog_model_count = fetch_azure_catalog_count
|
|
165
171
|
body.fetch("value", []).map { |d| { id: d["name"], display_name: d["name"] } }
|
|
166
172
|
else
|
|
167
173
|
body.fetch("data", []).map { |e| { id: e["id"], display_name: e["id"] } }
|
|
168
174
|
end
|
|
169
175
|
end
|
|
170
176
|
|
|
177
|
+
def fetch_azure_catalog_count
|
|
178
|
+
response = fetch_connection(azure_base_url).get("/openai/v1/models") do |req|
|
|
179
|
+
req.headers["api-key"] = @api_key
|
|
180
|
+
end
|
|
181
|
+
return nil unless response.success?
|
|
182
|
+
|
|
183
|
+
JSON.parse(response.body).fetch("data", []).length
|
|
184
|
+
rescue StandardError
|
|
185
|
+
nil
|
|
186
|
+
end
|
|
187
|
+
|
|
171
188
|
def azure_v1_mode?
|
|
172
189
|
@api_version.blank?
|
|
173
190
|
end
|
|
@@ -71,12 +71,19 @@
|
|
|
71
71
|
</div>
|
|
72
72
|
</details>
|
|
73
73
|
<% elsif provider_credential.discovery_status != "discovering" %>
|
|
74
|
-
<div class="ck-model-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
<div class="ck-model-list__summary">
|
|
75
|
+
<span class="ck-model-list__summary-label">Available models <span class="ck-model-list__summary-count">0</span></span>
|
|
76
|
+
<span class="ck-model-list__summary-meta">
|
|
77
|
+
<button type="button" class="ck-icon-btn ck-model-list__refresh" title="Refresh models" aria-label="Refresh available models" onclick="event.preventDefault();fetch('<%= refresh_provider_credential_path(provider_credential) %>', {method:'POST',headers:{'X-CSRF-Token':document.querySelector('meta[name=csrf-token]').content}});if(window.ckStartDiscoveryPolling)ckStartDiscoveryPolling(8000)"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" width="13" height="13" aria-hidden="true"><path fill-rule="evenodd" d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.681.75.75 0 0 1-1.264-.808 6 6 0 0 1 9.44-.908l.84.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44.908l-.84-.84v1.68a.75.75 0 0 1-1.5 0V9.567a.75.75 0 0 1 .75-.75h3.182a.75.75 0 0 1 0 1.5h-1.37l.84.841a4.5 4.5 0 0 0 7.08-.681.75.75 0 0 1 1.024-.274Z" clip-rule="evenodd"/></svg></button>
|
|
78
|
+
</span>
|
|
79
79
|
</div>
|
|
80
80
|
<% end %>
|
|
81
|
+
|
|
82
|
+
<% if provider_credential.foundry_catalog_available? %>
|
|
83
|
+
<p class="ck-meta-copy ck-model-list__catalog">
|
|
84
|
+
<%= pluralize(provider_credential.catalog_model_count, "model") %> available to deploy in
|
|
85
|
+
<a href="https://ai.azure.com/" target="_blank" rel="noopener" class="ck-model-list__catalog-link">Azure AI Foundry <span aria-hidden="true">→</span></a>
|
|
86
|
+
</p>
|
|
87
|
+
<% end %>
|
|
81
88
|
</div>
|
|
82
89
|
</div>
|
|
@@ -72,12 +72,17 @@
|
|
|
72
72
|
|
|
73
73
|
<section class="ck-card--spaced">
|
|
74
74
|
<div class="ck-prompt-preview__header">
|
|
75
|
-
<p class="ck-kicker"
|
|
75
|
+
<p class="ck-kicker"><%= @response.status == "failed" ? "Error" : "Response" %></p>
|
|
76
76
|
<% if @run.prompt %>
|
|
77
77
|
<span class="ck-chip ck-chip--soft" style="text-transform: none;"><%= @run.prompt.llm_model %></span>
|
|
78
78
|
<% end %>
|
|
79
79
|
</div>
|
|
80
|
-
|
|
80
|
+
<% if @response.status == "failed" %>
|
|
81
|
+
<% err = @response.error_payload || {} %>
|
|
82
|
+
<div class="ck-code-scroll-wrap"><pre class="ck-code ck-code--error"><%= err[:provider]&.titleize %><%= " #{err[:status]}" if err[:status].present? %><%= "\n\n" if err[:provider].present? || err[:status].present? %><%= err[:message] %></pre></div>
|
|
83
|
+
<% else %>
|
|
84
|
+
<div class="ck-code-scroll-wrap"><pre class="ck-code"><%= ck_format_maybe_json(@response.response_text) %></pre></div>
|
|
85
|
+
<% end %>
|
|
81
86
|
</section>
|
|
82
87
|
|
|
83
88
|
<% if @response.expected_output.present? %>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<% clickable = response.
|
|
1
|
+
<% clickable = response.terminal? %>
|
|
2
2
|
<tr id="response_<%= response.id %>"<% if clickable %> onclick="window.location='<%= run_response_path(run, response, sort: params[:sort]) %>'" style="cursor: pointer;"<% end %>>
|
|
3
3
|
<td class="ck-response-cell__index"><% if clickable %><%= link_to index, run_response_path(run, response, sort: params[:sort]), class: "ck-record-name" %><% else %><%= index %><% end %></td>
|
|
4
4
|
<td class="ck-response-cell__text">
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: completion-kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.27.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Damien Bastin
|
|
@@ -479,6 +479,7 @@ files:
|
|
|
479
479
|
- db/migrate/20260629000003_add_passed_to_completion_kit_reviews.rb
|
|
480
480
|
- db/migrate/20260706000001_add_expected_column_to_completion_kit_runs.rb
|
|
481
481
|
- db/migrate/20260708000001_add_api_version_to_completion_kit_provider_credentials.rb
|
|
482
|
+
- db/migrate/20260713000001_add_catalog_model_count_to_completion_kit_provider_credentials.rb
|
|
482
483
|
- lib/completion-kit.rb
|
|
483
484
|
- lib/completion_kit.rb
|
|
484
485
|
- lib/completion_kit/concurrency_check.rb
|