aia 1.1.0 → 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.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +5 -1
  3. data/.loki +231 -0
  4. data/.quality/flay_baseline.txt +1 -0
  5. data/.quality/flog_baseline.txt +29 -0
  6. data/.quality/reek_baseline.txt +80 -0
  7. data/.rubocop.yml +116 -0
  8. data/.version +1 -1
  9. data/CHANGELOG.md +266 -42
  10. data/IMPLEMENTATION_PLAN.md +506 -0
  11. data/README.md +266 -238
  12. data/Rakefile +118 -5
  13. data/architecture_review.md +314 -0
  14. data/bin/aia +16 -0
  15. data/docs/AGENTS.md +40 -0
  16. data/docs/advanced-prompting.md +67 -3
  17. data/docs/cli-reference.md +312 -56
  18. data/docs/configuration.md +130 -19
  19. data/docs/contributing.md +56 -2
  20. data/docs/directives-reference.md +593 -78
  21. data/docs/faq.md +85 -3
  22. data/docs/guides/available-models.md +1 -1
  23. data/docs/guides/basic-usage.md +6 -6
  24. data/docs/guides/chat.md +40 -16
  25. data/docs/guides/crew.md +239 -0
  26. data/docs/guides/executable-prompts.md +1 -1
  27. data/docs/guides/index.md +1 -0
  28. data/docs/guides/models.md +15 -0
  29. data/docs/index.md +29 -2
  30. data/docs/installation.md +44 -17
  31. data/docs/mcp-integration.md +40 -0
  32. data/docs/prompt_management.md +85 -86
  33. data/docs/security.md +47 -0
  34. data/docs/special_projects_guide.md +386 -0
  35. data/docs/tools-and-mcp-examples.md +23 -0
  36. data/docs/workflows-and-pipelines.md +84 -7
  37. data/examples/.gitignore +1 -0
  38. data/examples/00_setup_aia.sh +27 -44
  39. data/examples/11_multi_model.sh +4 -14
  40. data/examples/12_token_usage.sh +3 -12
  41. data/examples/18_tools.sh +10 -2
  42. data/examples/22_chat_mode.sh +0 -10
  43. data/examples/23_verify.sh +139 -0
  44. data/examples/24_decompose.sh +139 -0
  45. data/examples/25_spawn.sh +139 -0
  46. data/examples/26_debate.sh +97 -0
  47. data/examples/27_mention_routing.sh +157 -0
  48. data/examples/28_model_switching.sh +106 -0
  49. data/examples/29_agent_harness.sh +177 -0
  50. data/examples/README.md +65 -0
  51. data/examples/advanced_multi_robot_capabilities_without_examples.md +106 -0
  52. data/examples/aia_config.yml +1 -1
  53. data/examples/aia_config_orchestrator.yml +45 -0
  54. data/examples/common.sh +19 -0
  55. data/examples/context/tech_stack.md +2 -2
  56. data/examples/prompts_dir/project_summary +2 -2
  57. data/examples/prompts_dir/roles/orchestrator.md +21 -0
  58. data/examples/requirements/sinatra_taskflow_app.md +139 -0
  59. data/examples/rules/01_classify_ruby.rb +16 -0
  60. data/examples/rules/02_prefer_claude_for_code.rb +19 -0
  61. data/examples/rules/03_gate_prompt_length.rb +19 -0
  62. data/examples/rules/04_tool_selection.rb +41 -0
  63. data/examples/rules/README.md +30 -0
  64. data/examples/run_all.sh +48 -15
  65. data/examples/tools/word_count_tool.rb +1 -1
  66. data/lib/AGENTS.md +57 -0
  67. data/lib/aia/chat_loop.rb +306 -159
  68. data/lib/aia/config/cli_parser.rb +174 -111
  69. data/lib/aia/config/defaults.yml +62 -33
  70. data/lib/aia/config/mcp_parser.rb +39 -46
  71. data/lib/aia/config/model_spec.rb +34 -2
  72. data/lib/aia/config/validator.rb +121 -138
  73. data/lib/aia/config.rb +110 -145
  74. data/lib/aia/content_extractor.rb +153 -0
  75. data/lib/aia/cost_calculator.rb +38 -0
  76. data/lib/aia/crew.rb +164 -0
  77. data/lib/aia/debate_handler.rb +166 -0
  78. data/lib/aia/delegate_handler.rb +112 -0
  79. data/lib/aia/directive.rb +33 -18
  80. data/lib/aia/directive_processor.rb +16 -7
  81. data/lib/aia/directives/configuration_directives.rb +160 -20
  82. data/lib/aia/directives/context_directives.rb +38 -26
  83. data/lib/aia/directives/execution_directives.rb +136 -4
  84. data/lib/aia/directives/model_directives.rb +76 -34
  85. data/lib/aia/directives/trakflow_directives.rb +44 -0
  86. data/lib/aia/directives/utility_directives.rb +203 -6
  87. data/lib/aia/directives/web_and_file_directives.rb +96 -60
  88. data/lib/aia/errors.rb +15 -0
  89. data/lib/aia/fact_asserter.rb +27 -0
  90. data/lib/aia/fzf.rb +9 -31
  91. data/lib/aia/handler_context.rb +17 -0
  92. data/lib/aia/handler_protocol.rb +19 -0
  93. data/lib/aia/history_transfer.rb +55 -0
  94. data/lib/aia/input_collector.rb +3 -3
  95. data/lib/aia/layered_orchestrator.rb +448 -0
  96. data/lib/aia/logger.rb +24 -4
  97. data/lib/aia/mcp_config_normalizer.rb +35 -0
  98. data/lib/aia/mcp_connection_manager.rb +305 -0
  99. data/lib/aia/mcp_discovery.rb +44 -0
  100. data/lib/aia/mcp_grouper.rb +33 -0
  101. data/lib/aia/mcp_utility.rb +57 -0
  102. data/lib/aia/mention_router.rb +260 -0
  103. data/lib/aia/model_alias_registry.rb +97 -0
  104. data/lib/aia/model_switch_handler.rb +100 -0
  105. data/lib/aia/network_builder.rb +155 -0
  106. data/lib/aia/network_memory_manager.rb +55 -0
  107. data/lib/aia/patches/ruby_llm_streaming_error.rb +43 -0
  108. data/lib/aia/patches/ruby_llm_tool_error.rb +96 -0
  109. data/lib/aia/pipeline_orchestrator.rb +262 -0
  110. data/lib/aia/plugin_loader.rb +170 -0
  111. data/lib/aia/plugin_monitor.rb +208 -0
  112. data/lib/aia/prompt_decomposer.rb +157 -0
  113. data/lib/aia/prompt_handler.rb +19 -39
  114. data/lib/aia/robot_builder.rb +51 -0
  115. data/lib/aia/robot_factory.rb +334 -0
  116. data/lib/aia/robot_namer.rb +116 -0
  117. data/lib/aia/session.rb +83 -17
  118. data/lib/aia/session_tracker.rb +209 -0
  119. data/lib/aia/similarity_scorer.rb +39 -0
  120. data/lib/aia/skill_utils.rb +105 -1
  121. data/lib/aia/spawn_handler.rb +129 -0
  122. data/lib/aia/spawn_spec_parser.rb +65 -0
  123. data/lib/aia/special_mode_handler.rb +302 -0
  124. data/lib/aia/startup_coordinator.rb +150 -0
  125. data/lib/aia/streaming_runner.rb +169 -0
  126. data/lib/aia/system_prompt_assembler.rb +88 -0
  127. data/lib/aia/task_coordinator.rb +202 -0
  128. data/lib/aia/task_decomposer.rb +57 -0
  129. data/lib/aia/task_executor.rb +51 -0
  130. data/lib/aia/tfidf_math.rb +27 -0
  131. data/lib/aia/tool_filter/tfidf.rb +116 -0
  132. data/lib/aia/tool_filter/wordnet_expander.rb +127 -0
  133. data/lib/aia/tool_filter.rb +82 -0
  134. data/lib/aia/tool_filter_registry.rb +30 -0
  135. data/lib/aia/tool_filter_strategy.rb +143 -0
  136. data/lib/aia/tool_loader.rb +210 -0
  137. data/lib/aia/tool_utility.rb +30 -0
  138. data/lib/aia/tools/delegate_to_foreman_tool.rb +70 -0
  139. data/lib/aia/tools/recruit_robot_tool.rb +60 -0
  140. data/lib/aia/tools/reskill_robot_tool.rb +44 -0
  141. data/lib/aia/tools/task_board_tool.rb +114 -0
  142. data/lib/aia/trakflow_bridge.rb +173 -0
  143. data/lib/aia/turn_state.rb +94 -0
  144. data/lib/aia/ui_presenter.rb +166 -198
  145. data/lib/aia/utility.rb +134 -87
  146. data/lib/aia/{history_manager.rb → variable_input_collector.rb} +8 -9
  147. data/lib/aia/verification_network.rb +58 -0
  148. data/lib/aia.rb +108 -63
  149. data/mkdocs.yml +1 -0
  150. metadata +179 -56
  151. data/justfile +0 -215
  152. data/lib/aia/adapter/chat_execution.rb +0 -242
  153. data/lib/aia/adapter/error_handler.rb +0 -68
  154. data/lib/aia/adapter/gem_activator.rb +0 -57
  155. data/lib/aia/adapter/mcp_connector.rb +0 -274
  156. data/lib/aia/adapter/modality_handlers.rb +0 -167
  157. data/lib/aia/adapter/model_registry.rb +0 -81
  158. data/lib/aia/adapter/multi_model_chat.rb +0 -218
  159. data/lib/aia/adapter/provider_configurator.rb +0 -59
  160. data/lib/aia/adapter/tool_filter.rb +0 -85
  161. data/lib/aia/adapter/tool_loader.rb +0 -90
  162. data/lib/aia/chat_processor_service.rb +0 -164
  163. data/lib/aia/prompt_pipeline.rb +0 -183
  164. data/lib/aia/ruby_llm_adapter.rb +0 -95
  165. data/lib/extensions/openstruct_merge.rb +0 -48
  166. data/lib/extensions/ruby_llm/.irbrc +0 -56
  167. data/lib/extensions/ruby_llm/modalities.rb +0 -36
  168. data/lib/extensions/ruby_llm/provider_fix.rb +0 -79
  169. data/lib/refinements/string.rb +0 -16
  170. data/main.just +0 -76
@@ -1,164 +0,0 @@
1
- # lib/aia/chat_processor_service.rb
2
-
3
- module AIA
4
- class ChatProcessorService
5
- def initialize(ui_presenter, directive_processor = nil)
6
- @ui_presenter = ui_presenter
7
- @speaker = AIA.speak? ? AiClient.new(AIA.config.audio.speech_model) : nil
8
- @directive_processor = directive_processor
9
- end
10
-
11
-
12
- def speak(text)
13
- return unless AIA.speak?
14
-
15
- @speaker ||= AiClient.new(AIA.config.audio.speech_model) if AIA.config.audio.speech_model
16
-
17
- if @speaker
18
- system(AIA.config.audio.speak_command, @speaker.speak(text).path)
19
- else
20
- warn "Warning: Unable to speak. Speech model not configured properly."
21
- end
22
- end
23
-
24
-
25
- def process_prompt(prompt)
26
- result = nil
27
- @ui_presenter.with_spinner("Processing", determine_operation_type) do
28
- result = send_to_client(prompt)
29
- end
30
-
31
- # Debug output to understand what we're receiving
32
- logger.debug("Result received", result_class: result.class.name)
33
- logger.debug("Result details", inspect: result.inspect[0..500])
34
-
35
- # Preserve token information if available for metrics
36
- if result.is_a?(String)
37
- logger.debug("Processing result", type: "String")
38
- { content: result, metrics: nil }
39
- elsif result.respond_to?(:multi_model?) && result.multi_model?
40
- logger.debug("Processing result", type: "multi-model response")
41
- # Handle multi-model response with metrics
42
- {
43
- content: result.content,
44
- metrics: nil, # Individual model metrics handled separately
45
- multi_metrics: result.metrics_list
46
- }
47
- elsif result.respond_to?(:content)
48
- logger.debug("Processing result", type: "standard response with content method")
49
- # Standard response object with content method
50
- {
51
- content: result.content,
52
- metrics: {
53
- input_tokens: result.respond_to?(:input_tokens) ? result.input_tokens : nil,
54
- output_tokens: result.respond_to?(:output_tokens) ? result.output_tokens : nil,
55
- model_id: result.respond_to?(:model_id) ? result.model_id : nil
56
- }
57
- }
58
- else
59
- logger.debug("Processing result", type: "fallback (unexpected type)")
60
- # Fallback for unexpected response types
61
- { content: result.to_s, metrics: nil }
62
- end
63
- end
64
-
65
-
66
- # conversation is an Array of Hashes (single model) or Hash of Arrays (multi-model per-model contexts)
67
- # Each entry is an interchange with the LLM.
68
- def send_to_client(conversation_or_conversations)
69
- maybe_change_model
70
-
71
- # Handle per-model conversations (Hash) or single conversation (Array) - ADR-002 revised
72
- if conversation_or_conversations.is_a?(Hash)
73
- # Multi-model with per-model contexts: pass Hash directly to adapter
74
- logger.debug("Sending per-model conversations to client")
75
- result = AIA.client.chat(conversation_or_conversations)
76
- else
77
- # Single conversation for single model
78
- logger.debug("Sending conversation to client", conversation: conversation_or_conversations.inspect[0..500])
79
- result = AIA.client.chat(conversation_or_conversations)
80
- end
81
-
82
- logger.debug("Client returned", result_class: result.class.name, result: result.inspect[0..500])
83
- result
84
- end
85
-
86
-
87
- def maybe_change_model
88
- # With multiple models, we don't need to change the model in the same way
89
- # The RubyLLMAdapter now handles multiple models internally
90
- # This method is kept for backward compatibility but may not be needed
91
- models = AIA.config.models
92
- return if models.is_a?(Array) && models.size > 1
93
-
94
- return unless AIA.client.respond_to?(:model) && AIA.client.model.respond_to?(:id)
95
- client_model = AIA.client.model.id
96
-
97
- # Get the first model name for comparison
98
- first_model = models.first
99
- model_name = first_model.respond_to?(:name) ? first_model.name : first_model.to_s
100
-
101
- # client_model is the full resolved ID (e.g. "claude-sonnet-4-20250514"),
102
- # model_name is the configured alias (e.g. "claude-sonnet-4").
103
- # The alias is always a prefix/substring of the resolved ID, so check that way.
104
- unless client_model.downcase.include?(model_name.downcase)
105
- AIA.client = AIA.client.class.new
106
- end
107
- end
108
-
109
-
110
- def output_response(response)
111
- speak(response)
112
-
113
- out_file = AIA.config.output.file
114
-
115
- # Output to STDOUT or file based on out_file configuration
116
- if out_file.nil? || 'STDOUT' == out_file.upcase
117
- print "\nAI:\n "
118
- puts response
119
- else
120
- mode = AIA.append? ? 'a' : 'w'
121
- File.open(out_file, mode) do |file|
122
- file.puts "\nAI: "
123
- # Handle multi-line responses by adding proper indentation
124
- response_lines = response.to_s.split("\n")
125
- response_lines.each do |line|
126
- file.puts " #{line}"
127
- end
128
- end
129
- end
130
-
131
- history_file = AIA.config.output.history_file
132
- if history_file
133
- File.open(history_file, 'a') do |f|
134
- f.puts "=== #{Time.now} ==="
135
- f.puts "Prompt: #{AIA.config.prompt_id}"
136
- f.puts "Response: #{response}"
137
- f.puts "==="
138
- end
139
- end
140
- end
141
-
142
-
143
- def process_next_prompts(response, prompt_handler)
144
- if @directive_processor.directive?(response)
145
- directive_result = @directive_processor.process(response, @history_manager.history)
146
- response = directive_result[:result]
147
- @history_manager.history = directive_result[:modified_history] if directive_result[:modified_history]
148
- end
149
- end
150
-
151
-
152
- def determine_operation_type
153
- # With multiple models, determine operation type from the first model
154
- # or provide a generic description
155
- models = AIA.config.models
156
- if models.is_a?(Array) && models.size > 1
157
- "MULTI-MODEL PROCESSING"
158
- else
159
- mode = AIA.client.model.modalities
160
- mode.input.join(',') + " TO " + mode.output.join(',')
161
- end
162
- end
163
- end
164
- end
@@ -1,183 +0,0 @@
1
- # lib/aia/prompt_pipeline.rb
2
- # frozen_string_literal: true
3
-
4
- require "pm"
5
-
6
- module AIA
7
- class PromptPipeline
8
- include AIA::SkillUtils
9
-
10
- def initialize(prompt_handler, chat_processor, ui_presenter, input_collector)
11
- @prompt_handler = prompt_handler
12
- @chat_processor = chat_processor
13
- @ui_presenter = ui_presenter
14
- @input_collector = input_collector
15
- @include_context_flag = true
16
- end
17
-
18
- # Process all prompts in the pipeline sequentially
19
- def process_all
20
- prompt_count = 0
21
- total_prompts = AIA.config.pipeline.size
22
-
23
- until AIA.config.pipeline.empty?
24
- prompt_count += 1
25
- prompt_id = AIA.config.pipeline.shift
26
-
27
- puts "\n--- Processing prompt #{prompt_count}/#{total_prompts}: #{prompt_id} ---" if AIA.verbose? && total_prompts > 1
28
-
29
- process_single(prompt_id)
30
- end
31
- end
32
-
33
- # Process a single prompt with all its requirements
34
- def process_single(prompt_id)
35
- return if prompt_id.nil? || prompt_id.empty?
36
-
37
- prompt_text = build_prompt_text(prompt_id)
38
- return unless prompt_text
39
-
40
- send_and_get_response(prompt_text)
41
- end
42
-
43
- def build_prompt_text(prompt_id)
44
- role_id = AIA.config.prompts.role
45
-
46
- begin
47
- prompt_parsed = @prompt_handler.fetch_prompt(prompt_id)
48
- rescue StandardError => e
49
- warn "Error processing prompt '#{prompt_id}': #{e.message}"
50
- AIA::LoggerManager.aia_logger.error("Error processing prompt '#{prompt_id}': #{e.message}")
51
- return nil
52
- end
53
-
54
- return nil unless prompt_parsed
55
-
56
- role_parsed = nil
57
- unless role_id.nil? || role_id.empty?
58
- begin
59
- role_parsed = @prompt_handler.fetch_role(role_id)
60
- rescue StandardError => e
61
- warn "Warning: Could not load role '#{role_id}': #{e.message}"
62
- AIA::LoggerManager.aia_logger.warn("Could not load role '#{role_id}': #{e.message}")
63
- end
64
- end
65
-
66
- # Merge parameters from role and prompt
67
- all_params = {}
68
- all_params.merge!(role_parsed.metadata.parameters) if role_parsed&.metadata&.parameters
69
- all_params.merge!(prompt_parsed.metadata.parameters) if prompt_parsed.metadata&.parameters
70
-
71
- # Collect parameter values from user
72
- values = @input_collector.collect(all_params)
73
-
74
- # Render role, skills, and prompt.
75
- # Order: role (personality) → skills (task instructions) → prompt (user request)
76
- parts = []
77
- parts << role_parsed.to_s(values) if role_parsed
78
- load_skills(AIA.config.prompts.skills).each { |body| parts << body }
79
- parts << prompt_parsed.to_s(values)
80
-
81
- if @include_context_flag
82
- # Process stdin content
83
- if AIA.config.stdin_content && !AIA.config.stdin_content.strip.empty?
84
- parts << PM.parse(AIA.config.stdin_content).to_s
85
- end
86
- end
87
-
88
- prompt_text = parts.join("\n\n")
89
-
90
- if @include_context_flag
91
- prompt_text = add_context_files(prompt_text)
92
- @include_context_flag = false
93
- end
94
-
95
- prompt_text
96
- end
97
-
98
- # Load skill bodies for the given skill IDs in order.
99
- # Each skill lives at skills_dir/<name>/SKILL.md; supports prefix matching.
100
- # Path-based IDs (starting with /, ~/, ./, ../) are resolved as direct paths.
101
- # Returns only the body content (front matter stripped).
102
- def load_skills(skill_ids)
103
- return [] if skill_ids.nil? || skill_ids.empty?
104
-
105
- skills_dir = AIA.config.skills.dir
106
-
107
- Array(skill_ids).filter_map do |skill_name|
108
- skill_name = skill_name.to_s.strip
109
- next if skill_name.empty?
110
-
111
- unless path_based_id?(skill_name) || Dir.exist?(skills_dir)
112
- warn "Warning: No skill matching '#{skill_name}' found in #{skills_dir}"
113
- next
114
- end
115
-
116
- skill_dir = find_skill_dir(skill_name, skills_dir)
117
- unless skill_dir
118
- if path_based_id?(skill_name)
119
- warn "Warning: No skill directory found at '#{File.expand_path(skill_name)}'"
120
- else
121
- warn "Warning: No skill matching '#{skill_name}' found in #{skills_dir}"
122
- end
123
- next
124
- end
125
-
126
- next skill_body(File.read(skill_dir)) if File.file?(skill_dir)
127
-
128
- skill_path = File.join(skill_dir, 'SKILL.md')
129
- unless File.exist?(skill_path)
130
- warn "Warning: Skill '#{skill_name}' has no SKILL.md in #{skill_dir}"
131
- next
132
- end
133
-
134
- skill_body(File.read(skill_path))
135
- end
136
- end
137
-
138
- # Add context files to prompt text
139
- def add_context_files(prompt_text)
140
- return prompt_text unless AIA.config.context_files && !AIA.config.context_files.empty?
141
-
142
- context = AIA.config.context_files.map do |file|
143
- File.read(file) rescue "Error reading file: #{file}"
144
- end.join("\n\n")
145
-
146
- "#{prompt_text}\n\nContext:\n#{context}"
147
- end
148
-
149
- private
150
-
151
- # Send prompt to AI and handle the response
152
- def send_and_get_response(prompt_text)
153
- response_data = @chat_processor.process_prompt(prompt_text)
154
-
155
- if response_data.is_a?(Hash)
156
- content = response_data[:content]
157
- metrics = response_data[:metrics]
158
- multi_metrics = response_data[:multi_metrics]
159
- else
160
- content = response_data
161
- metrics = nil
162
- multi_metrics = nil
163
- end
164
-
165
- @chat_processor.output_response(content)
166
-
167
- if AIA.config.flags.tokens
168
- if multi_metrics
169
- @ui_presenter.display_multi_model_metrics(multi_metrics)
170
- elsif metrics
171
- @ui_presenter.display_token_metrics(metrics)
172
- end
173
- end
174
-
175
- # Process any directives in the response
176
- directive_processor = DirectiveProcessor.new
177
- if directive_processor.directive?(content)
178
- directive_result = directive_processor.process(content, nil)
179
- puts "\nDirective output: #{directive_result}" if directive_result && !directive_result.strip.empty?
180
- end
181
- end
182
- end
183
- end
@@ -1,95 +0,0 @@
1
- # lib/aia/ruby_llm_adapter.rb
2
- # frozen_string_literal: true
3
-
4
- require 'fileutils'
5
- require 'json'
6
- require_relative '../extensions/ruby_llm/provider_fix'
7
- require_relative 'adapter/gem_activator'
8
- require_relative 'adapter/provider_configurator'
9
- require_relative 'adapter/model_registry'
10
- require_relative 'adapter/tool_loader'
11
- require_relative 'adapter/mcp_connector'
12
- require_relative 'adapter/tool_filter'
13
- require_relative 'adapter/chat_execution'
14
- require_relative 'adapter/multi_model_chat'
15
- require_relative 'adapter/modality_handlers'
16
- require_relative 'adapter/error_handler'
17
-
18
- module AIA
19
- class RubyLLMAdapter
20
- include Adapter::ChatExecution
21
- include Adapter::MultiModelChat
22
- include Adapter::ModalityHandlers
23
- include Adapter::ErrorHandler
24
-
25
- # Re-export for backward compatibility (tests reference AIA::RubyLLMAdapter::MultiModelResponse)
26
- MultiModelResponse = Adapter::MultiModelChat::MultiModelResponse
27
-
28
- attr_reader :tools, :model_specs, :chats
29
-
30
- # Delegates to the first chat instance's model object.
31
- # Used by single-model callers to inspect the active model.
32
- def model
33
- @chats.values.first&.model
34
- end
35
-
36
- def initialize
37
- @model_specs = extract_models_config # Full specs with role info
38
- @models = extract_model_names(@model_specs) # Just model names for backward compat
39
- @chats = {}
40
- @contexts = {} # Store isolated contexts for each model
41
-
42
- Adapter::ProviderConfigurator.configure
43
- Adapter::ModelRegistry.new.refresh
44
- setup_chats_with_tools
45
- end
46
-
47
- private
48
-
49
- # Helper to access the AIA logger for application-level logging
50
- def logger
51
- @logger ||= LoggerManager.aia_logger
52
- end
53
-
54
- def extract_models_config
55
- # Use config.models which returns array of ModelSpec objects
56
- models_config = AIA.config.models
57
-
58
- if models_config.nil? || models_config.empty?
59
- # Fallback to default
60
- [{model: 'gpt-4o-mini', role: nil, instance: 1, internal_id: 'gpt-4o-mini'}]
61
- else
62
- # Convert ModelSpec objects to hash format expected by adapter
63
- models_config.map do |spec|
64
- if spec.respond_to?(:name)
65
- # ModelSpec object
66
- {model: spec.name, role: spec.role, instance: spec.instance, internal_id: spec.internal_id}
67
- elsif spec.is_a?(Hash)
68
- # Hash format (legacy or from config.model accessor)
69
- model_name = spec[:model] || spec[:name]
70
- {model: model_name, role: spec[:role], instance: spec[:instance] || 1, internal_id: spec[:internal_id] || model_name}
71
- elsif spec.is_a?(String)
72
- # String format (legacy)
73
- {model: spec, role: nil, instance: 1, internal_id: spec}
74
- else
75
- # Unknown format, skip
76
- nil
77
- end
78
- end.compact
79
- end
80
- end
81
-
82
- def extract_model_names(model_specs)
83
- # Extract just the model names from the specs
84
- # For models with instance > 1, use internal_id (e.g., "gpt-4o#2")
85
- model_specs.map do |spec|
86
- spec[:internal_id]
87
- end
88
- end
89
-
90
- def get_model_spec(internal_id)
91
- # Find the spec for a given internal_id
92
- @model_specs.find { |spec| spec[:internal_id] == internal_id }
93
- end
94
- end
95
- end
@@ -1,48 +0,0 @@
1
- # lib/extensions/openstruct_merge.rb
2
- #
3
-
4
- require 'ostruct'
5
-
6
- class OpenStruct
7
- def self.merge(*args)
8
- result = OpenStruct.new
9
-
10
- args.each do |arg|
11
- unless [Hash, OpenStruct].include?(arg.class)
12
- raise ArgumentError, "Only OpenStruct or Hash objects are allowed. bad: #{arg.class}"
13
- end
14
-
15
- arg.each_pair do |key, value|
16
- set_value(result, key, value)
17
- end
18
- end
19
-
20
- result
21
- end
22
-
23
- # Sets value in result OpenStruct, handling nested OpenStruct and Hash objects
24
- # Skip nil values to avoid overwriting existing values with nil
25
- def self.set_value(result, key, value)
26
- # Skip nil values - don't overwrite existing values with nil
27
- return if value.nil?
28
-
29
- if value.is_a?(OpenStruct) || value.is_a?(Hash)
30
- current_value = result[key]
31
- current_value = {} if current_value.nil?
32
- merged_value = merge(current_value, value.to_h)
33
- result[key] = merged_value
34
- else
35
- result[key] = value
36
- end
37
- end
38
- end
39
-
40
- __END__
41
-
42
- # Usage example
43
- os1 = OpenStruct.new(a: 1, b: 2, e: OpenStruct.new(x: 9))
44
- os2 = OpenStruct.new(b: 3, c: 4)
45
- os3 = {d: 5, e: {y: 10}}
46
-
47
- merged_os = OpenStruct.merge(os1, os2, os3)
48
- puts merged_os.inspect
@@ -1,56 +0,0 @@
1
- require 'ruby_llm'
2
- require_relative 'modalities'
3
-
4
- RubyLLM.configure do |config|
5
- config.openai_api_key = ENV['OPENAI_API_KEY']
6
- end
7
-
8
- C = RubyLLM::Chat.new
9
- M = C.model.modalities
10
-
11
- __END__
12
-
13
- # I = ["text", "image", "pdf", "audio", "file"]
14
- # O = ["text", "embeddings", "audio", "image", "moderation"]
15
-
16
- # I.each do |i|
17
- # puts '#'
18
- # O.each do |o|
19
- # puts <<~HEREDOC
20
- # def #{i}_to_#{o}? = input.include?('#{i}') && output.include?('#{o}')
21
- # HEREDOC
22
- # end
23
- # end
24
-
25
-
26
-
27
- #
28
- def text_to_text? = input.include?('text') && output.include?('text')
29
- def text_to_embeddings? = input.include?('text') && output.include?('embeddings')
30
- def text_to_audio? = input.include?('text') && output.include?('audio')
31
- def text_to_image? = input.include?('text') && output.include?('image')
32
- def text_to_moderation? = input.include?('text') && output.include?('moderation')
33
- #
34
- def image_to_text? = input.include?('image') && output.include?('text')
35
- def image_to_embeddings? = input.include?('image') && output.include?('embeddings')
36
- def image_to_audio? = input.include?('image') && output.include?('audio')
37
- def image_to_image? = input.include?('image') && output.include?('image')
38
- def image_to_moderation? = input.include?('image') && output.include?('moderation')
39
- #
40
- def pdf_to_text? = input.include?('pdf') && output.include?('text')
41
- def pdf_to_embeddings? = input.include?('pdf') && output.include?('embeddings')
42
- def pdf_to_audio? = input.include?('pdf') && output.include?('audio')
43
- def pdf_to_image? = input.include?('pdf') && output.include?('image')
44
- def pdf_to_moderation? = input.include?('pdf') && output.include?('moderation')
45
- #
46
- def audio_to_text? = input.include?('audio') && output.include?('text')
47
- def audio_to_embeddings? = input.include?('audio') && output.include?('embeddings')
48
- def audio_to_audio? = input.include?('audio') && output.include?('audio')
49
- def audio_to_image? = input.include?('audio') && output.include?('image')
50
- def audio_to_moderation? = input.include?('audio') && output.include?('moderation')
51
- #
52
- def file_to_text? = input.include?('file') && output.include?('text')
53
- def file_to_embeddings? = input.include?('file') && output.include?('embeddings')
54
- def file_to_audio? = input.include?('file') && output.include?('audio')
55
- def file_to_image? = input.include?('file') && output.include?('image')
56
- def file_to_moderation? = input.include?('file') && output.include?('moderation')
@@ -1,36 +0,0 @@
1
- # lib/extensions/ruby_llm/modalities.rb
2
-
3
- require 'ruby_llm'
4
-
5
- class RubyLLM::Model::Modalities
6
- #
7
- def text_to_text? = input.include?('text') && output.include?('text')
8
- def text_to_embeddings? = input.include?('text') && output.include?('embeddings')
9
- def text_to_audio? = input.include?('text') && output.include?('audio')
10
- def text_to_image? = input.include?('text') && output.include?('image')
11
- def text_to_moderation? = input.include?('text') && output.include?('moderation')
12
- #
13
- def image_to_text? = input.include?('image') && output.include?('text')
14
- def image_to_embeddings? = input.include?('image') && output.include?('embeddings')
15
- def image_to_audio? = input.include?('image') && output.include?('audio')
16
- def image_to_image? = input.include?('image') && output.include?('image')
17
- def image_to_moderation? = input.include?('image') && output.include?('moderation')
18
- #
19
- def pdf_to_text? = input.include?('pdf') && output.include?('text')
20
- def pdf_to_embeddings? = input.include?('pdf') && output.include?('embeddings')
21
- def pdf_to_audio? = input.include?('pdf') && output.include?('audio')
22
- def pdf_to_image? = input.include?('pdf') && output.include?('image')
23
- def pdf_to_moderation? = input.include?('pdf') && output.include?('moderation')
24
- #
25
- def audio_to_text? = input.include?('audio') && output.include?('text')
26
- def audio_to_embeddings? = input.include?('audio') && output.include?('embeddings')
27
- def audio_to_audio? = input.include?('audio') && output.include?('audio')
28
- def audio_to_image? = input.include?('audio') && output.include?('image')
29
- def audio_to_moderation? = input.include?('audio') && output.include?('moderation')
30
- #
31
- def file_to_text? = input.include?('file') && output.include?('text')
32
- def file_to_embeddings? = input.include?('file') && output.include?('embeddings')
33
- def file_to_audio? = input.include?('file') && output.include?('audio')
34
- def file_to_image? = input.include?('file') && output.include?('image')
35
- def file_to_moderation? = input.include?('file') && output.include?('moderation')
36
- end
@@ -1,79 +0,0 @@
1
- # lib/extensions/ruby_llm/provider_fix.rb
2
- #
3
- # Monkey patch to fix LM Studio compatibility with RubyLLM
4
- # LM Studio sometimes returns response.body as a String that fails JSON parsing
5
- # This causes "String does not have #dig method" errors in parse_error
6
-
7
- # Load RubyLLM first to ensure Provider class exists
8
- require 'ruby_llm'
9
-
10
- module RubyLLM
11
- module ProviderErrorFix
12
- # Override the parse_error method to handle String responses from LM Studio
13
- # Parses error response from provider API.
14
- #
15
- # Supports two error formats:
16
- # 1. OpenAI standard: {"error": {"message": "...", "type": "...", "code": "..."}}
17
- # 2. Simple format: {"error": "error message"}
18
- #
19
- # @param response [Faraday::Response] The HTTP response
20
- # @return [String, nil] The error message or nil if parsing fails
21
- #
22
- # @example OpenAI format
23
- # response = double(body: '{"error": {"message": "Rate limit exceeded"}}')
24
- # parse_error(response) #=> "Rate limit exceeded"
25
- #
26
- # @example Simple format (LM Studio, some local providers)
27
- # response = double(body: '{"error": "Token limit exceeded"}')
28
- # parse_error(response) #=> "Token limit exceeded"
29
- def parse_error(response)
30
- return if response.body.empty?
31
-
32
- body = try_parse_json(response.body)
33
- case body
34
- when Hash
35
- # Handle both formats:
36
- # - {"error": "message"} (LM Studio, some providers)
37
- # - {"error": {"message": "..."}} (OpenAI standard)
38
- error_value = body['error']
39
- return nil unless error_value
40
-
41
- case error_value
42
- when Hash
43
- error_value['message']
44
- when String
45
- error_value
46
- else
47
- error_value.to_s if error_value
48
- end
49
- when Array
50
- body.filter_map do |part|
51
- next unless part.is_a?(Hash)
52
-
53
- error_value = part['error']
54
- next unless error_value
55
-
56
- case error_value
57
- when Hash then error_value['message']
58
- when String then error_value
59
- else error_value.to_s if error_value
60
- end
61
- end.join('. ')
62
- else
63
- body.to_s
64
- end
65
- rescue StandardError => e
66
- RubyLLM.logger.debug "Error parsing response: #{e.message}"
67
- nil
68
- end
69
- end
70
- end
71
-
72
- # Apply the prepend to all Provider subclasses
73
- # LM Studio uses the OpenAI provider, so we need to prepend to all provider classes
74
- RubyLLM::Provider.prepend(RubyLLM::ProviderErrorFix)
75
-
76
- # Also prepend to all registered provider classes
77
- RubyLLM::Provider.providers.each do |slug, provider_class|
78
- provider_class.prepend(RubyLLM::ProviderErrorFix)
79
- end
@@ -1,16 +0,0 @@
1
- # lib/aia_refinements/string.rb
2
-
3
-
4
- module Refinements
5
- refine String do
6
- def include_all?(substrings)
7
- Array(substrings).all? { |substring| self.include?(substring) }
8
- end
9
- alias :all? :include_all?
10
-
11
- def include_any?(substrings)
12
- Array(substrings).any? { |substring| self.include?(substring) }
13
- end
14
- alias :any? :include_any?
15
- end
16
- end