raif 1.3.0 → 1.4.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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -5
  3. data/app/assets/builds/raif.css +4 -1
  4. data/app/assets/builds/raif_admin.css +13 -1
  5. data/app/assets/javascript/raif/controllers/conversations_controller.js +1 -1
  6. data/app/assets/stylesheets/raif/admin/conversation.scss +16 -0
  7. data/app/assets/stylesheets/raif/conversations.scss +3 -0
  8. data/app/assets/stylesheets/raif.scss +2 -1
  9. data/app/controllers/raif/admin/application_controller.rb +16 -0
  10. data/app/controllers/raif/admin/configs_controller.rb +94 -0
  11. data/app/controllers/raif/admin/model_completions_controller.rb +18 -1
  12. data/app/controllers/raif/admin/model_tool_invocations_controller.rb +7 -1
  13. data/app/controllers/raif/admin/stats/model_tool_invocations_controller.rb +21 -0
  14. data/app/controllers/raif/admin/stats/tasks_controller.rb +15 -6
  15. data/app/controllers/raif/admin/stats_controller.rb +32 -3
  16. data/app/controllers/raif/conversation_entries_controller.rb +1 -0
  17. data/app/controllers/raif/conversations_controller.rb +10 -2
  18. data/app/jobs/raif/conversation_entry_job.rb +8 -6
  19. data/app/models/raif/admin/task_stat.rb +7 -0
  20. data/app/models/raif/agent.rb +63 -2
  21. data/app/models/raif/agents/native_tool_calling_agent.rb +101 -56
  22. data/app/models/raif/application_record.rb +18 -0
  23. data/app/models/raif/concerns/agent_inference_stats.rb +35 -0
  24. data/app/models/raif/concerns/json_schema_definition.rb +40 -5
  25. data/app/models/raif/concerns/llms/anthropic/message_formatting.rb +28 -0
  26. data/app/models/raif/concerns/llms/anthropic/response_tool_calls.rb +24 -0
  27. data/app/models/raif/concerns/llms/anthropic/tool_formatting.rb +4 -0
  28. data/app/models/raif/concerns/llms/bedrock/message_formatting.rb +36 -0
  29. data/app/models/raif/concerns/llms/bedrock/response_tool_calls.rb +26 -0
  30. data/app/models/raif/concerns/llms/bedrock/tool_formatting.rb +4 -0
  31. data/app/models/raif/concerns/llms/google/message_formatting.rb +109 -0
  32. data/app/models/raif/concerns/llms/google/response_tool_calls.rb +32 -0
  33. data/app/models/raif/concerns/llms/google/tool_formatting.rb +72 -0
  34. data/app/models/raif/concerns/llms/message_formatting.rb +11 -5
  35. data/app/models/raif/concerns/llms/open_ai/json_schema_validation.rb +3 -3
  36. data/app/models/raif/concerns/llms/open_ai_completions/message_formatting.rb +22 -0
  37. data/app/models/raif/concerns/llms/open_ai_completions/response_tool_calls.rb +22 -0
  38. data/app/models/raif/concerns/llms/open_ai_completions/tool_formatting.rb +4 -0
  39. data/app/models/raif/concerns/llms/open_ai_responses/message_formatting.rb +17 -0
  40. data/app/models/raif/concerns/llms/open_ai_responses/response_tool_calls.rb +26 -0
  41. data/app/models/raif/concerns/llms/open_ai_responses/tool_formatting.rb +4 -0
  42. data/app/models/raif/concerns/run_with.rb +127 -0
  43. data/app/models/raif/conversation.rb +91 -8
  44. data/app/models/raif/conversation_entry.rb +32 -1
  45. data/app/models/raif/embedding_model.rb +2 -1
  46. data/app/models/raif/embedding_models/open_ai.rb +1 -1
  47. data/app/models/raif/llm.rb +27 -2
  48. data/app/models/raif/llms/anthropic.rb +7 -19
  49. data/app/models/raif/llms/bedrock.rb +6 -20
  50. data/app/models/raif/llms/google.rb +140 -0
  51. data/app/models/raif/llms/open_ai_base.rb +19 -5
  52. data/app/models/raif/llms/open_ai_completions.rb +6 -11
  53. data/app/models/raif/llms/open_ai_responses.rb +6 -16
  54. data/app/models/raif/llms/open_router.rb +7 -13
  55. data/app/models/raif/model_completion.rb +61 -0
  56. data/app/models/raif/model_tool.rb +10 -2
  57. data/app/models/raif/model_tool_invocation.rb +38 -6
  58. data/app/models/raif/model_tools/agent_final_answer.rb +2 -7
  59. data/app/models/raif/model_tools/provider_managed/code_execution.rb +4 -0
  60. data/app/models/raif/model_tools/provider_managed/image_generation.rb +4 -0
  61. data/app/models/raif/model_tools/provider_managed/web_search.rb +4 -0
  62. data/app/models/raif/streaming_responses/google.rb +71 -0
  63. data/app/models/raif/task.rb +55 -12
  64. data/app/models/raif/user_tool_invocation.rb +19 -0
  65. data/app/views/layouts/raif/admin.html.erb +12 -1
  66. data/app/views/raif/admin/agents/_agent.html.erb +8 -0
  67. data/app/views/raif/admin/agents/_conversation_message.html.erb +28 -6
  68. data/app/views/raif/admin/agents/index.html.erb +2 -0
  69. data/app/views/raif/admin/agents/show.html.erb +46 -1
  70. data/app/views/raif/admin/configs/show.html.erb +117 -0
  71. data/app/views/raif/admin/conversations/_conversation_entry.html.erb +29 -34
  72. data/app/views/raif/admin/conversations/show.html.erb +2 -0
  73. data/app/views/raif/admin/model_completions/_model_completion.html.erb +9 -0
  74. data/app/views/raif/admin/model_completions/index.html.erb +26 -0
  75. data/app/views/raif/admin/model_completions/show.html.erb +124 -61
  76. data/app/views/raif/admin/model_tool_invocations/index.html.erb +22 -1
  77. data/app/views/raif/admin/model_tools/_list.html.erb +16 -0
  78. data/app/views/raif/admin/model_tools/_model_tool.html.erb +36 -0
  79. data/app/views/raif/admin/stats/_stats_tile.html.erb +34 -0
  80. data/app/views/raif/admin/stats/index.html.erb +71 -88
  81. data/app/views/raif/admin/stats/model_tool_invocations/index.html.erb +43 -0
  82. data/app/views/raif/admin/stats/tasks/index.html.erb +20 -6
  83. data/app/views/raif/admin/tasks/index.html.erb +6 -1
  84. data/app/views/raif/admin/tasks/show.html.erb +36 -3
  85. data/app/views/raif/conversation_entries/_form.html.erb +3 -0
  86. data/app/views/raif/conversations/_conversation.html.erb +10 -0
  87. data/app/views/raif/conversations/_entry_processed.turbo_stream.erb +12 -0
  88. data/app/views/raif/conversations/index.html.erb +23 -0
  89. data/config/locales/admin.en.yml +33 -1
  90. data/config/locales/en.yml +33 -4
  91. data/config/routes.rb +2 -0
  92. data/db/migrate/20250904194456_add_generating_entry_response_to_raif_conversations.rb +7 -0
  93. data/db/migrate/20250911125234_add_source_to_raif_tasks.rb +7 -0
  94. data/db/migrate/20251020005853_add_source_to_raif_agents.rb +7 -0
  95. data/db/migrate/20251020011346_rename_task_run_args_to_run_with.rb +7 -0
  96. data/db/migrate/20251020011405_add_run_with_to_raif_agents.rb +13 -0
  97. data/db/migrate/20251024160119_add_llm_messages_max_length_to_raif_conversations.rb +14 -0
  98. data/db/migrate/20251124185033_add_provider_tool_call_id_to_raif_model_tool_invocations.rb +7 -0
  99. data/db/migrate/20251128202941_add_tool_choice_to_raif_model_completions.rb +7 -0
  100. data/db/migrate/20260118144846_add_source_to_raif_conversations.rb +7 -0
  101. data/db/migrate/20260119000000_add_failure_tracking_to_raif_model_completions.rb +10 -0
  102. data/db/migrate/20260119000001_add_completed_at_to_raif_model_completions.rb +8 -0
  103. data/db/migrate/20260119000002_add_started_at_to_raif_model_completions.rb +8 -0
  104. data/lib/generators/raif/agent/templates/agent.rb.tt +1 -1
  105. data/lib/generators/raif/agent/templates/application_agent.rb.tt +1 -1
  106. data/lib/generators/raif/conversation/templates/conversation.rb.tt +6 -0
  107. data/lib/generators/raif/install/templates/initializer.rb +78 -10
  108. data/lib/generators/raif/task/templates/task.rb.tt +1 -1
  109. data/lib/raif/configuration.rb +37 -2
  110. data/lib/raif/engine.rb +8 -0
  111. data/lib/raif/errors/instance_dependent_schema_error.rb +8 -0
  112. data/lib/raif/errors/streaming_error.rb +6 -3
  113. data/lib/raif/errors.rb +1 -0
  114. data/lib/raif/evals/llm_judge.rb +2 -2
  115. data/lib/raif/evals/llm_judges/binary.rb +3 -3
  116. data/lib/raif/evals/llm_judges/comparative.rb +3 -3
  117. data/lib/raif/evals/llm_judges/scored.rb +1 -1
  118. data/lib/raif/evals/llm_judges/summarization.rb +2 -2
  119. data/lib/raif/evals/run.rb +1 -0
  120. data/lib/raif/json_schema_builder.rb +14 -0
  121. data/lib/raif/llm_registry.rb +207 -37
  122. data/lib/raif/messages.rb +180 -0
  123. data/lib/raif/version.rb +1 -1
  124. data/lib/raif.rb +9 -0
  125. data/lib/tasks/annotate_rb.rake +10 -0
  126. data/spec/support/rspec_helpers.rb +8 -8
  127. metadata +44 -9
  128. data/app/models/raif/agents/re_act_agent.rb +0 -127
  129. data/app/models/raif/agents/re_act_step.rb +0 -32
  130. data/app/models/raif/concerns/task_run_args.rb +0 -62
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Raif::StreamingResponses::Google
4
+
5
+ def initialize
6
+ @response_json = { "candidates" => [{ "content" => { "parts" => [] } }], "usageMetadata" => {} }
7
+ @finish_reason = nil
8
+ end
9
+
10
+ def process_streaming_event(event_type, event)
11
+ delta = nil
12
+
13
+ # Google streams complete candidate objects, so we need to extract the new text
14
+ candidates = event["candidates"]
15
+ if candidates.present?
16
+ candidate = candidates[0]
17
+
18
+ # Check for finish reason
19
+ @finish_reason = candidate["finishReason"] if candidate["finishReason"].present?
20
+
21
+ # Process content parts
22
+ parts = candidate.dig("content", "parts")
23
+ delta = process_content_parts(parts) if parts.present?
24
+ end
25
+
26
+ # Update usage metadata
27
+ usage_metadata = event["usageMetadata"]
28
+ @response_json["usageMetadata"] = usage_metadata if usage_metadata.present?
29
+
30
+ [delta, @finish_reason]
31
+ end
32
+
33
+ def current_response_json
34
+ @response_json
35
+ end
36
+
37
+ private
38
+
39
+ def process_content_parts(parts)
40
+ delta = nil
41
+
42
+ parts.each_with_index do |part, index|
43
+ if part.key?("text")
44
+ delta = part["text"]
45
+ accumulate_text_part(part, index)
46
+ else
47
+ # For non-text parts (e.g., functionCall), just store directly.
48
+ # Note: This works because we don't enable streamFunctionCallArguments in the API request.
49
+ # Without that opt-in flag, function calls arrive complete in a single chunk.
50
+ # If streaming function call arguments is enabled in the future (Gemini 3+ models),
51
+ # this would need to accumulate partialArgs similar to how we accumulate text.
52
+ @response_json["candidates"][0]["content"]["parts"][index] = part
53
+ end
54
+ end
55
+
56
+ delta
57
+ end
58
+
59
+ def accumulate_text_part(part, index)
60
+ existing_part = @response_json.dig("candidates", 0, "content", "parts", index)
61
+
62
+ if existing_part.present? && existing_part.key?("text")
63
+ # Accumulate text from incremental chunks
64
+ existing_part["text"] += part["text"]
65
+ else
66
+ # First text chunk for this index
67
+ @response_json["candidates"][0]["content"]["parts"][index] = part.dup
68
+ end
69
+ end
70
+
71
+ end
@@ -1,5 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # == Schema Information
4
+ #
5
+ # Table name: raif_tasks
6
+ #
7
+ # id :bigint not null, primary key
8
+ # available_model_tools :jsonb not null
9
+ # completed_at :datetime
10
+ # creator_type :string
11
+ # failed_at :datetime
12
+ # llm_model_key :string not null
13
+ # prompt :text
14
+ # raw_response :text
15
+ # requested_language_key :string
16
+ # response_format :integer default("text"), not null
17
+ # run_with :jsonb
18
+ # source_type :string
19
+ # started_at :datetime
20
+ # system_prompt :text
21
+ # type :string not null
22
+ # created_at :datetime not null
23
+ # updated_at :datetime not null
24
+ # creator_id :bigint
25
+ # source_id :bigint
26
+ #
27
+ # Indexes
28
+ #
29
+ # index_raif_tasks_on_completed_at (completed_at)
30
+ # index_raif_tasks_on_created_at (created_at)
31
+ # index_raif_tasks_on_creator (creator_type,creator_id)
32
+ # index_raif_tasks_on_failed_at (failed_at)
33
+ # index_raif_tasks_on_source (source_type,source_id)
34
+ # index_raif_tasks_on_started_at (started_at)
35
+ # index_raif_tasks_on_type (type)
36
+ # index_raif_tasks_on_type_and_completed_at (type,completed_at)
37
+ # index_raif_tasks_on_type_and_failed_at (type,failed_at)
38
+ # index_raif_tasks_on_type_and_started_at (type,started_at)
39
+ #
3
40
  module Raif
4
41
  class Task < Raif::ApplicationRecord
5
42
  include Raif::Concerns::HasLlm
@@ -9,11 +46,12 @@ module Raif
9
46
  include Raif::Concerns::LlmResponseParsing
10
47
  include Raif::Concerns::LlmTemperature
11
48
  include Raif::Concerns::JsonSchemaDefinition
12
- include Raif::Concerns::TaskRunArgs
49
+ include Raif::Concerns::RunWith
13
50
 
14
51
  llm_temperature 0.7
15
52
 
16
53
  belongs_to :creator, polymorphic: true, optional: true
54
+ belongs_to :source, polymorphic: true, optional: true
17
55
 
18
56
  validates :creator, presence: true, unless: -> { Raif.config.task_creator_optional }
19
57
 
@@ -25,8 +63,6 @@ module Raif
25
63
 
26
64
  normalizes :prompt, :system_prompt, with: ->(text){ text&.strip }
27
65
 
28
- delegate :json_response_schema, to: :class
29
-
30
66
  scope :completed, -> { where.not(completed_at: nil) }
31
67
  scope :failed, -> { where.not(failed_at: nil) }
32
68
  scope :in_progress, -> { where.not(started_at: nil).where(completed_at: nil, failed_at: nil) }
@@ -35,7 +71,7 @@ module Raif
35
71
  attr_accessor :files, :images
36
72
 
37
73
  after_initialize -> { self.available_model_tools ||= [] }
38
- after_initialize -> { self.task_run_args ||= {} }
74
+ after_initialize -> { self.run_with ||= {} }
39
75
 
40
76
  def status
41
77
  if completed_at?
@@ -67,7 +103,6 @@ module Raif
67
103
  started_at: Time.current,
68
104
  images: images,
69
105
  files: files,
70
- task_run_args: serialize_task_run_args(args),
71
106
  **args
72
107
  )
73
108
 
@@ -95,7 +130,6 @@ module Raif
95
130
  update_columns(started_at: Time.current) if started_at.nil?
96
131
 
97
132
  populate_prompts unless skip_prompt_population
98
- messages = [{ "role" => "user", "content" => message_content }]
99
133
 
100
134
  mc = llm.chat(
101
135
  messages: messages,
@@ -120,6 +154,10 @@ module Raif
120
154
  run(skip_prompt_population: true)
121
155
  end
122
156
 
157
+ def messages
158
+ [{ "role" => "user", "content" => message_content }]
159
+ end
160
+
123
161
  # Returns the LLM prompt for the task.
124
162
  #
125
163
  # @param creator [Object, nil] The creator of the task (polymorphic association), optional
@@ -146,6 +184,13 @@ module Raif
146
184
  end
147
185
  end
148
186
 
187
+ # Instance method to get the JSON response schema
188
+ # For instance-dependent schemas, builds the schema with this instance as context
189
+ # For class-level schemas, returns the class-level schema
190
+ def json_response_schema
191
+ schema_for_instance(:json_response)
192
+ end
193
+
149
194
  def build_prompt
150
195
  raise NotImplementedError, "Raif::Task subclasses must implement #build_prompt"
151
196
  end
@@ -189,15 +234,13 @@ module Raif
189
234
  end
190
235
 
191
236
  def process_model_tool_invocations
192
- return unless response_format_json?
193
- return unless parsed_response.is_a?(Hash)
194
- return unless parsed_response["tools"].present? && parsed_response["tools"].is_a?(Array)
237
+ return unless raif_model_completion&.response_tool_calls.present?
195
238
 
196
- parsed_response["tools"].each do |t|
197
- tool_klass = available_model_tools_map[t["name"]]
239
+ raif_model_completion.response_tool_calls.each do |tool_call|
240
+ tool_klass = available_model_tools_map[tool_call["name"]]
198
241
  next unless tool_klass
199
242
 
200
- tool_klass.invoke_tool(tool_arguments: t["arguments"], source: self)
243
+ tool_klass.invoke_tool(provider_tool_call_id: tool_call["provider_tool_call_id"], tool_arguments: tool_call["arguments"], source: self)
201
244
  end
202
245
  end
203
246
 
@@ -1,5 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # == Schema Information
4
+ #
5
+ # Table name: raif_user_tool_invocations
6
+ #
7
+ # id :bigint not null, primary key
8
+ # tool_settings :jsonb not null
9
+ # type :string not null
10
+ # created_at :datetime not null
11
+ # updated_at :datetime not null
12
+ # raif_conversation_entry_id :bigint not null
13
+ #
14
+ # Indexes
15
+ #
16
+ # index_raif_user_tool_invocations_on_raif_conversation_entry_id (raif_conversation_entry_id)
17
+ #
18
+ # Foreign Keys
19
+ #
20
+ # fk_rails_... (raif_conversation_entry_id => raif_conversation_entries.id)
21
+ #
3
22
  class Raif::UserToolInvocation < Raif::ApplicationRecord
4
23
  belongs_to :raif_conversation_entry, class_name: "Raif::ConversationEntry"
5
24
 
@@ -35,7 +35,7 @@
35
35
  <div class="position-sticky pt-3">
36
36
  <ul class="nav flex-column">
37
37
  <li class="nav-item">
38
- <a class="nav-link <%= current_page?(raif.admin_stats_path) ? "active" : "" %>" href="<%= raif.admin_stats_path %>">
38
+ <a class="nav-link <%= params[:controller].start_with?("raif/admin/stats") ? "active" : "" %>" href="<%= raif.admin_stats_path %>">
39
39
  <span class="d-inline-block me-2">
40
40
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-bar-chart" viewBox="0 0 16 16">
41
41
  <path d="M4 11H2v3h2v-3zm5-4H7v7h2V7zm5-5v12h-2V2h2zm-2-1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1h-2zM6 7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7zm-5 4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-3z" />
@@ -96,6 +96,17 @@
96
96
  <%= t("raif.admin.common.agents") %>
97
97
  </a>
98
98
  </li>
99
+ <li class="nav-item">
100
+ <a class="nav-link <%= current_page?(raif.admin_config_path) ? "active" : "" %>" href="<%= raif.admin_config_path %>">
101
+ <span class="d-inline-block me-2">
102
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-gear" viewBox="0 0 16 16">
103
+ <path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492zM5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0z" />
104
+ <path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52l-.094-.319z" />
105
+ </svg>
106
+ </span>
107
+ <%= t("raif.admin.common.config") %>
108
+ </a>
109
+ </li>
99
110
  </ul>
100
111
  </div>
101
112
  </div>
@@ -1,5 +1,6 @@
1
1
  <tr id="<%= dom_id(agent) %>" class="raif-agent">
2
2
  <td><%= link_to "##{agent.id}", raif.admin_agent_path(agent) %></td>
3
+ <td><small class="text-muted"><%= agent.type.demodulize %></small></td>
3
4
  <td><small class="text-muted"><%= agent.created_at.rfc822 %></small></td>
4
5
  <td><small class="text-muted"><%= truncate(agent.task, length: 100) %></small></td>
5
6
  <td>
@@ -14,5 +15,12 @@
14
15
  <% end %>
15
16
  </td>
16
17
  <td><%= agent.iteration_count %> / <%= agent.max_iterations %></td>
18
+ <td>
19
+ <% if agent.total_cost && agent.total_cost > 0 %>
20
+ <small><%= "$" %><%= number_with_precision(agent.total_cost, precision: 6) %></small>
21
+ <% else %>
22
+ -
23
+ <% end %>
24
+ </td>
17
25
  <td><small class="text-muted"><%= truncate(agent.final_answer, length: 100) if agent.final_answer %></small></td>
18
26
  </tr>
@@ -1,15 +1,37 @@
1
- <div class="message <%= message["role"] %> mb-3">
1
+ <% message_type = message["type"] || message["role"] %>
2
+ <div class="message <%= message_type %> mb-3">
2
3
  <div class="message-header d-flex justify-content-between align-items-center mb-2 px-2">
3
- <strong class="<%= message["role"] == "user" ? "text-primary" : "text-success" %>">
4
- <%= message["role"].capitalize %>
5
- <% if message[:counter] == 0 && message["role"] == 'user' %>
6
- (<%= t("raif.admin.common.initial_task") %>)
4
+ <strong class="<%= conversation_message_header_class(message) %>">
5
+ <% if message_type == "tool_call" %>
6
+ <%= t("raif.admin.common.tool_call") %>: <%= message["name"] %>
7
+ <% elsif message_type == "tool_call_result" %>
8
+ <%= t("raif.admin.common.tool_result") %>
9
+ <% else %>
10
+ <%= message["role"]&.capitalize %>
11
+ <% if message[:counter] == 0 && message["role"] == 'user' %>
12
+ (<%= t("raif.admin.common.initial_task") %>)
13
+ <% end %>
14
+ <% end %>
15
+ <% if is_final_answer %>
16
+ <span class="badge bg-info ms-2"><%= t("raif.admin.common.final_answer") %></span>
7
17
  <% end %>
8
18
  </strong>
9
19
  <span class="badge bg-secondary">#<%= message_count %></span>
10
20
  </div>
11
21
 
12
22
  <div class="message-content px-3 py-2">
13
- <code><%= message["content"] %></code>
23
+ <% if message_type == "tool_call" %>
24
+ <% if message["assistant_message"].present? %>
25
+ <div class="mb-2"><code><%= message["assistant_message"] %></code></div>
26
+ <% end %>
27
+ <div>
28
+ <strong><%= t("raif.admin.common.arguments") %>:</strong>
29
+ <code><%= message["arguments"].to_json %></code>
30
+ </div>
31
+ <% elsif message_type == "tool_call_result" %>
32
+ <code><%= message["result"].is_a?(String) ? message["result"] : JSON.pretty_generate(message["result"]) %></code>
33
+ <% else %>
34
+ <code><%= message["content"] %></code>
35
+ <% end %>
14
36
  </div>
15
37
  </div>
@@ -8,10 +8,12 @@
8
8
  <thead class="table-light">
9
9
  <tr>
10
10
  <th><%= t("raif.admin.common.id") %></th>
11
+ <th><%= t("raif.admin.common.type") %></th>
11
12
  <th><%= t("raif.admin.common.created_at") %></th>
12
13
  <th><%= t("raif.admin.common.task") %></th>
13
14
  <th><%= t("raif.admin.common.status") %></th>
14
15
  <th><%= t("raif.admin.common.iterations") %></th>
16
+ <th><%= t("raif.admin.common.total_cost") %></th>
15
17
  <th><%= t("raif.admin.common.final_answer") %></th>
16
18
  </tr>
17
19
  </thead>
@@ -50,9 +50,50 @@
50
50
  <div class="col-md-9"><%= @agent.requested_language_key %></div>
51
51
  </div>
52
52
  <% end %>
53
+ <div class="row mb-3">
54
+ <div class="col-md-3"><strong><%= t("raif.admin.common.prompt_tokens") %>:</strong></div>
55
+ <div class="col-md-9">
56
+ <% if @agent.total_prompt_tokens > 0 %>
57
+ <%= number_with_delimiter(@agent.total_prompt_tokens) %>
58
+ <% if @agent.total_prompt_token_cost > 0 %>
59
+ <small>(<%= t("raif.admin.common.est_cost") %>: <%= "$" %><%= number_with_precision(@agent.total_prompt_token_cost, precision: 6) %>)</small>
60
+ <% end %>
61
+ <% else %>
62
+ -
63
+ <% end %>
64
+ </div>
65
+ </div>
66
+ <div class="row mb-3">
67
+ <div class="col-md-3"><strong><%= t("raif.admin.common.completion_tokens") %>:</strong></div>
68
+ <div class="col-md-9">
69
+ <% if @agent.total_completion_tokens > 0 %>
70
+ <%= number_with_delimiter(@agent.total_completion_tokens) %>
71
+ <% if @agent.total_output_token_cost > 0 %>
72
+ <small>(<%= t("raif.admin.common.est_cost") %>: <%= "$" %><%= number_with_precision(@agent.total_output_token_cost, precision: 6) %>)</small>
73
+ <% end %>
74
+ <% else %>
75
+ -
76
+ <% end %>
77
+ </div>
78
+ </div>
79
+ <div class="row mb-3">
80
+ <div class="col-md-3"><strong><%= t("raif.admin.common.total_tokens") %>:</strong></div>
81
+ <div class="col-md-9">
82
+ <% if @agent.total_tokens_sum > 0 %>
83
+ <%= number_with_delimiter(@agent.total_tokens_sum) %>
84
+ <% if @agent.total_cost > 0 %>
85
+ <small>(<%= t("raif.admin.common.est_cost") %>: <%= "$" %><%= number_with_precision(@agent.total_cost, precision: 6) %>)</small>
86
+ <% end %>
87
+ <% else %>
88
+ -
89
+ <% end %>
90
+ </div>
91
+ </div>
53
92
  </div>
54
93
  </div>
55
94
 
95
+ <%= render "raif/admin/model_tools/list", model_tools: @agent.available_model_tools_map %>
96
+
56
97
  <div class="card mb-4">
57
98
  <div class="card-header">
58
99
  <h5 class="mb-0"><%= t("raif.admin.common.task") %></h5>
@@ -91,7 +132,11 @@
91
132
  <div class="conversation-history p-3">
92
133
  <% @agent.conversation_history.each_with_index do |message, index| %>
93
134
  <%= render partial: "raif/admin/agents/conversation_message",
94
- locals: { message: message, message_count: index + 1 } %>
135
+ locals: {
136
+ message: message,
137
+ message_count: index + 1,
138
+ is_final_answer: message["type"] == "tool_call" && message["name"] == "agent_final_answer"
139
+ } %>
95
140
  <% end %>
96
141
  </div>
97
142
  </div>
@@ -0,0 +1,117 @@
1
+ <h1 class="my-4"><%= t("raif.admin.config.show.title") %></h1>
2
+
3
+ <div class="row">
4
+ <div class="col-12">
5
+ <div class="card mb-4">
6
+ <div class="card-header bg-light">
7
+ <h5 class="mb-0"><%= t("raif.admin.config.show.configuration_settings") %></h5>
8
+ </div>
9
+ <div class="card-body">
10
+ <table class="table table-sm table-hover mb-0">
11
+ <thead>
12
+ <tr>
13
+ <th style="width: 40%"><%= t("raif.admin.config.show.setting") %></th>
14
+ <th style="width: 60%"><%= t("raif.admin.config.show.value") %></th>
15
+ </tr>
16
+ </thead>
17
+ <tbody>
18
+ <% @config_settings.each do |item| %>
19
+ <tr>
20
+ <td class="font-monospace"><%= item[:key] %></td>
21
+ <td>
22
+ <% if item[:value].is_a?(TrueClass) || item[:value].is_a?(FalseClass) %>
23
+ <span class="badge <%= item[:value] ? "bg-success" : "bg-secondary" %>">
24
+ <%= item[:value] %>
25
+ </span>
26
+ <% elsif item[:value].to_s.include?("*") %>
27
+ <code class="text-muted"><%= item[:value] %></code>
28
+ <% elsif item[:value].is_a?(String) && item[:value].length > 80 %>
29
+ <small class="text-muted"><%= item[:value] %></small>
30
+ <% elsif item[:value].present? %>
31
+ <code><%= item[:value] %></code>
32
+ <% else %>
33
+ <span class="text-muted fst-italic"><%= t("raif.admin.config.show.not_set") %></span>
34
+ <% end %>
35
+ </td>
36
+ </tr>
37
+ <% end %>
38
+ </tbody>
39
+ </table>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </div>
44
+
45
+ <div class="row mt-4">
46
+ <div class="col-12">
47
+ <div class="card">
48
+ <div class="card-header bg-light">
49
+ <h5 class="mb-0"><%= t("raif.admin.config.show.registered_llms") %></h5>
50
+ </div>
51
+ <div class="card-body">
52
+ <% if Raif.llm_registry.any? %>
53
+ <table class="table table-sm table-hover mb-0">
54
+ <thead>
55
+ <tr>
56
+ <th><%= t("raif.admin.config.show.key") %></th>
57
+ </tr>
58
+ </thead>
59
+ <tbody>
60
+ <% Raif.llm_registry.each do |key, _llm_config| %>
61
+ <tr>
62
+ <td>
63
+ <code><%= key %></code>
64
+ <% if key.to_s == @config.default_llm_model_key.to_s %>
65
+ <span class="badge bg-primary ms-2"><%= t("raif.admin.config.show.default") %></span>
66
+ <% end %>
67
+ </td>
68
+ </tr>
69
+ <% end %>
70
+ </tbody>
71
+ </table>
72
+ <% else %>
73
+ <div class="alert alert-warning mb-0">
74
+ <%= t("raif.admin.config.show.no_llms_registered") %>
75
+ </div>
76
+ <% end %>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+
82
+ <div class="row mt-4">
83
+ <div class="col-12">
84
+ <div class="card">
85
+ <div class="card-header bg-light">
86
+ <h5 class="mb-0"><%= t("raif.admin.config.show.registered_embedding_models") %></h5>
87
+ </div>
88
+ <div class="card-body">
89
+ <% if Raif.embedding_model_registry.present? %>
90
+ <table class="table table-sm table-hover mb-0">
91
+ <thead>
92
+ <tr>
93
+ <th><%= t("raif.admin.config.show.key") %></th>
94
+ </tr>
95
+ </thead>
96
+ <tbody>
97
+ <% Raif.embedding_model_registry.each do |key, _model_config| %>
98
+ <tr>
99
+ <td>
100
+ <code><%= key %></code>
101
+ <% if key.to_s == @config.default_embedding_model_key.to_s %>
102
+ <span class="badge bg-primary ms-2"><%= t("raif.admin.config.show.default") %></span>
103
+ <% end %>
104
+ </td>
105
+ </tr>
106
+ <% end %>
107
+ </tbody>
108
+ </table>
109
+ <% else %>
110
+ <div class="alert alert-info mb-0">
111
+ <%= t("raif.admin.config.show.no_embedding_models_registered") %>
112
+ </div>
113
+ <% end %>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ </div>
@@ -36,45 +36,40 @@
36
36
  <% if entry.raif_model_tool_invocations.any? %>
37
37
  <div class="mb-3">
38
38
  <strong><%= t("raif.admin.common.model_tool_invocations") %>:</strong>
39
- <div class="ms-3 mt-2">
39
+ <div class="conversation-history ms-3 mt-2">
40
40
  <% entry.raif_model_tool_invocations.each do |invocation| %>
41
- <div class="card mb-2">
42
- <div class="card-body p-3">
43
- <div class="d-flex w-100 justify-content-between align-items-start">
44
- <div>
45
- <h6 class="mb-1">
46
- <%= link_to invocation.tool_name, raif.admin_model_tool_invocation_path(invocation) %>
47
- </h6>
48
- <% if invocation.completed_at? %>
49
- <span class="badge bg-success"><%= t("raif.admin.common.completed") %></span>
50
- <% elsif invocation.failed_at? %>
51
- <span class="badge bg-danger"><%= t("raif.admin.common.failed") %></span>
52
- <% else %>
53
- <span class="badge bg-secondary"><%= t("raif.admin.common.pending") %></span>
54
- <% end %>
55
- </div>
56
- <small class="text-muted"><%= invocation.created_at.rfc822 %></small>
57
- </div>
58
- <div class="mt-2">
41
+ <div class="message tool_call mb-3">
42
+ <div class="message-header d-flex justify-content-between align-items-center mb-2 px-2">
43
+ <strong class="text-warning">
44
+ <%= t("raif.admin.common.tool_call") %>: <%= link_to invocation.tool_name, raif.admin_model_tool_invocation_path(invocation) %>
45
+ <% if invocation.completed_at? %>
46
+ <span class="badge bg-success ms-2"><%= t("raif.admin.common.completed") %></span>
47
+ <% elsif invocation.failed_at? %>
48
+ <span class="badge bg-danger ms-2"><%= t("raif.admin.common.failed") %></span>
49
+ <% else %>
50
+ <span class="badge bg-secondary ms-2"><%= t("raif.admin.common.pending") %></span>
51
+ <% end %>
52
+ </strong>
53
+ <small class="text-muted"><%= invocation.created_at.rfc822 %></small>
54
+ </div>
55
+ <div class="message-content px-3 py-2">
56
+ <div>
59
57
  <strong><%= t("raif.admin.common.arguments") %>:</strong>
60
- <pre class="pre-wrap mt-1"><%= begin
61
- JSON.pretty_generate(invocation.tool_arguments)
62
- rescue StandardError
63
- invocation.tool_arguments
64
- end %></pre>
58
+ <code><%= invocation.tool_arguments.to_json %></code>
65
59
  </div>
66
- <% if invocation.result.present? %>
67
- <div class="mt-2">
68
- <strong><%= t("raif.admin.common.result") %>:</strong>
69
- <pre class="pre-wrap mt-1"><%= begin
70
- JSON.pretty_generate(invocation.result)
71
- rescue StandardError
72
- invocation.result
73
- end %></pre>
74
- </div>
75
- <% end %>
76
60
  </div>
77
61
  </div>
62
+
63
+ <% if invocation.result.present? %>
64
+ <div class="message tool_call_result mb-3">
65
+ <div class="message-header d-flex justify-content-between align-items-center mb-2 px-2">
66
+ <strong class="text-success"><%= t("raif.admin.common.tool_result") %></strong>
67
+ </div>
68
+ <div class="message-content px-3 py-2">
69
+ <code><%= invocation.result.is_a?(String) ? invocation.result : JSON.pretty_generate(invocation.result) %></code>
70
+ </div>
71
+ </div>
72
+ <% end %>
78
73
  <% end %>
79
74
  </div>
80
75
  </div>
@@ -33,6 +33,8 @@
33
33
  </div>
34
34
  </div>
35
35
 
36
+ <%= render "raif/admin/model_tools/list", model_tools: @conversation.available_model_tools_map %>
37
+
36
38
  <div class="card mb-4">
37
39
  <div class="card-header">
38
40
  <h5 class="mb-0"><%= t("raif.admin.common.system_prompt") %></h5>
@@ -4,6 +4,15 @@
4
4
  <td><%= model_completion.source_type %> #<%= model_completion.source_id %></td>
5
5
  <td><%= model_completion.llm_model_key %></td>
6
6
  <td><%= model_completion.response_format %></td>
7
+ <td>
8
+ <% if model_completion.failed? %>
9
+ <span class="badge bg-danger"><%= t("raif.admin.common.failed") %></span>
10
+ <% elsif model_completion.completed? %>
11
+ <span class="badge bg-success"><%= t("raif.admin.common.completed") %></span>
12
+ <% else %>
13
+ <span class="badge bg-secondary"><%= t("raif.admin.common.pending") %></span>
14
+ <% end %>
15
+ </td>
7
16
  <td><%= model_completion.total_tokens ? number_with_delimiter(model_completion.total_tokens) : "-" %></td>
8
17
  <td><%= model_completion.total_cost ? number_to_currency(model_completion.total_cost, precision: 6) : "-" %></td>
9
18
  <td>