omniai-llama 0.0.1
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 +7 -0
- data/Gemfile +17 -0
- data/README.md +432 -0
- data/lib/omniai/llama/chat/choice_serializer.rb +29 -0
- data/lib/omniai/llama/chat/message_serializer.rb +31 -0
- data/lib/omniai/llama/chat/response_serializer.rb +50 -0
- data/lib/omniai/llama/chat/usage_serializer.rb +49 -0
- data/lib/omniai/llama/chat.rb +63 -0
- data/lib/omniai/llama/client.rb +67 -0
- data/lib/omniai/llama/config.rb +23 -0
- data/lib/omniai/llama/version.rb +7 -0
- data/lib/omniai/llama.rb +24 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 25c0a8a9ba448d407d0f8300d377d0e3a10a0aa1b18343540a0b32f3eb3baeb5
|
4
|
+
data.tar.gz: 67d4173043933e6314df901969eae71b0ab1137dfd8da4365377824eeed167dd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2e43f7b12d1dbcbe2b02c9a7bc206ee0c1980c231843cb7fc292774548fed69ebb492a17aa8d451374a106bcc7d24f9ec0af27cce34004459493174b74854243
|
7
|
+
data.tar.gz: b991b560116560bff895708f89cc28d01c20a1f477ab69625fb910249bdc7b853eb91f23666b8d1fedb7caaac5f715bc01c3cfa749ee91a0d4929d9e9ac1f3ec
|
data/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gemspec
|
6
|
+
|
7
|
+
gem "rake"
|
8
|
+
gem "redcarpet"
|
9
|
+
gem "rspec"
|
10
|
+
gem "rspec_junit_formatter"
|
11
|
+
gem "rubocop"
|
12
|
+
gem "rubocop-basic"
|
13
|
+
gem "rubocop-rake"
|
14
|
+
gem "rubocop-rspec"
|
15
|
+
gem "simplecov"
|
16
|
+
gem "webmock"
|
17
|
+
gem "yard"
|
data/README.md
ADDED
@@ -0,0 +1,432 @@
|
|
1
|
+
# OmniAI::Llama
|
2
|
+
|
3
|
+
[](https://github.com/ksylvest/omniai-llama/blob/main/LICENSE)
|
4
|
+
[](https://rubygems.org/gems/omniai-llama)
|
5
|
+
[](https://github.com/ksylvest/omniai-llama)
|
6
|
+
[](https://omniai-llama.ksylvest.com)
|
7
|
+
[](https://circleci.com/gh/ksylvest/omniai-llama)
|
8
|
+
|
9
|
+
A implementation of the [OmniAI](https://github.com/ksylvest/omniai) for api.llama.com.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
```sh
|
14
|
+
gem install omniai-llama
|
15
|
+
```
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
### Client
|
20
|
+
|
21
|
+
A client is setup as follows if `ENV['LLAMA_API_KEY']` exists:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
client = OmniAI::Llama::Client.new
|
25
|
+
```
|
26
|
+
|
27
|
+
A client may also be passed the following options:
|
28
|
+
|
29
|
+
- `api_key` (required - default is `ENV['LLAMA_API_KEY']`)
|
30
|
+
|
31
|
+
### Configuration
|
32
|
+
|
33
|
+
Global configuration is supported for the following options:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
OmniAI::Llama.configure do |config|
|
37
|
+
config.api_key = 'sk-...' # default: ENV['LLAMA_API_KEY']
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
### Chat
|
42
|
+
|
43
|
+
A chat completion is generated by passing in a simple text prompt:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
completion = client.chat('Tell me a joke!')
|
47
|
+
completion.content # 'Why did the chicken cross the road? To get to the other side.'
|
48
|
+
```
|
49
|
+
|
50
|
+
A chat completion may also be generated by using a prompt builder:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
completion = client.chat do |prompt|
|
54
|
+
prompt.system('Your are an expert in geography.')
|
55
|
+
prompt.user('What is the capital of Canada?')
|
56
|
+
end
|
57
|
+
completion.content # 'The capital of Canada is Ottawa.'
|
58
|
+
```
|
59
|
+
|
60
|
+
#### Model
|
61
|
+
|
62
|
+
`model` takes an optional string (default is `gpt-4o`):
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
completion = client.chat('How fast is a cheetah?', model: OmniAI::Llama::Chat::Model::GPT_3_5_TURBO)
|
66
|
+
completion.content # 'A cheetah can reach speeds over 100 km/h.'
|
67
|
+
```
|
68
|
+
|
69
|
+
[OpenAI API Reference `model`](https://platform.openai.com/docs/api-reference/chat/create#chat-create-model)
|
70
|
+
|
71
|
+
#### Temperature
|
72
|
+
|
73
|
+
`temperature` takes an optional float between `0.0` and `2.0` (defaults is `0.7`):
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
completion = client.chat('Pick a number between 1 and 5', temperature: 2.0)
|
77
|
+
completion.content # '3'
|
78
|
+
```
|
79
|
+
|
80
|
+
[OpenAI API Reference `temperature`](https://platform.openai.com/docs/api-reference/chat/create#chat-create-temperature)
|
81
|
+
|
82
|
+
#### Stream
|
83
|
+
|
84
|
+
`stream` takes an optional a proc to stream responses in real-time chunks instead of waiting for a complete response:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
stream = proc do |chunk|
|
88
|
+
print(chunk.content) # 'Better', 'three', 'hours', ...
|
89
|
+
end
|
90
|
+
client.chat('Be poetic.', stream:)
|
91
|
+
```
|
92
|
+
|
93
|
+
[OpenAI API Reference `stream`](https://platform.openai.com/docs/api-reference/chat/create#chat-create-stream)
|
94
|
+
|
95
|
+
#### Format
|
96
|
+
|
97
|
+
`format` takes an optional symbol (`:json`) and that setes the `response_format` to `json_object`:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
completion = client.chat(format: :json) do |prompt|
|
101
|
+
prompt.system(OmniAI::Chat::JSON_PROMPT)
|
102
|
+
prompt.user('What is the name of the drummer for the Beatles?')
|
103
|
+
end
|
104
|
+
JSON.parse(completion.content) # { "name": "Ringo" }
|
105
|
+
```
|
106
|
+
|
107
|
+
[OpenAI API Reference `response_format`](https://platform.openai.com/docs/api-reference/chat/create#chat-create-stream)
|
108
|
+
|
109
|
+
> When using JSON mode, you must also instruct the model to produce JSON yourself via a system or user message.
|
110
|
+
|
111
|
+
### Transcribe
|
112
|
+
|
113
|
+
A transcription is generated by passing in a path to a file:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
transcription = client.transcribe(file.path)
|
117
|
+
transcription.text # '...'
|
118
|
+
```
|
119
|
+
|
120
|
+
#### Prompt
|
121
|
+
|
122
|
+
`prompt` is optional and can provide additional context for transcribing:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
transcription = client.transcribe(file.path, prompt: '')
|
126
|
+
transcription.text # '...'
|
127
|
+
```
|
128
|
+
|
129
|
+
[OpenAI API Reference `prompt`](https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-prompt)
|
130
|
+
|
131
|
+
#### Format
|
132
|
+
|
133
|
+
`format` is optional and supports `json`, `text`, `srt` or `vtt`:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
transcription = client.transcribe(file.path, format: OmniAI::Transcribe::Format::TEXT)
|
137
|
+
transcription.text # '...'
|
138
|
+
```
|
139
|
+
|
140
|
+
[OpenAI API Reference `response_format`](https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-response_format)
|
141
|
+
|
142
|
+
#### Language
|
143
|
+
|
144
|
+
`language` is optional and may improve accuracy and latency:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
transcription = client.transcribe(file.path, language: OmniAI::Transcribe::Language::SPANISH)
|
148
|
+
transcription.text
|
149
|
+
```
|
150
|
+
|
151
|
+
[OpenAI API Reference `language`](https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-language)
|
152
|
+
|
153
|
+
#### Temperature
|
154
|
+
|
155
|
+
`temperature` is optional and must be between 0.0 (more deterministic) and 1.0 (less deterministic):
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
transcription = client.transcribe(file.path, temperature: 0.2)
|
159
|
+
transcription.text
|
160
|
+
```
|
161
|
+
|
162
|
+
[OpenAI API Reference `temperature`](https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-temperature)
|
163
|
+
|
164
|
+
### Speak
|
165
|
+
|
166
|
+
Speech can be generated by passing text with a block:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
File.open('example.ogg', 'wb') do |file|
|
170
|
+
client.speak('How can a clam cram in a clean cream can?') do |chunk|
|
171
|
+
file << chunk
|
172
|
+
end
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
If a block is not provided then a tempfile is returned:
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
tempfile = client.speak('Can you can a can as a canner can can a can?')
|
180
|
+
tempfile.close
|
181
|
+
tempfile.unlink
|
182
|
+
```
|
183
|
+
|
184
|
+
#### Voice
|
185
|
+
|
186
|
+
`voice` is optional and must be one of the supported voices:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
client.speak('She sells seashells by the seashore.', voice: OmniAI::Llama::Speak::Voice::SHIMMER)
|
190
|
+
```
|
191
|
+
|
192
|
+
[OpenAI API Reference `voice`](https://platform.openai.com/docs/api-reference/audio/createSpeech#audio-createspeech-voice)
|
193
|
+
|
194
|
+
#### Model
|
195
|
+
|
196
|
+
`model` is optional and must be either `tts-1` or `tts-1-hd` (default):
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
client.speak('I saw a kitten eating chicken in the kitchen.', format: OmniAI::Llama::Speak::Model::TTS_1)
|
200
|
+
```
|
201
|
+
|
202
|
+
[OpenAI API Refernce `model`](https://platform.openai.com/docs/api-reference/audio/createSpeech#audio-createspeech-model)
|
203
|
+
|
204
|
+
#### Speed
|
205
|
+
|
206
|
+
`speed` is optional and must be between 0.25 and 0.40:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
client.speak('How much wood would a woodchuck chuck if a woodchuck could chuck wood?', speed: 4.0)
|
210
|
+
```
|
211
|
+
|
212
|
+
[OmniAI API Reference `speed`](https://platform.openai.com/docs/api-reference/audio/createSpeech#audio-createspeech-speed)
|
213
|
+
|
214
|
+
#### Format
|
215
|
+
|
216
|
+
`format` is optional and supports `MP3` (default), `OPUS`, `AAC`, `FLAC`, `WAV` or `PCM`:
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
client.speak('A pessemistic pest exists amidst us.', format: OmniAI::Llama::Speak::Format::FLAC)
|
220
|
+
```
|
221
|
+
|
222
|
+
[OpenAI API Reference `format`](https://platform.openai.com/docs/api-reference/audio/createSpeech#audio-createspeech-response_format)
|
223
|
+
|
224
|
+
## Files
|
225
|
+
|
226
|
+
### Finding an File
|
227
|
+
|
228
|
+
```ruby
|
229
|
+
client.files.find(id: 'file_...')
|
230
|
+
```
|
231
|
+
|
232
|
+
### Listing all Files
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
client.files.all
|
236
|
+
```
|
237
|
+
|
238
|
+
### Uploading a File
|
239
|
+
|
240
|
+
#### Using a File
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
file = client.files.build(io: File.open('demo.pdf', 'wb'))
|
244
|
+
file.save!
|
245
|
+
```
|
246
|
+
|
247
|
+
#### Using a Path
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
file = client.files.build(io: 'demo.pdf'))
|
251
|
+
file.save!
|
252
|
+
```
|
253
|
+
|
254
|
+
### Downloading a File
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
file = client.files.find(id: 'file_...')
|
258
|
+
File.open('...', 'wb') do |file|
|
259
|
+
file.content do |chunk|
|
260
|
+
file << chunk
|
261
|
+
end
|
262
|
+
end
|
263
|
+
```
|
264
|
+
|
265
|
+
### Destroying a File
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
client.files.destroy!('file_...')
|
269
|
+
```
|
270
|
+
|
271
|
+
## Assistants
|
272
|
+
|
273
|
+
### Finding an Assistant
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
client.assistants.find(id: 'asst_...')
|
277
|
+
```
|
278
|
+
|
279
|
+
### Listing all Assistants
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
client.assistants.all
|
283
|
+
```
|
284
|
+
|
285
|
+
### Creating an Assistant
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
assistant = client.assistants.build
|
289
|
+
assistant.name = 'Ringo'
|
290
|
+
assistant.model = OmniAI::Llama::Chat::Model::GPT_4
|
291
|
+
assistant.description = 'The drummer for the Beatles.'
|
292
|
+
assistant.save!
|
293
|
+
```
|
294
|
+
|
295
|
+
### Updating an Assistant
|
296
|
+
|
297
|
+
```ruby
|
298
|
+
assistant = client.assistants.find(id: 'asst_...')
|
299
|
+
assistant.name = 'George'
|
300
|
+
assistant.model = OmniAI::Llama::Chat::Model::GPT_4
|
301
|
+
assistant.description = 'A guitarist for the Beatles.'
|
302
|
+
assistant.save!
|
303
|
+
```
|
304
|
+
|
305
|
+
### Destroying an Assistant
|
306
|
+
|
307
|
+
```ruby
|
308
|
+
client.assistants.destroy!('asst_...')
|
309
|
+
```
|
310
|
+
|
311
|
+
## Threads
|
312
|
+
|
313
|
+
### Finding a Thread
|
314
|
+
|
315
|
+
```ruby
|
316
|
+
client.threads.find(id: 'thread_...')
|
317
|
+
```
|
318
|
+
|
319
|
+
### Creating a Thread
|
320
|
+
|
321
|
+
```ruby
|
322
|
+
thread = client.threads.build
|
323
|
+
thread.metadata = { user: 'Ringo' }
|
324
|
+
thread.save!
|
325
|
+
```
|
326
|
+
|
327
|
+
### Updating a Thread
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
thread = client.threads.find(id: 'thread_...')
|
331
|
+
thread.metadata = { user: 'Ringo' }
|
332
|
+
thread.save!
|
333
|
+
```
|
334
|
+
|
335
|
+
### Destroying a Threads
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
client.threads.destroy!('thread_...')
|
339
|
+
```
|
340
|
+
|
341
|
+
### Messages
|
342
|
+
|
343
|
+
#### Finding a Message
|
344
|
+
|
345
|
+
```ruby
|
346
|
+
thread = client.threads.find(id: 'thread_...')
|
347
|
+
message = thread.messages.find(id: 'msg_...')
|
348
|
+
message.save!
|
349
|
+
```
|
350
|
+
|
351
|
+
#### Listing all Messages
|
352
|
+
|
353
|
+
```ruby
|
354
|
+
thread = client.threads.find(id: 'thread_...')
|
355
|
+
thread.messages.all
|
356
|
+
```
|
357
|
+
|
358
|
+
#### Creating a Message
|
359
|
+
|
360
|
+
```ruby
|
361
|
+
thread = client.threads.find(id: 'thread_...')
|
362
|
+
message = thread.messages.build(role: 'user', content: 'Hello?')
|
363
|
+
message.save!
|
364
|
+
```
|
365
|
+
|
366
|
+
#### Updating a Message
|
367
|
+
|
368
|
+
```ruby
|
369
|
+
thread = client.threads.find(id: 'thread_...')
|
370
|
+
message = thread.messages.build(role: 'user', content: 'Hello?')
|
371
|
+
message.save!
|
372
|
+
```
|
373
|
+
|
374
|
+
### Runs
|
375
|
+
|
376
|
+
#### Finding a Run
|
377
|
+
|
378
|
+
```ruby
|
379
|
+
thread = client.threads.find(id: 'thread_...')
|
380
|
+
run = thread.runs.find(id: 'run_...')
|
381
|
+
run.save!
|
382
|
+
```
|
383
|
+
|
384
|
+
#### Listing all Runs
|
385
|
+
|
386
|
+
```ruby
|
387
|
+
thread = client.threads.find(id: 'thread_...')
|
388
|
+
thread.runs.all
|
389
|
+
```
|
390
|
+
|
391
|
+
#### Creating a Run
|
392
|
+
|
393
|
+
```ruby
|
394
|
+
run = client.runs.find(id: 'thread_...')
|
395
|
+
run = thread.runs.build
|
396
|
+
run.metadata = { user: 'Ringo' }
|
397
|
+
run.save!
|
398
|
+
```
|
399
|
+
|
400
|
+
#### Updating a Run
|
401
|
+
|
402
|
+
```ruby
|
403
|
+
thread = client.threads.find(id: 'thread_...')
|
404
|
+
run = thread.messages.find(id: 'run_...')
|
405
|
+
run.metadata = { user: 'Ringo' }
|
406
|
+
run.save!
|
407
|
+
```
|
408
|
+
|
409
|
+
#### Polling a Run
|
410
|
+
|
411
|
+
```ruby
|
412
|
+
run.terminated? # false
|
413
|
+
run.poll!
|
414
|
+
run.terminated? # true
|
415
|
+
run.status # 'cancelled' / 'failed' / 'completed' / 'expired'
|
416
|
+
```
|
417
|
+
|
418
|
+
#### Cancelling a Run
|
419
|
+
|
420
|
+
```ruby
|
421
|
+
thread = client.threads.find(id: 'thread_...')
|
422
|
+
run = thread.runs.cancel!(id: 'run_...')
|
423
|
+
```
|
424
|
+
|
425
|
+
### Embed
|
426
|
+
|
427
|
+
Text can be converted into a vector embedding for similarity comparison usage via:
|
428
|
+
|
429
|
+
```ruby
|
430
|
+
response = client.embed('The quick brown fox jumps over a lazy dog.')
|
431
|
+
response.embedding # [0.0, ...]
|
432
|
+
```
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAI
|
4
|
+
module Llama
|
5
|
+
class Chat
|
6
|
+
# Overrides choice serialize / deserialize for the following payload:
|
7
|
+
#
|
8
|
+
# {
|
9
|
+
# content: {
|
10
|
+
# type: "text",
|
11
|
+
# text: "Hello!",
|
12
|
+
# },
|
13
|
+
# role: "assistant",
|
14
|
+
# stop_reason: "stop",
|
15
|
+
# tool_calls: [],
|
16
|
+
# }
|
17
|
+
module ChoiceSerializer
|
18
|
+
# @param data [Hash]
|
19
|
+
# @param context [OmniAI::Context]
|
20
|
+
#
|
21
|
+
# @return [OmniAI::Chat::Response]
|
22
|
+
def self.deserialize(data, context:)
|
23
|
+
message = OmniAI::Chat::Message.deserialize(data, context:)
|
24
|
+
OmniAI::Chat::Choice.new(message:)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAI
|
4
|
+
module Llama
|
5
|
+
class Chat
|
6
|
+
# Overrides choice serialize / deserialize for the following payload:
|
7
|
+
#
|
8
|
+
# {
|
9
|
+
# content: {
|
10
|
+
# type: "text",
|
11
|
+
# text: "Hello!",
|
12
|
+
# },
|
13
|
+
# role: "assistant",
|
14
|
+
# stop_reason: "stop",
|
15
|
+
# tool_calls: [],
|
16
|
+
# }
|
17
|
+
module MessageSerializer
|
18
|
+
# @param data [Hash]
|
19
|
+
# @param context [OmniAI::Context]
|
20
|
+
#
|
21
|
+
# @return [OmniAI::Chat::Message]
|
22
|
+
def self.deserialize(data, context:)
|
23
|
+
role = data["role"]
|
24
|
+
content = OmniAI::Chat::Content.deserialize(data["content"], context:)
|
25
|
+
|
26
|
+
OmniAI::Chat::Message.new(content:, role:)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAI
|
4
|
+
module Llama
|
5
|
+
class Chat
|
6
|
+
# Overrides response serialize / deserialize for the following payload:
|
7
|
+
#
|
8
|
+
# {
|
9
|
+
# completion_message: {
|
10
|
+
# content: {
|
11
|
+
# type: "text",
|
12
|
+
# text: "Hello!",
|
13
|
+
# },
|
14
|
+
# role: "assistant",
|
15
|
+
# stop_reason: "stop",
|
16
|
+
# tool_calls: [],
|
17
|
+
# },
|
18
|
+
# metrics: [
|
19
|
+
# {
|
20
|
+
# metric: "num_completion_tokens",
|
21
|
+
# value: 25,
|
22
|
+
# unit: "tokens",
|
23
|
+
# },
|
24
|
+
# {
|
25
|
+
# metric: "num_prompt_tokens",
|
26
|
+
# value: 25,
|
27
|
+
# unit: "tokens",
|
28
|
+
# },
|
29
|
+
# {
|
30
|
+
# metric: "num_total_tokens",
|
31
|
+
# value: 50,
|
32
|
+
# unit: "tokens",
|
33
|
+
# },
|
34
|
+
# ],
|
35
|
+
# }
|
36
|
+
module ResponseSerializer
|
37
|
+
# @param data [Hash]
|
38
|
+
# @param context [OmniAI::Context]
|
39
|
+
#
|
40
|
+
# @return [OmniAI::Chat::Response]
|
41
|
+
def self.deserialize(data, context:)
|
42
|
+
usage = OmniAI::Chat::Usage.deserialize(data["metrics"], context:) if data["metrics"]
|
43
|
+
choice = OmniAI::Chat::Choice.deserialize(data["completion_message"], context:)
|
44
|
+
|
45
|
+
OmniAI::Chat::Response.new(data:, choices: [choice], usage:)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAI
|
4
|
+
module Llama
|
5
|
+
class Chat
|
6
|
+
# Overrides response serialize / deserialize for the following payload:
|
7
|
+
#
|
8
|
+
# [
|
9
|
+
# {
|
10
|
+
# metric: "num_completion_tokens",
|
11
|
+
# value: 25,
|
12
|
+
# unit: "tokens",
|
13
|
+
# },
|
14
|
+
# {
|
15
|
+
# metric: "num_prompt_tokens",
|
16
|
+
# value: 25,
|
17
|
+
# unit: "tokens",
|
18
|
+
# },
|
19
|
+
# {
|
20
|
+
# metric: "num_total_tokens",
|
21
|
+
# value: 50,
|
22
|
+
# unit: "tokens",
|
23
|
+
# },
|
24
|
+
# ]
|
25
|
+
module UsageSerializer
|
26
|
+
module Metric
|
27
|
+
NUM_PROMPT_TOKENS = "num_prompt_tokens"
|
28
|
+
NUM_COMPLETION_TOKENS = "num_completion_tokens"
|
29
|
+
NUM_TOTAL_TOKENS = "num_total_tokens"
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param data [Hash]
|
33
|
+
#
|
34
|
+
# @return [OmniAI::Chat::Response]
|
35
|
+
def self.deserialize(data, *)
|
36
|
+
prompt = data.find { |metric| metric["metric"] == Metric::NUM_PROMPT_TOKENS }
|
37
|
+
completion = data.find { |metric| metric["metric"] == Metric::NUM_COMPLETION_TOKENS }
|
38
|
+
total = data.find { |metric| metric["metric"] == Metric::NUM_TOTAL_TOKENS }
|
39
|
+
|
40
|
+
input_tokens = prompt ? prompt["value"] : 0
|
41
|
+
output_tokens = completion ? completion["value"] : 0
|
42
|
+
total_tokens = total ? total["value"] : 0
|
43
|
+
|
44
|
+
OmniAI::Chat::Usage.new(input_tokens:, output_tokens:, total_tokens:)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAI
|
4
|
+
module Llama
|
5
|
+
# An OpenAI chat implementation.
|
6
|
+
#
|
7
|
+
# Usage:
|
8
|
+
#
|
9
|
+
# completion = OmniAI::Llama::Chat.process!(client: client) do |prompt|
|
10
|
+
# prompt.system('You are an expert in the field of AI.')
|
11
|
+
# prompt.user('What are the biggest risks of AI?')
|
12
|
+
# end
|
13
|
+
# completion.choice.message.content # '...'
|
14
|
+
class Chat < OmniAI::Chat
|
15
|
+
JSON_RESPONSE_FORMAT = { type: "json_object" }.freeze
|
16
|
+
DEFAULT_STREAM_OPTIONS = { include_usage: ENV.fetch("OMNIAI_STREAM_USAGE", "on").eql?("on") }.freeze
|
17
|
+
|
18
|
+
module Model
|
19
|
+
LLAMA_4_SCOUT_17B_16E_INSTRUCT_FP8 = "Llama-4-Scout-17B-16E-Instruct-FP8"
|
20
|
+
LLAMA_4_MAVERICK_17B_128E_INSTRUCT_FP_8 = "Llama-4-Maverick-17B-128E-Instruct-FP8"
|
21
|
+
LLAMA_3_3_70B_INSTRUCT = "Llama-3.3-70B-Instruct"
|
22
|
+
LLAMA_3_3_8B_INSTRUCT = "Llama-3.3-8B-Instruct"
|
23
|
+
LLAMA_4_SCOUT = LLAMA_4_SCOUT_17B_16E_INSTRUCT_FP8
|
24
|
+
LLAMA_4_MAVERICK = LLAMA_4_MAVERICK_17B_128E_INSTRUCT_FP_8
|
25
|
+
end
|
26
|
+
|
27
|
+
DEFAULT_MODEL = Model::LLAMA_4_SCOUT
|
28
|
+
|
29
|
+
# @return [Context]
|
30
|
+
CONTEXT = Context.build do |context|
|
31
|
+
context.deserializers[:response] = ResponseSerializer.method(:deserialize)
|
32
|
+
context.deserializers[:choice] = ChoiceSerializer.method(:deserialize)
|
33
|
+
context.deserializers[:message] = MessageSerializer.method(:deserialize)
|
34
|
+
context.deserializers[:usage] = UsageSerializer.method(:deserialize)
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
# @return [Context]
|
40
|
+
def context
|
41
|
+
CONTEXT
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Hash]
|
45
|
+
def payload
|
46
|
+
OmniAI::Llama.config.chat_options.merge({
|
47
|
+
messages: @prompt.serialize,
|
48
|
+
model: @model,
|
49
|
+
response_format: (JSON_RESPONSE_FORMAT if @format.eql?(:json)),
|
50
|
+
stream: stream? || nil,
|
51
|
+
stream_options: (DEFAULT_STREAM_OPTIONS if stream?),
|
52
|
+
temperature: @temperature,
|
53
|
+
tools: (@tools.map(&:serialize) if @tools&.any?),
|
54
|
+
}).compact
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [String]
|
58
|
+
def path
|
59
|
+
"/#{OmniAI::Llama::Client::VERSION}/chat/completions"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAI
|
4
|
+
module Llama
|
5
|
+
# A Llama client implementation. Usage:
|
6
|
+
#
|
7
|
+
# w/ `api_key``:
|
8
|
+
# client = OmniAI::Llama::Client.new(api_key: '...')
|
9
|
+
#
|
10
|
+
# w/ ENV['LLAMA_API_KEY']:
|
11
|
+
#
|
12
|
+
# ENV['LLAMA_API_KEY'] = '...'
|
13
|
+
# client = OmniAI::Llama::Client.new
|
14
|
+
#
|
15
|
+
# w/ config:
|
16
|
+
#
|
17
|
+
# OmniAI::Llama.configure do |config|
|
18
|
+
# config.api_key = '...'
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# client = OmniAI::Llama::Client.new
|
22
|
+
class Client < OmniAI::Client
|
23
|
+
VERSION = "v1"
|
24
|
+
|
25
|
+
# @param api_key [String, nil] optional - defaults to `OmniAI::Llama.config.api_key`
|
26
|
+
# @param host [String] optional - defaults to `OmniAI::Llama.config.host`
|
27
|
+
# @param logger [Logger, nil] optional - defaults to `OmniAI::Llama.config.logger`
|
28
|
+
# @param timeout [Integer, nil] optional - defaults to `OmniAI::Llama.config.timeout`
|
29
|
+
def initialize(
|
30
|
+
api_key: OmniAI::Llama.config.api_key,
|
31
|
+
host: OmniAI::Llama.config.host,
|
32
|
+
logger: OmniAI::Llama.config.logger,
|
33
|
+
timeout: OmniAI::Llama.config.timeout
|
34
|
+
)
|
35
|
+
raise(ArgumentError, %(ENV['LLAMA_API_KEY'] must be defined or `api_key` must be passed)) if api_key.nil?
|
36
|
+
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [HTTP::Client]
|
41
|
+
def connection
|
42
|
+
@connection ||= begin
|
43
|
+
http = super
|
44
|
+
http = http.auth("Bearer #{@api_key}") if @api_key
|
45
|
+
http
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# @raise [OmniAI::Error]
|
50
|
+
#
|
51
|
+
# @param messages [String] optional
|
52
|
+
# @param model [String] optional
|
53
|
+
# @param format [Symbol] optional :text or :json
|
54
|
+
# @param temperature [Float, nil] optional
|
55
|
+
# @param stream [Proc, nil] optional
|
56
|
+
# @param tools [Array<OmniAI::Tool>, nil] optional
|
57
|
+
#
|
58
|
+
# @yield [prompt]
|
59
|
+
# @yieldparam prompt [OmniAI::Chat::Prompt]
|
60
|
+
#
|
61
|
+
# @return [OmniAI::Chat::Completion]
|
62
|
+
def chat(messages = nil, model: Chat::DEFAULT_MODEL, temperature: nil, format: nil, stream: nil, tools: nil, &)
|
63
|
+
Chat.process!(messages, model:, temperature:, format:, stream:, tools:, client: self, &)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAI
|
4
|
+
module Llama
|
5
|
+
# Configuration for OpenAI.
|
6
|
+
class Config < OmniAI::Config
|
7
|
+
DEFAULT_HOST = "https://api.llama.com"
|
8
|
+
|
9
|
+
# @param api_key [String, nil] optional - defaults to `ENV['LLAMA_API_KEY']`
|
10
|
+
# @param host [String, nil] optional - defaults to ENV['LLAMA_HOST'] w/ fallback to `DEFAULT_HOST`
|
11
|
+
# @param logger [Logger, nil] optional
|
12
|
+
# @param timeout [Integer, Hash, nil] optional
|
13
|
+
def initialize(
|
14
|
+
api_key: ENV.fetch("LLAMA_API_KEY", nil),
|
15
|
+
host: ENV.fetch("LLAMA_HOST", DEFAULT_HOST),
|
16
|
+
logger: nil,
|
17
|
+
timeout: nil
|
18
|
+
)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/omniai/llama.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "event_stream_parser"
|
4
|
+
require "omniai"
|
5
|
+
require "zeitwerk"
|
6
|
+
|
7
|
+
loader = Zeitwerk::Loader.for_gem
|
8
|
+
loader.push_dir(__dir__, namespace: OmniAI)
|
9
|
+
loader.setup
|
10
|
+
|
11
|
+
module OmniAI
|
12
|
+
# A namespace for everything Llama.
|
13
|
+
module Llama
|
14
|
+
# @return [OmniAI::Llama::Config]
|
15
|
+
def self.config
|
16
|
+
@config ||= Config.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# @yield [OmniAI::Llama::Config]
|
20
|
+
def self.configure
|
21
|
+
yield config
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: omniai-llama
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kevin Sylvestre
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-05-01 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: event_stream_parser
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: omniai
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.2'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2.2'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: zeitwerk
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
description: An implementation of OmniAI for OpenAI
|
55
|
+
email:
|
56
|
+
- kevin@ksylvest.com
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- Gemfile
|
62
|
+
- README.md
|
63
|
+
- lib/omniai/llama.rb
|
64
|
+
- lib/omniai/llama/chat.rb
|
65
|
+
- lib/omniai/llama/chat/choice_serializer.rb
|
66
|
+
- lib/omniai/llama/chat/message_serializer.rb
|
67
|
+
- lib/omniai/llama/chat/response_serializer.rb
|
68
|
+
- lib/omniai/llama/chat/usage_serializer.rb
|
69
|
+
- lib/omniai/llama/client.rb
|
70
|
+
- lib/omniai/llama/config.rb
|
71
|
+
- lib/omniai/llama/version.rb
|
72
|
+
homepage: https://github.com/ksylvest/omniai-llama
|
73
|
+
licenses:
|
74
|
+
- MIT
|
75
|
+
metadata:
|
76
|
+
homepage_uri: https://github.com/ksylvest/omniai-llama
|
77
|
+
changelog_uri: https://github.com/ksylvest/omniai-llama/releases
|
78
|
+
rubygems_mfa_required: 'true'
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 3.2.0
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubygems_version: 3.6.6
|
94
|
+
specification_version: 4
|
95
|
+
summary: A generalized framework for interacting with OpenAI
|
96
|
+
test_files: []
|