aia 1.1.1 → 2.0.0.0.pre.alpha
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/.envrc +5 -1
- data/.loki +231 -0
- data/.quality/flay_baseline.txt +1 -0
- data/.quality/flog_baseline.txt +29 -0
- data/.quality/reek_baseline.txt +80 -0
- data/.rubocop.yml +116 -0
- data/.version +1 -1
- data/CHANGELOG.md +259 -50
- data/IMPLEMENTATION_PLAN.md +506 -0
- data/README.md +266 -238
- data/Rakefile +118 -5
- data/architecture_review.md +314 -0
- data/bin/aia +16 -0
- data/docs/AGENTS.md +40 -0
- data/docs/advanced-prompting.md +67 -3
- data/docs/cli-reference.md +312 -56
- data/docs/configuration.md +130 -19
- data/docs/contributing.md +56 -2
- data/docs/directives-reference.md +593 -78
- data/docs/faq.md +85 -3
- data/docs/guides/available-models.md +1 -1
- data/docs/guides/basic-usage.md +6 -6
- data/docs/guides/chat.md +40 -16
- data/docs/guides/crew.md +239 -0
- data/docs/guides/executable-prompts.md +1 -1
- data/docs/guides/index.md +1 -0
- data/docs/guides/models.md +15 -0
- data/docs/index.md +29 -2
- data/docs/installation.md +44 -17
- data/docs/mcp-integration.md +40 -0
- data/docs/prompt_management.md +85 -86
- data/docs/security.md +47 -0
- data/docs/special_projects_guide.md +386 -0
- data/docs/tools-and-mcp-examples.md +23 -0
- data/docs/workflows-and-pipelines.md +84 -7
- data/examples/.gitignore +1 -0
- data/examples/00_setup_aia.sh +27 -44
- data/examples/11_multi_model.sh +4 -14
- data/examples/12_token_usage.sh +3 -12
- data/examples/18_tools.sh +10 -2
- data/examples/22_chat_mode.sh +0 -10
- data/examples/23_verify.sh +139 -0
- data/examples/24_decompose.sh +139 -0
- data/examples/25_spawn.sh +139 -0
- data/examples/26_debate.sh +97 -0
- data/examples/27_mention_routing.sh +157 -0
- data/examples/28_model_switching.sh +106 -0
- data/examples/29_agent_harness.sh +177 -0
- data/examples/README.md +65 -0
- data/examples/advanced_multi_robot_capabilities_without_examples.md +106 -0
- data/examples/aia_config.yml +1 -1
- data/examples/aia_config_orchestrator.yml +45 -0
- data/examples/common.sh +18 -6
- data/examples/context/tech_stack.md +2 -2
- data/examples/prompts_dir/roles/orchestrator.md +21 -0
- data/examples/requirements/sinatra_taskflow_app.md +139 -0
- data/examples/rules/01_classify_ruby.rb +16 -0
- data/examples/rules/02_prefer_claude_for_code.rb +19 -0
- data/examples/rules/03_gate_prompt_length.rb +19 -0
- data/examples/rules/04_tool_selection.rb +41 -0
- data/examples/rules/README.md +30 -0
- data/examples/run_all.sh +48 -15
- data/examples/tools/word_count_tool.rb +1 -1
- data/lib/AGENTS.md +57 -0
- data/lib/aia/chat_loop.rb +304 -164
- data/lib/aia/config/cli_parser.rb +174 -111
- data/lib/aia/config/defaults.yml +62 -33
- data/lib/aia/config/mcp_parser.rb +39 -46
- data/lib/aia/config/model_spec.rb +34 -2
- data/lib/aia/config/validator.rb +108 -142
- data/lib/aia/config.rb +110 -145
- data/lib/aia/content_extractor.rb +153 -0
- data/lib/aia/cost_calculator.rb +38 -0
- data/lib/aia/crew.rb +164 -0
- data/lib/aia/debate_handler.rb +166 -0
- data/lib/aia/delegate_handler.rb +112 -0
- data/lib/aia/directive.rb +33 -18
- data/lib/aia/directive_processor.rb +16 -7
- data/lib/aia/directives/configuration_directives.rb +160 -20
- data/lib/aia/directives/context_directives.rb +38 -26
- data/lib/aia/directives/execution_directives.rb +136 -4
- data/lib/aia/directives/model_directives.rb +76 -34
- data/lib/aia/directives/trakflow_directives.rb +44 -0
- data/lib/aia/directives/utility_directives.rb +203 -6
- data/lib/aia/directives/web_and_file_directives.rb +96 -60
- data/lib/aia/errors.rb +15 -0
- data/lib/aia/fact_asserter.rb +27 -0
- data/lib/aia/fzf.rb +9 -31
- data/lib/aia/handler_context.rb +17 -0
- data/lib/aia/handler_protocol.rb +19 -0
- data/lib/aia/history_transfer.rb +55 -0
- data/lib/aia/input_collector.rb +3 -3
- data/lib/aia/layered_orchestrator.rb +448 -0
- data/lib/aia/logger.rb +24 -4
- data/lib/aia/mcp_config_normalizer.rb +35 -0
- data/lib/aia/mcp_connection_manager.rb +305 -0
- data/lib/aia/mcp_discovery.rb +44 -0
- data/lib/aia/mcp_grouper.rb +33 -0
- data/lib/aia/mcp_utility.rb +57 -0
- data/lib/aia/mention_router.rb +260 -0
- data/lib/aia/model_alias_registry.rb +97 -0
- data/lib/aia/model_switch_handler.rb +100 -0
- data/lib/aia/network_builder.rb +155 -0
- data/lib/aia/network_memory_manager.rb +55 -0
- data/lib/aia/patches/ruby_llm_streaming_error.rb +43 -0
- data/lib/aia/patches/ruby_llm_tool_error.rb +96 -0
- data/lib/aia/pipeline_orchestrator.rb +262 -0
- data/lib/aia/plugin_loader.rb +170 -0
- data/lib/aia/plugin_monitor.rb +208 -0
- data/lib/aia/prompt_decomposer.rb +157 -0
- data/lib/aia/prompt_handler.rb +19 -39
- data/lib/aia/robot_builder.rb +51 -0
- data/lib/aia/robot_factory.rb +334 -0
- data/lib/aia/robot_namer.rb +116 -0
- data/lib/aia/session.rb +83 -17
- data/lib/aia/session_tracker.rb +209 -0
- data/lib/aia/similarity_scorer.rb +39 -0
- data/lib/aia/skill_utils.rb +105 -1
- data/lib/aia/spawn_handler.rb +129 -0
- data/lib/aia/spawn_spec_parser.rb +65 -0
- data/lib/aia/special_mode_handler.rb +302 -0
- data/lib/aia/startup_coordinator.rb +150 -0
- data/lib/aia/streaming_runner.rb +169 -0
- data/lib/aia/system_prompt_assembler.rb +88 -0
- data/lib/aia/task_coordinator.rb +202 -0
- data/lib/aia/task_decomposer.rb +57 -0
- data/lib/aia/task_executor.rb +51 -0
- data/lib/aia/tfidf_math.rb +27 -0
- data/lib/aia/tool_filter/tfidf.rb +116 -0
- data/lib/aia/tool_filter/wordnet_expander.rb +127 -0
- data/lib/aia/tool_filter.rb +82 -0
- data/lib/aia/tool_filter_registry.rb +30 -0
- data/lib/aia/tool_filter_strategy.rb +143 -0
- data/lib/aia/tool_loader.rb +210 -0
- data/lib/aia/tool_utility.rb +30 -0
- data/lib/aia/tools/delegate_to_foreman_tool.rb +70 -0
- data/lib/aia/tools/recruit_robot_tool.rb +60 -0
- data/lib/aia/tools/reskill_robot_tool.rb +44 -0
- data/lib/aia/tools/task_board_tool.rb +114 -0
- data/lib/aia/trakflow_bridge.rb +173 -0
- data/lib/aia/turn_state.rb +94 -0
- data/lib/aia/ui_presenter.rb +166 -198
- data/lib/aia/utility.rb +134 -87
- data/lib/aia/{history_manager.rb → variable_input_collector.rb} +8 -9
- data/lib/aia/verification_network.rb +58 -0
- data/lib/aia.rb +108 -63
- data/mkdocs.yml +1 -0
- metadata +179 -56
- data/justfile +0 -215
- data/lib/aia/adapter/chat_execution.rb +0 -242
- data/lib/aia/adapter/error_handler.rb +0 -68
- data/lib/aia/adapter/gem_activator.rb +0 -57
- data/lib/aia/adapter/mcp_connector.rb +0 -274
- data/lib/aia/adapter/modality_handlers.rb +0 -167
- data/lib/aia/adapter/model_registry.rb +0 -81
- data/lib/aia/adapter/multi_model_chat.rb +0 -218
- data/lib/aia/adapter/provider_configurator.rb +0 -59
- data/lib/aia/adapter/tool_filter.rb +0 -85
- data/lib/aia/adapter/tool_loader.rb +0 -90
- data/lib/aia/chat_processor_service.rb +0 -178
- data/lib/aia/prompt_pipeline.rb +0 -183
- data/lib/aia/ruby_llm_adapter.rb +0 -95
- data/lib/extensions/openstruct_merge.rb +0 -48
- data/lib/extensions/ruby_llm/.irbrc +0 -56
- data/lib/extensions/ruby_llm/modalities.rb +0 -36
- data/lib/extensions/ruby_llm/provider_fix.rb +0 -79
- data/lib/refinements/string.rb +0 -16
- data/main.just +0 -76
data/lib/aia/ui_presenter.rb
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# lib/aia/ui_presenter.rb
|
|
2
2
|
|
|
3
3
|
require 'tty-screen'
|
|
4
|
+
require 'tty-spinner'
|
|
5
|
+
require 'tty-table'
|
|
4
6
|
require 'reline'
|
|
7
|
+
require 'fileutils'
|
|
5
8
|
|
|
6
9
|
module AIA
|
|
7
10
|
class UIPresenter
|
|
8
|
-
USER_PROMPT = "Follow up (cntl-D or 'exit' to end) #=> "
|
|
9
|
-
|
|
11
|
+
USER_PROMPT = "Follow up (cntl-D or 'exit' to end) #=> ".freeze
|
|
12
|
+
HISTORY_FILE = File.join(Dir.home, '.config', 'aia', 'chat_history')
|
|
13
|
+
MAX_HISTORY = 50
|
|
10
14
|
|
|
11
15
|
def initialize
|
|
12
16
|
@terminal_width = TTY::Screen.width
|
|
@@ -16,27 +20,19 @@ module AIA
|
|
|
16
20
|
puts "#{'═' * @terminal_width}\n"
|
|
17
21
|
end
|
|
18
22
|
|
|
19
|
-
|
|
20
23
|
def display_ai_response(response)
|
|
21
24
|
puts "\nAI: "
|
|
22
25
|
format_chat_response(response)
|
|
23
|
-
|
|
24
|
-
out_file = AIA.config.output.file
|
|
25
|
-
if out_file && !out_file.nil?
|
|
26
|
-
File.open(out_file, 'a') do |file|
|
|
27
|
-
file.puts "\nAI: "
|
|
28
|
-
format_chat_response(response, file)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
26
|
end
|
|
32
27
|
|
|
33
|
-
|
|
34
28
|
def format_chat_response(response, output = $stdout)
|
|
35
29
|
indent = ' '
|
|
36
30
|
|
|
37
31
|
# Convert RubyLLM::Message to string if necessary
|
|
38
|
-
response_text = if response.is_a?(RubyLLM::Message)
|
|
32
|
+
response_text = if defined?(RubyLLM::Message) && response.is_a?(RubyLLM::Message)
|
|
39
33
|
response.content.to_s
|
|
34
|
+
elsif response.respond_to?(:reply)
|
|
35
|
+
response.reply.to_s
|
|
40
36
|
elsif response.respond_to?(:to_s)
|
|
41
37
|
response.to_s
|
|
42
38
|
else
|
|
@@ -67,17 +63,14 @@ module AIA
|
|
|
67
63
|
end
|
|
68
64
|
end
|
|
69
65
|
|
|
70
|
-
|
|
71
66
|
def display_separator
|
|
72
67
|
puts "\n#{'─' * @terminal_width}"
|
|
73
68
|
end
|
|
74
69
|
|
|
75
|
-
|
|
76
70
|
def display_chat_end
|
|
77
71
|
puts "\nChat session ended."
|
|
78
72
|
end
|
|
79
73
|
|
|
80
|
-
|
|
81
74
|
# This is the follow up question in a chat session
|
|
82
75
|
def ask_question
|
|
83
76
|
puts USER_PROMPT
|
|
@@ -85,24 +78,34 @@ module AIA
|
|
|
85
78
|
begin
|
|
86
79
|
input = Reline.readline('', true)
|
|
87
80
|
return nil if input.nil? # Handle Ctrl+D
|
|
88
|
-
|
|
81
|
+
save_chat_history unless input.strip.empty?
|
|
89
82
|
input
|
|
90
83
|
rescue Interrupt
|
|
91
84
|
puts "\nChat session interrupted."
|
|
92
|
-
|
|
85
|
+
'exit'
|
|
93
86
|
end
|
|
94
87
|
end
|
|
95
88
|
|
|
89
|
+
# Load persistent chat history into Reline
|
|
90
|
+
def load_chat_history
|
|
91
|
+
Reline::HISTORY.clear
|
|
92
|
+
history_file = chat_history_file
|
|
93
|
+
return unless history_file && File.exist?(history_file)
|
|
94
|
+
|
|
95
|
+
lines = File.readlines(history_file, chomp: true).last(MAX_HISTORY)
|
|
96
|
+
lines.each { |line| Reline::HISTORY << line }
|
|
97
|
+
end
|
|
98
|
+
|
|
96
99
|
def display_info(message)
|
|
97
|
-
puts "\n#{message}"
|
|
100
|
+
$stderr.puts "\n#{message}"
|
|
98
101
|
end
|
|
99
102
|
|
|
100
103
|
def display_error(message)
|
|
101
|
-
|
|
104
|
+
$stderr.puts "\n❌ ERROR: #{message}\n"
|
|
102
105
|
end
|
|
103
106
|
|
|
104
107
|
def display_warning(message)
|
|
105
|
-
|
|
108
|
+
$stderr.puts "\n⚠ WARNING: #{message}\n"
|
|
106
109
|
end
|
|
107
110
|
|
|
108
111
|
def with_spinner(message = "Processing", operation_type = nil)
|
|
@@ -120,211 +123,176 @@ module AIA
|
|
|
120
123
|
|
|
121
124
|
def display_token_metrics(metrics)
|
|
122
125
|
return unless metrics
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
|
|
127
|
+
model_id = metrics[:model_id] || 'unknown'
|
|
128
|
+
input_tokens = metrics[:input_tokens] || 0
|
|
129
|
+
output_tokens = metrics[:output_tokens] || 0
|
|
130
|
+
total_tokens = input_tokens + output_tokens
|
|
131
|
+
time_str = format_elapsed(metrics[:elapsed])
|
|
132
|
+
|
|
128
133
|
if AIA.config.flags.cost
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
134
|
+
cost_data = calculate_cost(metrics)
|
|
135
|
+
if cost_data[:available]
|
|
136
|
+
header = %w[Model Input Output Total Cost x1000 Time]
|
|
137
|
+
row = [
|
|
138
|
+
model_id,
|
|
139
|
+
input_tokens, output_tokens, total_tokens,
|
|
140
|
+
"$#{'%.5f' % cost_data[:total_cost]}",
|
|
141
|
+
"$#{'%.2f' % (cost_data[:total_cost] * 1000)}",
|
|
142
|
+
time_str
|
|
143
|
+
]
|
|
144
|
+
alignments = %i[left right right right right right right]
|
|
145
|
+
else
|
|
146
|
+
header = %w[Model Input Output Total Cost Time]
|
|
147
|
+
row = [model_id, input_tokens, output_tokens, total_tokens, "N/A", time_str]
|
|
148
|
+
alignments = %i[left right right right right right]
|
|
144
149
|
end
|
|
150
|
+
else
|
|
151
|
+
header = %w[Model Input Output Total Time]
|
|
152
|
+
row = [model_id, input_tokens, output_tokens, total_tokens, time_str]
|
|
153
|
+
alignments = %i[left right right right right]
|
|
145
154
|
end
|
|
155
|
+
|
|
156
|
+
table = TTY::Table.new(header, [row])
|
|
157
|
+
rendered = table.render(:unicode, resize: true, alignments: alignments, padding: [0, 1])
|
|
158
|
+
|
|
159
|
+
puts rendered
|
|
160
|
+
write_to_output_file(rendered)
|
|
146
161
|
end
|
|
147
162
|
|
|
163
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
148
164
|
def display_multi_model_metrics(metrics_list)
|
|
149
165
|
return unless metrics_list && !metrics_list.empty?
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
output_lines << sprintf("%-20s %10s %10s %10s %12s %10s",
|
|
167
|
-
"Model", "Input", "Output", "Total", "Cost", "x1000")
|
|
168
|
-
output_lines << "─" * table_width
|
|
169
|
-
else
|
|
170
|
-
output_lines << sprintf("%-20s %10s %10s %10s",
|
|
171
|
-
"Model", "Input", "Output", "Total")
|
|
172
|
-
output_lines << "─" * table_width
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# Process each model
|
|
176
|
-
total_input = 0
|
|
177
|
-
total_output = 0
|
|
178
|
-
total_cost = 0.0
|
|
179
|
-
|
|
180
|
-
metrics_list.each do |metrics|
|
|
181
|
-
# Use display_name if available (includes role), otherwise fall back to model_id
|
|
182
|
-
model_name = metrics[:display_name] || metrics[:model_id]
|
|
183
|
-
# Truncate model name if too long
|
|
184
|
-
model_name = model_name[0..17] + ".." if model_name.to_s.length > 19
|
|
185
|
-
|
|
186
|
-
input_tokens = metrics[:input_tokens] || 0
|
|
166
|
+
|
|
167
|
+
show_cost = AIA.config.flags.cost
|
|
168
|
+
show_similarity = metrics_list.any? { |m| m.key?(:similarity) }
|
|
169
|
+
total_input = 0
|
|
170
|
+
total_output = 0
|
|
171
|
+
total_cost = 0.0
|
|
172
|
+
max_elapsed = 0.0
|
|
173
|
+
|
|
174
|
+
header = %w[Model Input Output Total]
|
|
175
|
+
header += %w[Cost x1000] if show_cost
|
|
176
|
+
header << "Time"
|
|
177
|
+
header << "Sim" if show_similarity
|
|
178
|
+
|
|
179
|
+
rows = metrics_list.map do |metrics|
|
|
180
|
+
model_name = (metrics[:model_id] || metrics[:display_name]).to_s
|
|
181
|
+
input_tokens = metrics[:input_tokens] || 0
|
|
187
182
|
output_tokens = metrics[:output_tokens] || 0
|
|
188
|
-
total_tokens
|
|
189
|
-
|
|
190
|
-
|
|
183
|
+
total_tokens = input_tokens + output_tokens
|
|
184
|
+
elapsed = metrics[:elapsed]
|
|
185
|
+
time_str = format_elapsed(elapsed)
|
|
186
|
+
|
|
187
|
+
total_input += input_tokens
|
|
188
|
+
total_output += output_tokens
|
|
189
|
+
max_elapsed = [max_elapsed, elapsed || 0].max
|
|
190
|
+
|
|
191
|
+
row = [model_name, input_tokens, output_tokens, total_tokens]
|
|
192
|
+
|
|
193
|
+
if show_cost
|
|
191
194
|
cost_data = calculate_cost(metrics)
|
|
192
195
|
if cost_data[:available]
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
row << "$#{'%.5f' % cost_data[:total_cost]}"
|
|
197
|
+
row << "$#{'%.2f' % (cost_data[:total_cost] * 1000)}"
|
|
195
198
|
total_cost += cost_data[:total_cost]
|
|
196
199
|
else
|
|
197
|
-
|
|
198
|
-
x1000_str = "N/A"
|
|
200
|
+
row += ["N/A", "N/A"]
|
|
199
201
|
end
|
|
200
|
-
|
|
201
|
-
output_lines << sprintf("%-20s %10d %10d %10d %12s %10s",
|
|
202
|
-
model_name, input_tokens, output_tokens, total_tokens, cost_str, x1000_str)
|
|
203
|
-
else
|
|
204
|
-
output_lines << sprintf("%-20s %10d %10d %10d",
|
|
205
|
-
model_name, input_tokens, output_tokens, total_tokens)
|
|
206
202
|
end
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
# Display totals row
|
|
213
|
-
output_lines << "─" * table_width
|
|
214
|
-
total_tokens = total_input + total_output
|
|
215
|
-
|
|
216
|
-
if AIA.config.flags.cost && total_cost > 0
|
|
217
|
-
cost_str = "$#{'%.5f' % total_cost}"
|
|
218
|
-
x1000_str = "$#{'%.2f' % (total_cost * 1000)}"
|
|
219
|
-
output_lines << sprintf("%-20s %10d %10d %10d %12s %10s",
|
|
220
|
-
"TOTAL", total_input, total_output, total_tokens, cost_str, x1000_str)
|
|
221
|
-
else
|
|
222
|
-
output_lines << sprintf("%-20s %10d %10d %10d",
|
|
223
|
-
"TOTAL", total_input, total_output, total_tokens)
|
|
203
|
+
|
|
204
|
+
row << time_str
|
|
205
|
+
row << format_similarity(metrics[:similarity]) if show_similarity
|
|
206
|
+
row
|
|
224
207
|
end
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
208
|
+
|
|
209
|
+
# Totals row
|
|
210
|
+
all_tokens = total_input + total_output
|
|
211
|
+
total_time = format_elapsed(max_elapsed)
|
|
212
|
+
rows << :separator
|
|
213
|
+
|
|
214
|
+
totals = ["TOTAL", total_input, total_output, all_tokens]
|
|
215
|
+
if show_cost && total_cost.positive?
|
|
216
|
+
totals << "$#{'%.5f' % total_cost}"
|
|
217
|
+
totals << "$#{'%.2f' % (total_cost * 1000)}"
|
|
218
|
+
elsif show_cost
|
|
219
|
+
totals += ["", ""]
|
|
237
220
|
end
|
|
221
|
+
totals << total_time
|
|
222
|
+
totals << "" if show_similarity
|
|
223
|
+
rows << totals
|
|
224
|
+
|
|
225
|
+
alignments = %i[left right right right]
|
|
226
|
+
alignments += %i[right right] if show_cost
|
|
227
|
+
alignments << :right
|
|
228
|
+
alignments << :right if show_similarity
|
|
229
|
+
|
|
230
|
+
table = TTY::Table.new(header, rows)
|
|
231
|
+
rendered = table.render(:unicode, resize: true, alignments: alignments, padding: [0, 1])
|
|
232
|
+
|
|
233
|
+
puts "\nMulti-Model Token Usage"
|
|
234
|
+
puts rendered
|
|
235
|
+
write_to_output_file("Multi-Model Token Usage\n#{rendered}")
|
|
238
236
|
end
|
|
237
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
239
238
|
|
|
240
239
|
private
|
|
241
|
-
|
|
242
|
-
def
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
240
|
+
|
|
241
|
+
def save_chat_history
|
|
242
|
+
history_file = chat_history_file
|
|
243
|
+
return unless history_file
|
|
244
|
+
dir = File.dirname(history_file)
|
|
245
|
+
FileUtils.mkdir_p(dir)
|
|
246
|
+
|
|
247
|
+
entries = Reline::HISTORY.to_a.last(MAX_HISTORY)
|
|
248
|
+
File.write(history_file, entries.join("\n") + "\n")
|
|
250
249
|
end
|
|
251
|
-
|
|
252
|
-
def
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
total = metrics[:input_tokens] + metrics[:output_tokens]
|
|
259
|
-
lines << "Total tokens: #{total}"
|
|
250
|
+
|
|
251
|
+
def chat_history_file
|
|
252
|
+
config = AIA.config
|
|
253
|
+
if config.respond_to?(:output) && config.output.respond_to?(:history_file)
|
|
254
|
+
hf = config.output.history_file
|
|
255
|
+
return nil if hf == false # --no-history-file disables chat history
|
|
256
|
+
return File.expand_path(hf) if hf
|
|
260
257
|
end
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
def display_metrics_with_cost(metrics)
|
|
266
|
-
cost_data = calculate_cost(metrics)
|
|
267
|
-
|
|
268
|
-
if cost_data[:available]
|
|
269
|
-
puts "Input tokens: #{metrics[:input_tokens]} ($#{'%.5f' % cost_data[:input_cost]})"
|
|
270
|
-
puts "Output tokens: #{metrics[:output_tokens]} ($#{'%.5f' % cost_data[:output_cost]})"
|
|
271
|
-
puts "Total cost: $#{'%.5f' % cost_data[:total_cost]}"
|
|
272
|
-
puts "Cost x1000: $#{'%.2f' % (cost_data[:total_cost] * 1000)}"
|
|
273
|
-
else
|
|
274
|
-
puts "Input tokens: #{metrics[:input_tokens] || 'N/A'}"
|
|
275
|
-
puts "Output tokens: #{metrics[:output_tokens] || 'N/A'}"
|
|
276
|
-
total = (metrics[:input_tokens] || 0) + (metrics[:output_tokens] || 0)
|
|
277
|
-
puts "Total tokens: #{total}"
|
|
278
|
-
puts "Cost: N/A (pricing not available)"
|
|
258
|
+
if config.respond_to?(:paths) && config.paths.respond_to?(:aia_dir) && config.paths.aia_dir
|
|
259
|
+
return File.join(File.expand_path(config.paths.aia_dir), 'chat_history')
|
|
279
260
|
end
|
|
261
|
+
HISTORY_FILE
|
|
280
262
|
end
|
|
281
|
-
|
|
282
|
-
def
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
263
|
+
|
|
264
|
+
def format_similarity(score)
|
|
265
|
+
return "ref" if score.nil?
|
|
266
|
+
"%.1f%%" % (score * 100)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def format_elapsed(seconds)
|
|
270
|
+
return "" unless seconds
|
|
271
|
+
|
|
272
|
+
if seconds < 60
|
|
273
|
+
"%.1fs" % seconds
|
|
291
274
|
else
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
lines << "Total tokens: #{total}"
|
|
296
|
-
lines << "Cost: N/A (pricing not available)"
|
|
275
|
+
minutes = (seconds / 60).to_i
|
|
276
|
+
secs = seconds % 60
|
|
277
|
+
"#{minutes}m %04.1fs" % secs
|
|
297
278
|
end
|
|
298
|
-
|
|
299
|
-
lines
|
|
300
279
|
end
|
|
301
|
-
|
|
280
|
+
|
|
281
|
+
def write_to_output_file(text)
|
|
282
|
+
out_file = AIA.config.output.file
|
|
283
|
+
return unless out_file
|
|
284
|
+
|
|
285
|
+
File.open(out_file, 'a') { |f| f.puts text }
|
|
286
|
+
end
|
|
287
|
+
|
|
302
288
|
def calculate_cost(metrics)
|
|
303
289
|
return { available: false } unless metrics[:model_id] && metrics[:input_tokens] && metrics[:output_tokens]
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
input_price = model_info.input_price_per_million
|
|
311
|
-
output_price = model_info.output_price_per_million
|
|
312
|
-
|
|
313
|
-
return { available: false } unless input_price && output_price
|
|
314
|
-
|
|
315
|
-
input_cost = metrics[:input_tokens] * input_price / 1_000_000.0
|
|
316
|
-
output_cost = metrics[:output_tokens] * output_price / 1_000_000.0
|
|
317
|
-
total_cost = input_cost + output_cost
|
|
318
|
-
|
|
319
|
-
{
|
|
320
|
-
available: true,
|
|
321
|
-
input_cost: input_cost,
|
|
322
|
-
output_cost: output_cost,
|
|
323
|
-
total_cost: total_cost
|
|
324
|
-
}
|
|
325
|
-
rescue StandardError => e
|
|
326
|
-
{ available: false, error: e.message }
|
|
327
|
-
end
|
|
290
|
+
|
|
291
|
+
CostCalculator.calculate(
|
|
292
|
+
model_id: metrics[:model_id],
|
|
293
|
+
input_tokens: metrics[:input_tokens],
|
|
294
|
+
output_tokens: metrics[:output_tokens]
|
|
295
|
+
)
|
|
328
296
|
end
|
|
329
297
|
end
|
|
330
298
|
end
|