aia 0.9.7 → 0.9.9
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/.config/tocer/configuration.yml +2 -1
- data/.version +1 -1
- data/CHANGELOG.md +15 -1
- data/README.md +43 -0
- data/Rakefile +16 -8
- data/examples/directives/ask.rb +21 -0
- data/examples/tools/edit_file.rb +2 -0
- data/examples/tools/incomplete/calculator_tool.rb +70 -0
- data/examples/tools/incomplete/composite_analysis_tool.rb +89 -0
- data/examples/tools/incomplete/data_science_kit.rb +128 -0
- data/examples/tools/incomplete/database_query_tool.rb +100 -0
- data/examples/tools/incomplete/devops_toolkit.rb +112 -0
- data/examples/tools/incomplete/error_handling_tool.rb +109 -0
- data/examples/tools/incomplete/pdf_page_reader.rb +32 -0
- data/examples/tools/incomplete/secure_tool_template.rb +117 -0
- data/examples/tools/incomplete/weather_tool.rb +110 -0
- data/examples/tools/incomplete/workflow_manager_tool.rb +145 -0
- data/examples/tools/list_files.rb +2 -0
- data/examples/tools/mcp/README.md +1 -0
- data/examples/tools/mcp/github_mcp_server.rb +41 -0
- data/examples/tools/mcp/imcp.rb +15 -0
- data/examples/tools/read_file.rb +2 -0
- data/examples/tools/run_shell_command.rb +2 -0
- data/lib/aia/chat_processor_service.rb +3 -26
- data/lib/aia/config.rb +542 -414
- data/lib/aia/context_manager.rb +3 -8
- data/lib/aia/directive_processor.rb +24 -11
- data/lib/aia/ruby_llm_adapter.rb +78 -10
- data/lib/aia/session.rb +313 -215
- data/lib/aia/ui_presenter.rb +7 -5
- data/lib/aia/utility.rb +26 -6
- data/lib/aia.rb +5 -1
- metadata +32 -12
- data/lib/aia/shell_command_executor.rb +0 -109
data/lib/aia/context_manager.rb
CHANGED
@@ -26,14 +26,9 @@ module AIA
|
|
26
26
|
# @param system_prompt [String, nil] The system prompt to potentially prepend.
|
27
27
|
# @return [Array<Hash>] The conversation context array.
|
28
28
|
def get_context(system_prompt: nil)
|
29
|
-
#
|
30
|
-
if
|
31
|
-
|
32
|
-
(
|
33
|
-
@context.empty? ||
|
34
|
-
@context.first[:role] != 'system'
|
35
|
-
)
|
36
|
-
add_system_prompt(system_prompt)
|
29
|
+
# Add or replace system prompt if provided and not empty
|
30
|
+
if system_prompt && !system_prompt.strip.empty?
|
31
|
+
add_system_prompt(system_prompt)
|
37
32
|
end
|
38
33
|
@context
|
39
34
|
end
|
@@ -162,19 +162,19 @@ module AIA
|
|
162
162
|
spaces = " "*indent
|
163
163
|
width = TTY::Screen.width - indent - 2
|
164
164
|
|
165
|
-
if
|
165
|
+
if AIA.config.tools.empty?
|
166
|
+
puts "No tools are available"
|
167
|
+
else
|
166
168
|
puts
|
167
169
|
puts "Available Tools"
|
168
170
|
puts "==============="
|
169
171
|
|
170
|
-
AIA.config.tools.
|
171
|
-
|
172
|
-
puts "\n#{
|
173
|
-
puts "-"*
|
174
|
-
puts WordWrapper::MinimumRaggedness.new(width,
|
172
|
+
AIA.config.tools.each do |tool|
|
173
|
+
name = tool.respond_to?(:name) ? tool.name : tool.class.name
|
174
|
+
puts "\n#{name}"
|
175
|
+
puts "-"*name.size
|
176
|
+
puts WordWrapper::MinimumRaggedness.new(width, tool.description).wrap.split("\n").map{|s| spaces+s+"\n"}.join
|
175
177
|
end
|
176
|
-
else
|
177
|
-
puts "No tools configured"
|
178
178
|
end
|
179
179
|
puts
|
180
180
|
|
@@ -185,8 +185,10 @@ module AIA
|
|
185
185
|
def pipeline(args = [], context_manager=nil)
|
186
186
|
if args.empty?
|
187
187
|
ap AIA.config.pipeline
|
188
|
+
elsif 1 == args.size
|
189
|
+
AIA.config.pipeline += args.first.split(',').map(&:strip).reject{|id| id.empty?}
|
188
190
|
else
|
189
|
-
AIA.config.pipeline += args.map
|
191
|
+
AIA.config.pipeline += args.map{|id| id.gsub(',', '').strip}.reject{|id| id.empty?}
|
190
192
|
end
|
191
193
|
''
|
192
194
|
end
|
@@ -258,7 +260,15 @@ module AIA
|
|
258
260
|
|
259
261
|
desc "Shortcut for //config model _and_ //config model = value"
|
260
262
|
def model(args, context_manager=nil)
|
261
|
-
|
263
|
+
if args.empty?
|
264
|
+
puts
|
265
|
+
puts AIA.config.client.model.to_h.pretty_inspect
|
266
|
+
puts
|
267
|
+
else
|
268
|
+
send(:config, args.prepend('model'), context_manager)
|
269
|
+
end
|
270
|
+
|
271
|
+
return ''
|
262
272
|
end
|
263
273
|
|
264
274
|
desc "Shortcut for //config temperature _and_ //config temperature = value"
|
@@ -343,9 +353,12 @@ module AIA
|
|
343
353
|
counter = 0
|
344
354
|
|
345
355
|
RubyLLM.models.all.each do |llm|
|
356
|
+
cw = llm.context_window
|
357
|
+
caps = llm.capabilities.join(',')
|
346
358
|
inputs = llm.modalities.input.join(',')
|
347
359
|
outputs = llm.modalities.output.join(',')
|
348
|
-
|
360
|
+
mode = "#{inputs} to #{outputs}"
|
361
|
+
entry = "- #{llm.id} (#{llm.provider}) cw: #{cw} mode: #{mode} caps: #{caps}"
|
349
362
|
|
350
363
|
if query.nil? || query.empty?
|
351
364
|
counter += 1
|
data/lib/aia/ruby_llm_adapter.rb
CHANGED
@@ -66,31 +66,81 @@ module AIA
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
|
69
70
|
def setup_chat_with_tools
|
70
71
|
begin
|
71
|
-
@chat
|
72
|
+
@chat = RubyLLM.chat(model: @model)
|
73
|
+
@model = @chat.model.name if @model.nil? # using default model
|
72
74
|
rescue => e
|
73
75
|
STDERR.puts "ERROR: #{e.message}"
|
74
76
|
exit 1
|
75
77
|
end
|
76
78
|
|
77
|
-
|
79
|
+
unless @chat.model.supports_functions?
|
80
|
+
AIA.config.tools = []
|
81
|
+
AIA.config.tool_names = ""
|
82
|
+
return
|
83
|
+
end
|
84
|
+
|
85
|
+
load_tools
|
78
86
|
|
79
|
-
|
80
|
-
|
81
|
-
|
87
|
+
@chat.with_tools(*tools) unless tools.empty?
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def load_tools
|
92
|
+
@tools = []
|
93
|
+
|
94
|
+
support_local_tools
|
95
|
+
support_mcp
|
96
|
+
filter_tools_by_allowed_list
|
97
|
+
filter_tools_by_rejected_list
|
98
|
+
drop_duplicate_tools
|
99
|
+
|
100
|
+
if tools.empty?
|
101
|
+
AIA.config.tool_names = ""
|
102
|
+
else
|
103
|
+
AIA.config.tool_names = @tools.map(&:name).join(', ')
|
104
|
+
AIA.config.tools = @tools
|
82
105
|
end
|
106
|
+
end
|
107
|
+
|
83
108
|
|
84
|
-
|
109
|
+
def support_local_tools
|
110
|
+
@tools += ObjectSpace.each_object(Class).select do |klass|
|
85
111
|
klass < RubyLLM::Tool
|
86
112
|
end
|
113
|
+
end
|
114
|
+
|
87
115
|
|
88
|
-
|
89
|
-
|
90
|
-
|
116
|
+
def support_mcp
|
117
|
+
RubyLLM::MCP.establish_connection
|
118
|
+
@tools += RubyLLM::MCP.tools
|
119
|
+
rescue => e
|
120
|
+
STDERR.puts "Warning: Failed to connect MCP clients: #{e.message}"
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def drop_duplicate_tools
|
125
|
+
seen_names = Set.new
|
126
|
+
original_size = @tools.size
|
127
|
+
|
128
|
+
@tools.select! do |tool|
|
129
|
+
tool_name = tool.name
|
130
|
+
if seen_names.include?(tool_name)
|
131
|
+
STDERR.puts "WARNING: Duplicate tool name detected: '#{tool_name}'. Only the first occurrence will be used."
|
132
|
+
false
|
133
|
+
else
|
134
|
+
seen_names.add(tool_name)
|
135
|
+
true
|
136
|
+
end
|
91
137
|
end
|
138
|
+
|
139
|
+
removed_count = original_size - @tools.size
|
140
|
+
STDERR.puts "Removed #{removed_count} duplicate tools" if removed_count > 0
|
92
141
|
end
|
93
142
|
|
143
|
+
|
94
144
|
# TODO: Need to rethink this dispatcher pattern w/r/t RubyLLM's capabilities
|
95
145
|
# This code was originally designed for AiClient
|
96
146
|
#
|
@@ -117,7 +167,7 @@ module AIA
|
|
117
167
|
end
|
118
168
|
|
119
169
|
def transcribe(audio_file)
|
120
|
-
@chat.ask("Transcribe this audio", with: audio_file)
|
170
|
+
@chat.ask("Transcribe this audio", with: audio_file).content
|
121
171
|
end
|
122
172
|
|
123
173
|
def speak(text)
|
@@ -195,6 +245,24 @@ module AIA
|
|
195
245
|
|
196
246
|
private
|
197
247
|
|
248
|
+
def filter_tools_by_allowed_list
|
249
|
+
return if AIA.config.allowed_tools.nil?
|
250
|
+
|
251
|
+
@tools.select! do |tool|
|
252
|
+
tool_name = tool.respond_to?(:name) ? tool.name : tool.class.name
|
253
|
+
AIA.config.allowed_tools.any? { |allowed| tool_name.include?(allowed) }
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def filter_tools_by_rejected_list
|
258
|
+
return if AIA.config.rejected_tools.nil?
|
259
|
+
|
260
|
+
@tools.reject! do |tool|
|
261
|
+
tool_name = tool.respond_to?(:name) ? tool.name : tool.class.name
|
262
|
+
AIA.config.rejected_tools.any? { |rejected| tool_name.include?(rejected) }
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
198
266
|
def extract_model_parts
|
199
267
|
parts = AIA.config.model.split('/')
|
200
268
|
parts.map!(&:strip)
|