ace-llm 0.36.3 → 0.38.2
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/.ace-defaults/llm/providers/anthropic.yml +29 -14
- data/.ace-defaults/llm/providers/google.yml +28 -14
- data/.ace-defaults/llm/providers/groq.yml +60 -5
- data/.ace-defaults/llm/providers/lmstudio.yml +1 -1
- data/.ace-defaults/llm/providers/mistral.yml +45 -14
- data/.ace-defaults/llm/providers/openai.yml +46 -14
- data/.ace-defaults/llm/providers/openrouter.yml +229 -32
- data/.ace-defaults/llm/providers/togetherai.yml +43 -10
- data/.ace-defaults/llm/providers/xai.yml +21 -8
- data/.ace-defaults/llm/providers/zai.yml +17 -6
- data/CHANGELOG.md +43 -0
- data/README.md +2 -0
- data/exe/ace-llm +2 -1
- data/handbook/guides/llm-query-tool-reference.g.md +78 -626
- data/lib/ace/llm/atoms/provider_config_validator.rb +53 -0
- data/lib/ace/llm/cli/commands/query.rb +10 -2
- data/lib/ace/llm/molecules/model_limit_resolver.rb +88 -0
- data/lib/ace/llm/version.rb +1 -1
- data/lib/ace/llm.rb +1 -0
- metadata +5 -4
|
@@ -11,6 +11,7 @@ module Ace
|
|
|
11
11
|
# Optional fields with expected types
|
|
12
12
|
OPTIONAL_FIELDS = {
|
|
13
13
|
"models" => Array,
|
|
14
|
+
"limits" => Hash,
|
|
14
15
|
"api_key" => [Hash, NilClass],
|
|
15
16
|
"capabilities" => Array,
|
|
16
17
|
"default_options" => Hash,
|
|
@@ -69,6 +70,7 @@ module Ace
|
|
|
69
70
|
validate_class(config["class"], errors) if config["class"]
|
|
70
71
|
validate_gem(config["gem"], errors) if config["gem"]
|
|
71
72
|
validate_models(config["models"], warnings) if config["models"]
|
|
73
|
+
validate_limits(config["limits"], errors, warnings) if config["limits"]
|
|
72
74
|
validate_api_key(config["api_key"], errors, warnings) if config["api_key"]
|
|
73
75
|
validate_capabilities(config["capabilities"], warnings) if config["capabilities"]
|
|
74
76
|
validate_default_options(config["default_options"], warnings) if config["default_options"]
|
|
@@ -147,6 +149,57 @@ module Ace
|
|
|
147
149
|
end
|
|
148
150
|
end
|
|
149
151
|
|
|
152
|
+
def validate_limits(limits, errors, warnings)
|
|
153
|
+
unless limits.is_a?(Hash)
|
|
154
|
+
errors << "Limits must be a Hash, got #{limits.class}"
|
|
155
|
+
return
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
valid_limit_keys = %w[default models]
|
|
159
|
+
unknown_limit_keys = limits.keys - valid_limit_keys
|
|
160
|
+
unless unknown_limit_keys.empty?
|
|
161
|
+
warnings << "Unknown limits keys: #{unknown_limit_keys.join(", ")}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
validate_limit_entry(limits["default"], "limits.default", errors) if limits["default"]
|
|
165
|
+
|
|
166
|
+
return unless limits["models"]
|
|
167
|
+
|
|
168
|
+
unless limits["models"].is_a?(Hash)
|
|
169
|
+
errors << "limits.models must be a Hash, got #{limits["models"].class}"
|
|
170
|
+
return
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
limits["models"].each do |model_name, entry|
|
|
174
|
+
if model_name.to_s.strip.empty?
|
|
175
|
+
errors << "limits.models entries must have non-empty model names"
|
|
176
|
+
next
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
validate_limit_entry(entry, "limits.models.#{model_name}", errors)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def validate_limit_entry(entry, path, errors)
|
|
184
|
+
unless entry.is_a?(Hash)
|
|
185
|
+
errors << "#{path} must be a Hash, got #{entry.class}"
|
|
186
|
+
return
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
valid_keys = %w[context output]
|
|
190
|
+
unknown_keys = entry.keys - valid_keys
|
|
191
|
+
unless unknown_keys.empty?
|
|
192
|
+
errors << "#{path} contains unknown keys: #{unknown_keys.join(", ")}"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
entry.each do |field, value|
|
|
196
|
+
next unless valid_keys.include?(field)
|
|
197
|
+
next if value.is_a?(Integer) && value >= 0
|
|
198
|
+
|
|
199
|
+
errors << "#{path}.#{field} must be a non-negative Integer"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
150
203
|
# Validate API key configuration
|
|
151
204
|
def validate_api_key(api_key_config, errors, warnings)
|
|
152
205
|
if api_key_config.is_a?(Hash)
|
|
@@ -33,6 +33,8 @@ module Ace
|
|
|
33
33
|
option :prompt, type: :string, desc: "Prompt text (overrides positional PROMPT)"
|
|
34
34
|
option :force, type: :boolean, default: false, desc: "Force overwrite existing files"
|
|
35
35
|
option :interactive, type: :boolean, default: false, desc: "Start an interactive CLI session instead of one-shot query"
|
|
36
|
+
option :fallback, type: :boolean, default: true, desc: "Use provider fallback for this query"
|
|
37
|
+
option :json, type: :boolean, default: false, desc: "Output command results as JSON"
|
|
36
38
|
|
|
37
39
|
option :version, type: :boolean, desc: "Show version information"
|
|
38
40
|
option :list_providers, type: :boolean, desc: "List available LLM providers"
|
|
@@ -55,6 +57,7 @@ module Ace
|
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
@prompt = options[:prompt] || prompt_text
|
|
60
|
+
options[:format] ||= "json" if options[:json]
|
|
58
61
|
|
|
59
62
|
return show_help if @provider_model.nil? && @prompt.nil?
|
|
60
63
|
return show_provider_help if @prompt.nil? || @prompt.empty?
|
|
@@ -90,6 +93,8 @@ module Ace
|
|
|
90
93
|
puts " --model MODEL Model name (overrides PROVIDER[:MODEL])"
|
|
91
94
|
puts " --prompt PROMPT Prompt text (overrides positional PROMPT)"
|
|
92
95
|
puts " --interactive Start an interactive CLI session"
|
|
96
|
+
puts " --no-fallback Disable provider fallback for this query"
|
|
97
|
+
puts " --json Output command results as JSON"
|
|
93
98
|
puts " --force Force overwrite existing files"
|
|
94
99
|
puts " -q, --quiet Suppress config summary output"
|
|
95
100
|
puts " -d, --debug Enable debug output"
|
|
@@ -104,6 +109,7 @@ module Ace
|
|
|
104
109
|
puts ' ace-llm claude:sonnet "Hi" --cli-args "dangerously-skip-permissions"'
|
|
105
110
|
puts ' ace-llm claude:sonnet "Hi" --cli-args "--model=claude-sonnet-4-0 --verbose"'
|
|
106
111
|
puts ' ace-llm codex:gpt@yolo "/as-assign-drive abc123@010" --interactive'
|
|
112
|
+
puts ' ace-llm gemini:pro "ping" --no-fallback --timeout 15 --max-tokens 4'
|
|
107
113
|
puts ""
|
|
108
114
|
puts "Provider Aliases:"
|
|
109
115
|
puts " Short aliases for common provider:MODEL combinations:"
|
|
@@ -136,7 +142,7 @@ module Ace
|
|
|
136
142
|
return if quiet?(options)
|
|
137
143
|
|
|
138
144
|
require "ace/core"
|
|
139
|
-
summary_keys = %w[provider_model preset temperature max_tokens format timeout system_append cli_args]
|
|
145
|
+
summary_keys = %w[provider_model preset temperature max_tokens format timeout system_append cli_args fallback]
|
|
140
146
|
Ace::Core::Atoms::ConfigSummary.display(
|
|
141
147
|
command: "query",
|
|
142
148
|
config: options.merge(provider_model: @provider_model),
|
|
@@ -183,7 +189,8 @@ module Ace
|
|
|
183
189
|
model: resolved_model_override,
|
|
184
190
|
cli_args: options[:cli_args],
|
|
185
191
|
system_append: system_append_text,
|
|
186
|
-
preset: options[:preset]
|
|
192
|
+
preset: options[:preset],
|
|
193
|
+
fallback: (options[:fallback] == false ? false : nil)
|
|
187
194
|
)
|
|
188
195
|
|
|
189
196
|
output_response(response, options)
|
|
@@ -207,6 +214,7 @@ module Ace
|
|
|
207
214
|
conflicts << "--max-tokens" unless options[:max_tokens].nil?
|
|
208
215
|
explicit_format = options[:format]
|
|
209
216
|
conflicts << "--format" if explicit_format && explicit_format != "text"
|
|
217
|
+
conflicts << "--no-fallback" if options[:fallback] == false
|
|
210
218
|
return if conflicts.empty?
|
|
211
219
|
|
|
212
220
|
raise Ace::Support::Cli::Error, "Interactive mode does not support #{conflicts.join(', ')}"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ace/support/models"
|
|
4
|
+
require_relative "client_registry"
|
|
5
|
+
require_relative "provider_model_parser"
|
|
6
|
+
|
|
7
|
+
module Ace
|
|
8
|
+
module LLM
|
|
9
|
+
module Molecules
|
|
10
|
+
# Resolves model context/output limits against the expanded concrete target.
|
|
11
|
+
class ModelLimitResolver
|
|
12
|
+
DEFAULT_CONTEXT_LIMIT = 200_000
|
|
13
|
+
|
|
14
|
+
ResolveResult = Struct.new(
|
|
15
|
+
:provider,
|
|
16
|
+
:model,
|
|
17
|
+
:context_limit,
|
|
18
|
+
:output_limit,
|
|
19
|
+
:source,
|
|
20
|
+
:original_target,
|
|
21
|
+
keyword_init: true
|
|
22
|
+
) do
|
|
23
|
+
def full_model
|
|
24
|
+
return nil if provider.to_s.empty? || model.to_s.empty?
|
|
25
|
+
|
|
26
|
+
"#{provider}:#{model}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.resolve(target, **kwargs)
|
|
31
|
+
new(**kwargs).resolve(target)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def initialize(registry: nil, parser: nil)
|
|
35
|
+
@registry = registry || ClientRegistry.new
|
|
36
|
+
@parser = parser || ProviderModelParser.new(registry: @registry)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def resolve(target)
|
|
40
|
+
original_target = target.to_s
|
|
41
|
+
return fallback_result(original_target) if original_target.strip.empty?
|
|
42
|
+
|
|
43
|
+
parsed = @parser.parse(original_target)
|
|
44
|
+
return fallback_result(original_target) if parsed.invalid?
|
|
45
|
+
|
|
46
|
+
provider_config = @registry.get_provider(parsed.provider)
|
|
47
|
+
return fallback_result(original_target, provider: parsed.provider, model: parsed.model) unless provider_config
|
|
48
|
+
|
|
49
|
+
limits = Ace::Support::Models::Atoms::ProviderConfigReader.extract_limits(provider_config)
|
|
50
|
+
default_limits = limits["default"] || {}
|
|
51
|
+
model_limits = limits.dig("models", parsed.model) || {}
|
|
52
|
+
merged_limits = default_limits.merge(model_limits)
|
|
53
|
+
|
|
54
|
+
ResolveResult.new(
|
|
55
|
+
provider: parsed.provider,
|
|
56
|
+
model: parsed.model,
|
|
57
|
+
context_limit: merged_limits["context"] || DEFAULT_CONTEXT_LIMIT,
|
|
58
|
+
output_limit: merged_limits["output"],
|
|
59
|
+
source: limit_source(default_limits, model_limits),
|
|
60
|
+
original_target: original_target
|
|
61
|
+
)
|
|
62
|
+
rescue
|
|
63
|
+
fallback_result(original_target)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def limit_source(default_limits, model_limits)
|
|
69
|
+
return :model_override if model_limits.any?
|
|
70
|
+
return :provider_default if default_limits.any?
|
|
71
|
+
|
|
72
|
+
:fallback
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def fallback_result(original_target, provider: nil, model: nil)
|
|
76
|
+
ResolveResult.new(
|
|
77
|
+
provider: provider,
|
|
78
|
+
model: model,
|
|
79
|
+
context_limit: DEFAULT_CONTEXT_LIMIT,
|
|
80
|
+
output_limit: nil,
|
|
81
|
+
source: :fallback,
|
|
82
|
+
original_target: original_target
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/ace/llm/version.rb
CHANGED
data/lib/ace/llm.rb
CHANGED
|
@@ -25,6 +25,7 @@ require_relative "llm/molecules/file_io_handler"
|
|
|
25
25
|
require_relative "llm/molecules/llm_alias_resolver"
|
|
26
26
|
require_relative "llm/molecules/role_resolver"
|
|
27
27
|
require_relative "llm/molecules/provider_model_parser"
|
|
28
|
+
require_relative "llm/molecules/model_limit_resolver"
|
|
28
29
|
require_relative "llm/molecules/format_handlers"
|
|
29
30
|
require_relative "llm/molecules/client_registry"
|
|
30
31
|
require_relative "llm/molecules/fallback_orchestrator"
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-llm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.38.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-04-
|
|
10
|
+
date: 2026-04-26 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-support-cli
|
|
@@ -57,14 +57,14 @@ dependencies:
|
|
|
57
57
|
requirements:
|
|
58
58
|
- - "~>"
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0.
|
|
60
|
+
version: '0.12'
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '0.
|
|
67
|
+
version: '0.12'
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
69
|
name: faraday
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -289,6 +289,7 @@ files:
|
|
|
289
289
|
- lib/ace/llm/molecules/format_handlers.rb
|
|
290
290
|
- lib/ace/llm/molecules/interactive_command_builder.rb
|
|
291
291
|
- lib/ace/llm/molecules/llm_alias_resolver.rb
|
|
292
|
+
- lib/ace/llm/molecules/model_limit_resolver.rb
|
|
292
293
|
- lib/ace/llm/molecules/openai_compatible_params.rb
|
|
293
294
|
- lib/ace/llm/molecules/preset_loader.rb
|
|
294
295
|
- lib/ace/llm/molecules/provider_loader.rb
|