raif 1.3.0 → 1.5.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 +8 -7
- data/app/assets/builds/raif.css +4 -1
- data/app/assets/builds/raif_admin.css +52 -2
- data/app/assets/builds/raif_admin_sprockets.js +2709 -0
- data/app/assets/javascript/raif/admin/copy_to_clipboard_controller.js +132 -0
- data/app/assets/javascript/raif/admin/cost_estimate_controller.js +80 -0
- data/app/assets/javascript/raif/admin/judge_config_controller.js +23 -0
- data/app/assets/javascript/raif/admin/select_all_checkboxes_controller.js +33 -0
- data/app/assets/javascript/raif/admin/sortable_table_controller.js +51 -0
- data/app/assets/javascript/raif/admin/table_search_controller.js +15 -0
- data/app/assets/javascript/raif/admin/tom_select_controller.js +33 -0
- data/app/assets/javascript/raif/controllers/conversations_controller.js +1 -1
- data/app/assets/javascript/raif_admin.js +23 -0
- data/app/assets/javascript/raif_admin_sprockets.js +24 -0
- data/app/assets/stylesheets/raif/admin/conversation.scss +16 -0
- data/app/assets/stylesheets/raif/conversations.scss +3 -0
- data/app/assets/stylesheets/raif.scss +2 -1
- data/app/assets/stylesheets/raif_admin.scss +50 -1
- data/app/controllers/raif/admin/agents_controller.rb +27 -1
- data/app/controllers/raif/admin/application_controller.rb +16 -0
- data/app/controllers/raif/admin/configs_controller.rb +95 -0
- data/app/controllers/raif/admin/llms_controller.rb +27 -0
- data/app/controllers/raif/admin/model_completions_controller.rb +24 -1
- data/app/controllers/raif/admin/model_tool_invocations_controller.rb +7 -1
- data/app/controllers/raif/admin/prompt_studio/agents_controller.rb +25 -0
- data/app/controllers/raif/admin/prompt_studio/base_controller.rb +32 -0
- data/app/controllers/raif/admin/prompt_studio/batch_runs_controller.rb +102 -0
- data/app/controllers/raif/admin/prompt_studio/conversations_controller.rb +25 -0
- data/app/controllers/raif/admin/prompt_studio/tasks_controller.rb +64 -0
- data/app/controllers/raif/admin/stats/model_tool_invocations_controller.rb +21 -0
- data/app/controllers/raif/admin/stats/tasks_controller.rb +15 -6
- data/app/controllers/raif/admin/stats_controller.rb +32 -3
- data/app/controllers/raif/admin/tasks_controller.rb +5 -0
- data/app/controllers/raif/conversation_entries_controller.rb +1 -0
- data/app/controllers/raif/conversations_controller.rb +10 -2
- data/app/helpers/raif/application_helper.rb +40 -0
- data/app/jobs/raif/conversation_entry_job.rb +8 -6
- data/app/jobs/raif/prompt_studio_batch_run_item_job.rb +11 -0
- data/app/jobs/raif/prompt_studio_batch_run_job.rb +15 -0
- data/app/jobs/raif/prompt_studio_task_run_job.rb +36 -0
- data/app/models/raif/admin/task_stat.rb +7 -0
- data/app/models/raif/agent.rb +98 -6
- data/app/models/raif/agents/native_tool_calling_agent.rb +179 -52
- data/app/models/raif/application_record.rb +18 -0
- data/app/models/raif/concerns/agent_inference_stats.rb +35 -0
- data/app/models/raif/concerns/has_prompt_templates.rb +88 -0
- data/app/models/raif/concerns/has_runtime_duration.rb +41 -0
- data/app/models/raif/concerns/json_schema_definition.rb +54 -6
- data/app/models/raif/concerns/llm_prompt_caching.rb +20 -0
- data/app/models/raif/concerns/llms/anthropic/message_formatting.rb +34 -0
- data/app/models/raif/concerns/llms/anthropic/response_tool_calls.rb +24 -0
- data/app/models/raif/concerns/llms/anthropic/tool_formatting.rb +8 -0
- data/app/models/raif/concerns/llms/bedrock/message_formatting.rb +43 -0
- data/app/models/raif/concerns/llms/bedrock/response_tool_calls.rb +26 -0
- data/app/models/raif/concerns/llms/bedrock/tool_formatting.rb +8 -0
- data/app/models/raif/concerns/llms/google/message_formatting.rb +112 -0
- data/app/models/raif/concerns/llms/google/response_tool_calls.rb +32 -0
- data/app/models/raif/concerns/llms/google/tool_formatting.rb +76 -0
- data/app/models/raif/concerns/llms/message_formatting.rb +41 -5
- data/app/models/raif/concerns/llms/open_ai/json_schema_validation.rb +3 -3
- data/app/models/raif/concerns/llms/open_ai_completions/message_formatting.rb +22 -0
- data/app/models/raif/concerns/llms/open_ai_completions/response_tool_calls.rb +22 -0
- data/app/models/raif/concerns/llms/open_ai_completions/tool_formatting.rb +8 -0
- data/app/models/raif/concerns/llms/open_ai_responses/message_formatting.rb +17 -0
- data/app/models/raif/concerns/llms/open_ai_responses/response_tool_calls.rb +26 -0
- data/app/models/raif/concerns/llms/open_ai_responses/tool_formatting.rb +8 -0
- data/app/models/raif/concerns/provider_managed_tool_calls.rb +162 -0
- data/app/models/raif/concerns/run_with.rb +127 -0
- data/app/models/raif/conversation.rb +112 -8
- data/app/models/raif/conversation_entry.rb +38 -4
- data/app/models/raif/embedding_model.rb +2 -1
- data/app/models/raif/embedding_models/bedrock.rb +10 -1
- data/app/models/raif/embedding_models/google.rb +37 -0
- data/app/models/raif/embedding_models/open_ai.rb +1 -1
- data/app/models/raif/evals/llm_judge.rb +70 -0
- data/{lib → app/models}/raif/evals/llm_judges/binary.rb +41 -3
- data/{lib → app/models}/raif/evals/llm_judges/comparative.rb +41 -3
- data/{lib → app/models}/raif/evals/llm_judges/scored.rb +39 -1
- data/{lib → app/models}/raif/evals/llm_judges/summarization.rb +40 -2
- data/app/models/raif/llm.rb +104 -4
- data/app/models/raif/llms/anthropic.rb +32 -22
- data/app/models/raif/llms/bedrock.rb +64 -24
- data/app/models/raif/llms/google.rb +166 -0
- data/app/models/raif/llms/open_ai_base.rb +23 -5
- data/app/models/raif/llms/open_ai_completions.rb +14 -12
- data/app/models/raif/llms/open_ai_responses.rb +14 -17
- data/app/models/raif/llms/open_router.rb +16 -15
- data/app/models/raif/model_completion.rb +103 -1
- data/app/models/raif/model_tool.rb +55 -5
- data/app/models/raif/model_tool_invocation.rb +68 -6
- data/app/models/raif/model_tools/agent_final_answer.rb +2 -7
- data/app/models/raif/model_tools/provider_managed/code_execution.rb +4 -0
- data/app/models/raif/model_tools/provider_managed/image_generation.rb +4 -0
- data/app/models/raif/model_tools/provider_managed/web_search.rb +4 -0
- data/app/models/raif/prompt_studio_batch_run.rb +155 -0
- data/app/models/raif/prompt_studio_batch_run_item.rb +220 -0
- data/app/models/raif/streaming_responses/bedrock.rb +60 -1
- data/app/models/raif/streaming_responses/google.rb +71 -0
- data/app/models/raif/task.rb +85 -18
- data/app/models/raif/user_tool_invocation.rb +19 -0
- data/app/views/layouts/raif/admin.html.erb +43 -2
- data/app/views/raif/admin/agents/_agent.html.erb +9 -0
- data/app/views/raif/admin/agents/_conversation_message.html.erb +28 -6
- data/app/views/raif/admin/agents/index.html.erb +50 -0
- data/app/views/raif/admin/agents/show.html.erb +50 -1
- data/app/views/raif/admin/configs/show.html.erb +117 -0
- data/app/views/raif/admin/conversations/_conversation_entry.html.erb +29 -34
- data/app/views/raif/admin/conversations/show.html.erb +2 -0
- data/app/views/raif/admin/llms/index.html.erb +110 -0
- data/app/views/raif/admin/model_completions/_model_completion.html.erb +10 -5
- data/app/views/raif/admin/model_completions/index.html.erb +40 -1
- data/app/views/raif/admin/model_completions/show.html.erb +256 -84
- data/app/views/raif/admin/model_tool_invocations/index.html.erb +22 -1
- data/app/views/raif/admin/model_tool_invocations/show.html.erb +18 -0
- data/app/views/raif/admin/model_tools/_list.html.erb +16 -0
- data/app/views/raif/admin/model_tools/_model_tool.html.erb +36 -0
- data/app/views/raif/admin/prompt_studio/agents/index.html.erb +56 -0
- data/app/views/raif/admin/prompt_studio/agents/show.html.erb +57 -0
- data/app/views/raif/admin/prompt_studio/batch_runs/_batch_run_item.html.erb +54 -0
- data/app/views/raif/admin/prompt_studio/batch_runs/_judge_config_fields.html.erb +76 -0
- data/app/views/raif/admin/prompt_studio/batch_runs/_judge_detail_modal.html.erb +27 -0
- data/app/views/raif/admin/prompt_studio/batch_runs/_modal.html.erb +35 -0
- data/app/views/raif/admin/prompt_studio/batch_runs/_progress.html.erb +78 -0
- data/app/views/raif/admin/prompt_studio/batch_runs/show.html.erb +49 -0
- data/app/views/raif/admin/prompt_studio/conversations/index.html.erb +48 -0
- data/app/views/raif/admin/prompt_studio/conversations/show.html.erb +36 -0
- data/app/views/raif/admin/prompt_studio/shared/_nav_tabs.html.erb +17 -0
- data/app/views/raif/admin/prompt_studio/shared/_prompt_comparison.html.erb +87 -0
- data/app/views/raif/admin/prompt_studio/shared/_type_filter.html.erb +54 -0
- data/app/views/raif/admin/prompt_studio/tasks/_task_result.html.erb +145 -0
- data/app/views/raif/admin/prompt_studio/tasks/_task_row.html.erb +12 -0
- data/app/views/raif/admin/prompt_studio/tasks/_task_type_filter.html.erb +58 -0
- data/app/views/raif/admin/prompt_studio/tasks/_tasks_table.html.erb +22 -0
- data/app/views/raif/admin/prompt_studio/tasks/index.html.erb +35 -0
- data/app/views/raif/admin/prompt_studio/tasks/show.html.erb +19 -0
- data/app/views/raif/admin/stats/_stats_tile.html.erb +34 -0
- data/app/views/raif/admin/stats/index.html.erb +71 -88
- data/app/views/raif/admin/stats/model_tool_invocations/index.html.erb +43 -0
- data/app/views/raif/admin/stats/tasks/index.html.erb +20 -6
- data/app/views/raif/admin/tasks/_task.html.erb +1 -0
- data/app/views/raif/admin/tasks/index.html.erb +23 -6
- data/app/views/raif/admin/tasks/show.html.erb +56 -3
- data/app/views/raif/conversation_entries/_form.html.erb +3 -0
- data/app/views/raif/conversation_entries/_message.html.erb +10 -6
- data/app/views/raif/conversations/_conversation.html.erb +10 -0
- data/app/views/raif/conversations/_entry_processed.turbo_stream.erb +12 -0
- data/app/views/raif/conversations/index.html.erb +23 -0
- data/config/importmap.rb +8 -0
- data/config/locales/admin.en.yml +161 -1
- data/config/locales/en.yml +67 -4
- data/config/routes.rb +10 -0
- data/db/migrate/20250904194456_add_generating_entry_response_to_raif_conversations.rb +7 -0
- data/db/migrate/20250911125234_add_source_to_raif_tasks.rb +7 -0
- data/db/migrate/20251020005853_add_source_to_raif_agents.rb +7 -0
- data/db/migrate/20251020011346_rename_task_run_args_to_run_with.rb +7 -0
- data/db/migrate/20251020011405_add_run_with_to_raif_agents.rb +13 -0
- data/db/migrate/20251024160119_add_llm_messages_max_length_to_raif_conversations.rb +14 -0
- data/db/migrate/20251124185033_add_provider_tool_call_id_to_raif_model_tool_invocations.rb +7 -0
- data/db/migrate/20251128202941_add_tool_choice_to_raif_model_completions.rb +7 -0
- data/db/migrate/20260118144846_add_source_to_raif_conversations.rb +7 -0
- data/db/migrate/20260119000000_add_failure_tracking_to_raif_model_completions.rb +10 -0
- data/db/migrate/20260119000001_add_completed_at_to_raif_model_completions.rb +8 -0
- data/db/migrate/20260119000002_add_started_at_to_raif_model_completions.rb +8 -0
- data/db/migrate/20260307000000_add_prompt_studio_run_to_raif_tasks.rb +7 -0
- data/db/migrate/20260308000000_create_raif_prompt_studio_batch_runs.rb +27 -0
- data/db/migrate/20260308000001_create_raif_prompt_studio_batch_run_items.rb +24 -0
- data/db/migrate/20260407000000_add_cache_token_columns_to_raif_model_completions.rb +8 -0
- data/lib/generators/raif/agent/agent_generator.rb +18 -0
- data/lib/generators/raif/agent/templates/agent.rb.tt +7 -5
- data/lib/generators/raif/agent/templates/application_agent.rb.tt +1 -1
- data/lib/generators/raif/agent/templates/system_prompt.erb.tt +3 -0
- data/lib/generators/raif/conversation/conversation_generator.rb +19 -1
- data/lib/generators/raif/conversation/templates/conversation.rb.tt +6 -0
- data/lib/generators/raif/conversation/templates/system_prompt.erb.tt +4 -0
- data/lib/generators/raif/install/templates/initializer.rb +117 -8
- data/lib/generators/raif/task/task_generator.rb +18 -0
- data/lib/generators/raif/task/templates/prompt.erb.tt +4 -0
- data/lib/generators/raif/task/templates/task.rb.tt +10 -9
- data/lib/raif/configuration.rb +47 -2
- data/lib/raif/embedding_model_registry.rb +8 -0
- data/lib/raif/engine.rb +24 -1
- data/lib/raif/errors/blank_response_error.rb +8 -0
- data/lib/raif/errors/instance_dependent_schema_error.rb +8 -0
- data/lib/raif/errors/prompt_template_error.rb +15 -0
- data/lib/raif/errors/streaming_error.rb +6 -3
- data/lib/raif/errors.rb +3 -0
- data/lib/raif/evals/run.rb +1 -0
- data/lib/raif/evals.rb +0 -6
- data/lib/raif/json_schema_builder.rb +14 -0
- data/lib/raif/llm_registry.rb +433 -42
- data/lib/raif/messages.rb +180 -0
- data/lib/raif/prompt_studio_comparison_builder.rb +138 -0
- data/lib/raif/token_estimator.rb +28 -0
- data/lib/raif/version.rb +1 -1
- data/lib/raif.rb +11 -0
- data/lib/tasks/annotate_rb.rake +10 -0
- data/spec/support/rspec_helpers.rb +15 -9
- data/spec/support/test_task.rb +9 -0
- data/spec/support/test_template_task.rb +41 -0
- metadata +108 -15
- data/app/models/raif/agents/re_act_agent.rb +0 -127
- data/app/models/raif/agents/re_act_step.rb +0 -32
- data/app/models/raif/concerns/task_run_args.rb +0 -62
- data/lib/raif/evals/llm_judge.rb +0 -32
- /data/{lib → app/models}/raif/evals/scoring_rubric.rb +0 -0
|
@@ -40,4 +40,21 @@ module Raif::Concerns::Llms::OpenAiResponses::MessageFormatting
|
|
|
40
40
|
raise Raif::Errors::InvalidModelFileInputError, "Invalid model image input source type: #{file_input.source_type}"
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
|
+
|
|
44
|
+
def format_tool_call_message(tool_call)
|
|
45
|
+
{
|
|
46
|
+
"type" => "function_call",
|
|
47
|
+
"call_id" => tool_call["provider_tool_call_id"],
|
|
48
|
+
"name" => tool_call["name"],
|
|
49
|
+
"arguments" => JSON.generate(tool_call["arguments"])
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def format_tool_call_result_message(tool_call_result)
|
|
54
|
+
{
|
|
55
|
+
"type" => "function_call_output",
|
|
56
|
+
"call_id" => tool_call_result["provider_tool_call_id"],
|
|
57
|
+
"output" => tool_call_result["result"].is_a?(String) ? tool_call_result["result"] : JSON.generate(tool_call_result["result"])
|
|
58
|
+
}
|
|
59
|
+
end
|
|
43
60
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Raif::Concerns::Llms::OpenAiResponses::ResponseToolCalls
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
def extract_response_tool_calls(resp)
|
|
7
|
+
return if resp["output"].blank?
|
|
8
|
+
|
|
9
|
+
tool_calls = []
|
|
10
|
+
resp["output"].each do |output_item|
|
|
11
|
+
next unless output_item["type"] == "function_call"
|
|
12
|
+
|
|
13
|
+
tool_calls << {
|
|
14
|
+
"provider_tool_call_id" => output_item["call_id"],
|
|
15
|
+
"name" => output_item["name"],
|
|
16
|
+
"arguments" => begin
|
|
17
|
+
JSON.parse(output_item["arguments"])
|
|
18
|
+
rescue JSON::ParserError
|
|
19
|
+
output_item["arguments"]
|
|
20
|
+
end
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
tool_calls.any? ? tool_calls : nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -39,4 +39,12 @@ module Raif::Concerns::Llms::OpenAiResponses::ToolFormatting
|
|
|
39
39
|
"Invalid provider-managed tool: #{tool.name} for #{key}"
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
|
+
|
|
43
|
+
def build_forced_tool_choice(tool_name)
|
|
44
|
+
{ "type" => "function", "name" => tool_name }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def build_required_tool_choice
|
|
48
|
+
"required"
|
|
49
|
+
end
|
|
42
50
|
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Raif::Concerns::ProviderManagedToolCalls
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
# Provider-managed tool data is not normalized by the provider SDKs the same
|
|
7
|
+
# way developer-managed tool calls are. This method smooths those differences
|
|
8
|
+
# into one admin-friendly structure for the model completion page.
|
|
9
|
+
def provider_managed_tool_calls
|
|
10
|
+
# Memoized for repeated reads during a request/render. This assumes the
|
|
11
|
+
# completion's response payload is not mutated after first access.
|
|
12
|
+
@provider_managed_tool_calls ||= begin
|
|
13
|
+
tool_calls = extract_provider_managed_tool_calls
|
|
14
|
+
tool_calls = inferred_provider_managed_tool_calls if tool_calls.empty?
|
|
15
|
+
|
|
16
|
+
tool_calls.map do |tool_call|
|
|
17
|
+
next tool_call unless tool_call["tool_name"] == "web_search"
|
|
18
|
+
|
|
19
|
+
# Search sources can come from explicit provider result blocks
|
|
20
|
+
# (Anthropic) or from top-level citations (OpenAI / Google), so we
|
|
21
|
+
# merge both.
|
|
22
|
+
tool_call.merge("sources" => merge_provider_managed_sources(tool_call["sources"], citations))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns citations with URLs sanitized to only allow http/https schemes.
|
|
28
|
+
def sanitized_citations
|
|
29
|
+
@sanitized_citations ||= Array(citations).map do |citation|
|
|
30
|
+
url = citation["url"]
|
|
31
|
+
safe_url = url.present? && url.match?(%r{\Ahttps?://}i) ? url : nil
|
|
32
|
+
citation.merge("url" => safe_url)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def extract_provider_managed_tool_calls
|
|
39
|
+
response_blocks = Array(response_array).select { |block| block.is_a?(Hash) }
|
|
40
|
+
result_blocks_by_tool_use_id = response_blocks.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |block, hash|
|
|
41
|
+
next if block["tool_use_id"].blank?
|
|
42
|
+
|
|
43
|
+
hash[block["tool_use_id"]] << block
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
response_blocks.filter_map do |block|
|
|
47
|
+
case block["type"]
|
|
48
|
+
when "server_tool_use"
|
|
49
|
+
# Anthropic stores the tool invocation in one block and the result in a
|
|
50
|
+
# separate block keyed by `tool_use_id`.
|
|
51
|
+
build_provider_managed_server_tool_call(block, result_blocks_by_tool_use_id)
|
|
52
|
+
when "web_search_call", "web_search_preview",
|
|
53
|
+
"code_interpreter_call", "code_interpreter",
|
|
54
|
+
"image_generation_call", "image_generation"
|
|
55
|
+
# OpenAI Responses persists provider-managed calls as top-level typed
|
|
56
|
+
# blocks like `web_search_call`, `code_interpreter`, etc.
|
|
57
|
+
build_provider_managed_tool_call_from_type(block)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def build_provider_managed_server_tool_call(block, result_blocks_by_tool_use_id)
|
|
63
|
+
tool_name = normalize_provider_managed_tool_name(block["name"])
|
|
64
|
+
return unless provider_managed_tool_available?(tool_name)
|
|
65
|
+
|
|
66
|
+
raw_result = result_blocks_by_tool_use_id[block["id"]].presence
|
|
67
|
+
{
|
|
68
|
+
"tool_name" => tool_name,
|
|
69
|
+
"provider_tool_call_id" => block["id"],
|
|
70
|
+
"status" => block["status"],
|
|
71
|
+
"arguments" => block["input"].presence,
|
|
72
|
+
"sources" => extract_provider_managed_sources(raw_result),
|
|
73
|
+
"raw_result" => raw_result,
|
|
74
|
+
"inferred" => false
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def build_provider_managed_tool_call_from_type(block)
|
|
79
|
+
tool_name = normalize_provider_managed_tool_name(block["type"])
|
|
80
|
+
return unless provider_managed_tool_available?(tool_name)
|
|
81
|
+
|
|
82
|
+
payload = block.except("id", "type", "status").presence
|
|
83
|
+
{
|
|
84
|
+
"tool_name" => tool_name,
|
|
85
|
+
"provider_tool_call_id" => block["id"],
|
|
86
|
+
"status" => block["status"],
|
|
87
|
+
"arguments" => payload,
|
|
88
|
+
"sources" => [],
|
|
89
|
+
"raw_result" => payload,
|
|
90
|
+
"inferred" => false
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def inferred_provider_managed_tool_calls
|
|
95
|
+
# Google currently gives us citations for provider-managed web search, but
|
|
96
|
+
# not a first-class tool call block in `response_array`, so we infer a
|
|
97
|
+
# single search invocation when web search was available and citations exist.
|
|
98
|
+
return [] unless provider_managed_tool_available?("web_search") && citations.present?
|
|
99
|
+
|
|
100
|
+
[{
|
|
101
|
+
"tool_name" => "web_search",
|
|
102
|
+
"provider_tool_call_id" => nil,
|
|
103
|
+
"status" => "completed",
|
|
104
|
+
"arguments" => nil,
|
|
105
|
+
"sources" => merge_provider_managed_sources([], citations),
|
|
106
|
+
"raw_result" => nil,
|
|
107
|
+
"inferred" => true
|
|
108
|
+
}]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def extract_provider_managed_sources(result_blocks)
|
|
112
|
+
Array(result_blocks).flat_map do |result_block|
|
|
113
|
+
Array(result_block["content"]).filter_map do |content_block|
|
|
114
|
+
next unless content_block.is_a?(Hash) && content_block["type"] == "web_search_result"
|
|
115
|
+
|
|
116
|
+
{
|
|
117
|
+
"title" => content_block["title"],
|
|
118
|
+
"url" => normalize_provider_managed_source_url(content_block["url"]),
|
|
119
|
+
"page_age" => content_block["page_age"]
|
|
120
|
+
}.compact
|
|
121
|
+
end
|
|
122
|
+
end.uniq { |source| source["url"].presence || source["title"] }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def merge_provider_managed_sources(existing_sources, extra_sources)
|
|
126
|
+
(Array(existing_sources) + Array(extra_sources)).filter_map do |source|
|
|
127
|
+
next unless source.is_a?(Hash)
|
|
128
|
+
|
|
129
|
+
{
|
|
130
|
+
"title" => source["title"],
|
|
131
|
+
"url" => normalize_provider_managed_source_url(source["url"]),
|
|
132
|
+
"page_age" => source["page_age"]
|
|
133
|
+
}.compact.presence
|
|
134
|
+
end.uniq { |source| source["url"].presence || source["title"] }
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def normalize_provider_managed_tool_name(name)
|
|
138
|
+
case name.to_s
|
|
139
|
+
when "web_search", "web_search_call", "web_search_preview"
|
|
140
|
+
"web_search"
|
|
141
|
+
when "code_execution", "code_interpreter", "code_interpreter_call"
|
|
142
|
+
"code_execution"
|
|
143
|
+
when "image_generation", "image_generation_call"
|
|
144
|
+
"image_generation"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def provider_managed_tool_available?(tool_name)
|
|
149
|
+
return false if tool_name.blank?
|
|
150
|
+
|
|
151
|
+
available_model_tools_map[tool_name]&.provider_managed?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def normalize_provider_managed_source_url(url)
|
|
155
|
+
return if url.blank?
|
|
156
|
+
|
|
157
|
+
url = Raif::Utils::HtmlFragmentProcessor.strip_tracking_parameters(url)
|
|
158
|
+
return unless url.match?(%r{\Ahttps?://}i)
|
|
159
|
+
|
|
160
|
+
url
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Raif::Concerns::RunWith
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
class_attribute :_run_with_args, instance_writer: false, default: []
|
|
8
|
+
|
|
9
|
+
# Backward compatibility alias
|
|
10
|
+
class_attribute :_task_run_args, instance_writer: false, default: []
|
|
11
|
+
|
|
12
|
+
# Automatically serialize run_with args before validation on create
|
|
13
|
+
before_validation :serialize_run_with_to_column, on: :create
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class_methods do
|
|
17
|
+
# Scope for querying records by run_with arguments
|
|
18
|
+
# @param args [Hash] Key-value pairs to match in the run_with column
|
|
19
|
+
# @example
|
|
20
|
+
# Task.having_run_with(document: doc)
|
|
21
|
+
# Task.having_run_with(user: user, options: { foo: "bar" })
|
|
22
|
+
def having_run_with(**args)
|
|
23
|
+
return all if args.empty?
|
|
24
|
+
|
|
25
|
+
# Serialize args the same way we do for storage (handles GID conversion)
|
|
26
|
+
serialized = serialize_run_with(args)
|
|
27
|
+
|
|
28
|
+
# Avoid matching all records if args didn't match declared run_with arguments
|
|
29
|
+
return none if args.any? && serialized.empty?
|
|
30
|
+
|
|
31
|
+
# Use database-specific JSON containment query
|
|
32
|
+
case connection.adapter_name.downcase
|
|
33
|
+
when "postgresql"
|
|
34
|
+
# PostgreSQL: Use JSONB containment operator
|
|
35
|
+
where("run_with @> ?", serialized.to_json)
|
|
36
|
+
when "mysql2", "trilogy"
|
|
37
|
+
# MySQL: Use JSON_CONTAINS function
|
|
38
|
+
where("JSON_CONTAINS(run_with, ?)", serialized.to_json)
|
|
39
|
+
else
|
|
40
|
+
raise "Unsupported database: #{connection.adapter_name}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# DSL for declaring persistent run arguments that will be serialized to the database
|
|
45
|
+
# @param name [Symbol] The name of the argument
|
|
46
|
+
def run_with(name)
|
|
47
|
+
# Ensure each class has its own array copy
|
|
48
|
+
self._run_with_args = _run_with_args.dup
|
|
49
|
+
_run_with_args << name.to_sym
|
|
50
|
+
|
|
51
|
+
# Keep backward compatibility for _task_run_args class attribute
|
|
52
|
+
self._task_run_args = _task_run_args.dup
|
|
53
|
+
_task_run_args << name.to_sym
|
|
54
|
+
|
|
55
|
+
# Define getter that pulls from run_with JSON column
|
|
56
|
+
define_method(name) do
|
|
57
|
+
return instance_variable_get("@#{name}") if instance_variable_defined?("@#{name}")
|
|
58
|
+
|
|
59
|
+
value = run_with&.dig(name.to_s)
|
|
60
|
+
return unless value
|
|
61
|
+
|
|
62
|
+
# Deserialize GID if it's a string starting with gid://
|
|
63
|
+
deserialized = if value.is_a?(String) && value.start_with?("gid://")
|
|
64
|
+
begin
|
|
65
|
+
GlobalID::Locator.locate(value)
|
|
66
|
+
rescue ActiveRecord::RecordNotFound
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
else
|
|
70
|
+
value
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
instance_variable_set("@#{name}", deserialized)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Define setter that stores in memory (for use during run)
|
|
77
|
+
define_method("#{name}=") do |value|
|
|
78
|
+
instance_variable_set("@#{name}", value)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Backward compatibility alias
|
|
83
|
+
alias_method :task_run_arg, :run_with
|
|
84
|
+
|
|
85
|
+
# Transform run args into a hash that can be stored in the run_with database column
|
|
86
|
+
def serialize_run_with(args)
|
|
87
|
+
serialized_args = {}
|
|
88
|
+
_run_with_args.each do |arg_name|
|
|
89
|
+
next unless args.key?(arg_name)
|
|
90
|
+
|
|
91
|
+
value = args[arg_name]
|
|
92
|
+
serialized_args[arg_name.to_s] = if value.respond_to?(:to_global_id)
|
|
93
|
+
value.to_global_id.to_s
|
|
94
|
+
else
|
|
95
|
+
value
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
serialized_args
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Backward compatibility alias
|
|
103
|
+
alias_method :serialize_task_run_args, :serialize_run_with
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
# Automatically called before validation on create to serialize run_with args
|
|
109
|
+
# Collects all declared run_with arguments from instance variables and serializes them
|
|
110
|
+
# to the run_with JSON column
|
|
111
|
+
def serialize_run_with_to_column
|
|
112
|
+
args = {}
|
|
113
|
+
|
|
114
|
+
# Collect all run_with args that were set via instance variables
|
|
115
|
+
self.class._run_with_args.each do |arg_name|
|
|
116
|
+
if instance_variable_defined?("@#{arg_name}")
|
|
117
|
+
args[arg_name] = instance_variable_get("@#{arg_name}")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Merge serialized args into run_with hash if any args were set
|
|
122
|
+
if args.any?
|
|
123
|
+
self.run_with ||= {}
|
|
124
|
+
self.run_with = self.run_with.merge(self.class.serialize_run_with(args))
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -1,12 +1,64 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: raif_conversations
|
|
6
|
+
#
|
|
7
|
+
# id :bigint not null, primary key
|
|
8
|
+
# available_model_tools :jsonb not null
|
|
9
|
+
# available_user_tools :jsonb not null
|
|
10
|
+
# conversation_entries_count :integer default(0), not null
|
|
11
|
+
# creator_type :string not null
|
|
12
|
+
# generating_entry_response :boolean default(FALSE), not null
|
|
13
|
+
# llm_messages_max_length :integer
|
|
14
|
+
# llm_model_key :string not null
|
|
15
|
+
# requested_language_key :string
|
|
16
|
+
# response_format :integer default("text"), not null
|
|
17
|
+
# source_type :string
|
|
18
|
+
# system_prompt :text
|
|
19
|
+
# type :string not null
|
|
20
|
+
# created_at :datetime not null
|
|
21
|
+
# updated_at :datetime not null
|
|
22
|
+
# creator_id :bigint not null
|
|
23
|
+
# source_id :bigint
|
|
24
|
+
#
|
|
25
|
+
# Indexes
|
|
26
|
+
#
|
|
27
|
+
# index_raif_conversations_on_created_at (created_at)
|
|
28
|
+
# index_raif_conversations_on_creator (creator_type,creator_id)
|
|
29
|
+
# index_raif_conversations_on_source (source_type,source_id)
|
|
30
|
+
#
|
|
3
31
|
class Raif::Conversation < Raif::ApplicationRecord
|
|
32
|
+
prepend Raif::Concerns::HasPromptTemplates
|
|
33
|
+
|
|
4
34
|
include Raif::Concerns::HasLlm
|
|
5
35
|
include Raif::Concerns::HasRequestedLanguage
|
|
6
36
|
include Raif::Concerns::HasAvailableModelTools
|
|
7
37
|
include Raif::Concerns::LlmResponseParsing
|
|
38
|
+
include Raif::Concerns::LlmPromptCaching
|
|
8
39
|
|
|
9
40
|
belongs_to :creator, polymorphic: true
|
|
41
|
+
belongs_to :source, polymorphic: true, optional: true
|
|
42
|
+
|
|
43
|
+
class << self
|
|
44
|
+
def before_prompt_model_for_entry_response(&block)
|
|
45
|
+
@before_prompt_model_for_entry_response_blocks ||= []
|
|
46
|
+
@before_prompt_model_for_entry_response_blocks << block if block
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def before_prompt_model_for_entry_response_blocks
|
|
50
|
+
blocks = []
|
|
51
|
+
|
|
52
|
+
# Collect blocks from ancestors (in reverse order so parent blocks run first)
|
|
53
|
+
ancestors.reverse_each do |klass|
|
|
54
|
+
if klass.instance_variable_defined?(:@before_prompt_model_for_entry_response_blocks)
|
|
55
|
+
blocks.concat(klass.instance_variable_get(:@before_prompt_model_for_entry_response_blocks))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
blocks
|
|
60
|
+
end
|
|
61
|
+
end
|
|
10
62
|
|
|
11
63
|
has_many :entries, class_name: "Raif::ConversationEntry", dependent: :destroy, foreign_key: :raif_conversation_id, inverse_of: :raif_conversation
|
|
12
64
|
|
|
@@ -14,6 +66,7 @@ class Raif::Conversation < Raif::ApplicationRecord
|
|
|
14
66
|
|
|
15
67
|
after_initialize -> { self.available_model_tools ||= [] }
|
|
16
68
|
after_initialize -> { self.available_user_tools ||= [] }
|
|
69
|
+
after_initialize -> { self.llm_messages_max_length ||= Raif.config.conversation_llm_messages_max_length_default }
|
|
17
70
|
|
|
18
71
|
before_validation ->{ self.type ||= "Raif::Conversation" }, on: :create
|
|
19
72
|
|
|
@@ -39,17 +92,33 @@ class Raif::Conversation < Raif::ApplicationRecord
|
|
|
39
92
|
end
|
|
40
93
|
|
|
41
94
|
def prompt_model_for_entry_response(entry:, &block)
|
|
42
|
-
|
|
95
|
+
self.class.before_prompt_model_for_entry_response_blocks.each do |callback_block|
|
|
96
|
+
instance_exec(entry, &callback_block)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
self.system_prompt = build_system_prompt
|
|
100
|
+
self.generating_entry_response = true
|
|
101
|
+
save!
|
|
43
102
|
|
|
44
|
-
llm.chat(
|
|
103
|
+
model_completion = llm.chat(
|
|
45
104
|
messages: llm_messages,
|
|
46
105
|
source: entry,
|
|
47
106
|
response_format: response_format.to_sym,
|
|
48
107
|
system_prompt: system_prompt,
|
|
49
108
|
available_model_tools: available_model_tools,
|
|
109
|
+
anthropic_prompt_caching_enabled: self.class.anthropic_prompt_caching_enabled,
|
|
110
|
+
bedrock_prompt_caching_enabled: self.class.bedrock_prompt_caching_enabled,
|
|
50
111
|
&block
|
|
51
112
|
)
|
|
113
|
+
|
|
114
|
+
self.generating_entry_response = false
|
|
115
|
+
save!
|
|
116
|
+
|
|
117
|
+
model_completion
|
|
52
118
|
rescue StandardError => e
|
|
119
|
+
self.generating_entry_response = false
|
|
120
|
+
save!
|
|
121
|
+
|
|
53
122
|
Rails.logger.error("Error processing conversation entry ##{entry.id}. #{e.message}")
|
|
54
123
|
Rails.logger.error(e.backtrace.join("\n"))
|
|
55
124
|
|
|
@@ -75,14 +144,36 @@ class Raif::Conversation < Raif::ApplicationRecord
|
|
|
75
144
|
def llm_messages
|
|
76
145
|
messages = []
|
|
77
146
|
|
|
78
|
-
entries
|
|
79
|
-
|
|
147
|
+
# Apply max length limit to entries if configured (nil means no limit)
|
|
148
|
+
included_entries = entries.oldest_first.includes(:raif_model_tool_invocations)
|
|
149
|
+
included_entries = included_entries.last(llm_messages_max_length) if llm_messages_max_length.present?
|
|
150
|
+
|
|
151
|
+
included_entries.each do |entry|
|
|
152
|
+
unless entry.user_message.blank?
|
|
153
|
+
messages << Raif::Messages::UserMessage.new(content: entry.user_message).to_h
|
|
154
|
+
end
|
|
155
|
+
|
|
80
156
|
next unless entry.completed?
|
|
81
157
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
158
|
+
tool_invocations = entry.raif_model_tool_invocations.to_a
|
|
159
|
+
|
|
160
|
+
if tool_invocations.any?
|
|
161
|
+
# First tool call includes the assistant's message (if any).
|
|
162
|
+
# For the result payload we send the model-facing observation when the tool
|
|
163
|
+
# opts into observations, while keeping the raw invocation.result persisted
|
|
164
|
+
# for admin/UI rendering.
|
|
165
|
+
first_invocation = tool_invocations.shift
|
|
166
|
+
messages << first_invocation.as_tool_call_message(assistant_message: entry.model_response_message.presence)
|
|
167
|
+
messages << first_invocation.as_tool_call_result_message(result: tool_result_for_llm(first_invocation))
|
|
168
|
+
|
|
169
|
+
# Remaining tool calls (if multiple)
|
|
170
|
+
tool_invocations.each do |tool_invocation|
|
|
171
|
+
messages << tool_invocation.as_tool_call_message
|
|
172
|
+
messages << tool_invocation.as_tool_call_result_message(result: tool_result_for_llm(tool_invocation))
|
|
173
|
+
end
|
|
174
|
+
elsif entry.model_response_message.present?
|
|
175
|
+
# No tool calls, just a regular assistant response
|
|
176
|
+
messages << Raif::Messages::AssistantMessage.new(content: entry.model_response_message).to_h
|
|
86
177
|
end
|
|
87
178
|
end
|
|
88
179
|
|
|
@@ -93,4 +184,17 @@ class Raif::Conversation < Raif::ApplicationRecord
|
|
|
93
184
|
available_user_tools.map(&:constantize)
|
|
94
185
|
end
|
|
95
186
|
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
def tool_result_for_llm(tool_invocation)
|
|
190
|
+
# Some tools persist a compact structured result for display/admin purposes but
|
|
191
|
+
# need to send richer text/XML back to the model for the continuation turn.
|
|
192
|
+
return tool_invocation.result unless tool_invocation.triggers_observation_to_model?
|
|
193
|
+
|
|
194
|
+
tool = tool_invocation.tool
|
|
195
|
+
return tool_invocation.result unless tool.respond_to?(:observation_for_invocation)
|
|
196
|
+
|
|
197
|
+
tool.observation_for_invocation(tool_invocation).presence || tool_invocation.result
|
|
198
|
+
end
|
|
199
|
+
|
|
96
200
|
end
|
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: raif_conversation_entries
|
|
6
|
+
#
|
|
7
|
+
# id :bigint not null, primary key
|
|
8
|
+
# completed_at :datetime
|
|
9
|
+
# creator_type :string not null
|
|
10
|
+
# failed_at :datetime
|
|
11
|
+
# model_response_message :text
|
|
12
|
+
# raw_response :text
|
|
13
|
+
# started_at :datetime
|
|
14
|
+
# user_message :text
|
|
15
|
+
# created_at :datetime not null
|
|
16
|
+
# updated_at :datetime not null
|
|
17
|
+
# creator_id :bigint not null
|
|
18
|
+
# raif_conversation_id :bigint not null
|
|
19
|
+
#
|
|
20
|
+
# Indexes
|
|
21
|
+
#
|
|
22
|
+
# index_raif_conversation_entries_on_created_at (created_at)
|
|
23
|
+
# index_raif_conversation_entries_on_creator (creator_type,creator_id)
|
|
24
|
+
# index_raif_conversation_entries_on_raif_conversation_id (raif_conversation_id)
|
|
25
|
+
#
|
|
26
|
+
# Foreign Keys
|
|
27
|
+
#
|
|
28
|
+
# fk_rails_... (raif_conversation_id => raif_conversations.id)
|
|
29
|
+
#
|
|
3
30
|
class Raif::ConversationEntry < Raif::ApplicationRecord
|
|
4
31
|
include Raif::Concerns::InvokesModelTools
|
|
5
32
|
include Raif::Concerns::HasAvailableModelTools
|
|
@@ -32,8 +59,7 @@ class Raif::ConversationEntry < Raif::ApplicationRecord
|
|
|
32
59
|
def add_user_tool_invocation_to_user_message
|
|
33
60
|
return unless raif_user_tool_invocation.present?
|
|
34
61
|
|
|
35
|
-
|
|
36
|
-
self.user_message = [user_message, raif_user_tool_invocation.as_user_message].join(separator)
|
|
62
|
+
self.user_message = [user_message, raif_user_tool_invocation.as_user_message].join("\n\n")
|
|
37
63
|
end
|
|
38
64
|
|
|
39
65
|
def response_format
|
|
@@ -47,7 +73,7 @@ class Raif::ConversationEntry < Raif::ApplicationRecord
|
|
|
47
73
|
def process_entry!
|
|
48
74
|
self.model_response_message = ""
|
|
49
75
|
|
|
50
|
-
|
|
76
|
+
model_completion = raif_conversation.prompt_model_for_entry_response(entry: self) do |model_completion, _delta, _sse_event|
|
|
51
77
|
self.raw_response = model_completion.raw_response
|
|
52
78
|
self.model_response_message = raif_conversation.process_model_response_message(
|
|
53
79
|
message: model_completion.parsed_response(force_reparse: true),
|
|
@@ -63,6 +89,10 @@ class Raif::ConversationEntry < Raif::ApplicationRecord
|
|
|
63
89
|
broadcast_replace_to raif_conversation
|
|
64
90
|
end
|
|
65
91
|
|
|
92
|
+
# Failed prompt attempts can still persist a model completion for debugging.
|
|
93
|
+
# Avoid clearing the has_one association with nil, which would delete that row.
|
|
94
|
+
self.raif_model_completion = model_completion if model_completion.present?
|
|
95
|
+
|
|
66
96
|
if raif_model_completion.present? && (raif_model_completion.parsed_response.present? || raif_model_completion.response_tool_calls.present?)
|
|
67
97
|
extract_message_and_invoke_tools!
|
|
68
98
|
create_entry_for_observation! if triggers_observation_to_model?
|
|
@@ -98,7 +128,11 @@ private
|
|
|
98
128
|
tool_klass = available_model_tools_map[tool_call["name"]]
|
|
99
129
|
next if tool_klass.nil?
|
|
100
130
|
|
|
101
|
-
tool_klass.invoke_tool(
|
|
131
|
+
tool_klass.invoke_tool(
|
|
132
|
+
provider_tool_call_id: tool_call["provider_tool_call_id"],
|
|
133
|
+
tool_arguments: tool_call["arguments"],
|
|
134
|
+
source: self
|
|
135
|
+
)
|
|
102
136
|
end
|
|
103
137
|
|
|
104
138
|
completed!
|
|
@@ -5,6 +5,7 @@ class Raif::EmbeddingModel
|
|
|
5
5
|
|
|
6
6
|
attr_accessor :key,
|
|
7
7
|
:api_name,
|
|
8
|
+
:display_name,
|
|
8
9
|
:input_token_cost,
|
|
9
10
|
:default_output_vector_size
|
|
10
11
|
|
|
@@ -13,7 +14,7 @@ class Raif::EmbeddingModel
|
|
|
13
14
|
validates :key, presence: true
|
|
14
15
|
|
|
15
16
|
def name
|
|
16
|
-
I18n.t("raif.embedding_model_names.#{key}")
|
|
17
|
+
I18n.t("raif.embedding_model_names.#{key}", default: display_name || key.to_s.humanize)
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def generate_embedding!(input, dimensions: nil)
|
|
@@ -29,6 +29,15 @@ private
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def bedrock_client
|
|
32
|
-
@bedrock_client ||=
|
|
32
|
+
@bedrock_client ||= begin
|
|
33
|
+
client_options = {
|
|
34
|
+
region: Raif.config.aws_bedrock_region
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
client_options[:http_read_timeout] = Raif.config.request_read_timeout if Raif.config.request_read_timeout
|
|
38
|
+
client_options[:http_open_timeout] = Raif.config.request_open_timeout if Raif.config.request_open_timeout
|
|
39
|
+
|
|
40
|
+
Aws::BedrockRuntime::Client.new(client_options)
|
|
41
|
+
end
|
|
33
42
|
end
|
|
34
43
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Raif::EmbeddingModels::Google < Raif::EmbeddingModel
|
|
4
|
+
def generate_embedding!(input, dimensions: nil)
|
|
5
|
+
unless input.is_a?(String)
|
|
6
|
+
raise ArgumentError, "Raif::EmbeddingModels::Google#generate_embedding! input must be a string"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
response = connection.post("models/#{api_name}:embedContent") do |req|
|
|
10
|
+
req.body = build_request_parameters(input, dimensions:)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
response.body.dig("embedding", "values")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def build_request_parameters(input, dimensions: nil)
|
|
19
|
+
params = {
|
|
20
|
+
content: {
|
|
21
|
+
parts: [{ text: input }]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
params[:outputDimensionality] = dimensions if dimensions.present?
|
|
26
|
+
params
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def connection
|
|
30
|
+
@connection ||= Faraday.new(url: "https://generativelanguage.googleapis.com/v1beta", request: Raif.default_request_options) do |f|
|
|
31
|
+
f.headers["x-goog-api-key"] = Raif.config.google_api_key
|
|
32
|
+
f.request :json
|
|
33
|
+
f.response :json
|
|
34
|
+
f.response :raise_error
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -30,7 +30,7 @@ private
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def connection
|
|
33
|
-
@connection ||= Faraday.new(url:
|
|
33
|
+
@connection ||= Faraday.new(url: Raif.config.open_ai_embedding_base_url, request: Raif.default_request_options) do |f|
|
|
34
34
|
f.headers["Authorization"] = "Bearer #{Raif.config.open_ai_api_key}"
|
|
35
35
|
f.request :json
|
|
36
36
|
f.response :json
|