foobara-llm-backed-command 0.0.5 → 0.0.6
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/CHANGELOG.md +4 -0
- data/README.md +480 -5
- data/lib/foobara/llm_backed_command.rb +7 -0
- metadata +25 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b425fcdb4e888c59b2bc32f4a7c4a4d066b11cf6fcfee6f6dcb8fc00da88cc21
|
4
|
+
data.tar.gz: b27ebd00c36a49a2e79001e1122e783a4dcd1406a4d5ef61891af726c64821df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 24facf55a6108124f8f0109b53acd0e2b5782d52f4ee812f8a7f223f725cdaedbb79a3bad11b0c67efa0253c612edacf50f7db56025c91991dac23e7344306f7
|
7
|
+
data.tar.gz: 80d4bd1e19ecdfd28b58ff84026ed5245c06dce067a0a7dffb5ed887bf97fe35395274eb2f25a581ab08ba53a7448b9e01dcaf67c032ef07e70c4096cd89994f
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,22 @@
|
|
1
|
-
# Foobara::LlmBackedCommand
|
1
|
+
# Foobara::LlmBackedCommand/Foobara::LlmBackedExecuteMethod
|
2
2
|
|
3
|
-
Provides a clean and quick way to implement a Foobara::Command that
|
3
|
+
Provides a clean and quick way to implement a Foobara::Command that defers to an LLM for the answer
|
4
|
+
|
5
|
+
<!-- TOC -->
|
6
|
+
* [Foobara::LlmBackedCommand/Foobara::LlmBackedExecuteMethod](#foobarallmbackedcommandfoobarallmbackedexecutemethod)
|
7
|
+
* [Installation](#installation)
|
8
|
+
* [Usage](#usage)
|
9
|
+
* [Choosing a different model and llm service](#choosing-a-different-model-and-llm-service)
|
10
|
+
* [Using it as a mixin instead of a class](#using-it-as-a-mixin-instead-of-a-class)
|
11
|
+
* [Typical Foobara stuff: exposing it on the command-line](#typical-foobara-stuff-exposing-it-on-the-command-line)
|
12
|
+
* [Exposing commands through Rack](#exposing-commands-through-rack)
|
13
|
+
* [Exposing commands through Rails](#exposing-commands-through-rails)
|
14
|
+
* [Use with models](#use-with-models)
|
15
|
+
* [Use with entities](#use-with-entities)
|
16
|
+
* [Complete scripts to play with](#complete-scripts-to-play-with)
|
17
|
+
* [Contributing](#contributing)
|
18
|
+
* [License](#license)
|
19
|
+
<!-- TOC -->
|
4
20
|
|
5
21
|
## Installation
|
6
22
|
|
@@ -9,12 +25,471 @@ Typical stuff: add `gem "foobara-llm-backed-command` to your Gemfile or .gemspec
|
|
9
25
|
|
10
26
|
## Usage
|
11
27
|
|
12
|
-
|
28
|
+
To play with these examples you can do `gem install foobara-llm-backed-command foobara-anthropic-api`
|
29
|
+
and then the following:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
ENV["ANTHROPIC_API_KEY"] = "<your key here>"
|
33
|
+
|
34
|
+
require 'foobara/llm_backed_command'
|
35
|
+
|
36
|
+
class DetermineLanguage < Foobara::LlmBackedCommand
|
37
|
+
inputs code_snippet: :string
|
38
|
+
result most_likely: :string, probabilities: { ruby: :float, c: :float, smalltalk: :float, java: :float }
|
39
|
+
end
|
40
|
+
|
41
|
+
puts DetermineLanguage.run!(code_snippet: "puts 'Hello, World'")
|
42
|
+
```
|
43
|
+
|
44
|
+
Running this script outputs:
|
45
|
+
|
46
|
+
```
|
47
|
+
$ ./demo
|
48
|
+
{most_likely: "ruby", probabilities: {ruby: 0.95, c: 0.01, smalltalk: 0.02, java: 0.02}}
|
49
|
+
```
|
50
|
+
|
51
|
+
Here we have only specified the command name and the inputs/result. Our LLM of choice
|
52
|
+
will be used to get the answer, and it will be structured according to the result type.
|
53
|
+
|
54
|
+
The default LLM model is claude-3-7-sonnet, but you can use others:
|
55
|
+
|
56
|
+
### Choosing a different model and llm service
|
57
|
+
|
58
|
+
One way to do this is by creating an input called `llm_model`
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
ENV["ANTHROPIC_API_KEY"] = "<your key here>"
|
62
|
+
ENV["OPENAI_API_KEY"] = "<your key here>"
|
63
|
+
ENV["OLLAMA_API_URL"] = "<your ollama API if different than http://localhost:11434>"
|
64
|
+
|
65
|
+
require "foobara/anthropic_api"
|
66
|
+
require "foobara/open_ai_api"
|
67
|
+
require "foobara/ollama_api"
|
68
|
+
require 'foobara/llm_backed_command'
|
69
|
+
|
70
|
+
class DetermineLanguage < Foobara::LlmBackedCommand
|
71
|
+
inputs do
|
72
|
+
code_snippet :string, :required
|
73
|
+
llm_model Foobara::Ai::AnswerBot::Types.model_enum, default: "claude-3-7-sonnet-20250219"
|
74
|
+
end
|
75
|
+
|
76
|
+
result most_likely: :string, probabilities: { ruby: :float, c: :float, smalltalk: :float, java: :float }
|
77
|
+
end
|
78
|
+
|
79
|
+
inputs = { llm_model: "chat-gpt-3-5-turbo", code_snippet: "puts 'Hello, World'" }
|
80
|
+
command = DetermineLanguage.new(inputs)
|
81
|
+
outcome = command.run
|
82
|
+
|
83
|
+
puts outcome.success? ? outcome.result : outcome.errors_hash
|
84
|
+
```
|
85
|
+
|
86
|
+
### Using it as a mixin instead of a class
|
87
|
+
|
88
|
+
If you need the inheritance slot for some other command base class, you can use the mixin:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
class DetermineLanguage < Foobara::Command
|
92
|
+
include Foobara::LlmBackedExecuteMethod
|
93
|
+
|
94
|
+
inputs code_snippet: :string
|
95
|
+
result most_likely: :string, probabilities: { ruby: :float, c: :float, smalltalk: :float, java: :float }
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
### Typical Foobara stuff: exposing it on the command-line
|
100
|
+
|
101
|
+
Probably best to refer to Foobara for details instead of turning this README.md into a Foobara tutorial, but
|
102
|
+
these can be used as any other Foobara command. So exposing
|
103
|
+
the command on the command line is easy (requires `gem install foobara-sh-cli-connector fooara-dotenv-loader`)
|
104
|
+
(switching to dotenv for convenience):
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
#!/usr/bin/env ruby
|
108
|
+
|
109
|
+
require "foobara/load_dotenv"
|
110
|
+
Foobara::LoadDotenv.run!(dir: __dir__)
|
111
|
+
|
112
|
+
require "foobara/anthropic_api"
|
113
|
+
require "foobara/open_ai_api"
|
114
|
+
require "foobara/ollama_api"
|
115
|
+
require "foobara/llm_backed_command"
|
116
|
+
require "foobara/sh_cli_connector"
|
117
|
+
|
118
|
+
class DetermineLanguage < Foobara::LlmBackedCommand
|
119
|
+
inputs do
|
120
|
+
code_snippet :string, :required
|
121
|
+
llm_model Foobara::Ai::AnswerBot::Types.model_enum, default: "claude-3-7-sonnet-20250219"
|
122
|
+
end
|
123
|
+
result most_likely: :string, probabilities: { ruby: :float, c: :float, smalltalk: :float, java: :float }
|
124
|
+
end
|
125
|
+
|
126
|
+
Foobara::CommandConnectors::ShCliConnector.new(single_command_mode: DetermineLanguage).run(ARGV)
|
127
|
+
```
|
128
|
+
|
129
|
+
which allows:
|
130
|
+
|
131
|
+
```
|
132
|
+
$ ./determine-language --help
|
133
|
+
Usage: determine-language [INPUTS]
|
134
|
+
|
135
|
+
Inputs:
|
136
|
+
|
137
|
+
-c, --code-snippet CODE_SNIPPET Required
|
138
|
+
-l, --llm-model LLM_MODEL One of: babbage-002, chatgpt-4o-latest, claude-2.0, claude-2.1,
|
139
|
+
claude-3-5-haiku-20241022, claude-3-5-sonnet-20240620, claude-3-5-sonnet-20241022,
|
140
|
+
claude-3-7-sonnet-20250219, claude-3-haiku-20240307, claude-3-opus-20240229,
|
141
|
+
claude-3-sonnet-20240229, dall-e-2, dall-e-3, davinci-002, gpt-3.5-turbo,
|
142
|
+
gpt-3.5-turbo-0125, gpt-3.5-turbo-1106, gpt-3.5-turbo-16k, gpt-3.5-turbo-instruct,
|
143
|
+
gpt-3.5-turbo-instruct-0914, gpt-4, gpt-4-0125-preview, gpt-4-0613,
|
144
|
+
gpt-4-1106-preview, gpt-4-turbo, gpt-4-turbo-2024-04-09, gpt-4-turbo-preview,
|
145
|
+
gpt-4.5-preview, gpt-4.5-preview-2025-02-27, gpt-4o, gpt-4o-2024-05-13,
|
146
|
+
gpt-4o-2024-08-06, gpt-4o-2024-11-20, gpt-4o-audio-preview, gpt-4o-audio-preview-2024-10-01,
|
147
|
+
gpt-4o-audio-preview-2024-12-17, gpt-4o-mini, gpt-4o-mini-2024-07-18,
|
148
|
+
gpt-4o-mini-audio-preview, gpt-4o-mini-audio-preview-2024-12-17,
|
149
|
+
gpt-4o-mini-realtime-preview, gpt-4o-mini-realtime-preview-2024-12-17,
|
150
|
+
gpt-4o-mini-search-preview, gpt-4o-mini-search-preview-2025-03-11,
|
151
|
+
gpt-4o-realtime-preview, gpt-4o-realtime-preview-2024-10-01, gpt-4o-realtime-preview-2024-12-17,
|
152
|
+
gpt-4o-search-preview, gpt-4o-search-preview-2025-03-11, llama3:8b,
|
153
|
+
o1, o1-2024-12-17, o1-mini, o1-mini-2024-09-12, o1-preview, o1-preview-2024-09-12,
|
154
|
+
o3-mini, o3-mini-2025-01-31, omni-moderation-2024-09-26, omni-moderation-latest,
|
155
|
+
smollm2:135m, text-embedding-3-large, text-embedding-3-small, text-embedding-ada-002,
|
156
|
+
tts-1, tts-1-1106, tts-1-hd, tts-1-hd-1106, whisper-1.
|
157
|
+
Default: "claude-3-7-sonnet-20250219"
|
158
|
+
```
|
159
|
+
|
160
|
+
Running the program:
|
161
|
+
|
162
|
+
```
|
163
|
+
$ ./determine-language --code-snippet "Transcript show: 'Hello, World'"
|
164
|
+
most_likely: "smalltalk",
|
165
|
+
probabilities: {
|
166
|
+
ruby: 0.1,
|
167
|
+
c: 0.05,
|
168
|
+
smalltalk: 0.8,
|
169
|
+
java: 0.05
|
170
|
+
}
|
171
|
+
```
|
172
|
+
|
173
|
+
### Exposing commands through Rack
|
174
|
+
|
175
|
+
Or you can spin up a quick json API either through Rack or the Rails router. Here's an example with the rack connector
|
176
|
+
(requires `gem install foobara-rack-controller rackup puma`):
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
#!/usr/bin/env ruby
|
180
|
+
|
181
|
+
require "foobara/load_dotenv"
|
182
|
+
Foobara::LoadDotenv.run!(dir: __dir__)
|
183
|
+
|
184
|
+
require "foobara/llm_backed_command"
|
185
|
+
require "foobara/rack_connector"
|
186
|
+
require "rackup/server"
|
187
|
+
|
188
|
+
class DetermineLanguage < Foobara::LlmBackedCommand
|
189
|
+
inputs code_snippet: :string
|
190
|
+
result probabilities: { ruby: :float, c: :float, smalltalk: :float, java: :float },
|
191
|
+
most_likely: :string
|
192
|
+
end
|
193
|
+
|
194
|
+
command_connector = Foobara::CommandConnectors::Http::Rack.new
|
195
|
+
command_connector.connect(DetermineLanguage)
|
196
|
+
|
197
|
+
Rackup::Server.start(app: command_connector)
|
198
|
+
```
|
199
|
+
|
200
|
+
After we run this script we can hit it with curl:
|
201
|
+
|
202
|
+
```
|
203
|
+
$ curl http://localhost:9292/run/DetermineLanguage?code_snippet=System.out.println
|
204
|
+
{"probabilities":{"ruby":0.05,"c":0.1,"smalltalk":0.05,"java":0.8},"most_likely":"java"}
|
205
|
+
```
|
206
|
+
|
207
|
+
And we can run it from other systems in either Ruby or TypeScript.
|
208
|
+
|
209
|
+
In Ruby (requires `gem install foobara-remote-imports`):
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
#!/usr/bin/env ruby
|
213
|
+
|
214
|
+
require "foobara/remote_imports"
|
215
|
+
|
216
|
+
Foobara::RemoteImports::ImportCommand.run!(manifest_url: "http://localhost:9292/manifest", cache: true)
|
217
|
+
|
218
|
+
puts DetermineLanguage.run!(code_snippet: "System.out.println")
|
219
|
+
```
|
220
|
+
|
221
|
+
This outputs:
|
222
|
+
|
223
|
+
```
|
224
|
+
$ ./determine-language-client
|
225
|
+
{probabilities: {ruby: 0.05, c: 0.05, smalltalk: 0.1, java: 0.8}, most_likely: "java"}
|
226
|
+
```
|
227
|
+
|
228
|
+
And we can also generate a remote command in TypeScript (requires `gem install foob`)
|
229
|
+
|
230
|
+
From inside a TypeScript project:
|
231
|
+
|
232
|
+
```
|
233
|
+
$ foob g typescript-remote-commands --manifest-url http://localhost:9292/manifest
|
234
|
+
```
|
235
|
+
|
236
|
+
This will generate code so that we can do:
|
237
|
+
|
238
|
+
```TypeScript
|
239
|
+
import { DetermineLanguage } from "./DetermineLanguage"
|
240
|
+
|
241
|
+
const command = new DetermineLanguage({code_snippet: "System.out.println"})
|
242
|
+
const outcome = await command.run()
|
243
|
+
|
244
|
+
if (outcome.isSuccess) {
|
245
|
+
console.log(outcome.result)
|
246
|
+
} else {
|
247
|
+
console.error(outcome.errors_hash)
|
248
|
+
}
|
249
|
+
```
|
250
|
+
|
251
|
+
and everything will be fully typed, inputs, result, etc.
|
252
|
+
|
253
|
+
### Exposing commands through Rails
|
254
|
+
|
255
|
+
This has become too much of a Foobara tutorial so instead please refer to
|
256
|
+
https://github.com/foobara/rails-command-connector
|
257
|
+
|
258
|
+
### Use with models
|
259
|
+
|
260
|
+
You can of course use this with whatever Foobara concepts you want, including models (and entities to some extent.)
|
261
|
+
|
262
|
+
Here's more complex example that accepts data encapsulated in model instances and returns model instances:
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
#!/usr/bin/env ruby
|
266
|
+
|
267
|
+
require "foobara/load_dotenv"
|
268
|
+
|
269
|
+
Foobara::LoadDotenv.run!(env: "development", dir: __dir__)
|
270
|
+
|
271
|
+
require "foobara/anthropic_api"
|
272
|
+
# require "foobara/open_ai_api"
|
273
|
+
# require "foobara/ollama_api"
|
274
|
+
require "foobara/llm_backed_command"
|
275
|
+
|
276
|
+
class PossibleUsState < Foobara::Model
|
277
|
+
attributes do
|
278
|
+
name :string, :required,
|
279
|
+
"A name that potentially might be a name of a US state, spelled correctly or incorrectly"
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
class VerifiedUsState < Foobara::Model
|
284
|
+
attributes do
|
285
|
+
possible_us_state PossibleUsState, :required,
|
286
|
+
"The original possible US state that was passed in"
|
287
|
+
spelling_correction_required :boolean, :required, "Whether or not the original spelling was correct"
|
288
|
+
corrected_spelling :string, :allow_nil,
|
289
|
+
"If the original spelling was incorrect, the corrected spelling will be here"
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
class SelectUsStateNamesAndCorrectTheirSpelling < Foobara::LlmBackedCommand
|
294
|
+
description <<~DESCRIPTION
|
295
|
+
Accepts a list of possible US state names and sorts them into verified to be the name of a
|
296
|
+
US state and rejected to be the name of a non-US state, as well as correcting the spelling of
|
297
|
+
the US state name if it's not correct.
|
298
|
+
|
299
|
+
example:
|
300
|
+
|
301
|
+
If you pass in ["Kalifornia", "Los Angeles", "New York"] the result will be:
|
302
|
+
|
303
|
+
result[:verified].length # => 2
|
304
|
+
result[:verified][0].possible_us_state.name # => "Kalifornia"
|
305
|
+
result[:verified][0].spelling_correction_required # => true#{" "}
|
306
|
+
result[:verified][0].corrected_spelling # => "California"
|
307
|
+
result[:verified][1].possible_us_state.name # => "New York"
|
308
|
+
result[:verified][1].spelling_correction_required # => false
|
309
|
+
result[:verified][1].corrected_spelling # => nil
|
310
|
+
|
311
|
+
result[:rejected].length # => 1
|
312
|
+
result[:rejected][0].name # => "Los Angeles"
|
313
|
+
DESCRIPTION
|
314
|
+
|
315
|
+
inputs do
|
316
|
+
list_of_possible_us_states [PossibleUsState]
|
317
|
+
llm_model :string, default: "claude-3-7-sonnet-20250219"
|
318
|
+
end
|
319
|
+
|
320
|
+
result do
|
321
|
+
verified [VerifiedUsState]
|
322
|
+
rejected [PossibleUsState]
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
list_of_possible_us_states = [
|
327
|
+
PossibleUsState.new(name: "Grand Rapids"),
|
328
|
+
PossibleUsState.new(name: "Oregon"),
|
329
|
+
PossibleUsState.new(name: "Yutah"),
|
330
|
+
PossibleUsState.new(name: "Misisipi"),
|
331
|
+
PossibleUsState.new(name: "Tacoma"),
|
332
|
+
PossibleUsState.new(name: "Kalifornia"),
|
333
|
+
PossibleUsState.new(name: "Los Angeles"),
|
334
|
+
PossibleUsState.new(name: "New York")
|
335
|
+
]
|
336
|
+
|
337
|
+
command = SelectUsStateNamesAndCorrectTheirSpelling.new(list_of_possible_us_states:)
|
338
|
+
result = command.run!
|
339
|
+
|
340
|
+
puts "Considering:"
|
341
|
+
list_of_possible_us_states.each do |possible_us_state|
|
342
|
+
puts " #{possible_us_state.name}"
|
343
|
+
end
|
344
|
+
puts
|
345
|
+
|
346
|
+
puts "#{result[:verified].length} were verified as US states:"
|
347
|
+
result[:verified].each do |verified_us_state|
|
348
|
+
puts " #{verified_us_state.corrected_spelling || verified_us_state.possible_us_state.name}"
|
349
|
+
if verified_us_state.spelling_correction_required
|
350
|
+
puts " original incorrect spelling: #{verified_us_state.possible_us_state.name}"
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
puts
|
355
|
+
puts "#{result[:rejected].length} were rejected as non-US states:"
|
356
|
+
result[:rejected].each do |rejected_us_state|
|
357
|
+
puts " #{rejected_us_state.name}"
|
358
|
+
end
|
359
|
+
```
|
360
|
+
|
361
|
+
This script outputs:
|
362
|
+
|
363
|
+
```
|
364
|
+
Considering:
|
365
|
+
Grand Rapids
|
366
|
+
Oregon
|
367
|
+
Yutah
|
368
|
+
Misisipi
|
369
|
+
Tacoma
|
370
|
+
Kalifornia
|
371
|
+
Los Angeles
|
372
|
+
New York
|
373
|
+
|
374
|
+
5 were verified as US states:
|
375
|
+
Oregon
|
376
|
+
Utah
|
377
|
+
original incorrect spelling: Yutah
|
378
|
+
Mississippi
|
379
|
+
original incorrect spelling: Misisipi
|
380
|
+
California
|
381
|
+
original incorrect spelling: Kalifornia
|
382
|
+
New York
|
383
|
+
|
384
|
+
3 were rejected as non-US states:
|
385
|
+
Grand Rapids
|
386
|
+
Tacoma
|
387
|
+
Los Angeles
|
388
|
+
```
|
389
|
+
|
390
|
+
Note that we were able to access model attributes by methods just fine, and we didn't write any logic on how
|
391
|
+
to spell check or any logic on how determine what is or is not a US state.
|
392
|
+
|
393
|
+
### Use with entities
|
394
|
+
|
395
|
+
Unclear how practical using entities in this way would be, but, here's an interesting
|
396
|
+
example of asking a question about some persisted records:
|
397
|
+
|
398
|
+
```ruby
|
399
|
+
#!/usr/bin/env ruby
|
400
|
+
|
401
|
+
require "foobara/load_dotenv"
|
402
|
+
|
403
|
+
Foobara::LoadDotenv.run!(env: "development", dir: __dir__)
|
404
|
+
|
405
|
+
require "foobara/llm_backed_command"
|
406
|
+
require "foobara/sh_cli_connector"
|
407
|
+
require "foobara/local_files_crud_driver"
|
408
|
+
|
409
|
+
Foobara::Persistence.default_crud_driver = Foobara::LocalFilesCrudDriver.new
|
410
|
+
|
411
|
+
class Capybara < Foobara::Entity
|
412
|
+
attributes do
|
413
|
+
id :integer
|
414
|
+
name :string, :required
|
415
|
+
age :integer, :required
|
416
|
+
end
|
417
|
+
primary_key :id
|
418
|
+
end
|
419
|
+
|
420
|
+
class CreateCapybara < Foobara::Command
|
421
|
+
inputs Capybara.attributes_for_create
|
422
|
+
result Capybara
|
423
|
+
|
424
|
+
def execute
|
425
|
+
create_capybara
|
426
|
+
|
427
|
+
capybara
|
428
|
+
end
|
429
|
+
|
430
|
+
attr_accessor :capybara
|
431
|
+
|
432
|
+
def create_capybara
|
433
|
+
self.capybara = Capybara.create(inputs)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
class SelectOldestCapybara < Foobara::LlmBackedCommand
|
438
|
+
inputs capybaras: [Capybara]
|
439
|
+
result Capybara
|
440
|
+
end
|
441
|
+
|
442
|
+
connector = Foobara::CommandConnectors::ShCliConnector.new
|
443
|
+
|
444
|
+
connector.connect(CreateCapybara)
|
445
|
+
connector.connect(SelectOldestCapybara)
|
446
|
+
|
447
|
+
connector.run(ARGV)
|
448
|
+
```
|
449
|
+
|
450
|
+
We can create some capybara records like this:
|
451
|
+
|
452
|
+
```
|
453
|
+
$ ./oldest-capybara-example CreateCapybara --age 100 --name Fumiko
|
454
|
+
age: 100,
|
455
|
+
name: "Fumiko",
|
456
|
+
id: 1
|
457
|
+
$ ./oldest-capybara-example CreateCapybara --age 200 --name Barbara
|
458
|
+
age: 200,
|
459
|
+
name: "Barbara",
|
460
|
+
id: 2
|
461
|
+
$ ./oldest-capybara-example CreateCapybara --age 300 --name Basil
|
462
|
+
age: 300,
|
463
|
+
name: "Basil",
|
464
|
+
id: 3
|
465
|
+
```
|
466
|
+
|
467
|
+
And then ask who is older, Barbara or Basil, like so:
|
468
|
+
|
469
|
+
```
|
470
|
+
$ ./oldest-capybara-example SelectOldestCapybara --capybaras 2 3
|
471
|
+
id: 3,
|
472
|
+
name: "Basil",
|
473
|
+
age: 300
|
474
|
+
```
|
475
|
+
|
476
|
+
Basil is older. Pretty crazy we didn't have to write any logic about age comparisons at all.
|
477
|
+
And our SelectOldestCapybara command is only 4 lines long as a result.
|
478
|
+
|
479
|
+
### Complete scripts to play with
|
480
|
+
|
481
|
+
There are various scripts in [example_scripts/shorter](example_scripts/shorter)
|
482
|
+
and [example_scripts/higher_quality](example_scripts/higher_quality) that you can play with.
|
483
|
+
|
484
|
+
The difference between the two directories is that `higher_quality/` contains scripts that are more realistic
|
485
|
+
with more descriptions and comments and `shorter/` focuses on keeping the scripts short and simple.
|
486
|
+
|
487
|
+
Those directories also have a Gemfile each if helpful for pulling in dependencies
|
488
|
+
and so that you can use `bundle exec` there if needed.
|
13
489
|
|
14
490
|
## Contributing
|
15
491
|
|
16
|
-
Bug reports and pull requests are welcome on GitHub
|
17
|
-
at https://github.com/foobara/llm-backed-command
|
492
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/foobara/llm-backed-command
|
18
493
|
|
19
494
|
## License
|
20
495
|
|
@@ -4,6 +4,13 @@ require "foobara/command_connectors"
|
|
4
4
|
require "foobara/ai"
|
5
5
|
require "foobara/json_schema_generator"
|
6
6
|
|
7
|
+
if Foobara::Ai.foobara_all_command.empty?
|
8
|
+
# :nocov:
|
9
|
+
raise "No api services loaded. " \
|
10
|
+
"Did you forget to set a URL/API key env var or a require for either ollama, anthropic, or openai?"
|
11
|
+
# :nocov:
|
12
|
+
end
|
13
|
+
|
7
14
|
Foobara::Util.require_directory "#{__dir__}/../../src"
|
8
15
|
|
9
16
|
Foobara::Monorepo.project "llm_backed_command", project_path: "#{__dir__}/../../"
|
metadata
CHANGED
@@ -1,42 +1,56 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foobara-llm-backed-command
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miles Georgi
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-05-21 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: foobara
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: 0.0.92
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 0.0.92
|
12
26
|
- !ruby/object:Gem::Dependency
|
13
27
|
name: foobara-ai
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
15
29
|
requirements:
|
16
|
-
- - "
|
30
|
+
- - "~>"
|
17
31
|
- !ruby/object:Gem::Version
|
18
|
-
version:
|
32
|
+
version: 0.0.1
|
19
33
|
type: :runtime
|
20
34
|
prerelease: false
|
21
35
|
version_requirements: !ruby/object:Gem::Requirement
|
22
36
|
requirements:
|
23
|
-
- - "
|
37
|
+
- - "~>"
|
24
38
|
- !ruby/object:Gem::Version
|
25
|
-
version:
|
39
|
+
version: 0.0.1
|
26
40
|
- !ruby/object:Gem::Dependency
|
27
41
|
name: foobara-json-schema-generator
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
29
43
|
requirements:
|
30
|
-
- - "
|
44
|
+
- - "~>"
|
31
45
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
46
|
+
version: 0.0.1
|
33
47
|
type: :runtime
|
34
48
|
prerelease: false
|
35
49
|
version_requirements: !ruby/object:Gem::Requirement
|
36
50
|
requirements:
|
37
|
-
- - "
|
51
|
+
- - "~>"
|
38
52
|
- !ruby/object:Gem::Version
|
39
|
-
version:
|
53
|
+
version: 0.0.1
|
40
54
|
email:
|
41
55
|
- azimux@gmail.com
|
42
56
|
executables: []
|
@@ -72,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
86
|
- !ruby/object:Gem::Version
|
73
87
|
version: '0'
|
74
88
|
requirements: []
|
75
|
-
rubygems_version: 3.6.
|
89
|
+
rubygems_version: 3.6.2
|
76
90
|
specification_version: 4
|
77
91
|
summary: Provides an easy way to implement a command whose logic is managed by an
|
78
92
|
LLM
|