17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
# File 'lib/ace/llm/query_interface.rb', line 17
def self.query(provider_model, prompt = nil,
output: nil,
format: "text",
temperature: nil,
max_tokens: nil,
system: nil,
timeout: nil,
force: false,
debug: false,
model: nil,
prompt_override: nil,
fallback: nil,
fallback_providers: nil,
system_file: nil,
prompt_file: nil,
cli_args: nil,
system_append: nil,
preset: nil,
sandbox: nil,
working_dir: nil,
subprocess_env: nil,
last_message_file: nil)
registry = Molecules::ClientRegistry.new
parser = Molecules::ProviderModelParser.new(registry: registry)
parse_result = parser.parse(provider_model)
raise Error, parse_result.error unless parse_result.valid?
resolved_preset = resolve_preset_name(parse_result.preset, preset)
execution_overrides = load_execution_overrides(
provider: parse_result.provider,
preset: resolved_preset,
thinking_level: parse_result.thinking_level
)
final_model = model || parse_result.model
if final_model.nil? || final_model.empty?
raise Error, "No model specified and no default available for #{parse_result.provider}"
end
final_prompt = prompt_override || prompt
if final_prompt.nil? || final_prompt.empty?
raise Error, "No prompt specified. Use positional prompt or prompt_override: parameter"
end
messages = []
messages << {role: "system", content: system} if system && !system.empty?
messages << {role: "user", content: final_prompt}
generation_opts = {}
resolved_temperature = first_non_nil(temperature, execution_overrides["temperature"])
resolved_max_tokens = first_non_nil(max_tokens, execution_overrides["max_tokens"])
resolved_cli_args = first_non_nil(cli_args, execution_overrides["cli_args"])
resolved_system_append = first_non_empty(system_append, execution_overrides["system_append"])
resolved_sandbox = first_non_nil(sandbox, execution_overrides["sandbox"])
resolved_working_dir = first_non_nil(working_dir, execution_overrides["working_dir"])
resolved_subprocess_env = merge_hash_values(execution_overrides["subprocess_env"], subprocess_env)
generation_opts[:temperature] = resolved_temperature unless resolved_temperature.nil?
generation_opts[:max_tokens] = resolved_max_tokens unless resolved_max_tokens.nil?
generation_opts[:system_file] = system_file if system_file
generation_opts[:prompt_file] = prompt_file if prompt_file
generation_opts[:cli_args] = resolved_cli_args unless blank_value?(resolved_cli_args)
generation_opts[:system_append] = resolved_system_append unless blank_value?(resolved_system_append)
generation_opts[:sandbox] = resolved_sandbox if resolved_sandbox
generation_opts[:working_dir] = resolved_working_dir unless blank_value?(resolved_working_dir)
generation_opts[:subprocess_env] = resolved_subprocess_env unless resolved_subprocess_env.nil?
generation_opts[:last_message_file] = last_message_file if last_message_file
if debug
warn "Provider: #{parse_result.provider}"
warn "Model: #{final_model}"
warn "Preset: #{resolved_preset}" if resolved_preset
warn "Thinking level: #{parse_result.thinking_level}" if parse_result.thinking_level
warn "Temperature: #{resolved_temperature}" unless resolved_temperature.nil?
warn "Max tokens: #{resolved_max_tokens}" unless resolved_max_tokens.nil?
end
fallback_config = load_fallback_config(fallback, fallback_providers, parser: parser)
timeout_value = first_non_nil(timeout, execution_overrides["timeout"], Molecules::ConfigLoader.get("llm.timeout"), 120)
resolved_timeout = normalize_timeout(timeout_value)
response = execute_with_fallback(
provider: parse_result.provider,
model: final_model,
messages: messages,
generation_opts: generation_opts,
registry: registry,
fallback_config: fallback_config,
timeout: resolved_timeout,
debug: debug
)
text_content = (response)
result = {
text: text_content,
model: final_model,
provider: parse_result.provider,
preset: resolved_preset,
thinking_level: parse_result.thinking_level,
usage: response[:usage],
metadata: response[:metadata]
}
if output && !output.empty?
handler = Molecules::FormatHandlers.get_handler(format)
formatted_content = case format
when "json"
handler.format(result)
when "yaml"
handler.format(result)
when "raw"
handler.format(response)
else
text_content
end
file_handler = Molecules::FileIoHandler.new
file_handler.write_content(formatted_content, output, format: format, force: force)
warn "Output written to: #{output}" if debug
end
result
end
|