raif 1.0.0 → 1.2.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/README.md +346 -43
- data/app/assets/builds/raif.css +26 -1
- data/app/assets/stylesheets/raif/admin/stats.scss +12 -0
- data/app/assets/stylesheets/raif/loader.scss +27 -1
- data/app/controllers/raif/admin/application_controller.rb +14 -0
- data/app/controllers/raif/admin/stats/tasks_controller.rb +25 -0
- data/app/controllers/raif/admin/stats_controller.rb +19 -0
- data/app/controllers/raif/admin/tasks_controller.rb +18 -2
- data/app/controllers/raif/conversations_controller.rb +5 -1
- data/app/models/raif/agent.rb +11 -9
- data/app/models/raif/agents/native_tool_calling_agent.rb +11 -1
- data/app/models/raif/agents/re_act_agent.rb +6 -0
- data/app/models/raif/concerns/has_available_model_tools.rb +1 -1
- data/app/models/raif/concerns/json_schema_definition.rb +28 -0
- data/app/models/raif/concerns/llm_response_parsing.rb +42 -14
- data/app/models/raif/concerns/llm_temperature.rb +17 -0
- data/app/models/raif/concerns/llms/anthropic/message_formatting.rb +51 -0
- data/app/models/raif/concerns/llms/anthropic/tool_formatting.rb +56 -0
- data/app/models/raif/concerns/llms/bedrock/message_formatting.rb +70 -0
- data/app/models/raif/concerns/llms/bedrock/tool_formatting.rb +37 -0
- data/app/models/raif/concerns/llms/message_formatting.rb +42 -0
- data/app/models/raif/concerns/llms/open_ai/json_schema_validation.rb +138 -0
- data/app/models/raif/concerns/llms/open_ai_completions/message_formatting.rb +41 -0
- data/app/models/raif/concerns/llms/open_ai_completions/tool_formatting.rb +26 -0
- data/app/models/raif/concerns/llms/open_ai_responses/message_formatting.rb +43 -0
- data/app/models/raif/concerns/llms/open_ai_responses/tool_formatting.rb +42 -0
- data/app/models/raif/conversation.rb +28 -7
- data/app/models/raif/conversation_entry.rb +40 -8
- data/app/models/raif/embedding_model.rb +22 -0
- data/app/models/raif/embedding_models/bedrock.rb +34 -0
- data/app/models/raif/embedding_models/open_ai.rb +40 -0
- data/app/models/raif/llm.rb +108 -9
- data/app/models/raif/llms/anthropic.rb +72 -57
- data/app/models/raif/llms/bedrock.rb +165 -0
- data/app/models/raif/llms/open_ai_base.rb +66 -0
- data/app/models/raif/llms/open_ai_completions.rb +100 -0
- data/app/models/raif/llms/open_ai_responses.rb +144 -0
- data/app/models/raif/llms/open_router.rb +88 -0
- data/app/models/raif/model_completion.rb +23 -2
- data/app/models/raif/model_file_input.rb +113 -0
- data/app/models/raif/model_image_input.rb +4 -0
- data/app/models/raif/model_tool.rb +82 -52
- data/app/models/raif/model_tool_invocation.rb +8 -6
- data/app/models/raif/model_tools/agent_final_answer.rb +18 -27
- data/app/models/raif/model_tools/fetch_url.rb +27 -36
- data/app/models/raif/model_tools/provider_managed/base.rb +9 -0
- data/app/models/raif/model_tools/provider_managed/code_execution.rb +5 -0
- data/app/models/raif/model_tools/provider_managed/image_generation.rb +5 -0
- data/app/models/raif/model_tools/provider_managed/web_search.rb +5 -0
- data/app/models/raif/model_tools/wikipedia_search.rb +46 -55
- data/app/models/raif/streaming_responses/anthropic.rb +63 -0
- data/app/models/raif/streaming_responses/bedrock.rb +89 -0
- data/app/models/raif/streaming_responses/open_ai_completions.rb +76 -0
- data/app/models/raif/streaming_responses/open_ai_responses.rb +54 -0
- data/app/models/raif/task.rb +71 -16
- data/app/views/layouts/raif/admin.html.erb +10 -0
- data/app/views/raif/admin/agents/show.html.erb +3 -1
- data/app/views/raif/admin/conversations/_conversation.html.erb +1 -1
- data/app/views/raif/admin/conversations/_conversation_entry.html.erb +48 -0
- data/app/views/raif/admin/conversations/show.html.erb +4 -2
- data/app/views/raif/admin/model_completions/_model_completion.html.erb +8 -0
- data/app/views/raif/admin/model_completions/index.html.erb +2 -0
- data/app/views/raif/admin/model_completions/show.html.erb +58 -3
- data/app/views/raif/admin/stats/index.html.erb +128 -0
- data/app/views/raif/admin/stats/tasks/index.html.erb +45 -0
- data/app/views/raif/admin/tasks/_task.html.erb +5 -4
- data/app/views/raif/admin/tasks/index.html.erb +20 -2
- data/app/views/raif/admin/tasks/show.html.erb +3 -1
- data/app/views/raif/conversation_entries/_citations.html.erb +9 -0
- data/app/views/raif/conversation_entries/_conversation_entry.html.erb +22 -14
- data/app/views/raif/conversation_entries/_form.html.erb +1 -1
- data/app/views/raif/conversation_entries/_form_with_available_tools.html.erb +4 -4
- data/app/views/raif/conversation_entries/_message.html.erb +14 -3
- data/config/locales/admin.en.yml +16 -0
- data/config/locales/en.yml +47 -3
- data/config/routes.rb +6 -0
- data/db/migrate/20250224234252_create_raif_tables.rb +1 -1
- data/db/migrate/20250421202149_add_response_format_to_raif_conversations.rb +7 -0
- data/db/migrate/20250424200755_add_cost_columns_to_raif_model_completions.rb +14 -0
- data/db/migrate/20250424232946_add_created_at_indexes.rb +11 -0
- data/db/migrate/20250502155330_add_status_indexes_to_raif_tasks.rb +14 -0
- data/db/migrate/20250507155314_add_retry_count_to_raif_model_completions.rb +7 -0
- data/db/migrate/20250527213016_add_response_id_and_response_array_to_model_completions.rb +14 -0
- data/db/migrate/20250603140622_add_citations_to_raif_model_completions.rb +13 -0
- data/db/migrate/20250603202013_add_stream_response_to_raif_model_completions.rb +7 -0
- data/lib/generators/raif/agent/agent_generator.rb +22 -12
- data/lib/generators/raif/agent/templates/agent.rb.tt +3 -3
- data/lib/generators/raif/agent/templates/application_agent.rb.tt +7 -0
- data/lib/generators/raif/conversation/conversation_generator.rb +10 -0
- data/lib/generators/raif/conversation/templates/application_conversation.rb.tt +7 -0
- data/lib/generators/raif/conversation/templates/conversation.rb.tt +16 -14
- data/lib/generators/raif/install/templates/initializer.rb +62 -6
- data/lib/generators/raif/model_tool/model_tool_generator.rb +0 -5
- data/lib/generators/raif/model_tool/templates/model_tool.rb.tt +69 -56
- data/lib/generators/raif/task/templates/task.rb.tt +34 -23
- data/lib/raif/configuration.rb +63 -4
- data/lib/raif/embedding_model_registry.rb +83 -0
- data/lib/raif/engine.rb +56 -7
- data/lib/raif/errors/{open_ai/api_error.rb → invalid_model_file_input_error.rb} +1 -3
- data/lib/raif/errors/{anthropic/api_error.rb → invalid_model_image_input_error.rb} +1 -3
- data/lib/raif/errors/streaming_error.rb +18 -0
- data/lib/raif/errors/unsupported_feature_error.rb +8 -0
- data/lib/raif/errors.rb +4 -2
- data/lib/raif/json_schema_builder.rb +104 -0
- data/lib/raif/llm_registry.rb +315 -0
- data/lib/raif/migration_checker.rb +74 -0
- data/lib/raif/utils/html_fragment_processor.rb +169 -0
- data/lib/raif/utils.rb +1 -0
- data/lib/raif/version.rb +1 -1
- data/lib/raif.rb +7 -32
- data/lib/tasks/raif_tasks.rake +9 -4
- metadata +62 -12
- data/app/models/raif/llms/bedrock_claude.rb +0 -134
- data/app/models/raif/llms/open_ai.rb +0 -259
- data/lib/raif/default_llms.rb +0 -37
data/lib/raif.rb
CHANGED
@@ -6,8 +6,14 @@ require "raif/engine"
|
|
6
6
|
require "raif/configuration"
|
7
7
|
require "raif/errors"
|
8
8
|
require "raif/utils"
|
9
|
-
require "raif/
|
9
|
+
require "raif/llm_registry"
|
10
|
+
require "raif/embedding_model_registry"
|
11
|
+
require "raif/json_schema_builder"
|
12
|
+
require "raif/migration_checker"
|
13
|
+
|
10
14
|
require "faraday"
|
15
|
+
require "event_stream_parser"
|
16
|
+
require "json-schema"
|
11
17
|
require "loofah"
|
12
18
|
require "pagy"
|
13
19
|
require "reverse_markdown"
|
@@ -16,7 +22,6 @@ require "turbo-rails"
|
|
16
22
|
module Raif
|
17
23
|
class << self
|
18
24
|
attr_accessor :configuration
|
19
|
-
attr_accessor :llm_registry
|
20
25
|
|
21
26
|
attr_writer :logger
|
22
27
|
end
|
@@ -32,34 +37,4 @@ module Raif
|
|
32
37
|
def self.logger
|
33
38
|
@logger ||= Rails.logger
|
34
39
|
end
|
35
|
-
|
36
|
-
def self.register_llm(llm_class, llm_config)
|
37
|
-
llm = llm_class.new(**llm_config)
|
38
|
-
|
39
|
-
unless llm.valid?
|
40
|
-
raise ArgumentError, "The LLM you tried to register is invalid: #{llm.errors.full_messages.join(", ")}"
|
41
|
-
end
|
42
|
-
|
43
|
-
@llm_registry ||= {}
|
44
|
-
@llm_registry[llm.key] = llm_config.merge(llm_class: llm_class)
|
45
|
-
end
|
46
|
-
|
47
|
-
def self.llm(model_key)
|
48
|
-
llm_config = llm_registry[model_key]
|
49
|
-
|
50
|
-
if llm_config.nil?
|
51
|
-
raise ArgumentError, "No LLM found for model key: #{model_key}. Available models: #{available_llm_keys.join(", ")}"
|
52
|
-
end
|
53
|
-
|
54
|
-
llm_class = llm_config[:llm_class]
|
55
|
-
llm_class.new(**llm_config.except(:llm_class))
|
56
|
-
end
|
57
|
-
|
58
|
-
def self.available_llms
|
59
|
-
llm_registry.values
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.available_llm_keys
|
63
|
-
llm_registry.keys
|
64
|
-
end
|
65
40
|
end
|
data/lib/tasks/raif_tasks.rake
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
namespace :raif do
|
4
|
+
namespace :install do
|
5
|
+
desc "Copy migrations from Raif to host application"
|
6
|
+
task :migrations do
|
7
|
+
ENV["FROM"] = "raif"
|
8
|
+
Rake::Task["railties:install:migrations"].invoke
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: raif
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Roesch
|
8
8
|
- Brian Leslie
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: aws-sdk-
|
14
|
+
name: aws-sdk-bedrockruntime
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
@@ -25,19 +25,19 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: event_stream_parser
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
33
|
+
version: '1.0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
40
|
+
version: '1.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: faraday
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -157,6 +157,7 @@ files:
|
|
157
157
|
- app/assets/javascript/raif/stream_actions/raif_scroll_to_bottom.js
|
158
158
|
- app/assets/stylesheets/raif.scss
|
159
159
|
- app/assets/stylesheets/raif/admin/conversation.scss
|
160
|
+
- app/assets/stylesheets/raif/admin/stats.scss
|
160
161
|
- app/assets/stylesheets/raif/loader.scss
|
161
162
|
- app/assets/stylesheets/raif_admin.scss
|
162
163
|
- app/controllers/raif/admin/agents_controller.rb
|
@@ -164,6 +165,8 @@ files:
|
|
164
165
|
- app/controllers/raif/admin/conversations_controller.rb
|
165
166
|
- app/controllers/raif/admin/model_completions_controller.rb
|
166
167
|
- app/controllers/raif/admin/model_tool_invocations_controller.rb
|
168
|
+
- app/controllers/raif/admin/stats/tasks_controller.rb
|
169
|
+
- app/controllers/raif/admin/stats_controller.rb
|
167
170
|
- app/controllers/raif/admin/tasks_controller.rb
|
168
171
|
- app/controllers/raif/application_controller.rb
|
169
172
|
- app/controllers/raif/conversation_entries_controller.rb
|
@@ -182,19 +185,47 @@ files:
|
|
182
185
|
- app/models/raif/concerns/has_llm.rb
|
183
186
|
- app/models/raif/concerns/has_requested_language.rb
|
184
187
|
- app/models/raif/concerns/invokes_model_tools.rb
|
188
|
+
- app/models/raif/concerns/json_schema_definition.rb
|
185
189
|
- app/models/raif/concerns/llm_response_parsing.rb
|
190
|
+
- app/models/raif/concerns/llm_temperature.rb
|
191
|
+
- app/models/raif/concerns/llms/anthropic/message_formatting.rb
|
192
|
+
- app/models/raif/concerns/llms/anthropic/tool_formatting.rb
|
193
|
+
- app/models/raif/concerns/llms/bedrock/message_formatting.rb
|
194
|
+
- app/models/raif/concerns/llms/bedrock/tool_formatting.rb
|
195
|
+
- app/models/raif/concerns/llms/message_formatting.rb
|
196
|
+
- app/models/raif/concerns/llms/open_ai/json_schema_validation.rb
|
197
|
+
- app/models/raif/concerns/llms/open_ai_completions/message_formatting.rb
|
198
|
+
- app/models/raif/concerns/llms/open_ai_completions/tool_formatting.rb
|
199
|
+
- app/models/raif/concerns/llms/open_ai_responses/message_formatting.rb
|
200
|
+
- app/models/raif/concerns/llms/open_ai_responses/tool_formatting.rb
|
186
201
|
- app/models/raif/conversation.rb
|
187
202
|
- app/models/raif/conversation_entry.rb
|
203
|
+
- app/models/raif/embedding_model.rb
|
204
|
+
- app/models/raif/embedding_models/bedrock.rb
|
205
|
+
- app/models/raif/embedding_models/open_ai.rb
|
188
206
|
- app/models/raif/llm.rb
|
189
207
|
- app/models/raif/llms/anthropic.rb
|
190
|
-
- app/models/raif/llms/
|
191
|
-
- app/models/raif/llms/
|
208
|
+
- app/models/raif/llms/bedrock.rb
|
209
|
+
- app/models/raif/llms/open_ai_base.rb
|
210
|
+
- app/models/raif/llms/open_ai_completions.rb
|
211
|
+
- app/models/raif/llms/open_ai_responses.rb
|
212
|
+
- app/models/raif/llms/open_router.rb
|
192
213
|
- app/models/raif/model_completion.rb
|
214
|
+
- app/models/raif/model_file_input.rb
|
215
|
+
- app/models/raif/model_image_input.rb
|
193
216
|
- app/models/raif/model_tool.rb
|
194
217
|
- app/models/raif/model_tool_invocation.rb
|
195
218
|
- app/models/raif/model_tools/agent_final_answer.rb
|
196
219
|
- app/models/raif/model_tools/fetch_url.rb
|
220
|
+
- app/models/raif/model_tools/provider_managed/base.rb
|
221
|
+
- app/models/raif/model_tools/provider_managed/code_execution.rb
|
222
|
+
- app/models/raif/model_tools/provider_managed/image_generation.rb
|
223
|
+
- app/models/raif/model_tools/provider_managed/web_search.rb
|
197
224
|
- app/models/raif/model_tools/wikipedia_search.rb
|
225
|
+
- app/models/raif/streaming_responses/anthropic.rb
|
226
|
+
- app/models/raif/streaming_responses/bedrock.rb
|
227
|
+
- app/models/raif/streaming_responses/open_ai_completions.rb
|
228
|
+
- app/models/raif/streaming_responses/open_ai_responses.rb
|
198
229
|
- app/models/raif/task.rb
|
199
230
|
- app/models/raif/user_tool_invocation.rb
|
200
231
|
- app/views/layouts/raif/admin.html.erb
|
@@ -212,9 +243,12 @@ files:
|
|
212
243
|
- app/views/raif/admin/model_tool_invocations/_model_tool_invocation.html.erb
|
213
244
|
- app/views/raif/admin/model_tool_invocations/index.html.erb
|
214
245
|
- app/views/raif/admin/model_tool_invocations/show.html.erb
|
246
|
+
- app/views/raif/admin/stats/index.html.erb
|
247
|
+
- app/views/raif/admin/stats/tasks/index.html.erb
|
215
248
|
- app/views/raif/admin/tasks/_task.html.erb
|
216
249
|
- app/views/raif/admin/tasks/index.html.erb
|
217
250
|
- app/views/raif/admin/tasks/show.html.erb
|
251
|
+
- app/views/raif/conversation_entries/_citations.html.erb
|
218
252
|
- app/views/raif/conversation_entries/_conversation_entry.html.erb
|
219
253
|
- app/views/raif/conversation_entries/_form.html.erb
|
220
254
|
- app/views/raif/conversation_entries/_form_with_available_tools.html.erb
|
@@ -234,9 +268,19 @@ files:
|
|
234
268
|
- config/locales/en.yml
|
235
269
|
- config/routes.rb
|
236
270
|
- db/migrate/20250224234252_create_raif_tables.rb
|
271
|
+
- db/migrate/20250421202149_add_response_format_to_raif_conversations.rb
|
272
|
+
- db/migrate/20250424200755_add_cost_columns_to_raif_model_completions.rb
|
273
|
+
- db/migrate/20250424232946_add_created_at_indexes.rb
|
274
|
+
- db/migrate/20250502155330_add_status_indexes_to_raif_tasks.rb
|
275
|
+
- db/migrate/20250507155314_add_retry_count_to_raif_model_completions.rb
|
276
|
+
- db/migrate/20250527213016_add_response_id_and_response_array_to_model_completions.rb
|
277
|
+
- db/migrate/20250603140622_add_citations_to_raif_model_completions.rb
|
278
|
+
- db/migrate/20250603202013_add_stream_response_to_raif_model_completions.rb
|
237
279
|
- lib/generators/raif/agent/agent_generator.rb
|
238
280
|
- lib/generators/raif/agent/templates/agent.rb.tt
|
281
|
+
- lib/generators/raif/agent/templates/application_agent.rb.tt
|
239
282
|
- lib/generators/raif/conversation/conversation_generator.rb
|
283
|
+
- lib/generators/raif/conversation/templates/application_conversation.rb.tt
|
240
284
|
- lib/generators/raif/conversation/templates/conversation.rb.tt
|
241
285
|
- lib/generators/raif/install/install_generator.rb
|
242
286
|
- lib/generators/raif/install/templates/initializer.rb
|
@@ -248,19 +292,25 @@ files:
|
|
248
292
|
- lib/generators/raif/views_generator.rb
|
249
293
|
- lib/raif.rb
|
250
294
|
- lib/raif/configuration.rb
|
251
|
-
- lib/raif/
|
295
|
+
- lib/raif/embedding_model_registry.rb
|
252
296
|
- lib/raif/engine.rb
|
253
297
|
- lib/raif/errors.rb
|
254
298
|
- lib/raif/errors/action_not_authorized_error.rb
|
255
|
-
- lib/raif/errors/anthropic/api_error.rb
|
256
299
|
- lib/raif/errors/invalid_config_error.rb
|
257
300
|
- lib/raif/errors/invalid_conversation_type_error.rb
|
301
|
+
- lib/raif/errors/invalid_model_file_input_error.rb
|
302
|
+
- lib/raif/errors/invalid_model_image_input_error.rb
|
258
303
|
- lib/raif/errors/invalid_user_tool_type_error.rb
|
259
|
-
- lib/raif/errors/open_ai/api_error.rb
|
260
304
|
- lib/raif/errors/open_ai/json_schema_error.rb
|
305
|
+
- lib/raif/errors/streaming_error.rb
|
306
|
+
- lib/raif/errors/unsupported_feature_error.rb
|
307
|
+
- lib/raif/json_schema_builder.rb
|
261
308
|
- lib/raif/languages.rb
|
309
|
+
- lib/raif/llm_registry.rb
|
310
|
+
- lib/raif/migration_checker.rb
|
262
311
|
- lib/raif/rspec.rb
|
263
312
|
- lib/raif/utils.rb
|
313
|
+
- lib/raif/utils/html_fragment_processor.rb
|
264
314
|
- lib/raif/utils/html_to_markdown_converter.rb
|
265
315
|
- lib/raif/utils/readable_content_extractor.rb
|
266
316
|
- lib/raif/version.rb
|
@@ -287,7 +337,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
287
337
|
- !ruby/object:Gem::Version
|
288
338
|
version: '0'
|
289
339
|
requirements: []
|
290
|
-
rubygems_version: 3.6.
|
340
|
+
rubygems_version: 3.6.7
|
291
341
|
specification_version: 4
|
292
342
|
summary: Raif (Ruby AI Framework) is a Rails engine that helps you add AI-powered
|
293
343
|
features to your Rails apps, such as tasks, conversations, and agents.
|
@@ -1,134 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Raif::Llms::BedrockClaude < Raif::Llm
|
4
|
-
|
5
|
-
def perform_model_completion!(model_completion)
|
6
|
-
if Raif.config.aws_bedrock_model_name_prefix.present?
|
7
|
-
model_completion.model_api_name = "#{Raif.config.aws_bedrock_model_name_prefix}.#{model_completion.model_api_name}"
|
8
|
-
end
|
9
|
-
|
10
|
-
params = build_api_parameters(model_completion)
|
11
|
-
resp = bedrock_client.converse(params)
|
12
|
-
|
13
|
-
model_completion.raw_response = if model_completion.response_format_json?
|
14
|
-
extract_json_response(resp)
|
15
|
-
else
|
16
|
-
extract_text_response(resp)
|
17
|
-
end
|
18
|
-
|
19
|
-
model_completion.completion_tokens = resp.usage.output_tokens
|
20
|
-
model_completion.prompt_tokens = resp.usage.input_tokens
|
21
|
-
model_completion.total_tokens = resp.usage.total_tokens
|
22
|
-
model_completion.save!
|
23
|
-
|
24
|
-
model_completion
|
25
|
-
end
|
26
|
-
|
27
|
-
protected
|
28
|
-
|
29
|
-
def bedrock_client
|
30
|
-
@bedrock_client ||= Aws::BedrockRuntime::Client.new(region: Raif.config.aws_bedrock_region)
|
31
|
-
end
|
32
|
-
|
33
|
-
def format_messages(messages)
|
34
|
-
messages.map(&:symbolize_keys).map do |message|
|
35
|
-
{
|
36
|
-
role: message[:role],
|
37
|
-
content: [{ text: message[:content] }]
|
38
|
-
}
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def build_api_parameters(model_completion)
|
43
|
-
params = {
|
44
|
-
model_id: model_completion.model_api_name,
|
45
|
-
inference_config: { max_tokens: model_completion.max_completion_tokens || 8192 },
|
46
|
-
messages: format_messages(model_completion.messages)
|
47
|
-
}
|
48
|
-
|
49
|
-
params[:system] = [{ text: model_completion.system_prompt }] if model_completion.system_prompt.present?
|
50
|
-
|
51
|
-
# Prepare tools configuration if needed
|
52
|
-
tools = []
|
53
|
-
|
54
|
-
# If we're looking for a JSON response, add a tool to the request that the model can use to provide a JSON response
|
55
|
-
if model_completion.response_format_json? && model_completion.json_response_schema.present?
|
56
|
-
tools << {
|
57
|
-
name: "json_response",
|
58
|
-
description: "Generate a structured JSON response based on the provided schema.",
|
59
|
-
input_schema: { json: model_completion.json_response_schema }
|
60
|
-
}
|
61
|
-
end
|
62
|
-
|
63
|
-
# If we support native tool use and have tools available, add them to the request
|
64
|
-
if supports_native_tool_use? && model_completion.available_model_tools.any?
|
65
|
-
model_completion.available_model_tools_map.each do |_tool_name, tool|
|
66
|
-
tools << {
|
67
|
-
name: tool.tool_name,
|
68
|
-
description: tool.tool_description,
|
69
|
-
input_schema: { json: tool.tool_arguments_schema }
|
70
|
-
}
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# Add tool configuration if any tools are available
|
75
|
-
if tools.any?
|
76
|
-
params[:tool_config] = {
|
77
|
-
tools: tools.map { |tool| { tool_spec: tool } }
|
78
|
-
}
|
79
|
-
end
|
80
|
-
|
81
|
-
params
|
82
|
-
end
|
83
|
-
|
84
|
-
def extract_text_response(resp)
|
85
|
-
# Get the message from the response object
|
86
|
-
message = resp.output.message
|
87
|
-
|
88
|
-
# Find the first text content block
|
89
|
-
text_block = message.content&.find do |content|
|
90
|
-
content.respond_to?(:text) && content.text.present?
|
91
|
-
end
|
92
|
-
|
93
|
-
text_block&.text
|
94
|
-
end
|
95
|
-
|
96
|
-
def extract_json_response(resp)
|
97
|
-
# Get the message from the response object
|
98
|
-
message = resp.output.message
|
99
|
-
|
100
|
-
return extract_text_response(resp) if message.content.nil?
|
101
|
-
|
102
|
-
# Look for tool_use blocks in the content array
|
103
|
-
tool_response = message.content.find do |content|
|
104
|
-
content.respond_to?(:tool_use) && content.tool_use.present? && content.tool_use.name == "json_response"
|
105
|
-
end
|
106
|
-
|
107
|
-
if tool_response&.tool_use
|
108
|
-
JSON.generate(tool_response.tool_use.input)
|
109
|
-
else
|
110
|
-
extract_text_response(resp)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
def extract_response_tool_calls(resp)
|
115
|
-
# Get the message from the response object
|
116
|
-
message = resp.output.message
|
117
|
-
return if message.content.nil?
|
118
|
-
|
119
|
-
# Find any tool_use blocks in the content array
|
120
|
-
tool_uses = message.content.select do |content|
|
121
|
-
content.respond_to?(:tool_use) && content.tool_use.present?
|
122
|
-
end
|
123
|
-
|
124
|
-
return if tool_uses.blank?
|
125
|
-
|
126
|
-
tool_uses.map do |content|
|
127
|
-
{
|
128
|
-
"name" => content.tool_use.name,
|
129
|
-
"arguments" => content.tool_use.input
|
130
|
-
}
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
end
|
@@ -1,259 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Raif::Llms::OpenAi < Raif::Llm
|
4
|
-
|
5
|
-
def perform_model_completion!(model_completion)
|
6
|
-
model_completion.temperature ||= default_temperature
|
7
|
-
parameters = build_chat_parameters(model_completion)
|
8
|
-
|
9
|
-
response = connection.post("chat/completions") do |req|
|
10
|
-
req.body = parameters.to_json
|
11
|
-
end
|
12
|
-
|
13
|
-
resp = JSON.parse(response.body)
|
14
|
-
|
15
|
-
# Handle API errors
|
16
|
-
unless response.success?
|
17
|
-
error_message = resp["error"]&.dig("message") || "OpenAI API error: #{response.status}"
|
18
|
-
raise Raif::Errors::OpenAi::ApiError, error_message
|
19
|
-
end
|
20
|
-
|
21
|
-
model_completion.update!(
|
22
|
-
response_tool_calls: extract_response_tool_calls(resp),
|
23
|
-
raw_response: resp.dig("choices", 0, "message", "content"),
|
24
|
-
completion_tokens: resp["usage"]["completion_tokens"],
|
25
|
-
prompt_tokens: resp["usage"]["prompt_tokens"],
|
26
|
-
total_tokens: resp["usage"]["total_tokens"],
|
27
|
-
response_format_parameter: parameters.dig(:response_format, :type)
|
28
|
-
)
|
29
|
-
|
30
|
-
model_completion
|
31
|
-
end
|
32
|
-
|
33
|
-
def connection
|
34
|
-
@connection ||= Faraday.new(url: "https://api.openai.com/v1") do |f|
|
35
|
-
f.headers["Content-Type"] = "application/json"
|
36
|
-
f.headers["Authorization"] = "Bearer #{Raif.config.open_ai_api_key}"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def validate_json_schema!(schema)
|
41
|
-
return if schema.blank?
|
42
|
-
|
43
|
-
errors = []
|
44
|
-
|
45
|
-
# Check if schema is present
|
46
|
-
if schema.blank?
|
47
|
-
errors << "JSON schema must include a 'schema' property"
|
48
|
-
else
|
49
|
-
# Check root object type
|
50
|
-
if schema[:type] != "object" && !schema.key?(:properties)
|
51
|
-
errors << "Root schema must be of type 'object' with 'properties'"
|
52
|
-
end
|
53
|
-
|
54
|
-
# Check all objects in the schema recursively
|
55
|
-
validate_object_properties(schema, errors)
|
56
|
-
|
57
|
-
# Check properties count (max 100 total)
|
58
|
-
validate_properties_count(schema, errors)
|
59
|
-
|
60
|
-
# Check nesting depth (max 5 levels)
|
61
|
-
validate_nesting_depth(schema, errors)
|
62
|
-
|
63
|
-
# Check for unsupported anyOf at root level
|
64
|
-
if schema[:anyOf].present? && schema[:properties].blank?
|
65
|
-
errors << "Root objects cannot be of type 'anyOf'"
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# Raise error if any validation issues found
|
70
|
-
if errors.any?
|
71
|
-
error_message = "Invalid JSON schema for OpenAI structured outputs: #{errors.join("; ")}\nSchema was: #{schema.inspect}"
|
72
|
-
raise Raif::Errors::OpenAi::JsonSchemaError, error_message
|
73
|
-
else
|
74
|
-
true
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
def extract_response_tool_calls(resp)
|
81
|
-
return if resp.dig("choices", 0, "message", "tool_calls").blank?
|
82
|
-
|
83
|
-
resp.dig("choices", 0, "message", "tool_calls").map do |tool_call|
|
84
|
-
{
|
85
|
-
"name" => tool_call["function"]["name"],
|
86
|
-
"arguments" => JSON.parse(tool_call["function"]["arguments"])
|
87
|
-
}
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def build_chat_parameters(model_completion)
|
92
|
-
formatted_system_prompt = model_completion.system_prompt.to_s.strip
|
93
|
-
|
94
|
-
# If the response format is JSON, we need to include "as json" in the system prompt.
|
95
|
-
# OpenAI requires this and will throw an error if it's not included.
|
96
|
-
if model_completion.response_format_json?
|
97
|
-
# Ensure system prompt ends with a period if not empty
|
98
|
-
if formatted_system_prompt.present? && !formatted_system_prompt.end_with?(".", "?", "!")
|
99
|
-
formatted_system_prompt += "."
|
100
|
-
end
|
101
|
-
formatted_system_prompt += " Return your response as JSON."
|
102
|
-
formatted_system_prompt.strip!
|
103
|
-
end
|
104
|
-
|
105
|
-
messages = model_completion.messages
|
106
|
-
messages_with_system = if !formatted_system_prompt.empty?
|
107
|
-
[{ "role" => "system", "content" => formatted_system_prompt }] + messages
|
108
|
-
else
|
109
|
-
messages
|
110
|
-
end
|
111
|
-
|
112
|
-
parameters = {
|
113
|
-
model: api_name,
|
114
|
-
messages: messages_with_system,
|
115
|
-
temperature: model_completion.temperature.to_f
|
116
|
-
}
|
117
|
-
|
118
|
-
# If the LLM supports native tool use and there are available tools, add them to the parameters
|
119
|
-
if supports_native_tool_use? && model_completion.available_model_tools.any?
|
120
|
-
parameters[:tools] = model_completion.available_model_tools_map.map do |_tool_name, tool|
|
121
|
-
validate_json_schema!(tool.tool_arguments_schema)
|
122
|
-
|
123
|
-
{
|
124
|
-
type: "function",
|
125
|
-
function: {
|
126
|
-
name: tool.tool_name,
|
127
|
-
description: tool.tool_description,
|
128
|
-
parameters: tool.tool_arguments_schema
|
129
|
-
}
|
130
|
-
}
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# Add response format if needed
|
135
|
-
response_format = determine_response_format(model_completion)
|
136
|
-
parameters[:response_format] = response_format if response_format
|
137
|
-
|
138
|
-
parameters
|
139
|
-
end
|
140
|
-
|
141
|
-
def determine_response_format(model_completion)
|
142
|
-
# Only configure response format for JSON outputs
|
143
|
-
return unless model_completion.response_format_json?
|
144
|
-
|
145
|
-
if model_completion.json_response_schema.present? && supports_structured_outputs?
|
146
|
-
validate_json_schema!(model_completion.json_response_schema)
|
147
|
-
|
148
|
-
{
|
149
|
-
type: "json_schema",
|
150
|
-
json_schema: {
|
151
|
-
name: "json_response_schema",
|
152
|
-
strict: true,
|
153
|
-
schema: model_completion.json_response_schema
|
154
|
-
}
|
155
|
-
}
|
156
|
-
else
|
157
|
-
# Default JSON mode for OpenAI models that don't support structured outputs or no schema is provided
|
158
|
-
{ type: "json_object" }
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def supports_structured_outputs?
|
163
|
-
# Not all OpenAI models support structured outputs:
|
164
|
-
# https://platform.openai.com/docs/guides/structured-outputs?api-mode=chat#supported-models
|
165
|
-
provider_settings[:supports_structured_outputs]
|
166
|
-
end
|
167
|
-
|
168
|
-
def validate_object_properties(schema, errors)
|
169
|
-
return unless schema.is_a?(Hash)
|
170
|
-
|
171
|
-
# Check if the current schema is an object and validate additionalProperties and required fields
|
172
|
-
if schema[:type] == "object"
|
173
|
-
if schema[:additionalProperties] != false
|
174
|
-
errors << "All objects must have 'additionalProperties' set to false"
|
175
|
-
end
|
176
|
-
|
177
|
-
# Check that all properties are required
|
178
|
-
if schema[:properties].is_a?(Hash) && schema[:properties].any?
|
179
|
-
property_keys = schema[:properties].keys
|
180
|
-
required_fields = schema[:required] || []
|
181
|
-
|
182
|
-
if required_fields.sort != property_keys.map(&:to_s).sort
|
183
|
-
errors << "All object properties must be listed in the 'required' array"
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
# Check if the current schema is an object and validate additionalProperties
|
189
|
-
if schema[:type] == "object"
|
190
|
-
if schema[:additionalProperties] != false
|
191
|
-
errors << "All objects must have 'additionalProperties' set to false"
|
192
|
-
end
|
193
|
-
|
194
|
-
# Check properties of the object recursively
|
195
|
-
if schema[:properties].is_a?(Hash)
|
196
|
-
schema[:properties].each_value do |property|
|
197
|
-
validate_object_properties(property, errors)
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
# Check array items
|
203
|
-
if schema[:type] == "array" && schema[:items].is_a?(Hash)
|
204
|
-
validate_object_properties(schema[:items], errors)
|
205
|
-
end
|
206
|
-
|
207
|
-
# Check anyOf
|
208
|
-
if schema[:anyOf].is_a?(Array)
|
209
|
-
schema[:anyOf].each do |option|
|
210
|
-
validate_object_properties(option, errors)
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def validate_properties_count(schema, errors, count = 0)
|
216
|
-
return count unless schema.is_a?(Hash)
|
217
|
-
|
218
|
-
if schema[:properties].is_a?(Hash)
|
219
|
-
count += schema[:properties].size
|
220
|
-
|
221
|
-
if count > 100
|
222
|
-
errors << "Schema exceeds maximum of 100 total object properties"
|
223
|
-
return count
|
224
|
-
end
|
225
|
-
|
226
|
-
# Check nested properties
|
227
|
-
schema[:properties].each_value do |property|
|
228
|
-
count = validate_properties_count(property, errors, count)
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
# Check array items
|
233
|
-
if schema[:type] == "array" && schema[:items].is_a?(Hash)
|
234
|
-
count = validate_properties_count(schema[:items], errors, count)
|
235
|
-
end
|
236
|
-
|
237
|
-
count
|
238
|
-
end
|
239
|
-
|
240
|
-
def validate_nesting_depth(schema, errors, depth = 1)
|
241
|
-
return unless schema.is_a?(Hash)
|
242
|
-
|
243
|
-
if depth > 5
|
244
|
-
errors << "Schema exceeds maximum nesting depth of 5 levels"
|
245
|
-
return
|
246
|
-
end
|
247
|
-
|
248
|
-
if schema[:properties].is_a?(Hash)
|
249
|
-
schema[:properties].each_value do |property|
|
250
|
-
validate_nesting_depth(property, errors, depth + 1)
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
# Check array items
|
255
|
-
if schema[:type] == "array" && schema[:items].is_a?(Hash)
|
256
|
-
validate_nesting_depth(schema[:items], errors, depth + 1)
|
257
|
-
end
|
258
|
-
end
|
259
|
-
end
|
data/lib/raif/default_llms.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Raif
|
4
|
-
def self.default_llms
|
5
|
-
{
|
6
|
-
Raif::Llms::OpenAi => [
|
7
|
-
{
|
8
|
-
key: :open_ai_gpt_4o_mini,
|
9
|
-
api_name: "gpt-4o-mini",
|
10
|
-
model_provider_settings: { supports_structured_outputs: true }
|
11
|
-
},
|
12
|
-
{
|
13
|
-
key: :open_ai_gpt_4o,
|
14
|
-
api_name: "gpt-4o",
|
15
|
-
model_provider_settings: { supports_structured_outputs: true }
|
16
|
-
},
|
17
|
-
{
|
18
|
-
key: :open_ai_gpt_3_5_turbo,
|
19
|
-
api_name: "gpt-3.5-turbo",
|
20
|
-
model_provider_settings: { supports_structured_outputs: false }
|
21
|
-
},
|
22
|
-
],
|
23
|
-
Raif::Llms::Anthropic => [
|
24
|
-
{ key: :anthropic_claude_3_7_sonnet, api_name: "claude-3-7-sonnet-latest", max_completion_tokens: 8192 },
|
25
|
-
{ key: :anthropic_claude_3_5_sonnet, api_name: "claude-3-5-sonnet-latest", max_completion_tokens: 8192 },
|
26
|
-
{ key: :anthropic_claude_3_5_haiku, api_name: "claude-3-5-haiku-latest", max_completion_tokens: 8192 },
|
27
|
-
{ key: :anthropic_claude_3_opus, api_name: "claude-3-opus-latest", max_completion_tokens: 4096 },
|
28
|
-
],
|
29
|
-
Raif::Llms::BedrockClaude => [
|
30
|
-
{ key: :bedrock_claude_3_5_sonnet, api_name: "anthropic.claude-3-5-sonnet-20241022-v2:0", max_completion_tokens: 8192 },
|
31
|
-
{ key: :bedrock_claude_3_7_sonnet, api_name: "anthropic.claude-3-7-sonnet-20250219-v1:0", max_completion_tokens: 8192 },
|
32
|
-
{ key: :bedrock_claude_3_5_haiku, api_name: "anthropic.claude-3-5-haiku-20241022-v1:0", max_completion_tokens: 8192 },
|
33
|
-
{ key: :bedrock_claude_3_opus, api_name: "anthropic.claude-3-opus-20240229-v1:0", max_completion_tokens: 4096 },
|
34
|
-
]
|
35
|
-
}
|
36
|
-
end
|
37
|
-
end
|