-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmini-a-modelman.js
More file actions
324 lines (295 loc) · 13 KB
/
mini-a-modelman.js
File metadata and controls
324 lines (295 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
// Mini-A model definition manager.
// Provides an interactive console workflow to create, import, rename,
// delete, and load encrypted OAF_MODEL/OAF_LC_MODEL configurations.
plugin("Console")
// Get command line arguments passed in by oJob or mini-a-con.js.
var args = isDef(global._args) ? global._args : processExpr(" ")
// Initialize OAF module
__initializeCon()
loadLib("mini-a-common.js")
// Load additional libraries if specified. This allows the manager to use
// the same optional helpers as the main agent when generating model lists.
if (isDef(args.libs) && args.libs.length > 0) {
__miniALoadLibraries(args.libs, log, logErr)
}
function buildOAFModelInit(args) {
// Base provider metadata used to prompt the user for provider-specific
// fields when building a new definition.
args.init = {
"providers": [
"openai",
"ollama",
"anthropic",
"gemini",
"bedrock"
],
"options": {
"openai": [
"model",
"timeout",
"temperature",
"key",
"url"
],
"gemini": [
"model",
"timeout",
"key",
"temperature",
"url"
],
"bedrock": [
"timeout",
"options.model",
"options.temperature",
"options.region",
"options.params.max_tokens"
],
"ollama": [
"model",
"url",
"timeout",
"temperature"
],
"anthropic": [
"model",
"key",
"timeout",
"temperature",
"url"
]
}
}
}
function buildOAFModel(args) {
buildOAFModelInit(args)
// Start with the provider selection and carry forward the resulting
// configuration as the user answers follow-up prompts.
var iProvider = args.type, _out = {}
if (isUnDef(args.type)) {
iProvider = __miniANormalizeChoiceIndex(askChoose("Choose a provider: ", args.init.providers.sort(), 8), args.init.providers.length)
if (iProvider < 0) return __
}
_out.type = args.init.providers[iProvider]
// Does it have a key?
if (isDef(args.key)) _out.key = args.key
if (isUnDef(args.key) && args.init.options[args.init.providers[iProvider]].includes("key")) {
_out.key = askEncrypt("Enter your API key (it will be stored encrypted): ")
if (_out.key == null || _out.key == "") delete _out.key
}
// Does it have a url?
if (isDef(args.url)) _out.url = args.url
if (isUnDef(args.url) && args.init.options[args.init.providers[iProvider]].includes("url")) {
_out.url = ask("Enter the API base URL (or leave blank for default): ")
if (_out.url == null || _out.url == "") delete _out.url
}
if (isDef(args.model)) _out.model = args.model
if (isUnDef(args.model) && args.init.options[args.init.providers[iProvider]].includes("model")) {
try {
var _models = $llm( clone(_out) ).getModels()
if (_models != null && _models.length > 0) {
if (isDef(_models[0].id)) {
var _m = _models.map(r => r.id).sort()
var _id = __miniANormalizeChoiceIndex(askChoose( "Choose a model: ", _m, 8 ), _m.length)
if (_id < 0) return __
_out.model = _m[_id]
} else if (isDef(_models[0].name)) {
var _m = _models.map(r => r.name).map(r => r.replace(/^models\//, "")).sort()
var _name = __miniANormalizeChoiceIndex(askChoose( "Choose a model: ", _m, 8 ), _m.length)
if (_name < 0) return __
_out.model = _m[_name]
}
}
} catch(e) {
$err(e)
}
}
// Manual fallback when discovery did not return any models.
if (isUnDef(_out.model) && args.init.options[args.init.providers[iProvider]].includes("model")) {
_out.model = ask("Enter the model to use (or leave blank for default): ")
if (_out.model == "") delete _out.model
}
// AWS Bedrock definitions use nested options instead of a top-level
// model property. Handle the prompt flow separately for clarity.
if (args.init.providers[iProvider] == "bedrock" && (isUnDef(_out.model) || _out.model == null || _out.model == "")) {
if (isUnDef(_out)) _out = {}
if (isUnDef(_out.options)) _out.options = {}
// Region
if (args.init.options[args.init.providers[iProvider]].includes("options.region")) {
_out.options.region = ask("Enter the AWS region (or leave blank for default): ")
if (_out.options.region == "") delete _out.options.region
}
var _models = $llm( clone(_out) ).getModels()
if (_models != null && _models.length > 0) {
if (isDef(_models[0].modelId)) {
var _m = _models.map(r => r.modelId).sort()
var _modelId = __miniANormalizeChoiceIndex(askChoose( "Choose a model: ", _m, 8 ), _m.length)
if (_modelId < 0) return __
_out.options.model = _m[_modelId]
} else if (isDef(_models[0].name)) {
var _m = _models.map(r => r.name).map(r => r.replace(/^models\//, "")).sort()
var _name = __miniANormalizeChoiceIndex(askChoose( "Choose a model: ", _m, 8 ), _m.length)
if (_name < 0) return __
_out.options.model = _m[_name]
}
}
if (isUnDef(_out.options.model) && args.init.options[args.init.providers[iProvider]].includes("options.model")) {
_out.options.model = ask("Enter the model to use (or leave blank for default): ")
if (_out.options.model == "") delete _out.options.model
}
}
// Timeout defaults to 15 minutes to keep parity with the rest of Mini-A.
_out.timeout = 900000
// Temperature handling differs for Bedrock because its API expects the
// value inside `options`.
if (isDef(args.temperature) && args.init.providers[iProvider] != "bedrock") _out.temperature = args.temperature
if (isUnDef(args.temperature) && args.init.options[args.init.providers[iProvider]].includes("temperature")) {
var temp = ask("Enter the temperature (leave blank for default): ")
if (temp != "") {
_out.temperature = parseFloat(temp)
}
}
if (args.init.providers[iProvider] == "bedrock" && _out.temperature == null) {
if (isUnDef(_out.options)) _out.options = {}
if (args.init.options[args.init.providers[iProvider]].includes("options.temperature")) {
if (isDef(args.temperature)) {
_out.options.temperature = args.temperature
} else {
var temp = ask("Enter the temperature (leave blank for default): ")
if (temp != "") {
_out.options.temperature = parseFloat(temp)
}
}
}
}
// Allow setting the maximum token count for Bedrock models when supported.
if (args.init.providers[iProvider] == "bedrock") {
if (isUnDef(_out.options)) _out.options = {}
if (args.init.options[args.init.providers[iProvider]].includes("options.params.max_tokens")) {
var maxTokens = ask("Enter the max tokens (leave blank for default): ")
if (maxTokens != "") {
_out.options.params = {}
_out.options.params.max_tokens = parseInt(maxTokens)
}
}
}
return _out
}
function mainOAFModel(args) {
var accentColor = "FG(218)"
var promptColor = "FG(41)"
if (!args.__noprint) {
const miniaLogo = ` ._ _ ${ansiColor(promptColor, "o")}._ ${ansiColor(promptColor, "o")} _
| | ||| ||~~(_|`
print(ansiColor("BOLD", miniaLogo) + ansiColor(accentColor, " LLM Model definitions management"))
print()
}
// All definitions are stored under the secure namespace `mini-a/models`.
// Optional encryption protects the credentials backing each model.
var _sec = $sec("mini-a", "models", __, askEncrypt("Enter the password securing LLM models definitions (or leave blank for no password): "))
var _shouldExit = false, _obj = __
while(!_shouldExit) {
var _lst
try {
_lst = _sec.list("models")
} catch(e) {
printErr("❌ Error accessing secure storage: " + (e.message.indexOf("BadPaddingException") >= 0 ? "access denied" : e.message))
break
}
if (isMap(_lst)) _lst = _lst.models
if (isUnDef(_lst)) _lst = []
// Combine existing definitions with the available actions shown to the
// user. The action ordering is mirrored later in the switch statement.
var _options = _lst.sort().map(r => "🤖 " + r).concat([ "───", "✨ New definition", "📥 Import definition", "📤 Export definition", "✏️ Rename definition", "🗑️ Delete definitions", "🔙 Go back" ])
var _action = __miniANormalizeChoiceIndex(askChoose("Choose a definition or an action: ", _options, 8), _options.length - 1)
if (_action < 0) _action = _options.length - 1
switch(_action) {
case _options.length - 6 - 1: // Separator
// Do nothing
break
case _options.length - 6: // New definition
print()
var _name = ask("✨ Name of the new definition: ")
if (isDef(_name) && _name.length > 0) {
var _newDef = buildOAFModel(args)
if (isMap(_newDef)) {
print("💾 Storing new definition '" + _name + "'...")
_sec.set(_name, _newDef, "models")
}
}
break
case _options.length - 5: // Import definition
print()
var _name = ask("📥 Name of the definition to import: ")
if (isUnDef(_name) || _name.length == 0) break
var _obj = ask("📥 Paste the definition content (in SLON/JSON format): ")
_obj = af.fromJSSLON(_obj)
if (isMap(_obj)) {
print("💾 Importing definition '" + _name + "'...")
_sec.set(_name, _obj, "models")
} else {
printErr("❌ Invalid definition format.")
}
break
case _options.length - 4: // Export definition
print()
var _exportName = __miniANormalizeChoiceIndex(askChoose("📤 Choose a definition to export: ", _lst.sort().concat([ "🔙 Go back" ]), 8), _lst.length)
if (_exportName >= 0 && _exportName < _lst.length) {
var _exportDef = _sec.get(_lst[_exportName], "models")
print("\n" + ansiColor("FAINT", "─────"))
print(af.toCSLON(_exportDef))
print(ansiColor("FAINT", "─────") + "\n")
}
break
case _options.length - 3: // Rename existing definition
print()
var _oldName = __miniANormalizeChoiceIndex(askChoose("✏️ Choose a definition to rename: ", _lst.sort().concat([ "🔙 Go back" ]), 8), _lst.length)
if (_oldName >= 0 && _oldName < _lst.length) {
var _newName = ask("✨ New name for the definition '" + _lst[_oldName] + "': ")
if (isDef(_newName) && _newName.length > 0) {
print("💾 Renaming definition '" + _lst[_oldName] + "' to '" + _newName + "'...")
var _def = _sec.get( _lst[_oldName], "models" )
_sec.set( _newName, _def, "models" )
_sec.unset( _lst[_oldName], "models" )
}
}
break
case _options.length - 2: // Delete existing definitions
var _sortedLst = _lst.sort()
var selectedIndexes = askChooseMultiple("🗑️ Choose definitions to delete (use space to select, enter to confirm): ", _sortedLst, 8)
if (!isArray(selectedIndexes)) selectedIndexes = []
if (selectedIndexes.length > 0) {
print("✖️ Deleting definitions...")
selectedIndexes.forEach(idx => {
_sec.unset(_sortedLst[idx], "models")
print(" definition '" + _sortedLst[idx] + "' deleted!")
})
}
break
case _options.length - 1: // Go back
if (!args.__noprint) print("Exiting...")
_shouldExit = true
break
default :
_obj = _sec.get( _lst[_action], "models" )
if (!args.__noprint) {
var _prefix = ow.format.isWindows() ? "set " : "export "
print(`${ansiColor("FAINT", "-----\nUse one of the following commands to set the model definition in your environment:")}\n`)
print(`${ansiColor("FAINT,ITALIC", _prefix + "OAF_MODEL=")}"${af.toCSLON(_obj)}"`)
print(`or`)
print(`${ansiColor("FAINT,ITALIC", _prefix + "OAF_LC_MODEL=")}"${af.toCSLON(_obj)}"`)
print(`\n${ansiColor("FAINT", "-----")}`)
} else {
print(`🤖 Model definition '${_lst[_action]}' loaded.`)
}
_shouldExit = true
}
if (!_shouldExit) print(ansiColor("FAINT", repeat((new Console()).getConsoleReader().getTerminal().getWidth(), "─")))
}
return _obj
}
var _result = mainOAFModel(args)
// Support for being called from mini-a-con.js
if (isDef(global.__mini_a_con_capture_model)) {
global.__mini_a_con_model_result = _result
}