raif 1.4.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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/app/assets/builds/raif_admin.css +40 -2
  4. data/app/assets/builds/raif_admin_sprockets.js +2709 -0
  5. data/app/assets/javascript/raif/admin/copy_to_clipboard_controller.js +132 -0
  6. data/app/assets/javascript/raif/admin/cost_estimate_controller.js +80 -0
  7. data/app/assets/javascript/raif/admin/judge_config_controller.js +23 -0
  8. data/app/assets/javascript/raif/admin/select_all_checkboxes_controller.js +33 -0
  9. data/app/assets/javascript/raif/admin/sortable_table_controller.js +51 -0
  10. data/app/assets/javascript/raif/admin/table_search_controller.js +15 -0
  11. data/app/assets/javascript/raif/admin/tom_select_controller.js +33 -0
  12. data/app/assets/javascript/raif_admin.js +23 -0
  13. data/app/assets/javascript/raif_admin_sprockets.js +24 -0
  14. data/app/assets/stylesheets/raif_admin.scss +50 -1
  15. data/app/controllers/raif/admin/agents_controller.rb +27 -1
  16. data/app/controllers/raif/admin/configs_controller.rb +1 -0
  17. data/app/controllers/raif/admin/llms_controller.rb +27 -0
  18. data/app/controllers/raif/admin/model_completions_controller.rb +6 -0
  19. data/app/controllers/raif/admin/prompt_studio/agents_controller.rb +25 -0
  20. data/app/controllers/raif/admin/prompt_studio/base_controller.rb +32 -0
  21. data/app/controllers/raif/admin/prompt_studio/batch_runs_controller.rb +102 -0
  22. data/app/controllers/raif/admin/prompt_studio/conversations_controller.rb +25 -0
  23. data/app/controllers/raif/admin/prompt_studio/tasks_controller.rb +64 -0
  24. data/app/controllers/raif/admin/tasks_controller.rb +5 -0
  25. data/app/helpers/raif/application_helper.rb +40 -0
  26. data/app/jobs/raif/prompt_studio_batch_run_item_job.rb +11 -0
  27. data/app/jobs/raif/prompt_studio_batch_run_job.rb +15 -0
  28. data/app/jobs/raif/prompt_studio_task_run_job.rb +36 -0
  29. data/app/models/raif/agent.rb +36 -5
  30. data/app/models/raif/agents/native_tool_calling_agent.rb +101 -19
  31. data/app/models/raif/concerns/has_prompt_templates.rb +88 -0
  32. data/app/models/raif/concerns/has_runtime_duration.rb +41 -0
  33. data/app/models/raif/concerns/json_schema_definition.rb +16 -3
  34. data/app/models/raif/concerns/llm_prompt_caching.rb +20 -0
  35. data/app/models/raif/concerns/llms/anthropic/message_formatting.rb +6 -0
  36. data/app/models/raif/concerns/llms/anthropic/tool_formatting.rb +5 -1
  37. data/app/models/raif/concerns/llms/bedrock/message_formatting.rb +7 -0
  38. data/app/models/raif/concerns/llms/bedrock/tool_formatting.rb +4 -0
  39. data/app/models/raif/concerns/llms/google/message_formatting.rb +5 -2
  40. data/app/models/raif/concerns/llms/google/tool_formatting.rb +4 -0
  41. data/app/models/raif/concerns/llms/message_formatting.rb +30 -0
  42. data/app/models/raif/concerns/llms/open_ai_completions/response_tool_calls.rb +1 -1
  43. data/app/models/raif/concerns/llms/open_ai_completions/tool_formatting.rb +4 -0
  44. data/app/models/raif/concerns/llms/open_ai_responses/tool_formatting.rb +4 -0
  45. data/app/models/raif/concerns/provider_managed_tool_calls.rb +162 -0
  46. data/app/models/raif/conversation.rb +24 -3
  47. data/app/models/raif/conversation_entry.rb +6 -3
  48. data/app/models/raif/embedding_models/bedrock.rb +10 -1
  49. data/app/models/raif/embedding_models/google.rb +37 -0
  50. data/app/models/raif/evals/llm_judge.rb +70 -0
  51. data/{lib → app/models}/raif/evals/llm_judges/binary.rb +38 -0
  52. data/{lib → app/models}/raif/evals/llm_judges/comparative.rb +38 -0
  53. data/{lib → app/models}/raif/evals/llm_judges/scored.rb +38 -0
  54. data/{lib → app/models}/raif/evals/llm_judges/summarization.rb +38 -0
  55. data/app/models/raif/llm.rb +82 -7
  56. data/app/models/raif/llms/anthropic.rb +26 -4
  57. data/app/models/raif/llms/bedrock.rb +59 -5
  58. data/app/models/raif/llms/google.rb +28 -2
  59. data/app/models/raif/llms/open_ai_base.rb +4 -0
  60. data/app/models/raif/llms/open_ai_completions.rb +9 -2
  61. data/app/models/raif/llms/open_ai_responses.rb +9 -2
  62. data/app/models/raif/llms/open_router.rb +10 -3
  63. data/app/models/raif/model_completion.rb +75 -34
  64. data/app/models/raif/model_tool.rb +45 -3
  65. data/app/models/raif/model_tool_invocation.rb +31 -1
  66. data/app/models/raif/prompt_studio_batch_run.rb +155 -0
  67. data/app/models/raif/prompt_studio_batch_run_item.rb +220 -0
  68. data/app/models/raif/streaming_responses/bedrock.rb +60 -1
  69. data/app/models/raif/task.rb +30 -6
  70. data/app/views/layouts/raif/admin.html.erb +31 -1
  71. data/app/views/raif/admin/agents/_agent.html.erb +1 -0
  72. data/app/views/raif/admin/agents/index.html.erb +48 -0
  73. data/app/views/raif/admin/agents/show.html.erb +4 -0
  74. data/app/views/raif/admin/llms/index.html.erb +110 -0
  75. data/app/views/raif/admin/model_completions/_model_completion.html.erb +3 -7
  76. data/app/views/raif/admin/model_completions/index.html.erb +14 -1
  77. data/app/views/raif/admin/model_completions/show.html.erb +164 -55
  78. data/app/views/raif/admin/model_tool_invocations/index.html.erb +1 -1
  79. data/app/views/raif/admin/model_tool_invocations/show.html.erb +18 -0
  80. data/app/views/raif/admin/prompt_studio/agents/index.html.erb +56 -0
  81. data/app/views/raif/admin/prompt_studio/agents/show.html.erb +57 -0
  82. data/app/views/raif/admin/prompt_studio/batch_runs/_batch_run_item.html.erb +54 -0
  83. data/app/views/raif/admin/prompt_studio/batch_runs/_judge_config_fields.html.erb +76 -0
  84. data/app/views/raif/admin/prompt_studio/batch_runs/_judge_detail_modal.html.erb +27 -0
  85. data/app/views/raif/admin/prompt_studio/batch_runs/_modal.html.erb +35 -0
  86. data/app/views/raif/admin/prompt_studio/batch_runs/_progress.html.erb +78 -0
  87. data/app/views/raif/admin/prompt_studio/batch_runs/show.html.erb +49 -0
  88. data/app/views/raif/admin/prompt_studio/conversations/index.html.erb +48 -0
  89. data/app/views/raif/admin/prompt_studio/conversations/show.html.erb +36 -0
  90. data/app/views/raif/admin/prompt_studio/shared/_nav_tabs.html.erb +17 -0
  91. data/app/views/raif/admin/prompt_studio/shared/_prompt_comparison.html.erb +87 -0
  92. data/app/views/raif/admin/prompt_studio/shared/_type_filter.html.erb +54 -0
  93. data/app/views/raif/admin/prompt_studio/tasks/_task_result.html.erb +145 -0
  94. data/app/views/raif/admin/prompt_studio/tasks/_task_row.html.erb +12 -0
  95. data/app/views/raif/admin/prompt_studio/tasks/_task_type_filter.html.erb +58 -0
  96. data/app/views/raif/admin/prompt_studio/tasks/_tasks_table.html.erb +22 -0
  97. data/app/views/raif/admin/prompt_studio/tasks/index.html.erb +35 -0
  98. data/app/views/raif/admin/prompt_studio/tasks/show.html.erb +19 -0
  99. data/app/views/raif/admin/tasks/_task.html.erb +1 -0
  100. data/app/views/raif/admin/tasks/index.html.erb +17 -5
  101. data/app/views/raif/admin/tasks/show.html.erb +20 -0
  102. data/app/views/raif/conversation_entries/_message.html.erb +10 -6
  103. data/config/importmap.rb +8 -0
  104. data/config/locales/admin.en.yml +128 -0
  105. data/config/locales/en.yml +36 -2
  106. data/config/routes.rb +8 -0
  107. data/db/migrate/20260307000000_add_prompt_studio_run_to_raif_tasks.rb +7 -0
  108. data/db/migrate/20260308000000_create_raif_prompt_studio_batch_runs.rb +27 -0
  109. data/db/migrate/20260308000001_create_raif_prompt_studio_batch_run_items.rb +24 -0
  110. data/db/migrate/20260407000000_add_cache_token_columns_to_raif_model_completions.rb +8 -0
  111. data/lib/generators/raif/agent/agent_generator.rb +18 -0
  112. data/lib/generators/raif/agent/templates/agent.rb.tt +7 -5
  113. data/lib/generators/raif/agent/templates/system_prompt.erb.tt +3 -0
  114. data/lib/generators/raif/conversation/conversation_generator.rb +19 -1
  115. data/lib/generators/raif/conversation/templates/system_prompt.erb.tt +4 -0
  116. data/lib/generators/raif/install/templates/initializer.rb +68 -27
  117. data/lib/generators/raif/task/task_generator.rb +18 -0
  118. data/lib/generators/raif/task/templates/prompt.erb.tt +4 -0
  119. data/lib/generators/raif/task/templates/task.rb.tt +9 -8
  120. data/lib/raif/configuration.rb +10 -0
  121. data/lib/raif/embedding_model_registry.rb +8 -0
  122. data/lib/raif/engine.rb +16 -1
  123. data/lib/raif/errors/blank_response_error.rb +8 -0
  124. data/lib/raif/errors/prompt_template_error.rb +15 -0
  125. data/lib/raif/errors.rb +2 -0
  126. data/lib/raif/evals.rb +0 -6
  127. data/lib/raif/llm_registry.rb +230 -9
  128. data/lib/raif/prompt_studio_comparison_builder.rb +138 -0
  129. data/lib/raif/token_estimator.rb +28 -0
  130. data/lib/raif/version.rb +1 -1
  131. data/lib/raif.rb +2 -0
  132. data/spec/support/rspec_helpers.rb +7 -1
  133. data/spec/support/test_task.rb +9 -0
  134. data/spec/support/test_template_task.rb +41 -0
  135. metadata +65 -7
  136. data/lib/raif/evals/llm_judge.rb +0 -32
  137. /data/{lib → app/models}/raif/evals/scoring_rubric.rb +0 -0
@@ -3,6 +3,8 @@
3
3
  class Raif::StreamingResponses::Bedrock
4
4
 
5
5
  def initialize_new_message
6
+ @reasoning_content_blocks = {}
7
+
6
8
  # Initialize empty AWS response object
7
9
  @message = Aws::BedrockRuntime::Types::Message.new(
8
10
  role: "assistant",
@@ -62,9 +64,12 @@ class Raif::StreamingResponses::Bedrock
62
64
  )
63
65
 
64
66
  @message.content[index].tool_use.input += event.delta.tool_use.input
67
+ elsif event.delta.is_a?(Aws::BedrockRuntime::Types::ContentBlockDelta::ReasoningContent)
68
+ accumulate_reasoning_content(index, event.delta.reasoning_content)
65
69
  end
66
70
  when :content_block_stop
67
- content_block = @message.content[event.content_block_index]
71
+ index = event.content_block_index
72
+ content_block = @message.content[index]
68
73
 
69
74
  if content_block&.tool_use&.input.is_a?(String)
70
75
  begin
@@ -73,6 +78,8 @@ class Raif::StreamingResponses::Bedrock
73
78
  # If parsing fails, leave as a string
74
79
  end
75
80
  end
81
+
82
+ finalize_reasoning_content(index)
76
83
  when :message_stop
77
84
  @response.stop_reason = event.stop_reason
78
85
  when :metadata
@@ -86,4 +93,56 @@ class Raif::StreamingResponses::Bedrock
86
93
  @response
87
94
  end
88
95
 
96
+ private
97
+
98
+ def accumulate_reasoning_content(index, reasoning_delta)
99
+ reasoning_content = reasoning_content_for(index)
100
+ reasoning_content[:seen] = true
101
+
102
+ case reasoning_delta
103
+ when Aws::BedrockRuntime::Types::ReasoningContentBlockDelta::Text
104
+ reasoning_content[:text] << reasoning_delta.text.to_s
105
+ when Aws::BedrockRuntime::Types::ReasoningContentBlockDelta::Signature
106
+ reasoning_content[:signature] = reasoning_delta.signature
107
+ when Aws::BedrockRuntime::Types::ReasoningContentBlockDelta::RedactedContent
108
+ reasoning_content[:redacted_content] << reasoning_delta.redacted_content.to_s
109
+ else
110
+ reasoning_content[:unknown] = true
111
+ end
112
+ end
113
+
114
+ def finalize_reasoning_content(index)
115
+ reasoning_content = @reasoning_content_blocks.delete(index)
116
+ return unless reasoning_content&.dig(:seen)
117
+
118
+ @message.content[index] = Aws::BedrockRuntime::Types::ContentBlock::ReasoningContent.new(
119
+ reasoning_content: build_reasoning_content(reasoning_content)
120
+ )
121
+ end
122
+
123
+ def build_reasoning_content(reasoning_content)
124
+ if reasoning_content[:text].blank? && reasoning_content[:signature].blank? && reasoning_content[:redacted_content].present?
125
+ return Aws::BedrockRuntime::Types::ReasoningContentBlock::RedactedContent.new(
126
+ redacted_content: reasoning_content[:redacted_content]
127
+ )
128
+ end
129
+
130
+ Aws::BedrockRuntime::Types::ReasoningContentBlock::ReasoningText.new(
131
+ reasoning_text: Aws::BedrockRuntime::Types::ReasoningTextBlock.new(
132
+ text: reasoning_content[:text],
133
+ signature: reasoning_content[:signature]
134
+ )
135
+ )
136
+ end
137
+
138
+ def reasoning_content_for(index)
139
+ @reasoning_content_blocks[index] ||= {
140
+ seen: false,
141
+ text: +"",
142
+ signature: nil,
143
+ redacted_content: +"",
144
+ unknown: false
145
+ }
146
+ end
147
+
89
148
  end
@@ -11,6 +11,7 @@
11
11
  # failed_at :datetime
12
12
  # llm_model_key :string not null
13
13
  # prompt :text
14
+ # prompt_studio_run :boolean default(FALSE), not null
14
15
  # raw_response :text
15
16
  # requested_language_key :string
16
17
  # response_format :integer default("text"), not null
@@ -39,12 +40,16 @@
39
40
  #
40
41
  module Raif
41
42
  class Task < Raif::ApplicationRecord
43
+ prepend Raif::Concerns::HasPromptTemplates
44
+
42
45
  include Raif::Concerns::HasLlm
43
46
  include Raif::Concerns::HasRequestedLanguage
44
47
  include Raif::Concerns::HasAvailableModelTools
48
+ include Raif::Concerns::HasRuntimeDuration
45
49
  include Raif::Concerns::InvokesModelTools
46
50
  include Raif::Concerns::LlmResponseParsing
47
51
  include Raif::Concerns::LlmTemperature
52
+ include Raif::Concerns::LlmPromptCaching
48
53
  include Raif::Concerns::JsonSchemaDefinition
49
54
  include Raif::Concerns::RunWith
50
55
 
@@ -95,7 +100,7 @@ module Raif
95
100
  # @param files [Array] Optional array of Raif::ModelFileInput objects to include with the prompt.
96
101
  # @param args [Hash] Additional arguments to pass to the instance of the task that is created.
97
102
  # @return [Raif::Task, nil] The task instance that was created and run.
98
- def self.run(creator: nil, available_model_tools: [], llm_model_key: nil, images: [], files: [], **args)
103
+ def self.run(creator: nil, available_model_tools: [], llm_model_key: nil, images: [], files: [], **args, &block)
99
104
  task = new(
100
105
  creator: creator,
101
106
  llm_model_key: llm_model_key,
@@ -107,7 +112,7 @@ module Raif
107
112
  )
108
113
 
109
114
  task.save!
110
- task.run
115
+ task.run(&block)
111
116
  task
112
117
  rescue StandardError => e
113
118
  task&.failed!
@@ -126,18 +131,28 @@ module Raif
126
131
  task
127
132
  end
128
133
 
129
- def run(skip_prompt_population: false)
134
+ def run(skip_prompt_population: false, &block)
130
135
  update_columns(started_at: Time.current) if started_at.nil?
131
136
 
132
137
  populate_prompts unless skip_prompt_population
133
138
 
139
+ streaming_block = if block_given?
140
+ proc do |model_completion, delta, sse_event|
141
+ update_columns(raw_response: model_completion.raw_response, updated_at: Time.current)
142
+ block.call(model_completion, delta, sse_event)
143
+ end
144
+ end
145
+
134
146
  mc = llm.chat(
135
147
  messages: messages,
136
148
  source: self,
137
149
  system_prompt: system_prompt,
138
150
  response_format: response_format.to_sym,
139
151
  available_model_tools: available_model_tools,
140
- temperature: self.class.temperature
152
+ temperature: self.class.temperature,
153
+ anthropic_prompt_caching_enabled: self.class.anthropic_prompt_caching_enabled,
154
+ bedrock_prompt_caching_enabled: self.class.bedrock_prompt_caching_enabled,
155
+ &streaming_block
141
156
  )
142
157
 
143
158
  self.raif_model_completion = mc.becomes(Raif::ModelCompletion)
@@ -149,9 +164,9 @@ module Raif
149
164
  self
150
165
  end
151
166
 
152
- def re_run
167
+ def re_run(&block)
153
168
  update_columns(started_at: Time.current)
154
- run(skip_prompt_population: true)
169
+ run(skip_prompt_population: true, &block)
155
170
  end
156
171
 
157
172
  def messages
@@ -191,6 +206,15 @@ module Raif
191
206
  schema_for_instance(:json_response)
192
207
  end
193
208
 
209
+ # Returns additional attributes to assign when creating tasks in Prompt Studio
210
+ # (reruns, batch runs, and judge tasks). Override in your ApplicationTask or
211
+ # task subclass to include app-specific attributes.
212
+ #
213
+ # @return [Hash] additional attributes to assign to the new task
214
+ def prompt_studio_task_attributes
215
+ {}
216
+ end
217
+
194
218
  def build_prompt
195
219
  raise NotImplementedError, "Raif::Task subclasses must implement #build_prompt"
196
220
  end
@@ -5,14 +5,23 @@
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
6
  <%= csrf_meta_tags %>
7
7
  <%= csp_meta_tag %>
8
+ <%= action_cable_meta_tag if respond_to?(:action_cable_meta_tag) %>
8
9
 
9
10
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
10
11
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous" nonce="<%= request.content_security_policy_nonce %>"></script>
11
12
 
13
+ <link href="https://cdn.jsdelivr.net/npm/tom-select@2.4.3/dist/css/tom-select.bootstrap5.min.css" rel="stylesheet">
14
+ <script src="https://cdn.jsdelivr.net/npm/tom-select@2.4.3/dist/js/tom-select.complete.min.js" nonce="<%= request.content_security_policy_nonce %>"></script>
15
+
16
+ <% if respond_to?(:javascript_importmap_tags) %>
17
+ <%= javascript_importmap_tags "raif_admin" %>
18
+ <% else %>
19
+ <%= javascript_include_tag "raif_admin_sprockets", nonce: true %>
20
+ <% end %>
12
21
  <%= stylesheet_link_tag "raif_admin" %>
13
22
  </head>
14
23
 
15
- <body class="raif-admin">
24
+ <body class="raif-admin" data-controller="raif--copy-to-clipboard">
16
25
  <nav class="navbar navbar-expand-md navbar-dark bg-dark">
17
26
  <div class="container-fluid">
18
27
  <a class="navbar-brand fw-bold" href="<%= raif.admin_tasks_path %>">
@@ -96,6 +105,27 @@
96
105
  <%= t("raif.admin.common.agents") %>
97
106
  </a>
98
107
  </li>
108
+ <li class="nav-item">
109
+ <a class="nav-link <%= current_page?(raif.admin_llms_path) ? "active" : "" %>" href="<%= raif.admin_llms_path %>">
110
+ <span class="d-inline-block me-2">
111
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-currency-dollar" viewBox="0 0 16 16">
112
+ <path d="M4 10.781c.148 1.667 1.513 2.85 3.591 3.003V15h1.043v-1.216c2.27-.179 3.678-1.438 3.678-3.3 0-1.59-.947-2.51-2.956-3.028l-.722-.187V3.467c1.122.11 1.879.714 2.07 1.616h1.47c-.166-1.6-1.54-2.748-3.54-2.875V1H7.591v1.233c-1.939.23-3.27 1.472-3.27 3.156 0 1.454.966 2.483 2.661 2.917l.61.162v4.031c-1.149-.17-1.94-.8-2.131-1.718H4zm3.391-3.836c-1.043-.263-1.6-.825-1.6-1.616 0-.944.704-1.641 1.8-1.828v3.495l-.2-.05zm1.591 1.872c1.287.323 1.852.859 1.852 1.769 0 1.097-.826 1.828-2.2 1.939V8.73l.348.086z" />
113
+ </svg>
114
+ </span>
115
+ <%= t("raif.admin.common.llms") %>
116
+ </a>
117
+ </li>
118
+ <li class="nav-item">
119
+ <a class="nav-link <%= params[:controller].start_with?("raif/admin/prompt_studio") ? "active" : "" %>" href="<%= raif.admin_prompt_studio_tasks_path %>">
120
+ <span class="d-inline-block me-2">
121
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-square" viewBox="0 0 16 16">
122
+ <path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z" />
123
+ <path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z" />
124
+ </svg>
125
+ </span>
126
+ <%= t("raif.admin.prompt_studio.common.prompt_studio") %>
127
+ </a>
128
+ </li>
99
129
  <li class="nav-item">
100
130
  <a class="nav-link <%= current_page?(raif.admin_config_path) ? "active" : "" %>" href="<%= raif.admin_config_path %>">
101
131
  <span class="d-inline-block me-2">
@@ -14,6 +14,7 @@
14
14
  <span class="badge bg-secondary"><%= t("raif.admin.common.pending") %></span>
15
15
  <% end %>
16
16
  </td>
17
+ <td><small class="text-muted"><%= agent.runtime_duration %></small></td>
17
18
  <td><%= agent.iteration_count %> / <%= agent.max_iterations %></td>
18
19
  <td>
19
20
  <% if agent.total_cost && agent.total_cost > 0 %>
@@ -2,6 +2,53 @@
2
2
 
3
3
  <div class="row">
4
4
  <div class="col-12">
5
+ <%= form_tag raif.admin_agents_path, method: :get, class: "mb-4" do %>
6
+ <div class="row align-items-end">
7
+ <div class="col-md-3">
8
+ <div class="form-group">
9
+ <label for="agent_type"><%= t("raif.admin.common.type") %></label>
10
+ <%= select_tag :agent_type,
11
+ options_for_select(
12
+ [[t("raif.admin.common.all"), "all"]] + @agent_types.map { |type| [type, type] },
13
+ @selected_type
14
+ ),
15
+ { class: "form-select", data: { controller: "raif--tom-select" } } %>
16
+ </div>
17
+ </div>
18
+ <div class="col-md-3">
19
+ <div class="form-group">
20
+ <label for="status"><%= t("raif.admin.common.status") %></label>
21
+ <%= select_tag :status,
22
+ options_for_select(
23
+ [
24
+ [t("raif.admin.common.all"), :all],
25
+ [t("raif.admin.common.completed"), :completed],
26
+ [t("raif.admin.common.failed"), :failed],
27
+ [t("raif.admin.common.running"), :running],
28
+ [t("raif.admin.common.pending"), :pending]
29
+ ],
30
+ @selected_status
31
+ ),
32
+ { class: "form-select", data: { controller: "raif--tom-select" } } %>
33
+ </div>
34
+ </div>
35
+ <div class="col-md-3">
36
+ <div class="form-group">
37
+ <label for="llm_model_key"><%= t("raif.admin.common.model") %></label>
38
+ <%= select_tag :llm_model_key,
39
+ options_for_select(
40
+ [[t("raif.admin.common.all"), ""]] + @llm_model_keys.map { |key| [key, key] },
41
+ @selected_llm_model_key
42
+ ),
43
+ { class: "form-select", data: { controller: "raif--tom-select" } } %>
44
+ </div>
45
+ </div>
46
+ <div class="col-md-2">
47
+ <%= submit_tag t("raif.admin.common.filter"), class: "btn btn-primary" %>
48
+ </div>
49
+ </div>
50
+ <% end %>
51
+
5
52
  <% if @agents.any? %>
6
53
  <div class="table-responsive">
7
54
  <table class="table table-striped table-hover">
@@ -12,6 +59,7 @@
12
59
  <th><%= t("raif.admin.common.created_at") %></th>
13
60
  <th><%= t("raif.admin.common.task") %></th>
14
61
  <th><%= t("raif.admin.common.status") %></th>
62
+ <th><%= t("raif.admin.common.duration") %></th>
15
63
  <th><%= t("raif.admin.common.iterations") %></th>
16
64
  <th><%= t("raif.admin.common.total_cost") %></th>
17
65
  <th><%= t("raif.admin.common.final_answer") %></th>
@@ -36,6 +36,10 @@
36
36
  <% end %>
37
37
  </div>
38
38
  </div>
39
+ <div class="row mb-3">
40
+ <div class="col-md-3"><strong><%= t("raif.admin.common.duration") %>:</strong></div>
41
+ <div class="col-md-9"><%= @agent.runtime_duration %></div>
42
+ </div>
39
43
  <div class="row mb-3">
40
44
  <div class="col-md-3"><strong><%= t("raif.admin.common.iterations") %>:</strong></div>
41
45
  <div class="col-md-9"><%= @agent.iteration_count %> / <%= @agent.max_iterations %></div>
@@ -0,0 +1,110 @@
1
+ <h1 class="my-4"><%= t("raif.admin.llms.index.title") %></h1>
2
+
3
+ <div class="row">
4
+ <div class="col-12">
5
+ <%= form_tag raif.admin_llms_path, method: :get, class: "mb-4" do %>
6
+ <div class="row align-items-end">
7
+ <div class="col-md-4">
8
+ <div class="form-group">
9
+ <label for="names"><%= t("raif.admin.llms.index.name") %></label>
10
+ <%= select_tag "names[]",
11
+ options_for_select(
12
+ @llm_names.map { |n| [n, n] },
13
+ @selected_names
14
+ ),
15
+ { id: "names", multiple: true, class: "form-select", data: { controller: "raif--tom-select" } } %>
16
+ </div>
17
+ </div>
18
+ <div class="col-md-4">
19
+ <div class="form-group">
20
+ <label for="providers"><%= t("raif.admin.llms.index.provider") %></label>
21
+ <%= select_tag "providers[]",
22
+ options_for_select(
23
+ @provider_names.map { |p| [p, p] },
24
+ @selected_providers
25
+ ),
26
+ { id: "providers", multiple: true, class: "form-select", data: { controller: "raif--tom-select" } } %>
27
+ </div>
28
+ </div>
29
+ <div class="col-md-2">
30
+ <%= submit_tag t("raif.admin.common.filter"), class: "btn btn-primary" %>
31
+ </div>
32
+ </div>
33
+ <% end %>
34
+
35
+ <div class="card" data-controller="raif--table-search raif--sortable-table">
36
+ <div class="card-header bg-light">
37
+ <input type="text"
38
+ class="form-control form-control-sm"
39
+ placeholder="<%= t("raif.admin.llms.index.search_placeholder") %>"
40
+ data-raif--table-search-target="input"
41
+ autocomplete="off"
42
+ data-action="input->raif--table-search#filter">
43
+ </div>
44
+ <div class="card-body">
45
+ <% if @llms.any? %>
46
+ <table class="table table-sm table-hover mb-0">
47
+ <thead>
48
+ <tr>
49
+ <th role="button" data-action="click->raif--sortable-table#sort" data-raif--sortable-table-target="header" data-col-index="0" data-sort-type="string" style="cursor: pointer;">
50
+ <%= t("raif.admin.llms.index.name") %>
51
+ </th>
52
+ <th role="button" data-action="click->raif--sortable-table#sort" data-raif--sortable-table-target="header" data-col-index="1" data-sort-type="string" style="cursor: pointer;">
53
+ <%= t("raif.admin.llms.index.provider") %>
54
+ </th>
55
+ <th role="button" data-action="click->raif--sortable-table#sort" data-raif--sortable-table-target="header" data-col-index="2" data-sort-type="string" style="cursor: pointer;">
56
+ <%= t("raif.admin.llms.index.api_name") %>
57
+ </th>
58
+ <th role="button" class="text-end" data-action="click->raif--sortable-table#sort" data-raif--sortable-table-target="header" data-col-index="3" data-sort-type="number" style="cursor: pointer;">
59
+ <%= t("raif.admin.llms.index.input_cost") %>
60
+ </th>
61
+ <th role="button" class="text-end" data-action="click->raif--sortable-table#sort" data-raif--sortable-table-target="header" data-col-index="4" data-sort-type="number" style="cursor: pointer;">
62
+ <%= t("raif.admin.llms.index.output_cost") %>
63
+ </th>
64
+ <th class="text-center"><%= t("raif.admin.common.default") %></th>
65
+ </tr>
66
+ </thead>
67
+ <tbody data-raif--sortable-table-target="body">
68
+ <% @llms.each do |llm| %>
69
+ <% input_cost = llm.input_token_cost.present? && llm.input_token_cost > 0 ? llm.input_token_cost * 1_000_000 : 0 %>
70
+ <% output_cost = llm.output_token_cost.present? && llm.output_token_cost > 0 ? llm.output_token_cost * 1_000_000 : 0 %>
71
+ <tr data-raif--table-search-target="row"
72
+ data-searchable="<%= "#{llm.name} #{llm.class.name.demodulize} #{llm.api_name}".downcase %>">
73
+ <td><%= llm.name %></td>
74
+ <td><code><%= llm.class.name.demodulize %></code></td>
75
+ <td><code><%= llm.api_name %></code></td>
76
+ <td class="text-end" data-sort-value="<%= input_cost %>">
77
+ <% if input_cost > 0 %>
78
+ $<%= format("%.2f", input_cost) %>
79
+ <% else %>
80
+ <span class="text-muted">-</span>
81
+ <% end %>
82
+ </td>
83
+ <td class="text-end" data-sort-value="<%= output_cost %>">
84
+ <% if output_cost > 0 %>
85
+ $<%= format("%.2f", output_cost) %>
86
+ <% else %>
87
+ <span class="text-muted">-</span>
88
+ <% end %>
89
+ </td>
90
+ <td class="text-center">
91
+ <% if llm.key.to_s == Raif.config.default_llm_model_key.to_s %>
92
+ <span class="badge bg-primary"><%= t("raif.admin.llms.index.default_badge") %></span>
93
+ <% end %>
94
+ </td>
95
+ </tr>
96
+ <% end %>
97
+ </tbody>
98
+ </table>
99
+ <p class="text-muted mt-3 mb-0">
100
+ <small><%= t("raif.admin.llms.index.cost_note") %></small>
101
+ </p>
102
+ <% else %>
103
+ <div class="alert alert-warning mb-0">
104
+ <%= t("raif.admin.llms.index.no_llms") %>
105
+ </div>
106
+ <% end %>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ </div>
@@ -13,14 +13,10 @@
13
13
  <span class="badge bg-secondary"><%= t("raif.admin.common.pending") %></span>
14
14
  <% end %>
15
15
  </td>
16
+ <td><small class="text-muted"><%= model_completion.runtime_duration %></small></td>
17
+ <td><%= model_completion.cache_read_input_tokens.to_i > 0 ? number_with_delimiter(model_completion.cache_read_input_tokens) : "-" %></td>
18
+ <td><%= model_completion.cache_creation_input_tokens.to_i > 0 ? number_with_delimiter(model_completion.cache_creation_input_tokens) : "-" %></td>
16
19
  <td><%= model_completion.total_tokens ? number_with_delimiter(model_completion.total_tokens) : "-" %></td>
17
20
  <td><%= model_completion.total_cost ? number_to_currency(model_completion.total_cost, precision: 6) : "-" %></td>
18
- <td>
19
- <% if model_completion.citations.present? %>
20
- <span class="badge bg-info"><%= model_completion.citations.length %></span>
21
- <% else %>
22
- <span class="text-muted">-</span>
23
- <% end %>
24
- </td>
25
21
  <td><small class="text-muted"><%= truncate(model_completion.raw_response, length: 100) %></small></td>
26
22
  </tr>
@@ -21,6 +21,17 @@
21
21
  { class: "form-select" } %>
22
22
  </div>
23
23
  </div>
24
+ <div class="col-md-4">
25
+ <div class="form-group">
26
+ <label for="llm_model_key"><%= t("raif.admin.common.model") %></label>
27
+ <%= select_tag :llm_model_key,
28
+ options_for_select(
29
+ [[t("raif.admin.common.all"), ""]] + @llm_model_keys.map { |key| [key, key] },
30
+ @selected_llm_model_key
31
+ ),
32
+ { class: "form-select", data: { controller: "raif--tom-select" } } %>
33
+ </div>
34
+ </div>
24
35
  <div class="col-md-2">
25
36
  <%= submit_tag t("raif.admin.common.filter"), class: "btn btn-primary" %>
26
37
  </div>
@@ -38,9 +49,11 @@
38
49
  <th><%= t("raif.admin.common.model") %></th>
39
50
  <th><%= t("raif.admin.common.response_format") %></th>
40
51
  <th><%= t("raif.admin.common.status") %></th>
52
+ <th><%= t("raif.admin.common.duration") %></th>
53
+ <th><%= t("raif.admin.common.cache_read_input_tokens") %></th>
54
+ <th><%= t("raif.admin.common.cache_creation_input_tokens") %></th>
41
55
  <th><%= t("raif.admin.common.total_tokens") %></th>
42
56
  <th><%= t("raif.admin.common.total_cost") %></th>
43
- <th><%= t("raif.admin.common.citations") %></th>
44
57
  <th><%= t("raif.admin.common.response") %></th>
45
58
  </tr>
46
59
  </thead>