aia 0.9.11 → 0.9.12
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/.version +1 -1
- data/CHANGELOG.md +66 -2
- data/README.md +133 -4
- data/docs/advanced-prompting.md +721 -0
- data/docs/cli-reference.md +582 -0
- data/docs/configuration.md +347 -0
- data/docs/contributing.md +332 -0
- data/docs/directives-reference.md +490 -0
- data/docs/examples/index.md +277 -0
- data/docs/examples/mcp/index.md +479 -0
- data/docs/examples/prompts/analysis/index.md +78 -0
- data/docs/examples/prompts/automation/index.md +108 -0
- data/docs/examples/prompts/development/index.md +125 -0
- data/docs/examples/prompts/index.md +333 -0
- data/docs/examples/prompts/learning/index.md +127 -0
- data/docs/examples/prompts/writing/index.md +62 -0
- data/docs/examples/tools/index.md +292 -0
- data/docs/faq.md +414 -0
- data/docs/guides/available-models.md +366 -0
- data/docs/guides/basic-usage.md +477 -0
- data/docs/guides/chat.md +474 -0
- data/docs/guides/executable-prompts.md +417 -0
- data/docs/guides/first-prompt.md +454 -0
- data/docs/guides/getting-started.md +455 -0
- data/docs/guides/image-generation.md +507 -0
- data/docs/guides/index.md +46 -0
- data/docs/guides/models.md +507 -0
- data/docs/guides/tools.md +856 -0
- data/docs/index.md +173 -0
- data/docs/installation.md +238 -0
- data/docs/mcp-integration.md +612 -0
- data/docs/prompt_management.md +579 -0
- data/docs/security.md +629 -0
- data/docs/tools-and-mcp-examples.md +1186 -0
- data/docs/workflows-and-pipelines.md +563 -0
- data/examples/tools/mcp/github_mcp_server.json +11 -0
- data/examples/tools/mcp/imcp.json +7 -0
- data/lib/aia/chat_processor_service.rb +19 -3
- data/lib/aia/config/base.rb +224 -0
- data/lib/aia/config/cli_parser.rb +409 -0
- data/lib/aia/config/defaults.rb +88 -0
- data/lib/aia/config/file_loader.rb +131 -0
- data/lib/aia/config/validator.rb +184 -0
- data/lib/aia/config.rb +10 -860
- data/lib/aia/directive_processor.rb +27 -372
- data/lib/aia/directives/configuration.rb +114 -0
- data/lib/aia/directives/execution.rb +37 -0
- data/lib/aia/directives/models.rb +178 -0
- data/lib/aia/directives/registry.rb +120 -0
- data/lib/aia/directives/utility.rb +70 -0
- data/lib/aia/directives/web_and_file.rb +71 -0
- data/lib/aia/prompt_handler.rb +23 -3
- data/lib/aia/ruby_llm_adapter.rb +307 -128
- data/lib/aia/session.rb +27 -14
- data/lib/aia/utility.rb +12 -8
- data/lib/aia.rb +11 -2
- data/lib/extensions/ruby_llm/.irbrc +56 -0
- data/mkdocs.yml +165 -0
- metadata +77 -20
- /data/{images → docs/assets/images}/aia.png +0 -0
@@ -0,0 +1,856 @@
|
|
1
|
+
# Tools Integration
|
2
|
+
|
3
|
+
AIA's tools system extends AI capabilities with custom Ruby functions, enabling AI models to perform actions, access external services, and process data beyond text generation.
|
4
|
+
|
5
|
+
## Understanding Tools
|
6
|
+
|
7
|
+
### What are Tools?
|
8
|
+
Tools are Ruby classes that inherit from `RubyLLM::Tool` and provide specific capabilities to AI models:
|
9
|
+
- **File operations**: Read, write, analyze files
|
10
|
+
- **Web interactions**: HTTP requests, API calls, web scraping
|
11
|
+
- **Data processing**: Analysis, transformation, calculations
|
12
|
+
- **System integration**: Shell commands, external services
|
13
|
+
- **Custom logic**: Business-specific operations
|
14
|
+
|
15
|
+
### Tool Architecture
|
16
|
+
```ruby
|
17
|
+
class MyTool < RubyLLM::Tool
|
18
|
+
description "Brief description of what this tool does"
|
19
|
+
|
20
|
+
def tool_method(parameter1, parameter2 = nil)
|
21
|
+
# Tool implementation
|
22
|
+
"Result that gets returned to the AI"
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def helper_method
|
28
|
+
# Internal helper methods
|
29
|
+
end
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
## Using Existing Tools
|
34
|
+
|
35
|
+
### Enabling Tools
|
36
|
+
```bash
|
37
|
+
# Use tools from a specific file
|
38
|
+
aia --tools my_tool.rb my_prompt
|
39
|
+
|
40
|
+
# Use all tools in a directory
|
41
|
+
aia --tools ./tools/ my_prompt
|
42
|
+
|
43
|
+
# Use multiple tool sources
|
44
|
+
aia --tools "./tools/,./custom_tools.rb,/shared/tools/" my_prompt
|
45
|
+
```
|
46
|
+
|
47
|
+
### Tool Security
|
48
|
+
```bash
|
49
|
+
# Restrict to specific tools
|
50
|
+
aia --tools ./tools/ --allowed_tools "file_reader,calculator" my_prompt
|
51
|
+
|
52
|
+
# Block dangerous tools
|
53
|
+
aia --tools ./tools/ --rejected_tools "file_writer,system_admin" my_prompt
|
54
|
+
|
55
|
+
# Combine restrictions
|
56
|
+
aia --tools ./tools/ --allowed_tools "safe_tools" --rejected_tools "dangerous_tools" my_prompt
|
57
|
+
```
|
58
|
+
|
59
|
+
### Discovering Available Tools
|
60
|
+
```bash
|
61
|
+
# List available tools
|
62
|
+
aia --tools ./tools/ tool_discovery_prompt
|
63
|
+
|
64
|
+
# Or within a prompt
|
65
|
+
//tools
|
66
|
+
```
|
67
|
+
|
68
|
+
## Creating Custom Tools
|
69
|
+
|
70
|
+
### Basic Tool Structure
|
71
|
+
```ruby
|
72
|
+
# ~/.aia/tools/file_analyzer.rb
|
73
|
+
class FileAnalyzer < RubyLLM::Tool
|
74
|
+
description "Analyzes files for structure, content, and metadata"
|
75
|
+
|
76
|
+
def analyze_file(file_path, analysis_type = "basic")
|
77
|
+
return "File not found: #{file_path}" unless File.exist?(file_path)
|
78
|
+
|
79
|
+
case analysis_type
|
80
|
+
when "basic"
|
81
|
+
basic_analysis(file_path)
|
82
|
+
when "detailed"
|
83
|
+
detailed_analysis(file_path)
|
84
|
+
when "security"
|
85
|
+
security_analysis(file_path)
|
86
|
+
else
|
87
|
+
"Unknown analysis type: #{analysis_type}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def file_stats(file_path)
|
92
|
+
return "File not found: #{file_path}" unless File.exist?(file_path)
|
93
|
+
|
94
|
+
stat = File.stat(file_path)
|
95
|
+
{
|
96
|
+
size: stat.size,
|
97
|
+
created: stat.ctime,
|
98
|
+
modified: stat.mtime,
|
99
|
+
permissions: stat.mode.to_s(8),
|
100
|
+
type: File.directory?(file_path) ? "directory" : "file"
|
101
|
+
}.to_json
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def basic_analysis(file_path)
|
107
|
+
content = File.read(file_path)
|
108
|
+
lines = content.lines.count
|
109
|
+
words = content.split.count
|
110
|
+
chars = content.length
|
111
|
+
|
112
|
+
"File: #{File.basename(file_path)}\nLines: #{lines}\nWords: #{words}\nCharacters: #{chars}"
|
113
|
+
end
|
114
|
+
|
115
|
+
def detailed_analysis(file_path)
|
116
|
+
basic = basic_analysis(file_path)
|
117
|
+
content = File.read(file_path)
|
118
|
+
|
119
|
+
# Language detection
|
120
|
+
ext = File.extname(file_path).downcase
|
121
|
+
language = detect_language(ext, content)
|
122
|
+
|
123
|
+
# Additional analysis
|
124
|
+
encoding = content.encoding.to_s
|
125
|
+
blank_lines = content.lines.count(&:strip.empty?)
|
126
|
+
|
127
|
+
"#{basic}\nLanguage: #{language}\nEncoding: #{encoding}\nBlank lines: #{blank_lines}"
|
128
|
+
end
|
129
|
+
|
130
|
+
def security_analysis(file_path)
|
131
|
+
content = File.read(file_path)
|
132
|
+
issues = []
|
133
|
+
|
134
|
+
# Check for potential security issues
|
135
|
+
issues << "Contains potential passwords" if content.match?(/password\s*=\s*["'][^"']+["']/i)
|
136
|
+
issues << "Contains API keys" if content.match?(/api[_-]?key\s*[:=]\s*["'][^"']+["']/i)
|
137
|
+
issues << "Contains hardcoded URLs" if content.match?/https?:\/\/[^\s]+/
|
138
|
+
issues << "Contains TODO/FIXME items" if content.match?/(TODO|FIXME|HACK)/i
|
139
|
+
|
140
|
+
if issues.empty?
|
141
|
+
"No obvious security issues found in #{File.basename(file_path)}"
|
142
|
+
else
|
143
|
+
"Security concerns in #{File.basename(file_path)}:\n- #{issues.join("\n- ")}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def detect_language(ext, content)
|
148
|
+
case ext
|
149
|
+
when '.rb' then 'Ruby'
|
150
|
+
when '.py' then 'Python'
|
151
|
+
when '.js' then 'JavaScript'
|
152
|
+
when '.java' then 'Java'
|
153
|
+
when '.cpp', '.cc', '.cxx' then 'C++'
|
154
|
+
when '.c' then 'C'
|
155
|
+
when '.go' then 'Go'
|
156
|
+
when '.rs' then 'Rust'
|
157
|
+
else
|
158
|
+
# Simple heuristics based on content
|
159
|
+
return 'Ruby' if content.match?(/def\s+\w+|class\s+\w+|require ['"]/)
|
160
|
+
return 'Python' if content.match?(/def \w+\(|import \w+|from \w+ import/)
|
161
|
+
return 'JavaScript' if content.match?(/function\s+\w+|const\s+\w+|let\s+\w+/)
|
162
|
+
'Unknown'
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
### Web Integration Tool
|
169
|
+
```ruby
|
170
|
+
# ~/.aia/tools/web_client.rb
|
171
|
+
require 'net/http'
|
172
|
+
require 'json'
|
173
|
+
require 'uri'
|
174
|
+
|
175
|
+
class WebClient < RubyLLM::Tool
|
176
|
+
description "Performs HTTP requests and web API interactions"
|
177
|
+
|
178
|
+
def get_url(url, headers = {})
|
179
|
+
uri = URI(url)
|
180
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
181
|
+
http.use_ssl = true if uri.scheme == 'https'
|
182
|
+
|
183
|
+
request = Net::HTTP::Get.new(uri)
|
184
|
+
headers.each { |key, value| request[key] = value }
|
185
|
+
|
186
|
+
response = http.request(request)
|
187
|
+
|
188
|
+
{
|
189
|
+
status: response.code,
|
190
|
+
headers: response.to_hash,
|
191
|
+
body: response.body
|
192
|
+
}.to_json
|
193
|
+
end
|
194
|
+
|
195
|
+
def post_json(url, data, headers = {})
|
196
|
+
uri = URI(url)
|
197
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
198
|
+
http.use_ssl = true if uri.scheme == 'https'
|
199
|
+
|
200
|
+
request = Net::HTTP::Post.new(uri)
|
201
|
+
request['Content-Type'] = 'application/json'
|
202
|
+
headers.each { |key, value| request[key] = value }
|
203
|
+
request.body = data.to_json
|
204
|
+
|
205
|
+
response = http.request(request)
|
206
|
+
|
207
|
+
{
|
208
|
+
status: response.code,
|
209
|
+
body: response.body
|
210
|
+
}.to_json
|
211
|
+
end
|
212
|
+
|
213
|
+
def check_url_status(url)
|
214
|
+
uri = URI(url)
|
215
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
216
|
+
http.use_ssl = true if uri.scheme == 'https'
|
217
|
+
http.open_timeout = 10
|
218
|
+
http.read_timeout = 10
|
219
|
+
|
220
|
+
begin
|
221
|
+
response = http.head(uri.path.empty? ? '/' : uri.path)
|
222
|
+
"#{url}: #{response.code} #{response.message}"
|
223
|
+
rescue => e
|
224
|
+
"#{url}: Error - #{e.message}"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def fetch_api_data(endpoint, api_key = nil, params = {})
|
229
|
+
uri = URI(endpoint)
|
230
|
+
|
231
|
+
# Add query parameters
|
232
|
+
unless params.empty?
|
233
|
+
uri.query = params.map { |k, v| "#{k}=#{v}" }.join('&')
|
234
|
+
end
|
235
|
+
|
236
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
237
|
+
http.use_ssl = true if uri.scheme == 'https'
|
238
|
+
|
239
|
+
request = Net::HTTP::Get.new(uri)
|
240
|
+
request['Authorization'] = "Bearer #{api_key}" if api_key
|
241
|
+
request['User-Agent'] = 'AIA-Tools/1.0'
|
242
|
+
|
243
|
+
response = http.request(request)
|
244
|
+
|
245
|
+
if response.code == '200'
|
246
|
+
JSON.parse(response.body)
|
247
|
+
else
|
248
|
+
{ error: "API request failed", status: response.code, message: response.body }
|
249
|
+
end
|
250
|
+
rescue JSON::ParserError
|
251
|
+
{ error: "Invalid JSON response", raw_body: response.body }
|
252
|
+
rescue => e
|
253
|
+
{ error: e.message }
|
254
|
+
end
|
255
|
+
end
|
256
|
+
```
|
257
|
+
|
258
|
+
### Data Analysis Tool
|
259
|
+
```ruby
|
260
|
+
# ~/.aia/tools/data_analyzer.rb
|
261
|
+
require 'csv'
|
262
|
+
require 'json'
|
263
|
+
|
264
|
+
class DataAnalyzer < RubyLLM::Tool
|
265
|
+
description "Analyzes CSV data, JSON files, and performs statistical calculations"
|
266
|
+
|
267
|
+
def analyze_csv(file_path, delimiter = ',')
|
268
|
+
return "File not found: #{file_path}" unless File.exist?(file_path)
|
269
|
+
|
270
|
+
begin
|
271
|
+
data = CSV.read(file_path, headers: true, col_sep: delimiter)
|
272
|
+
|
273
|
+
analysis = {
|
274
|
+
rows: data.length,
|
275
|
+
columns: data.headers.length,
|
276
|
+
headers: data.headers,
|
277
|
+
sample_data: data.first(3).map(&:to_h),
|
278
|
+
column_types: analyze_column_types(data),
|
279
|
+
missing_values: count_missing_values(data)
|
280
|
+
}
|
281
|
+
|
282
|
+
JSON.pretty_generate(analysis)
|
283
|
+
rescue => e
|
284
|
+
"Error analyzing CSV: #{e.message}"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def calculate_statistics(file_path, column_name)
|
289
|
+
return "File not found: #{file_path}" unless File.exist?(file_path)
|
290
|
+
|
291
|
+
begin
|
292
|
+
data = CSV.read(file_path, headers: true)
|
293
|
+
values = data[column_name].compact.map(&:to_f)
|
294
|
+
|
295
|
+
return "Column not found or no numeric data" if values.empty?
|
296
|
+
|
297
|
+
stats = {
|
298
|
+
count: values.length,
|
299
|
+
mean: values.sum / values.length.to_f,
|
300
|
+
median: median(values),
|
301
|
+
min: values.min,
|
302
|
+
max: values.max,
|
303
|
+
range: values.max - values.min,
|
304
|
+
std_dev: standard_deviation(values)
|
305
|
+
}
|
306
|
+
|
307
|
+
JSON.pretty_generate(stats)
|
308
|
+
rescue => e
|
309
|
+
"Error calculating statistics: #{e.message}"
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def find_correlations(file_path, columns = nil)
|
314
|
+
return "File not found: #{file_path}" unless File.exist?(file_path)
|
315
|
+
|
316
|
+
begin
|
317
|
+
data = CSV.read(file_path, headers: true)
|
318
|
+
|
319
|
+
# Get numeric columns
|
320
|
+
numeric_columns = columns || data.headers.select do |header|
|
321
|
+
data[header].compact.all? { |value| numeric?(value) }
|
322
|
+
end
|
323
|
+
|
324
|
+
correlations = {}
|
325
|
+
|
326
|
+
numeric_columns.combination(2) do |col1, col2|
|
327
|
+
values1 = data[col1].compact.map(&:to_f)
|
328
|
+
values2 = data[col2].compact.map(&:to_f)
|
329
|
+
|
330
|
+
if values1.length == values2.length && values1.length > 1
|
331
|
+
corr = correlation(values1, values2)
|
332
|
+
correlations["#{col1} vs #{col2}"] = corr.round(4)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
JSON.pretty_generate(correlations)
|
337
|
+
rescue => e
|
338
|
+
"Error calculating correlations: #{e.message}"
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def json_summary(file_path)
|
343
|
+
return "File not found: #{file_path}" unless File.exist?(file_path)
|
344
|
+
|
345
|
+
begin
|
346
|
+
data = JSON.parse(File.read(file_path))
|
347
|
+
|
348
|
+
summary = {
|
349
|
+
type: data.class.name,
|
350
|
+
structure: analyze_json_structure(data),
|
351
|
+
size: data.respond_to?(:length) ? data.length : 1,
|
352
|
+
keys: data.is_a?(Hash) ? data.keys : nil
|
353
|
+
}
|
354
|
+
|
355
|
+
JSON.pretty_generate(summary)
|
356
|
+
rescue JSON::ParserError => e
|
357
|
+
"Invalid JSON file: #{e.message}"
|
358
|
+
rescue => e
|
359
|
+
"Error analyzing JSON: #{e.message}"
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
private
|
364
|
+
|
365
|
+
def analyze_column_types(data)
|
366
|
+
types = {}
|
367
|
+
data.headers.each do |header|
|
368
|
+
sample_values = data[header].compact.first(100)
|
369
|
+
|
370
|
+
if sample_values.all? { |v| numeric?(v) }
|
371
|
+
types[header] = 'numeric'
|
372
|
+
elsif sample_values.all? { |v| date_like?(v) }
|
373
|
+
types[header] = 'date'
|
374
|
+
else
|
375
|
+
types[header] = 'text'
|
376
|
+
end
|
377
|
+
end
|
378
|
+
types
|
379
|
+
end
|
380
|
+
|
381
|
+
def count_missing_values(data)
|
382
|
+
missing = {}
|
383
|
+
data.headers.each do |header|
|
384
|
+
missing_count = data[header].count { |v| v.nil? || v.strip.empty? }
|
385
|
+
missing[header] = missing_count if missing_count > 0
|
386
|
+
end
|
387
|
+
missing
|
388
|
+
end
|
389
|
+
|
390
|
+
def numeric?(value)
|
391
|
+
Float(value) rescue false
|
392
|
+
end
|
393
|
+
|
394
|
+
def date_like?(value)
|
395
|
+
Date.parse(value) rescue false
|
396
|
+
end
|
397
|
+
|
398
|
+
def median(values)
|
399
|
+
sorted = values.sort
|
400
|
+
len = sorted.length
|
401
|
+
len.even? ? (sorted[len/2 - 1] + sorted[len/2]) / 2.0 : sorted[len/2]
|
402
|
+
end
|
403
|
+
|
404
|
+
def standard_deviation(values)
|
405
|
+
mean = values.sum / values.length.to_f
|
406
|
+
variance = values.sum { |v| (v - mean) ** 2 } / values.length.to_f
|
407
|
+
Math.sqrt(variance)
|
408
|
+
end
|
409
|
+
|
410
|
+
def correlation(x, y)
|
411
|
+
n = x.length
|
412
|
+
sum_x = x.sum
|
413
|
+
sum_y = y.sum
|
414
|
+
sum_x2 = x.sum { |v| v ** 2 }
|
415
|
+
sum_y2 = y.sum { |v| v ** 2 }
|
416
|
+
sum_xy = x.zip(y).sum { |a, b| a * b }
|
417
|
+
|
418
|
+
numerator = n * sum_xy - sum_x * sum_y
|
419
|
+
denominator = Math.sqrt((n * sum_x2 - sum_x ** 2) * (n * sum_y2 - sum_y ** 2))
|
420
|
+
|
421
|
+
denominator == 0 ? 0 : numerator / denominator
|
422
|
+
end
|
423
|
+
|
424
|
+
def analyze_json_structure(data, max_depth = 3, current_depth = 0)
|
425
|
+
return "..." if current_depth >= max_depth
|
426
|
+
|
427
|
+
case data
|
428
|
+
when Hash
|
429
|
+
sample_keys = data.keys.first(5)
|
430
|
+
structure = {}
|
431
|
+
sample_keys.each do |key|
|
432
|
+
structure[key] = analyze_json_structure(data[key], max_depth, current_depth + 1)
|
433
|
+
end
|
434
|
+
structure["..."] = "#{data.keys.length - 5} more keys" if data.keys.length > 5
|
435
|
+
structure
|
436
|
+
when Array
|
437
|
+
return [] if data.empty?
|
438
|
+
[analyze_json_structure(data.first, max_depth, current_depth + 1)]
|
439
|
+
else
|
440
|
+
data.class.name
|
441
|
+
end
|
442
|
+
end
|
443
|
+
end
|
444
|
+
```
|
445
|
+
|
446
|
+
## Advanced Tool Patterns
|
447
|
+
|
448
|
+
### Tool with Configuration
|
449
|
+
```ruby
|
450
|
+
class ConfigurableTool < RubyLLM::Tool
|
451
|
+
description "Tool that can be configured for different environments"
|
452
|
+
|
453
|
+
def initialize
|
454
|
+
super
|
455
|
+
@config = load_config
|
456
|
+
end
|
457
|
+
|
458
|
+
def process_data(input, environment = 'development')
|
459
|
+
config = @config[environment]
|
460
|
+
# Use environment-specific configuration
|
461
|
+
process_with_config(input, config)
|
462
|
+
end
|
463
|
+
|
464
|
+
private
|
465
|
+
|
466
|
+
def load_config
|
467
|
+
config_file = File.join(Dir.home, '.aia', 'tool_config.yml')
|
468
|
+
if File.exist?(config_file)
|
469
|
+
YAML.load_file(config_file)
|
470
|
+
else
|
471
|
+
default_config
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
def default_config
|
476
|
+
{
|
477
|
+
'development' => { 'api_endpoint' => 'http://localhost:3000', 'timeout' => 30 },
|
478
|
+
'production' => { 'api_endpoint' => 'https://api.example.com', 'timeout' => 10 }
|
479
|
+
}
|
480
|
+
end
|
481
|
+
end
|
482
|
+
```
|
483
|
+
|
484
|
+
### Tool with Caching
|
485
|
+
```ruby
|
486
|
+
class CachedTool < RubyLLM::Tool
|
487
|
+
description "Tool with intelligent caching for expensive operations"
|
488
|
+
|
489
|
+
def expensive_operation(input)
|
490
|
+
cache_key = Digest::MD5.hexdigest(input)
|
491
|
+
cache_file = "/tmp/tool_cache_#{cache_key}.json"
|
492
|
+
|
493
|
+
if File.exist?(cache_file) && fresh_cache?(cache_file)
|
494
|
+
JSON.parse(File.read(cache_file))
|
495
|
+
else
|
496
|
+
result = perform_expensive_operation(input)
|
497
|
+
File.write(cache_file, result.to_json)
|
498
|
+
result
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
private
|
503
|
+
|
504
|
+
def fresh_cache?(cache_file, max_age = 3600)
|
505
|
+
(Time.now - File.mtime(cache_file)) < max_age
|
506
|
+
end
|
507
|
+
|
508
|
+
def perform_expensive_operation(input)
|
509
|
+
# Simulate expensive operation
|
510
|
+
sleep 2
|
511
|
+
{ result: "Processed: #{input}", timestamp: Time.now }
|
512
|
+
end
|
513
|
+
end
|
514
|
+
```
|
515
|
+
|
516
|
+
### Error-Resilient Tool
|
517
|
+
```ruby
|
518
|
+
class ResilientTool < RubyLLM::Tool
|
519
|
+
description "Tool with comprehensive error handling and recovery"
|
520
|
+
|
521
|
+
def reliable_operation(input, max_retries = 3)
|
522
|
+
attempts = 0
|
523
|
+
|
524
|
+
begin
|
525
|
+
attempts += 1
|
526
|
+
perform_operation(input)
|
527
|
+
rescue StandardError => e
|
528
|
+
if attempts <= max_retries
|
529
|
+
wait_time = 2 ** attempts # Exponential backoff
|
530
|
+
sleep wait_time
|
531
|
+
retry
|
532
|
+
else
|
533
|
+
{
|
534
|
+
error: true,
|
535
|
+
message: "Operation failed after #{max_retries} attempts",
|
536
|
+
last_error: e.message,
|
537
|
+
suggestion: "Try with simpler input or check system resources"
|
538
|
+
}.to_json
|
539
|
+
end
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
def safe_file_operation(file_path)
|
544
|
+
return "File path required" if file_path.nil? || file_path.empty?
|
545
|
+
return "File not found: #{file_path}" unless File.exist?(file_path)
|
546
|
+
return "Access denied: #{file_path}" unless File.readable?(file_path)
|
547
|
+
|
548
|
+
begin
|
549
|
+
File.read(file_path)
|
550
|
+
rescue => e
|
551
|
+
"Error reading file: #{e.message}"
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
private
|
556
|
+
|
557
|
+
def perform_operation(input)
|
558
|
+
# Simulate operation that might fail
|
559
|
+
raise "Random failure" if rand < 0.3
|
560
|
+
{ success: true, data: "Processed #{input}" }
|
561
|
+
end
|
562
|
+
end
|
563
|
+
```
|
564
|
+
|
565
|
+
## Tool Integration in Prompts
|
566
|
+
|
567
|
+
### Basic Tool Usage
|
568
|
+
```markdown
|
569
|
+
# ~/.prompts/file_analysis.txt
|
570
|
+
//tools file_analyzer.rb
|
571
|
+
|
572
|
+
# File Analysis Report
|
573
|
+
|
574
|
+
Please analyze the following file:
|
575
|
+
File path: <%= file_path %>
|
576
|
+
|
577
|
+
Use the file_analyzer tool to:
|
578
|
+
1. Get basic file statistics
|
579
|
+
2. Perform detailed content analysis
|
580
|
+
3. Check for security issues
|
581
|
+
|
582
|
+
Provide a comprehensive report with recommendations.
|
583
|
+
```
|
584
|
+
|
585
|
+
### Multi-Tool Workflows
|
586
|
+
```markdown
|
587
|
+
# ~/.prompts/web_data_analysis.txt
|
588
|
+
//tools web_client.rb,data_analyzer.rb
|
589
|
+
|
590
|
+
# Web Data Analysis Pipeline
|
591
|
+
|
592
|
+
Data source URL: <%= api_url %>
|
593
|
+
API key: <%= api_key %>
|
594
|
+
|
595
|
+
## Step 1: Fetch Data
|
596
|
+
Use the web_client tool to retrieve data from the API endpoint.
|
597
|
+
|
598
|
+
## Step 2: Save and Analyze
|
599
|
+
Save the data to a temporary CSV file and use data_analyzer to:
|
600
|
+
- Generate summary statistics
|
601
|
+
- Identify data patterns
|
602
|
+
- Find correlations
|
603
|
+
|
604
|
+
## Step 3: Generate Insights
|
605
|
+
Based on the analysis, provide actionable insights and recommendations.
|
606
|
+
```
|
607
|
+
|
608
|
+
### Conditional Tool Usage
|
609
|
+
```ruby
|
610
|
+
# ~/.prompts/adaptive_analysis.txt
|
611
|
+
//ruby
|
612
|
+
input_file = '<%= input_file %>'
|
613
|
+
file_ext = File.extname(input_file).downcase
|
614
|
+
|
615
|
+
case file_ext
|
616
|
+
when '.csv'
|
617
|
+
puts "//tools data_analyzer.rb"
|
618
|
+
analysis_type = "CSV data analysis"
|
619
|
+
when '.json'
|
620
|
+
puts "//tools data_analyzer.rb"
|
621
|
+
analysis_type = "JSON structure analysis"
|
622
|
+
when '.rb', '.py', '.js'
|
623
|
+
puts "//tools file_analyzer.rb"
|
624
|
+
analysis_type = "Code analysis"
|
625
|
+
else
|
626
|
+
puts "//tools file_analyzer.rb"
|
627
|
+
analysis_type = "General file analysis"
|
628
|
+
end
|
629
|
+
|
630
|
+
puts "Selected #{analysis_type} for #{file_ext} file"
|
631
|
+
```
|
632
|
+
|
633
|
+
Perform #{analysis_type} on: <%= input_file %>
|
634
|
+
|
635
|
+
Provide detailed insights appropriate for the file type.
|
636
|
+
```
|
637
|
+
|
638
|
+
## Tool Security and Best Practices
|
639
|
+
|
640
|
+
### Security Guidelines
|
641
|
+
1. **Input Validation**: Always validate tool inputs
|
642
|
+
2. **File System Access**: Limit file access to safe directories
|
643
|
+
3. **Network Requests**: Validate URLs and handle errors
|
644
|
+
4. **Resource Limits**: Implement timeouts and size limits
|
645
|
+
5. **Error Handling**: Never expose system details in errors
|
646
|
+
|
647
|
+
### Performance Considerations
|
648
|
+
1. **Caching**: Cache expensive operations appropriately
|
649
|
+
2. **Timeouts**: Set reasonable timeouts for external calls
|
650
|
+
3. **Memory Management**: Handle large data sets efficiently
|
651
|
+
4. **Async Operations**: Use async patterns for I/O operations
|
652
|
+
5. **Resource Cleanup**: Properly clean up resources
|
653
|
+
|
654
|
+
### Testing Tools
|
655
|
+
```ruby
|
656
|
+
# test_tool.rb
|
657
|
+
require 'minitest/autorun'
|
658
|
+
require_relative 'my_tool'
|
659
|
+
|
660
|
+
class TestMyTool < Minitest::Test
|
661
|
+
def setup
|
662
|
+
@tool = MyTool.new
|
663
|
+
end
|
664
|
+
|
665
|
+
def test_basic_functionality
|
666
|
+
result = @tool.process_data("test input")
|
667
|
+
assert_kind_of String, result
|
668
|
+
refute_empty result
|
669
|
+
end
|
670
|
+
|
671
|
+
def test_error_handling
|
672
|
+
result = @tool.process_data(nil)
|
673
|
+
assert_includes result, "error"
|
674
|
+
end
|
675
|
+
end
|
676
|
+
```
|
677
|
+
|
678
|
+
## Tool Distribution and Sharing
|
679
|
+
|
680
|
+
### Tool Libraries
|
681
|
+
```bash
|
682
|
+
# Organize tools in libraries
|
683
|
+
~/.aia/tools/
|
684
|
+
├── core/ # Essential tools
|
685
|
+
│ ├── file_ops.rb
|
686
|
+
│ ├── web_client.rb
|
687
|
+
│ └── data_analysis.rb
|
688
|
+
├── development/ # Development tools
|
689
|
+
│ ├── code_analyzer.rb
|
690
|
+
│ ├── test_runner.rb
|
691
|
+
│ └── deploy_helper.rb
|
692
|
+
└── specialized/ # Domain-specific tools
|
693
|
+
├── finance_tools.rb
|
694
|
+
├── media_tools.rb
|
695
|
+
└── science_tools.rb
|
696
|
+
```
|
697
|
+
|
698
|
+
### Tool Documentation
|
699
|
+
```ruby
|
700
|
+
class DocumentedTool < RubyLLM::Tool
|
701
|
+
description "Example of well-documented tool with usage examples"
|
702
|
+
|
703
|
+
# Processes input data with specified options
|
704
|
+
# @param input [String] The input data to process
|
705
|
+
# @param format [String] Output format: 'json', 'csv', or 'text'
|
706
|
+
# @param options [Hash] Additional processing options
|
707
|
+
# @return [String] Processed data in specified format
|
708
|
+
# @example
|
709
|
+
# process_data("sample", "json", { detailed: true })
|
710
|
+
def process_data(input, format = 'text', options = {})
|
711
|
+
# Implementation
|
712
|
+
end
|
713
|
+
end
|
714
|
+
```
|
715
|
+
|
716
|
+
## Troubleshooting Tools
|
717
|
+
|
718
|
+
### Common Issues
|
719
|
+
1. **Tool Not Found**: Check tool file paths and syntax
|
720
|
+
2. **Permission Errors**: Verify file permissions and access rights
|
721
|
+
3. **Missing Dependencies**: Install required gems and libraries
|
722
|
+
4. **Method Errors**: Check method signatures and parameters
|
723
|
+
5. **Runtime Errors**: Add proper error handling and logging
|
724
|
+
|
725
|
+
### Debugging Tools
|
726
|
+
```ruby
|
727
|
+
class DebuggingTool < RubyLLM::Tool
|
728
|
+
description "Tool with extensive debugging capabilities"
|
729
|
+
|
730
|
+
def debug_operation(input)
|
731
|
+
debug_log("Starting operation with input: #{input}")
|
732
|
+
|
733
|
+
begin
|
734
|
+
result = process_input(input)
|
735
|
+
debug_log("Operation successful: #{result}")
|
736
|
+
result
|
737
|
+
rescue => e
|
738
|
+
debug_log("Operation failed: #{e.message}")
|
739
|
+
debug_log("Backtrace: #{e.backtrace.first(5).join("\n")}")
|
740
|
+
raise
|
741
|
+
end
|
742
|
+
end
|
743
|
+
|
744
|
+
private
|
745
|
+
|
746
|
+
def debug_log(message)
|
747
|
+
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
748
|
+
puts "[DEBUG #{timestamp}] #{message}" if debug_mode?
|
749
|
+
end
|
750
|
+
|
751
|
+
def debug_mode?
|
752
|
+
ENV['AIA_DEBUG'] == 'true'
|
753
|
+
end
|
754
|
+
end
|
755
|
+
```
|
756
|
+
|
757
|
+
## MCP Client Integration
|
758
|
+
|
759
|
+
### Model Context Protocol (MCP) Support
|
760
|
+
|
761
|
+
AIA supports MCP clients for extended functionality through external services:
|
762
|
+
|
763
|
+
#### GitHub MCP Server
|
764
|
+
```bash
|
765
|
+
# Install GitHub MCP server
|
766
|
+
brew install github-mcp-server
|
767
|
+
|
768
|
+
# Set required environment variable
|
769
|
+
export GITHUB_PERSONAL_ACCESS_TOKEN="your_token_here"
|
770
|
+
|
771
|
+
# Use with AIA
|
772
|
+
aia --tools examples/tools/mcp/github_mcp_server.rb --chat
|
773
|
+
```
|
774
|
+
|
775
|
+
**Capabilities:**
|
776
|
+
- Repository analysis and management
|
777
|
+
- Issue tracking and manipulation
|
778
|
+
- Pull request automation
|
779
|
+
- Code review assistance
|
780
|
+
- Project metrics and insights
|
781
|
+
|
782
|
+
#### iMCP for macOS
|
783
|
+
```bash
|
784
|
+
# Install iMCP (macOS only)
|
785
|
+
brew install --cask loopwork/tap/iMCP
|
786
|
+
|
787
|
+
# Use with AIA
|
788
|
+
aia --tools examples/tools/mcp/imcp.rb --chat
|
789
|
+
```
|
790
|
+
|
791
|
+
**Capabilities:**
|
792
|
+
- Access to macOS Notes app
|
793
|
+
- Calendar integration
|
794
|
+
- Contacts management
|
795
|
+
- System information access
|
796
|
+
- File system operations
|
797
|
+
|
798
|
+
### MCP Client Requirements
|
799
|
+
|
800
|
+
MCP clients require:
|
801
|
+
- The `ruby_llm-mcp` gem (automatically included with AIA)
|
802
|
+
- Proper MCP server installation and configuration
|
803
|
+
- Required environment variables and permissions
|
804
|
+
- Network access for external MCP servers
|
805
|
+
|
806
|
+
## Shared Tools Collection
|
807
|
+
|
808
|
+
### Using the Shared Tools Gem
|
809
|
+
|
810
|
+
AIA can use the [shared_tools gem](https://github.com/madbomber/shared_tools) for common functionality:
|
811
|
+
|
812
|
+
```bash
|
813
|
+
# Access all shared tools (included with AIA)
|
814
|
+
aia --require shared_tools/ruby_llm --chat
|
815
|
+
|
816
|
+
# Access specific shared tool
|
817
|
+
aia --require shared_tools/ruby_llm/edit_file --chat
|
818
|
+
|
819
|
+
# Combine with custom tools
|
820
|
+
aia --require shared_tools/ruby_llm --tools ~/my-tools/ --chat
|
821
|
+
|
822
|
+
# Use in batch prompts
|
823
|
+
aia --require shared_tools/ruby_llm my_prompt input.txt
|
824
|
+
```
|
825
|
+
|
826
|
+
### Available Shared Tools
|
827
|
+
|
828
|
+
The shared_tools collection includes:
|
829
|
+
- **File Operations**: Reading, writing, editing files
|
830
|
+
- **Data Processing**: JSON/CSV manipulation, data transformation
|
831
|
+
- **Web Operations**: HTTP requests, web scraping
|
832
|
+
- **System Operations**: Process management, system information
|
833
|
+
- **Utility Functions**: String processing, date manipulation
|
834
|
+
|
835
|
+
### ERB Integration with Shared Tools
|
836
|
+
|
837
|
+
```ruby
|
838
|
+
# In prompt files with ERB
|
839
|
+
//ruby
|
840
|
+
require 'shared_tools/ruby_llm'
|
841
|
+
```
|
842
|
+
|
843
|
+
Use shared tools directly within your prompts using Ruby directives.
|
844
|
+
|
845
|
+
## Related Documentation
|
846
|
+
|
847
|
+
- [Chat Mode](chat.md) - Using tools in interactive mode
|
848
|
+
- [Advanced Prompting](../advanced-prompting.md) - Complex tool integration patterns
|
849
|
+
- [MCP Integration](../mcp-integration.md) - Model Context Protocol details
|
850
|
+
- [Configuration](../configuration.md) - Tool configuration options
|
851
|
+
- [CLI Reference](../cli-reference.md) - Tool-related command-line options
|
852
|
+
- [Examples](../examples/tools/index.md) - Real-world tool examples
|
853
|
+
|
854
|
+
---
|
855
|
+
|
856
|
+
Tools transform AIA from a text processor into a powerful automation platform. Start with simple tools and gradually build more sophisticated capabilities as your needs grow!
|