raif 1.0.0 → 1.1.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 +200 -41
- data/app/assets/stylesheets/raif/admin/stats.scss +12 -0
- data/app/controllers/raif/admin/application_controller.rb +14 -0
- data/app/controllers/raif/admin/stats/tasks_controller.rb +25 -0
- data/app/controllers/raif/admin/stats_controller.rb +19 -0
- data/app/controllers/raif/admin/tasks_controller.rb +18 -2
- data/app/controllers/raif/conversations_controller.rb +5 -1
- data/app/models/raif/agent.rb +11 -9
- data/app/models/raif/agents/native_tool_calling_agent.rb +11 -1
- data/app/models/raif/agents/re_act_agent.rb +6 -0
- data/app/models/raif/concerns/has_available_model_tools.rb +1 -1
- data/app/models/raif/concerns/json_schema_definition.rb +28 -0
- data/app/models/raif/concerns/llm_response_parsing.rb +23 -1
- data/app/models/raif/concerns/llm_temperature.rb +17 -0
- data/app/models/raif/concerns/llms/anthropic/message_formatting.rb +51 -0
- data/app/models/raif/concerns/llms/bedrock_claude/message_formatting.rb +70 -0
- data/app/models/raif/concerns/llms/message_formatting.rb +41 -0
- data/app/models/raif/concerns/llms/open_ai/message_formatting.rb +41 -0
- data/app/models/raif/conversation.rb +11 -3
- data/app/models/raif/conversation_entry.rb +22 -6
- data/app/models/raif/embedding_model.rb +22 -0
- data/app/models/raif/embedding_models/bedrock_titan.rb +34 -0
- data/app/models/raif/embedding_models/open_ai.rb +40 -0
- data/app/models/raif/llm.rb +39 -6
- data/app/models/raif/llms/anthropic.rb +23 -28
- data/app/models/raif/llms/bedrock_claude.rb +33 -19
- data/app/models/raif/llms/open_ai.rb +14 -17
- data/app/models/raif/llms/open_router.rb +93 -0
- data/app/models/raif/model_completion.rb +21 -2
- data/app/models/raif/model_file_input.rb +113 -0
- data/app/models/raif/model_image_input.rb +4 -0
- data/app/models/raif/model_tool.rb +77 -51
- data/app/models/raif/model_tool_invocation.rb +8 -6
- data/app/models/raif/model_tools/agent_final_answer.rb +18 -27
- data/app/models/raif/model_tools/fetch_url.rb +27 -36
- data/app/models/raif/model_tools/wikipedia_search.rb +46 -55
- data/app/models/raif/task.rb +71 -16
- data/app/views/layouts/raif/admin.html.erb +10 -0
- data/app/views/raif/admin/agents/show.html.erb +3 -1
- data/app/views/raif/admin/conversations/_conversation.html.erb +1 -1
- data/app/views/raif/admin/conversations/show.html.erb +3 -1
- data/app/views/raif/admin/model_completions/_model_completion.html.erb +1 -0
- data/app/views/raif/admin/model_completions/index.html.erb +1 -0
- data/app/views/raif/admin/model_completions/show.html.erb +30 -3
- data/app/views/raif/admin/stats/index.html.erb +128 -0
- data/app/views/raif/admin/stats/tasks/index.html.erb +45 -0
- data/app/views/raif/admin/tasks/_task.html.erb +5 -4
- data/app/views/raif/admin/tasks/index.html.erb +20 -2
- data/app/views/raif/admin/tasks/show.html.erb +3 -1
- data/app/views/raif/conversation_entries/_conversation_entry.html.erb +18 -14
- data/app/views/raif/conversation_entries/_form.html.erb +1 -1
- data/app/views/raif/conversation_entries/_form_with_available_tools.html.erb +4 -4
- data/app/views/raif/conversation_entries/_message.html.erb +10 -3
- data/config/locales/admin.en.yml +14 -0
- data/config/locales/en.yml +25 -3
- data/config/routes.rb +6 -0
- data/db/migrate/20250421202149_add_response_format_to_raif_conversations.rb +7 -0
- data/db/migrate/20250424200755_add_cost_columns_to_raif_model_completions.rb +14 -0
- data/db/migrate/20250424232946_add_created_at_indexes.rb +11 -0
- data/db/migrate/20250502155330_add_status_indexes_to_raif_tasks.rb +14 -0
- data/db/migrate/20250507155314_add_retry_count_to_raif_model_completions.rb +7 -0
- data/lib/generators/raif/agent/agent_generator.rb +22 -12
- data/lib/generators/raif/agent/templates/agent.rb.tt +3 -3
- data/lib/generators/raif/agent/templates/application_agent.rb.tt +7 -0
- data/lib/generators/raif/conversation/conversation_generator.rb +10 -0
- data/lib/generators/raif/conversation/templates/application_conversation.rb.tt +7 -0
- data/lib/generators/raif/conversation/templates/conversation.rb.tt +13 -11
- data/lib/generators/raif/install/templates/initializer.rb +50 -6
- data/lib/generators/raif/model_tool/model_tool_generator.rb +0 -5
- data/lib/generators/raif/model_tool/templates/model_tool.rb.tt +69 -56
- data/lib/generators/raif/task/templates/task.rb.tt +34 -23
- data/lib/raif/configuration.rb +40 -3
- data/lib/raif/embedding_model_registry.rb +83 -0
- data/lib/raif/engine.rb +34 -1
- data/lib/raif/errors/{open_ai/api_error.rb → invalid_model_file_input_error.rb} +1 -3
- data/lib/raif/errors/{anthropic/api_error.rb → invalid_model_image_input_error.rb} +1 -3
- data/lib/raif/errors/unsupported_feature_error.rb +8 -0
- data/lib/raif/errors.rb +3 -2
- data/lib/raif/json_schema_builder.rb +104 -0
- data/lib/raif/llm_registry.rb +205 -0
- data/lib/raif/version.rb +1 -1
- data/lib/raif.rb +5 -32
- data/lib/tasks/raif_tasks.rake +9 -4
- metadata +32 -19
- data/lib/raif/default_llms.rb +0 -37
@@ -1,57 +1,48 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Raif::ModelTools::FetchUrl < Raif::ModelTool
|
4
|
+
tool_arguments_schema do
|
5
|
+
string "url", description: "The URL to fetch content from"
|
6
|
+
end
|
4
7
|
|
5
|
-
|
8
|
+
example_model_invocation do
|
6
9
|
{
|
7
10
|
"name": tool_name,
|
8
11
|
"arguments": { "url": "https://en.wikipedia.org/wiki/NASA" }
|
9
12
|
}
|
10
13
|
end
|
11
14
|
|
12
|
-
|
13
|
-
{
|
14
|
-
type: "object",
|
15
|
-
additionalProperties: false,
|
16
|
-
required: ["url"],
|
17
|
-
properties: {
|
18
|
-
url: {
|
19
|
-
type: "string",
|
20
|
-
description: "The URL to fetch content from"
|
21
|
-
}
|
22
|
-
}
|
23
|
-
}
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.tool_description
|
15
|
+
tool_description do
|
27
16
|
"Fetch a URL and return the page content as markdown"
|
28
17
|
end
|
29
18
|
|
30
|
-
|
31
|
-
|
19
|
+
class << self
|
20
|
+
def observation_for_invocation(tool_invocation)
|
21
|
+
return "No results found" unless tool_invocation.result.present?
|
32
22
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
23
|
+
<<~OBSERVATION
|
24
|
+
Result Status: #{tool_invocation.result["status"]}
|
25
|
+
Result Content:
|
26
|
+
#{tool_invocation.result["content"]}
|
27
|
+
OBSERVATION
|
28
|
+
end
|
39
29
|
|
40
|
-
|
41
|
-
|
42
|
-
|
30
|
+
def process_invocation(tool_invocation)
|
31
|
+
url = tool_invocation.tool_arguments["url"]
|
32
|
+
response = Faraday.get(url)
|
43
33
|
|
44
|
-
|
45
|
-
|
34
|
+
readable_content = Raif::Utils::ReadableContentExtractor.new(response.body).extract_readable_content
|
35
|
+
markdown_content = Raif::Utils::HtmlToMarkdownConverter.convert(readable_content)
|
46
36
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
37
|
+
tool_invocation.update!(
|
38
|
+
result: {
|
39
|
+
status: response.status,
|
40
|
+
content: markdown_content
|
41
|
+
}
|
42
|
+
)
|
53
43
|
|
54
|
-
|
44
|
+
tool_invocation.result
|
45
|
+
end
|
55
46
|
end
|
56
47
|
|
57
48
|
end
|
@@ -1,78 +1,69 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Raif::ModelTools::WikipediaSearch < Raif::ModelTool
|
4
|
+
tool_arguments_schema do
|
5
|
+
string "query", description: "The query to search Wikipedia for"
|
6
|
+
end
|
4
7
|
|
5
|
-
|
8
|
+
example_model_invocation do
|
6
9
|
{
|
7
10
|
"name" => tool_name,
|
8
11
|
"arguments" => { "query": "Jimmy Buffett" }
|
9
12
|
}
|
10
13
|
end
|
11
14
|
|
12
|
-
|
13
|
-
{
|
14
|
-
type: "object",
|
15
|
-
additionalProperties: false,
|
16
|
-
required: ["query"],
|
17
|
-
properties: {
|
18
|
-
query: {
|
19
|
-
type: "string",
|
20
|
-
description: "The query to search Wikipedia for"
|
21
|
-
}
|
22
|
-
}
|
23
|
-
}
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.tool_description
|
15
|
+
tool_description do
|
27
16
|
"Search Wikipedia for information"
|
28
17
|
end
|
29
18
|
|
30
|
-
|
31
|
-
|
19
|
+
class << self
|
20
|
+
def observation_for_invocation(tool_invocation)
|
21
|
+
return "No results found" unless tool_invocation.result.present?
|
32
22
|
|
33
|
-
|
34
|
-
|
23
|
+
JSON.pretty_generate(tool_invocation.result)
|
24
|
+
end
|
35
25
|
|
36
|
-
|
37
|
-
|
26
|
+
def process_invocation(tool_invocation)
|
27
|
+
query = tool_invocation.tool_arguments["query"]
|
38
28
|
|
39
|
-
|
29
|
+
conn = Faraday.new(url: "https://en.wikipedia.org/w/api.php")
|
40
30
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
31
|
+
response = conn.get do |req|
|
32
|
+
req.params["action"] = "query"
|
33
|
+
req.params["format"] = "json"
|
34
|
+
req.params["list"] = "search"
|
35
|
+
req.params["srsearch"] = query
|
36
|
+
req.params["srlimit"] = 5 # Limit to 5 results
|
37
|
+
req.params["srprop"] = "snippet"
|
38
|
+
end
|
49
39
|
|
50
|
-
|
51
|
-
|
52
|
-
|
40
|
+
if response.success?
|
41
|
+
results = JSON.parse(response.body)
|
42
|
+
search_results = results.dig("query", "search") || []
|
53
43
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
44
|
+
# Store the results in the tool_invocation
|
45
|
+
tool_invocation.update!(
|
46
|
+
result: {
|
47
|
+
results: search_results.map do |result|
|
48
|
+
{
|
49
|
+
title: result["title"],
|
50
|
+
snippet: result["snippet"],
|
51
|
+
page_id: result["pageid"],
|
52
|
+
url: "https://en.wikipedia.org/wiki/#{result["title"].gsub(" ", "_")}"
|
53
|
+
}
|
54
|
+
end
|
55
|
+
}
|
56
|
+
)
|
57
|
+
else
|
58
|
+
tool_invocation.update!(
|
59
|
+
result: {
|
60
|
+
error: "Failed to fetch results from Wikipedia API: #{response.status} #{response.reason_phrase}"
|
61
|
+
}
|
62
|
+
)
|
63
|
+
end
|
74
64
|
|
75
|
-
|
65
|
+
tool_invocation.result
|
66
|
+
end
|
76
67
|
end
|
77
68
|
|
78
69
|
end
|
data/app/models/raif/task.rb
CHANGED
@@ -7,6 +7,10 @@ module Raif
|
|
7
7
|
include Raif::Concerns::HasAvailableModelTools
|
8
8
|
include Raif::Concerns::InvokesModelTools
|
9
9
|
include Raif::Concerns::LlmResponseParsing
|
10
|
+
include Raif::Concerns::LlmTemperature
|
11
|
+
include Raif::Concerns::JsonSchemaDefinition
|
12
|
+
|
13
|
+
llm_temperature 0.7
|
10
14
|
|
11
15
|
belongs_to :creator, polymorphic: true
|
12
16
|
|
@@ -20,12 +24,25 @@ module Raif
|
|
20
24
|
|
21
25
|
delegate :json_response_schema, to: :class
|
22
26
|
|
23
|
-
|
27
|
+
scope :completed, -> { where.not(completed_at: nil) }
|
28
|
+
scope :failed, -> { where.not(failed_at: nil) }
|
29
|
+
scope :in_progress, -> { where.not(started_at: nil).where(completed_at: nil, failed_at: nil) }
|
30
|
+
scope :pending, -> { where(started_at: nil, completed_at: nil, failed_at: nil) }
|
31
|
+
|
32
|
+
attr_accessor :files, :images
|
24
33
|
|
25
|
-
|
26
|
-
raise ArgumentError, "response_format must be one of: #{response_formats.keys.join(", ")}" unless response_formats.keys.include?(format.to_s)
|
34
|
+
after_initialize -> { self.available_model_tools ||= [] }
|
27
35
|
|
28
|
-
|
36
|
+
def status
|
37
|
+
if completed_at?
|
38
|
+
:completed
|
39
|
+
elsif failed_at?
|
40
|
+
:failed
|
41
|
+
elsif started_at?
|
42
|
+
:in_progress
|
43
|
+
else
|
44
|
+
:pending
|
45
|
+
end
|
29
46
|
end
|
30
47
|
|
31
48
|
# The primary interface for running a task. It will hit the LLM with the task's prompt and system prompt and return a Raif::Task object.
|
@@ -34,10 +51,13 @@ module Raif
|
|
34
51
|
# @param creator [Object] The creator of the task (polymorphic association)
|
35
52
|
# @param available_model_tools [Array<Class>] Optional array of model tool classes that will be provided to the LLM for it to invoke.
|
36
53
|
# @param llm_model_key [Symbol, String] Optional key for the LLM model to use. If blank, Raif.config.default_llm_model_key will be used.
|
54
|
+
# @param images [Array] Optional array of Raif::ModelImageInput objects to include with the prompt.
|
55
|
+
# @param files [Array] Optional array of Raif::ModelFileInput objects to include with the prompt.
|
37
56
|
# @param args [Hash] Additional arguments to pass to the instance of the task that is created.
|
38
57
|
# @return [Raif::Task, nil] The task instance that was created and run.
|
39
|
-
def self.run(creator:, available_model_tools: [], llm_model_key: nil, **args)
|
40
|
-
task = new(creator:, llm_model_key:, available_model_tools:, started_at: Time.current, **args)
|
58
|
+
def self.run(creator:, available_model_tools: [], llm_model_key: nil, images: [], files: [], **args)
|
59
|
+
task = new(creator:, llm_model_key:, available_model_tools:, started_at: Time.current, images: images, files: files, **args)
|
60
|
+
|
41
61
|
task.save!
|
42
62
|
task.run
|
43
63
|
task
|
@@ -58,17 +78,19 @@ module Raif
|
|
58
78
|
task
|
59
79
|
end
|
60
80
|
|
61
|
-
def run
|
81
|
+
def run(skip_prompt_population: false)
|
62
82
|
update_columns(started_at: Time.current) if started_at.nil?
|
63
83
|
|
64
|
-
populate_prompts
|
65
|
-
messages = [{ "role" => "user", "content" =>
|
84
|
+
populate_prompts unless skip_prompt_population
|
85
|
+
messages = [{ "role" => "user", "content" => message_content }]
|
86
|
+
|
66
87
|
mc = llm.chat(
|
67
88
|
messages: messages,
|
68
89
|
source: self,
|
69
90
|
system_prompt: system_prompt,
|
70
91
|
response_format: response_format.to_sym,
|
71
|
-
available_model_tools: available_model_tools
|
92
|
+
available_model_tools: available_model_tools,
|
93
|
+
temperature: self.class.temperature
|
72
94
|
)
|
73
95
|
|
74
96
|
self.raif_model_completion = mc.becomes(Raif::ModelCompletion)
|
@@ -80,13 +102,18 @@ module Raif
|
|
80
102
|
self
|
81
103
|
end
|
82
104
|
|
105
|
+
def re_run
|
106
|
+
update_columns(started_at: Time.current)
|
107
|
+
run(skip_prompt_population: true)
|
108
|
+
end
|
109
|
+
|
83
110
|
# Returns the LLM prompt for the task.
|
84
111
|
#
|
85
112
|
# @param creator [Object] The creator of the task (polymorphic association)
|
86
113
|
# @param args [Hash] Additional arguments to pass to the instance of the task that is created.
|
87
114
|
# @return [String] The LLM prompt for the task.
|
88
115
|
def self.prompt(creator:, **args)
|
89
|
-
new(creator:, **args).
|
116
|
+
new(creator:, **args).build_prompt
|
90
117
|
end
|
91
118
|
|
92
119
|
# Returns the LLM system prompt for the task.
|
@@ -95,25 +122,53 @@ module Raif
|
|
95
122
|
# @param args [Hash] Additional arguments to pass to the instance of the task that is created.
|
96
123
|
# @return [String] The LLM system prompt for the task.
|
97
124
|
def self.system_prompt(creator:, **args)
|
98
|
-
new(creator:, **args).
|
125
|
+
new(creator:, **args).build_system_prompt
|
99
126
|
end
|
100
127
|
|
101
|
-
def self.json_response_schema
|
102
|
-
|
128
|
+
def self.json_response_schema(&block)
|
129
|
+
if block_given?
|
130
|
+
json_schema_definition(:json_response, &block)
|
131
|
+
elsif schema_defined?(:json_response)
|
132
|
+
schema_for(:json_response)
|
133
|
+
end
|
103
134
|
end
|
104
135
|
|
105
|
-
private
|
106
|
-
|
107
136
|
def build_prompt
|
108
137
|
raise NotImplementedError, "Raif::Task subclasses must implement #build_prompt"
|
109
138
|
end
|
110
139
|
|
111
140
|
def build_system_prompt
|
112
141
|
sp = Raif.config.task_system_prompt_intro
|
142
|
+
sp = sp.call(self) if sp.respond_to?(:call)
|
113
143
|
sp += system_prompt_language_preference if requested_language_key.present?
|
114
144
|
sp
|
115
145
|
end
|
116
146
|
|
147
|
+
private
|
148
|
+
|
149
|
+
def message_content
|
150
|
+
# If there are no images or files, just return the message content can just be a string with the prompt
|
151
|
+
return prompt if images.blank? && files.blank?
|
152
|
+
|
153
|
+
content = [{ "type" => "text", "text" => prompt }]
|
154
|
+
|
155
|
+
images.each do |image|
|
156
|
+
raise Raif::Errors::InvalidModelImageInputError,
|
157
|
+
"Images must be a Raif::ModelImageInput: #{image.inspect}" unless image.is_a?(Raif::ModelImageInput)
|
158
|
+
|
159
|
+
content << image
|
160
|
+
end
|
161
|
+
|
162
|
+
files.each do |file|
|
163
|
+
raise Raif::Errors::InvalidFileInputError,
|
164
|
+
"Files must be a Raif::ModelFileInput: #{file.inspect}" unless file.is_a?(Raif::ModelFileInput)
|
165
|
+
|
166
|
+
content << file
|
167
|
+
end
|
168
|
+
|
169
|
+
content
|
170
|
+
end
|
171
|
+
|
117
172
|
def populate_prompts
|
118
173
|
self.requested_language_key ||= creator.preferred_language_key if creator.respond_to?(:preferred_language_key)
|
119
174
|
self.prompt = build_prompt
|
@@ -32,6 +32,16 @@
|
|
32
32
|
<div class="col-md-3 col-lg-2 d-md-block bg-light sidebar">
|
33
33
|
<div class="position-sticky pt-3">
|
34
34
|
<ul class="nav flex-column">
|
35
|
+
<li class="nav-item">
|
36
|
+
<a class="nav-link <%= current_page?(raif.admin_stats_path) ? "active" : "" %>" href="<%= raif.admin_stats_path %>">
|
37
|
+
<span class="d-inline-block me-2">
|
38
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-bar-chart" viewBox="0 0 16 16">
|
39
|
+
<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" />
|
40
|
+
</svg>
|
41
|
+
</span>
|
42
|
+
<%= t("raif.admin.common.stats") %>
|
43
|
+
</a>
|
44
|
+
</li>
|
35
45
|
<li class="nav-item">
|
36
46
|
<a class="nav-link <%= current_page?(raif.admin_tasks_path) ? "active" : "" %>" href="<%= raif.admin_tasks_path %>">
|
37
47
|
<span class="d-inline-block me-2">
|
@@ -18,7 +18,9 @@
|
|
18
18
|
</div>
|
19
19
|
<div class="row mb-3">
|
20
20
|
<div class="col-md-3"><strong><%= t("raif.admin.common.creator") %>:</strong></div>
|
21
|
-
<div class="col-md-9"
|
21
|
+
<div class="col-md-9">
|
22
|
+
<%= @agent.creator.try(:raif_display_name) || "#{@agent.creator_type} ##{@agent.creator_id}" %>
|
23
|
+
</div>
|
22
24
|
</div>
|
23
25
|
<div class="row mb-3">
|
24
26
|
<div class="col-md-3"><strong><%= t("raif.admin.common.status") %>:</strong></div>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<tr id="<%= dom_id(conversation) %>" class="raif-conversation">
|
2
2
|
<td><%= link_to "##{conversation.id}", raif.admin_conversation_path(conversation) %></td>
|
3
3
|
<td><small class="text-muted"><%= conversation.created_at.rfc822 %></small></td>
|
4
|
-
<td><%= conversation.
|
4
|
+
<td><%= conversation.creator.try(:raif_display_name) || "#{conversation.creator_type} ##{conversation.creator_id}" %></td>
|
5
5
|
<td><%= conversation.type %></td>
|
6
6
|
<td><%= conversation.conversation_entries_count %></td>
|
7
7
|
</tr>
|
@@ -14,7 +14,9 @@
|
|
14
14
|
</div>
|
15
15
|
<div class="row mb-3">
|
16
16
|
<div class="col-md-3"><strong><%= t("raif.admin.common.creator") %>:</strong></div>
|
17
|
-
<div class="col-md-9"
|
17
|
+
<div class="col-md-9">
|
18
|
+
<%= @conversation.creator.try(:raif_display_name) || "#{@conversation.creator_type} ##{@conversation.creator_id}" %>
|
19
|
+
</div>
|
18
20
|
</div>
|
19
21
|
<div class="row mb-3">
|
20
22
|
<div class="col-md-3"><strong><%= t("raif.admin.common.type") %>:</strong></div>
|
@@ -5,5 +5,6 @@
|
|
5
5
|
<td><%= model_completion.llm_model_key %></td>
|
6
6
|
<td><%= model_completion.response_format %></td>
|
7
7
|
<td><%= model_completion.total_tokens ? number_with_delimiter(model_completion.total_tokens) : "-" %></td>
|
8
|
+
<td><%= model_completion.total_cost ? number_to_currency(model_completion.total_cost, precision: 6) : "-" %></td>
|
8
9
|
<td><small class="text-muted"><%= truncate(model_completion.raw_response, length: 100) %></small></td>
|
9
10
|
</tr>
|
@@ -13,6 +13,7 @@
|
|
13
13
|
<th><%= t("raif.admin.common.model") %></th>
|
14
14
|
<th><%= t("raif.admin.common.response_format") %></th>
|
15
15
|
<th><%= t("raif.admin.common.total_tokens") %></th>
|
16
|
+
<th><%= t("raif.admin.common.total_cost") %></th>
|
16
17
|
<th><%= t("raif.admin.common.response") %></th>
|
17
18
|
</tr>
|
18
19
|
</thead>
|
@@ -34,15 +34,42 @@
|
|
34
34
|
</div>
|
35
35
|
<div class="row mb-3">
|
36
36
|
<div class="col-md-3"><strong><%= t("raif.admin.common.prompt_tokens") %>:</strong></div>
|
37
|
-
<div class="col-md-9"
|
37
|
+
<div class="col-md-9">
|
38
|
+
<% if @model_completion.prompt_tokens %>
|
39
|
+
<%= number_with_delimiter(@model_completion.prompt_tokens) %>
|
40
|
+
<% if @model_completion.prompt_token_cost %>
|
41
|
+
<small>(<%= t("raif.admin.common.est_cost") %>: <%= "$" %><%= number_with_precision(@model_completion.prompt_token_cost, precision: 6) %>)</small>
|
42
|
+
<% end %>
|
43
|
+
<% else %>
|
44
|
+
-
|
45
|
+
<% end %>
|
46
|
+
</div>
|
38
47
|
</div>
|
39
48
|
<div class="row mb-3">
|
40
49
|
<div class="col-md-3"><strong><%= t("raif.admin.common.completion_tokens") %>:</strong></div>
|
41
|
-
<div class="col-md-9"
|
50
|
+
<div class="col-md-9">
|
51
|
+
<% if @model_completion.completion_tokens %>
|
52
|
+
<%= number_with_delimiter(@model_completion.completion_tokens) %>
|
53
|
+
<% if @model_completion.output_token_cost %>
|
54
|
+
<small>(<%= t("raif.admin.common.est_cost") %>: <%= "$" %><%= number_with_precision(@model_completion.output_token_cost, precision: 6) %>)</small>
|
55
|
+
<% end %>
|
56
|
+
<% else %>
|
57
|
+
-
|
58
|
+
<% end %>
|
59
|
+
</div>
|
42
60
|
</div>
|
43
61
|
<div class="row mb-3">
|
44
62
|
<div class="col-md-3"><strong><%= t("raif.admin.common.total_tokens") %>:</strong></div>
|
45
|
-
<div class="col-md-9"
|
63
|
+
<div class="col-md-9">
|
64
|
+
<% if @model_completion.total_tokens %>
|
65
|
+
<%= number_with_delimiter(@model_completion.total_tokens) %>
|
66
|
+
<% if @model_completion.total_cost %>
|
67
|
+
<small>(<%= t("raif.admin.common.est_cost") %>: <%= "$" %><%= number_with_precision(@model_completion.total_cost, precision: 6) %>)</small>
|
68
|
+
<% end %>
|
69
|
+
<% else %>
|
70
|
+
-
|
71
|
+
<% end %>
|
72
|
+
</div>
|
46
73
|
</div>
|
47
74
|
</div>
|
48
75
|
</div>
|
@@ -0,0 +1,128 @@
|
|
1
|
+
<div class="d-flex justify-content-between align-items-center my-4">
|
2
|
+
<h1 class="mb-0"><%= t("raif.admin.common.stats") %></h1>
|
3
|
+
|
4
|
+
<div class="period-filter">
|
5
|
+
<%= form_tag raif.admin_stats_path, method: :get, id: "period_filter_form", class: "d-flex align-items-center" do %>
|
6
|
+
<%= select_tag :period,
|
7
|
+
options_for_select(
|
8
|
+
[
|
9
|
+
[t("raif.admin.common.period_day"), "day"],
|
10
|
+
[t("raif.admin.common.period_week"), "week"],
|
11
|
+
[t("raif.admin.common.period_month"), "month"],
|
12
|
+
[t("raif.admin.common.period_all"), "all"]
|
13
|
+
],
|
14
|
+
@selected_period
|
15
|
+
),
|
16
|
+
class: "form-select form-select-sm me-2" %>
|
17
|
+
<%= submit_tag t("raif.admin.common.update"), class: "btn btn-sm btn-primary" %>
|
18
|
+
<% end %>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
<div class="row">
|
23
|
+
<div class="col-12">
|
24
|
+
<div class="card shadow-sm mb-4">
|
25
|
+
<div class="card-body">
|
26
|
+
<div class="row g-4">
|
27
|
+
<!-- Model Completions -->
|
28
|
+
<div class="col-md-4 col-lg-4">
|
29
|
+
<div class="stats-card p-3 border rounded shadow-sm h-100">
|
30
|
+
<div class="d-flex align-items-center mb-2">
|
31
|
+
<span class="stats-icon bg-primary-subtle text-primary rounded p-2 me-3">
|
32
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-robot" viewBox="0 0 16 16">
|
33
|
+
<path d="M6 12.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5ZM3 8.062C3 6.76 4.235 5.765 5.53 5.886a26.58 26.58 0 0 0 4.94 0C11.765 5.765 13 6.76 13 8.062v1.157a.933.933 0 0 1-.765.935c-.845.147-2.34.346-4.235.346-1.895 0-3.39-.2-4.235-.346A.933.933 0 0 1 3 9.219V8.062Zm4.542-.827a.25.25 0 0 0-.217.068l-.92.9a24.767 24.767 0 0 1-1.871-.183.25.25 0 0 0-.068.495c.55.076 1.232.149 2.02.193a.25.25 0 0 0 .189-.071l.754-.736.847 1.71a.25.25 0 0 0 .404.062l.932-.97a25.286 25.286 0 0 0 1.922-.188.25.25 0 0 0-.068-.495c-.538.074-1.207.145-1.98.189a.25.25 0 0 0-.166.076l-.754.785-.842-1.7a.25.25 0 0 0-.182-.135Z" />
|
34
|
+
<path d="M8.5 1.866a1 1 0 1 0-1 0V3h-2A4.5 4.5 0 0 0 1 7.5V8a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1v1a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v-.5A4.5 4.5 0 0 0 10.5 3h-2V1.866ZM14 7.5V13a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7.5A3.5 3.5 0 0 1 5.5 4h5A3.5 3.5 0 0 1 14 7.5Z" />
|
35
|
+
</svg>
|
36
|
+
</span>
|
37
|
+
<div>
|
38
|
+
<h6 class="mb-0 text-muted"><%= t("raif.admin.common.model_completions") %></h6>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
<div class="d-flex justify-content-between align-items-center">
|
42
|
+
<h3 class="fw-bold mb-0"><%= number_with_delimiter(@model_completion_count) %></h3>
|
43
|
+
<p class="text-muted mt-2 mb-0"><small><%= t("raif.admin.common.est_cost") %>: <%= number_to_currency(@model_completion_total_cost, precision: 6) %></small></p>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
</div>
|
47
|
+
|
48
|
+
<!-- Tasks -->
|
49
|
+
<div class="col-md-4 col-lg-4">
|
50
|
+
<div class="stats-card p-3 border rounded shadow-sm h-100">
|
51
|
+
<div class="d-flex align-items-center mb-2">
|
52
|
+
<span class="stats-icon bg-success-subtle text-success rounded p-2 me-3">
|
53
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-list-check" viewBox="0 0 16 16">
|
54
|
+
<path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM3.854 2.146a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 3.293l1.146-1.147a.5.5 0 0 1 .708 0zm0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 7.293l1.146-1.147a.5.5 0 0 1 .708 0zm0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 0 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0z" />
|
55
|
+
</svg>
|
56
|
+
</span>
|
57
|
+
<div>
|
58
|
+
<h6 class="mb-0 text-muted"><%= t("raif.admin.common.tasks") %></h6>
|
59
|
+
</div>
|
60
|
+
</div>
|
61
|
+
<div class="d-flex justify-content-between align-items-center">
|
62
|
+
<h3 class="fw-bold mb-0"><%= number_with_delimiter(@task_count) %></h3>
|
63
|
+
<div class="mt-2">
|
64
|
+
<%= link_to raif.admin_stats_tasks_path(period: @selected_period) do %>
|
65
|
+
<%= t("raif.admin.common.details") %> »
|
66
|
+
<% end %>
|
67
|
+
</div>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
</div>
|
71
|
+
|
72
|
+
<!-- Agents -->
|
73
|
+
<div class="col-md-4 col-lg-4">
|
74
|
+
<div class="stats-card p-3 border rounded shadow-sm h-100">
|
75
|
+
<div class="d-flex align-items-center mb-2">
|
76
|
+
<span class="stats-icon bg-danger-subtle text-danger rounded p-2 me-3">
|
77
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-cpu" viewBox="0 0 16 16">
|
78
|
+
<path d="M5 0a.5.5 0 0 1 .5.5V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2A2.5 2.5 0 0 1 14 4.5h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14a2.5 2.5 0 0 1-2.5 2.5v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14A2.5 2.5 0 0 1 2 11.5H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2A2.5 2.5 0 0 1 4.5 2V.5A.5.5 0 0 1 5 0zm-.5 3A1.5 1.5 0 0 0 3 4.5v7A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 11.5 3h-7zM5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3zM6.5 6a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z" />
|
79
|
+
</svg>
|
80
|
+
</span>
|
81
|
+
<div>
|
82
|
+
<h6 class="mb-0 text-muted"><%= t("raif.admin.common.agents") %></h6>
|
83
|
+
</div>
|
84
|
+
</div>
|
85
|
+
<h3 class="fw-bold mb-0"><%= number_with_delimiter(@agent_count) %></h3>
|
86
|
+
</div>
|
87
|
+
</div>
|
88
|
+
|
89
|
+
<!-- Conversations -->
|
90
|
+
<div class="col-md-4 col-lg-4">
|
91
|
+
<div class="stats-card p-3 border rounded shadow-sm h-100">
|
92
|
+
<div class="d-flex align-items-center mb-2">
|
93
|
+
<span class="stats-icon bg-info-subtle text-info rounded p-2 me-3">
|
94
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-chat-dots" viewBox="0 0 16 16">
|
95
|
+
<path d="M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" />
|
96
|
+
<path d="m2.165 15.803.02-.004c1.83-.363 2.948-.842 3.468-1.105A9.06 9.06 0 0 0 8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6a10.437 10.437 0 0 1-.524 2.318l-.003.011a10.722 10.722 0 0 1-.244.637c-.079.186.074.394.273.362a21.673 21.673 0 0 0 .693-.125zm.8-3.108a1 1 0 0 0-.287-.801C1.618 10.83 1 9.468 1 8c0-3.192 3.004-6 7-6s7 2.808 7 6c0 3.193-3.004 6-7 6a8.06 8.06 0 0 1-2.088-.272a1 1 0 0 0-.711.074c-.387.196-1.24.57-2.634.893a10.97 10.97 0 0 0 .398-2z" />
|
97
|
+
</svg>
|
98
|
+
</span>
|
99
|
+
<div>
|
100
|
+
<h6 class="mb-0 text-muted"><%= t("raif.admin.common.conversations") %></h6>
|
101
|
+
</div>
|
102
|
+
</div>
|
103
|
+
<h3 class="fw-bold mb-0"><%= number_with_delimiter(@conversation_count) %></h3>
|
104
|
+
</div>
|
105
|
+
</div>
|
106
|
+
|
107
|
+
<!-- Conversation Entries -->
|
108
|
+
<div class="col-md-4 col-lg-4">
|
109
|
+
<div class="stats-card p-3 border rounded shadow-sm h-100">
|
110
|
+
<div class="d-flex align-items-center mb-2">
|
111
|
+
<span class="stats-icon bg-warning-subtle text-warning rounded p-2 me-3">
|
112
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-chat-text" viewBox="0 0 16 16">
|
113
|
+
<path d="M2.678 11.894a1 1 0 0 1 .287.801 10.97 10.97 0 0 1-.398 2c1.395-.323 2.247-.697 2.634-.893a1 1 0 0 1 .71-.074A8.06 8.06 0 0 0 8 14c3.996 0 7-2.807 7-6 0-3.192-3.004-6-7-6S1 4.808 1 8c0 1.468.617 2.83 1.678 3.894zm-.493 3.905a21.682 21.682 0 0 1-.713.129c-.2.032-.352-.176-.273-.362a9.68 9.68 0 0 0 .244-.637l.003-.01c.248-.72.45-1.548.524-2.319C.743 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7-3.582 7-8 7a9.06 9.06 0 0 1-2.347-.306c-.52.263-1.639.742-3.468 1.105z" />
|
114
|
+
<path d="M4 5.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zM4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8zm0 2.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5z" />
|
115
|
+
</svg>
|
116
|
+
</span>
|
117
|
+
<div>
|
118
|
+
<h6 class="mb-0 text-muted"><%= t("raif.admin.common.conversation_entries") %></h6>
|
119
|
+
</div>
|
120
|
+
</div>
|
121
|
+
<h3 class="fw-bold mb-0"><%= number_with_delimiter(@conversation_entry_count) %></h3>
|
122
|
+
</div>
|
123
|
+
</div>
|
124
|
+
</div>
|
125
|
+
</div>
|
126
|
+
</div>
|
127
|
+
</div>
|
128
|
+
</div>
|