llm.rb 0.11.0 → 0.13.0
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/LICENSE +0 -0
- data/README.md +56 -19
- data/lib/llm/bot/builder.rb +0 -0
- data/lib/llm/bot/conversable.rb +12 -4
- data/lib/llm/bot/prompt/completion.rb +18 -0
- data/lib/llm/bot/prompt/respond.rb +9 -0
- data/lib/llm/bot.rb +10 -17
- data/lib/llm/buffer.rb +15 -1
- data/lib/llm/error.rb +4 -0
- data/lib/llm/{event_handler.rb → eventhandler.rb} +0 -0
- data/lib/llm/eventstream/event.rb +0 -0
- data/lib/llm/eventstream/parser.rb +0 -0
- data/lib/llm/eventstream.rb +0 -0
- data/lib/llm/file.rb +4 -3
- data/lib/llm/function.rb +4 -4
- data/lib/llm/message.rb +0 -0
- data/lib/llm/mime.rb +88 -6
- data/lib/llm/multipart.rb +0 -1
- data/lib/llm/object/builder.rb +0 -0
- data/lib/llm/object/kernel.rb +0 -0
- data/lib/llm/object.rb +2 -3
- data/lib/llm/provider.rb +3 -3
- data/lib/llm/providers/anthropic/error_handler.rb +2 -0
- data/lib/llm/providers/anthropic/format/completion_format.rb +0 -0
- data/lib/llm/providers/anthropic/format.rb +0 -0
- data/lib/llm/providers/anthropic/models.rb +2 -2
- data/lib/llm/providers/anthropic/response/completion.rb +0 -0
- data/lib/llm/providers/anthropic/stream_parser.rb +0 -0
- data/lib/llm/providers/anthropic.rb +10 -1
- data/lib/llm/providers/deepseek/format/completion_format.rb +0 -0
- data/lib/llm/providers/deepseek/format.rb +0 -0
- data/lib/llm/providers/deepseek.rb +10 -1
- data/lib/llm/providers/gemini/audio.rb +3 -3
- data/lib/llm/providers/gemini/error_handler.rb +2 -0
- data/lib/llm/providers/gemini/files.rb +8 -20
- data/lib/llm/providers/gemini/format/completion_format.rb +2 -2
- data/lib/llm/providers/gemini/format.rb +0 -0
- data/lib/llm/providers/gemini/images.rb +4 -4
- data/lib/llm/providers/gemini/models.rb +2 -2
- data/lib/llm/providers/gemini/response/completion.rb +2 -0
- data/lib/llm/providers/gemini/response/embedding.rb +1 -1
- data/lib/llm/providers/gemini/response/file.rb +0 -0
- data/lib/llm/providers/gemini/response/image.rb +0 -0
- data/lib/llm/providers/gemini/stream_parser.rb +0 -0
- data/lib/llm/providers/gemini.rb +13 -21
- data/lib/llm/providers/llamacpp.rb +12 -1
- data/lib/llm/providers/ollama/error_handler.rb +2 -0
- data/lib/llm/providers/ollama/format/completion_format.rb +0 -0
- data/lib/llm/providers/ollama/format.rb +0 -0
- data/lib/llm/providers/ollama/models.rb +0 -0
- data/lib/llm/providers/ollama/response/completion.rb +0 -0
- data/lib/llm/providers/ollama/response/embedding.rb +1 -2
- data/lib/llm/providers/ollama/stream_parser.rb +0 -0
- data/lib/llm/providers/ollama.rb +8 -11
- data/lib/llm/providers/openai/audio.rb +4 -4
- data/lib/llm/providers/openai/error_handler.rb +13 -1
- data/lib/llm/providers/openai/files.rb +8 -19
- data/lib/llm/providers/openai/format/completion_format.rb +0 -0
- data/lib/llm/providers/openai/format/moderation_format.rb +0 -0
- data/lib/llm/providers/openai/format/respond_format.rb +0 -0
- data/lib/llm/providers/openai/format.rb +0 -0
- data/lib/llm/providers/openai/images.rb +10 -10
- data/lib/llm/providers/openai/models.rb +2 -2
- data/lib/llm/providers/openai/moderations.rb +0 -0
- data/lib/llm/providers/openai/response/audio.rb +0 -0
- data/lib/llm/providers/openai/response/completion.rb +2 -2
- data/lib/llm/providers/openai/response/embedding.rb +3 -3
- data/lib/llm/providers/openai/response/file.rb +0 -0
- data/lib/llm/providers/openai/response/image.rb +0 -0
- data/lib/llm/providers/openai/response/moderations.rb +0 -0
- data/lib/llm/providers/openai/response/responds.rb +0 -1
- data/lib/llm/providers/openai/responses.rb +6 -25
- data/lib/llm/providers/openai/stream_parser.rb +1 -0
- data/lib/llm/providers/openai/vector_stores.rb +85 -3
- data/lib/llm/providers/openai.rb +10 -1
- data/lib/llm/providers/xai/images.rb +58 -0
- data/lib/llm/providers/xai.rb +72 -0
- data/lib/llm/response.rb +5 -0
- data/lib/llm/{json/schema → schema}/array.rb +3 -3
- data/lib/llm/{json/schema → schema}/boolean.rb +3 -3
- data/lib/llm/{json/schema → schema}/integer.rb +6 -6
- data/lib/llm/{json/schema → schema}/leaf.rb +9 -9
- data/lib/llm/{json/schema → schema}/null.rb +3 -3
- data/lib/llm/{json/schema → schema}/number.rb +6 -6
- data/lib/llm/{json/schema → schema}/object.rb +3 -3
- data/lib/llm/{json/schema → schema}/string.rb +5 -5
- data/lib/llm/{json/schema → schema}/version.rb +1 -1
- data/lib/llm/{json/schema.rb → schema.rb} +10 -13
- data/lib/llm/utils.rb +0 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +11 -2
- data/llm.gemspec +4 -4
- metadata +22 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f83248fa3bce285d75952b726cc7d1b097e61b61fb77edd55f39168922a8614c
|
4
|
+
data.tar.gz: 52fa691fad7eaa2e77e24245aba97ea469688f8307f869d533fac47707aa6caf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d17e104601295bbbd8dd5bd34fe5378e96367aacb832ddb81b7f0fdfd3fa54f39d98dff6568f3190a4d979de4382a6ebd2eba8ee2fb2cce0e67db2478be9a1df
|
7
|
+
data.tar.gz: d2e6bb0f507bdcf140bd7f44f9edd40c057f061a9b4bc7123a87bdf6fa40a6f16a99642965bdaf81aaaeab5e995a01587c79cd47e19c6a2b5f86e456fa4813a9
|
data/LICENSE
CHANGED
File without changes
|
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
## About
|
2
2
|
|
3
3
|
llm.rb is a zero-dependency Ruby toolkit for Large Language Models that
|
4
|
-
includes OpenAI, Gemini, Anthropic, DeepSeek, Ollama, and
|
5
|
-
toolkit includes full support for chat, streaming, tool calling,
|
6
|
-
images, files, and JSON Schema
|
4
|
+
includes OpenAI, Gemini, Anthropic, xAI (grok), DeepSeek, Ollama, and
|
5
|
+
LlamaCpp. The toolkit includes full support for chat, streaming, tool calling,
|
6
|
+
audio, images, files, and structured outputs (JSON Schema).
|
7
7
|
|
8
8
|
## Features
|
9
9
|
|
@@ -22,12 +22,41 @@ images, files, and JSON Schema generation.
|
|
22
22
|
- 🗣️ Text-to-speech, transcription, and translation
|
23
23
|
- 🖼️ Image generation, editing, and variation support
|
24
24
|
- 📎 File uploads and prompt-aware file interaction
|
25
|
-
- 💡 Multimodal prompts (text, images,
|
25
|
+
- 💡 Multimodal prompts (text, documents, audio, images, videos, URLs, etc)
|
26
26
|
|
27
|
-
####
|
27
|
+
#### Embeddings
|
28
28
|
- 🧮 Text embeddings and vector support
|
29
|
-
-
|
30
|
-
|
29
|
+
- 🧱 Includes support for OpenAI's vector stores API
|
30
|
+
|
31
|
+
#### Miscellaneous
|
32
|
+
- 📜 Model management and selection
|
33
|
+
- 🔧 Includes support for OpenAI's responses, moderations, and vector stores APIs
|
34
|
+
|
35
|
+
## Matrix
|
36
|
+
|
37
|
+
While the Features section above gives you the high-level picture, the table below
|
38
|
+
breaks things down by provider, so you can see exactly what’s supported where.
|
39
|
+
|
40
|
+
|
41
|
+
| Feature / Provider | OpenAI | Anthropic | Gemini | DeepSeek | xAI (Grok) | Ollama | LlamaCpp |
|
42
|
+
|--------------------------------------|:------:|:---------:|:------:|:--------:|:----------:|:------:|:--------:|
|
43
|
+
| **Chat Completions** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
44
|
+
| **Streaming** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
45
|
+
| **Tool Calling** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
46
|
+
| **JSON Schema / Structured Output** | ✅ | ❌ | ✅ | ❌ | ✅ | ✅* | ✅* |
|
47
|
+
| **Audio (TTS / Transcribe / Translate)** | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
48
|
+
| **Image Generation & Editing** | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ |
|
49
|
+
| **File Uploads** | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
50
|
+
| **Multimodal Prompts** *(text, documents, audio, images, videos, URLs, etc)* | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
51
|
+
| **Embeddings** | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
|
52
|
+
| **Models API** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
53
|
+
| **Local Model Support** | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
|
54
|
+
| **Vector Stores (RAG)** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
55
|
+
| **Responses** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
56
|
+
| **Moderations** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
57
|
+
|
58
|
+
\* JSON Schema support in Ollama/LlamaCpp depends on the model, not the API.
|
59
|
+
|
31
60
|
|
32
61
|
## Examples
|
33
62
|
|
@@ -49,6 +78,7 @@ require "llm"
|
|
49
78
|
llm = LLM.openai(key: "yourapikey")
|
50
79
|
llm = LLM.gemini(key: "yourapikey")
|
51
80
|
llm = LLM.anthropic(key: "yourapikey")
|
81
|
+
llm = LLM.xai(key: "yourapikey")
|
52
82
|
llm = LLM.deepseek(key: "yourapikey")
|
53
83
|
|
54
84
|
##
|
@@ -78,14 +108,14 @@ is made before sending a request to the LLM:
|
|
78
108
|
#!/usr/bin/env ruby
|
79
109
|
require "llm"
|
80
110
|
|
81
|
-
llm = LLM.openai(key: ENV["
|
111
|
+
llm = LLM.openai(key: ENV["KEY"])
|
82
112
|
bot = LLM::Bot.new(llm)
|
83
|
-
url = "https://
|
113
|
+
url = "https://en.wikipedia.org/wiki/Special:FilePath/Cognac_glass.jpg"
|
84
114
|
msgs = bot.chat do |prompt|
|
85
115
|
prompt.system "Your task is to answer all user queries"
|
86
116
|
prompt.user ["Tell me about this URL", URI(url)]
|
87
|
-
prompt.user ["Tell me about this
|
88
|
-
prompt.user "
|
117
|
+
prompt.user ["Tell me about this PDF", File.open("handbook.pdf", "rb")]
|
118
|
+
prompt.user "Are the URL and PDF similar to each other?"
|
89
119
|
end
|
90
120
|
|
91
121
|
# At this point, we execute a single request
|
@@ -110,14 +140,14 @@ to process a response in the same way:
|
|
110
140
|
#!/usr/bin/env ruby
|
111
141
|
require "llm"
|
112
142
|
|
113
|
-
llm = LLM.openai(key: ENV["
|
143
|
+
llm = LLM.openai(key: ENV["KEY"])
|
114
144
|
bot = LLM::Bot.new(llm)
|
115
|
-
url = "https://
|
145
|
+
url = "https://en.wikipedia.org/wiki/Special:FilePath/Cognac_glass.jpg"
|
116
146
|
bot.chat(stream: $stdout) do |prompt|
|
117
147
|
prompt.system "Your task is to answer all user queries"
|
118
148
|
prompt.user ["Tell me about this URL", URI(url)]
|
119
|
-
prompt.user ["Tell me about this
|
120
|
-
prompt.user "
|
149
|
+
prompt.user ["Tell me about this PDF", File.open("handbook.pdf", "rb")]
|
150
|
+
prompt.user "Are the URL and PDF similar to each other?"
|
121
151
|
end.to_a
|
122
152
|
```
|
123
153
|
|
@@ -137,7 +167,7 @@ require "llm"
|
|
137
167
|
##
|
138
168
|
# Objects
|
139
169
|
llm = LLM.openai(key: ENV["KEY"])
|
140
|
-
schema = llm.schema.object(
|
170
|
+
schema = llm.schema.object(probability: llm.schema.integer.required)
|
141
171
|
bot = LLM::Bot.new(llm, schema:)
|
142
172
|
bot.chat "Does the earth orbit the sun?", role: :user
|
143
173
|
bot.messages.find(&:assistant?).content! # => {probability: 1}
|
@@ -245,7 +275,7 @@ to represent a file stored with the LLM, and so on. These are objects you
|
|
245
275
|
can throw at the prompt and have them be understood automatically.
|
246
276
|
|
247
277
|
A prompt can also have multiple parts, and in that case, an array is given
|
248
|
-
as a prompt. Each element is considered to part of the prompt:
|
278
|
+
as a prompt. Each element is considered to be part of the prompt:
|
249
279
|
|
250
280
|
```ruby
|
251
281
|
#!/usr/bin/env ruby
|
@@ -463,8 +493,15 @@ over or doesn't cover at all. The API reference is available at
|
|
463
493
|
|
464
494
|
### Guides
|
465
495
|
|
466
|
-
|
467
|
-
|
496
|
+
* [An introduction to RAG with llm.rb](https://0x1eef.github.io/posts/an-introduction-to-rag-with-llm.rb/) –
|
497
|
+
a blog post that implements the RAG pattern in 32 lines of Ruby code
|
498
|
+
* [docs/](docs/) – the docs directory contains additional guides
|
499
|
+
|
500
|
+
|
501
|
+
## See also
|
502
|
+
|
503
|
+
* [llm-shell](https://github.com/llmrb/llm-shell) – a shell that uses llm.rb to
|
504
|
+
provide a command-line interface to LLMs.
|
468
505
|
|
469
506
|
## Install
|
470
507
|
|
data/lib/llm/bot/builder.rb
CHANGED
File without changes
|
data/lib/llm/bot/conversable.rb
CHANGED
@@ -12,8 +12,12 @@ class LLM::Bot
|
|
12
12
|
# @param [Hash] params
|
13
13
|
# @return [void]
|
14
14
|
def async_response(prompt, params = {})
|
15
|
-
|
16
|
-
|
15
|
+
if Array === prompt and prompt.empty?
|
16
|
+
@messages
|
17
|
+
else
|
18
|
+
role = params.delete(:role)
|
19
|
+
@messages << [LLM::Message.new(role, prompt), @params.merge(params), :respond]
|
20
|
+
end
|
17
21
|
end
|
18
22
|
|
19
23
|
##
|
@@ -22,8 +26,12 @@ class LLM::Bot
|
|
22
26
|
# @param [Hash] params
|
23
27
|
# @return [void]
|
24
28
|
def async_completion(prompt, params = {})
|
25
|
-
|
26
|
-
|
29
|
+
if Array === prompt and prompt.empty?
|
30
|
+
@messages
|
31
|
+
else
|
32
|
+
role = params.delete(:role)
|
33
|
+
@messages << [LLM::Message.new(role, prompt), @params.merge(params), :complete]
|
34
|
+
end
|
27
35
|
end
|
28
36
|
end
|
29
37
|
end
|
@@ -27,5 +27,23 @@ module LLM::Bot::Prompt
|
|
27
27
|
params = defaults.merge(params)
|
28
28
|
bot.chat prompt, params.merge(role: :user)
|
29
29
|
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# @param [String] prompt
|
33
|
+
# @param [Hash] params (see LLM::Provider#complete)
|
34
|
+
# @return [LLM::Bot]
|
35
|
+
def assistant(prompt, params = {})
|
36
|
+
params = defaults.merge(params)
|
37
|
+
bot.chat prompt, params.merge(role: :assistant)
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# @param [String] prompt
|
42
|
+
# @param [Hash] params (see LLM::Provider#complete)
|
43
|
+
# @return [LLM::Bot]
|
44
|
+
def model(prompt, params = {})
|
45
|
+
params = defaults.merge(params)
|
46
|
+
bot.chat prompt, params.merge(role: :model)
|
47
|
+
end
|
30
48
|
end
|
31
49
|
end
|
@@ -36,5 +36,14 @@ module LLM::Bot::Prompt
|
|
36
36
|
params = defaults.merge(params)
|
37
37
|
bot.respond prompt, params.merge(role: :user)
|
38
38
|
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# @param [String] prompt
|
42
|
+
# @param [Hash] params (see LLM::Provider#complete)
|
43
|
+
# @return [LLM::Bot]
|
44
|
+
def assistant(prompt, params = {})
|
45
|
+
params = defaults.merge(params)
|
46
|
+
bot.chat prompt, params.merge(role: :assistant)
|
47
|
+
end
|
39
48
|
end
|
40
49
|
end
|
data/lib/llm/bot.rb
CHANGED
@@ -3,33 +3,26 @@
|
|
3
3
|
module LLM
|
4
4
|
##
|
5
5
|
# {LLM::Bot LLM::Bot} provides an object that can maintain a
|
6
|
-
#
|
6
|
+
# conversation. A conversation can use the chat completions API
|
7
7
|
# that all LLM providers support or the responses API that currently
|
8
8
|
# only OpenAI supports.
|
9
9
|
#
|
10
|
-
# @example
|
10
|
+
# @example
|
11
11
|
# #!/usr/bin/env ruby
|
12
12
|
# require "llm"
|
13
13
|
#
|
14
|
-
# llm = LLM.openai(ENV["KEY"])
|
14
|
+
# llm = LLM.openai(key: ENV["KEY"])
|
15
15
|
# bot = LLM::Bot.new(llm)
|
16
|
+
# url = "https://en.wikipedia.org/wiki/Special:FilePath/Cognac_glass.jpg"
|
16
17
|
# msgs = bot.chat do |prompt|
|
17
|
-
# prompt.
|
18
|
-
# prompt.user "
|
19
|
-
# prompt.user "
|
18
|
+
# prompt.system "Your task is to answer all user queries"
|
19
|
+
# prompt.user ["Tell me about this URL", URI(url)]
|
20
|
+
# prompt.user ["Tell me about this PDF", File.open("handbook.pdf", "rb")]
|
21
|
+
# prompt.user "Are the URL and PDF similar to each other?"
|
20
22
|
# end
|
21
|
-
# msgs.each { print "[#{_1.role}]", _1.content, "\n" }
|
22
23
|
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
# require "llm"
|
26
|
-
#
|
27
|
-
# llm = LLM.openai(ENV["KEY"])
|
28
|
-
# bot = LLM::Bot.new(llm)
|
29
|
-
# bot.chat "What programming language should I learn next ?", role: :user
|
30
|
-
# bot.chat "Can you recommend a good book ?", role: :user
|
31
|
-
# bot.chat "Can you suggest a fun project to practice ?", role: :user
|
32
|
-
# bot.messages.each { print "[#{_1.role}]", _1.content, "\n" }
|
24
|
+
# # At this point, we execute a single request
|
25
|
+
# msgs.each { print "[#{_1.role}] ", _1.content, "\n" }
|
33
26
|
class Bot
|
34
27
|
require_relative "bot/prompt/completion"
|
35
28
|
require_relative "bot/prompt/respond"
|
data/lib/llm/buffer.rb
CHANGED
@@ -48,7 +48,14 @@ module LLM
|
|
48
48
|
end
|
49
49
|
|
50
50
|
##
|
51
|
-
#
|
51
|
+
# Returns the last message in the buffer
|
52
|
+
# @return [LLM::Message, nil]
|
53
|
+
def last
|
54
|
+
to_a[-1]
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# @param [[LLM::Message, Hash, Symbol]] item
|
52
59
|
# A message and its parameters
|
53
60
|
# @return [void]
|
54
61
|
def <<(item)
|
@@ -73,6 +80,13 @@ module LLM
|
|
73
80
|
"completed_count=#{@completed.size} pending_count=#{@pending.size}>"
|
74
81
|
end
|
75
82
|
|
83
|
+
##
|
84
|
+
# Returns true when the buffer is empty
|
85
|
+
# @return [Boolean]
|
86
|
+
def empty?
|
87
|
+
@pending.empty? and @completed.empty?
|
88
|
+
end
|
89
|
+
|
76
90
|
private
|
77
91
|
|
78
92
|
def empty!
|
data/lib/llm/error.rb
CHANGED
File without changes
|
File without changes
|
File without changes
|
data/lib/llm/eventstream.rb
CHANGED
File without changes
|
data/lib/llm/file.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
##
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# and as an input with certain methods
|
4
|
+
# {LLM::File LLM::File} represents a local file. It can be used
|
5
|
+
# as a prompt with certain providers (eg: Ollama, Gemini),
|
6
|
+
# and as an input with certain methods. It is usually not necessary
|
7
|
+
# to create an instance of LLM::File directly.
|
7
8
|
class LLM::File
|
8
9
|
##
|
9
10
|
# @return [String]
|
data/lib/llm/function.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
##
|
4
|
-
# The {LLM::Function LLM::Function} class represents a
|
5
|
-
#
|
4
|
+
# The {LLM::Function LLM::Function} class represents a local
|
5
|
+
# function that can be called by an LLM.
|
6
6
|
#
|
7
7
|
# @example example #1
|
8
8
|
# LLM.function(:system) do |fn|
|
@@ -53,7 +53,7 @@ class LLM::Function
|
|
53
53
|
# @yieldparam [LLM::Function] self The function object
|
54
54
|
def initialize(name, &b)
|
55
55
|
@name = name
|
56
|
-
@schema =
|
56
|
+
@schema = LLM::Schema.new
|
57
57
|
@called = false
|
58
58
|
@cancelled = false
|
59
59
|
yield(self)
|
@@ -72,7 +72,7 @@ class LLM::Function
|
|
72
72
|
end
|
73
73
|
|
74
74
|
##
|
75
|
-
# @yieldparam [
|
75
|
+
# @yieldparam [LLM::Schema] schema The schema object
|
76
76
|
# @return [void]
|
77
77
|
def params
|
78
78
|
@params = yield(@schema)
|
data/lib/llm/message.rb
CHANGED
File without changes
|
data/lib/llm/mime.rb
CHANGED
@@ -7,11 +7,8 @@ class LLM::Mime
|
|
7
7
|
# Lookup a mime type
|
8
8
|
# @return [String, nil]
|
9
9
|
def self.[](key)
|
10
|
-
|
11
|
-
|
12
|
-
else
|
13
|
-
types[key]
|
14
|
-
end
|
10
|
+
key = key.respond_to?(:path) ? File.extname(key.path) : key
|
11
|
+
types[key] || "application/octet-stream"
|
15
12
|
end
|
16
13
|
|
17
14
|
##
|
@@ -24,6 +21,15 @@ class LLM::Mime
|
|
24
21
|
".jpg" => "image/jpeg",
|
25
22
|
".jpeg" => "image/jpeg",
|
26
23
|
".webp" => "image/webp",
|
24
|
+
".gif" => "image/gif",
|
25
|
+
".bmp" => "image/bmp",
|
26
|
+
".tif" => "image/tiff",
|
27
|
+
".tiff" => "image/tiff",
|
28
|
+
".svg" => "image/svg+xml",
|
29
|
+
".ico" => "image/x-icon",
|
30
|
+
".apng" => "image/apng",
|
31
|
+
".jfif" => "image/jpeg",
|
32
|
+
".heic" => "image/heic",
|
27
33
|
|
28
34
|
# Videos
|
29
35
|
".flv" => "video/x-flv",
|
@@ -34,6 +40,12 @@ class LLM::Mime
|
|
34
40
|
".webm" => "video/webm",
|
35
41
|
".wmv" => "video/x-ms-wmv",
|
36
42
|
".3gp" => "video/3gpp",
|
43
|
+
".avi" => "video/x-msvideo",
|
44
|
+
".mkv" => "video/x-matroska",
|
45
|
+
".ogv" => "video/ogg",
|
46
|
+
".m4v" => "video/x-m4v",
|
47
|
+
".m2ts" => "video/mp2t",
|
48
|
+
".mts" => "video/mp2t",
|
37
49
|
|
38
50
|
# Audio
|
39
51
|
".aac" => "audio/aac",
|
@@ -45,10 +57,80 @@ class LLM::Mime
|
|
45
57
|
".pcm" => "audio/L16",
|
46
58
|
".wav" => "audio/wav",
|
47
59
|
".weba" => "audio/webm",
|
60
|
+
".oga" => "audio/ogg",
|
61
|
+
".ogg" => "audio/ogg",
|
62
|
+
".mid" => "audio/midi",
|
63
|
+
".midi" => "audio/midi",
|
64
|
+
".aiff" => "audio/aiff",
|
65
|
+
".aif" => "audio/aiff",
|
66
|
+
".amr" => "audio/amr",
|
67
|
+
".mka" => "audio/x-matroska",
|
68
|
+
".caf" => "audio/x-caf",
|
48
69
|
|
49
70
|
# Documents
|
50
71
|
".pdf" => "application/pdf",
|
51
|
-
".txt" => "text/plain"
|
72
|
+
".txt" => "text/plain",
|
73
|
+
".md" => "text/markdown",
|
74
|
+
".markdown" => "text/markdown",
|
75
|
+
".mkd" => "text/markdown",
|
76
|
+
".doc" => "application/msword",
|
77
|
+
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
78
|
+
".xls" => "application/vnd.ms-excel",
|
79
|
+
".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
80
|
+
".ppt" => "application/vnd.ms-powerpoint",
|
81
|
+
".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
82
|
+
".csv" => "text/csv",
|
83
|
+
".json" => "application/json",
|
84
|
+
".xml" => "application/xml",
|
85
|
+
".html" => "text/html",
|
86
|
+
".htm" => "text/html",
|
87
|
+
".odt" => "application/vnd.oasis.opendocument.text",
|
88
|
+
".odp" => "application/vnd.oasis.opendocument.presentation",
|
89
|
+
".ods" => "application/vnd.oasis.opendocument.spreadsheet",
|
90
|
+
".rtf" => "application/rtf",
|
91
|
+
".epub" => "application/epub+zip",
|
92
|
+
|
93
|
+
# Code
|
94
|
+
".js" => "application/javascript",
|
95
|
+
".jsx" => "text/jsx",
|
96
|
+
".ts" => "application/typescript",
|
97
|
+
".tsx" => "text/tsx",
|
98
|
+
".css" => "text/css",
|
99
|
+
".c" => "text/x-c",
|
100
|
+
".cpp" => "text/x-c++",
|
101
|
+
".h" => "text/x-c",
|
102
|
+
".rb" => "text/x-ruby",
|
103
|
+
".py" => "text/x-python",
|
104
|
+
".java" => "text/x-java-source",
|
105
|
+
".sh" => "application/x-sh",
|
106
|
+
".php" => "application/x-httpd-php",
|
107
|
+
".yml" => "text/yaml",
|
108
|
+
".yaml" => "text/yaml",
|
109
|
+
".go" => "text/x-go",
|
110
|
+
".rs" => "text/rust",
|
111
|
+
|
112
|
+
# Fonts
|
113
|
+
".woff" => "font/woff",
|
114
|
+
".woff2" => "font/woff2",
|
115
|
+
".ttf" => "font/ttf",
|
116
|
+
".otf" => "font/otf",
|
117
|
+
|
118
|
+
# Archives
|
119
|
+
".zip" => "application/zip",
|
120
|
+
".tar" => "application/x-tar",
|
121
|
+
".gz" => "application/gzip",
|
122
|
+
".bz2" => "application/x-bzip2",
|
123
|
+
".xz" => "application/x-xz",
|
124
|
+
".rar" => "application/vnd.rar",
|
125
|
+
".7z" => "application/x-7z-compressed",
|
126
|
+
".tar.gz" => "application/gzip",
|
127
|
+
".tar.bz2" => "application/x-bzip2",
|
128
|
+
".apk" => "application/vnd.android.package-archive",
|
129
|
+
".exe" => "application/x-msdownload",
|
130
|
+
|
131
|
+
# Others
|
132
|
+
".ics" => "text/calendar",
|
133
|
+
".vcf" => "text/vcard"
|
52
134
|
}
|
53
135
|
end
|
54
136
|
end
|
data/lib/llm/multipart.rb
CHANGED
data/lib/llm/object/builder.rb
CHANGED
File without changes
|
data/lib/llm/object/kernel.rb
CHANGED
File without changes
|
data/lib/llm/object.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
##
|
4
|
-
# The {LLM::Object LLM::Object} class encapsulates a Hash object
|
5
|
-
# allows a consumer to get and set Hash keys via regular methods. It is
|
4
|
+
# The {LLM::Object LLM::Object} class encapsulates a Hash object. It is
|
6
5
|
# similar in spirit to OpenStruct, and it was introduced after OpenStruct
|
7
|
-
# became a bundled gem
|
6
|
+
# became a bundled gem rather than a default gem in Ruby 3.5.
|
8
7
|
class LLM::Object < BasicObject
|
9
8
|
require_relative "object/builder"
|
10
9
|
require_relative "object/kernel"
|
data/lib/llm/provider.rb
CHANGED
@@ -52,7 +52,7 @@ class LLM::Provider
|
|
52
52
|
##
|
53
53
|
# Provides an interface to the chat completions API
|
54
54
|
# @example
|
55
|
-
# llm = LLM.openai(ENV["KEY"])
|
55
|
+
# llm = LLM.openai(key: ENV["KEY"])
|
56
56
|
# messages = [{role: "system", content: "Your task is to answer all of my questions"}]
|
57
57
|
# res = llm.complete("5 + 2 ?", messages:)
|
58
58
|
# print "[#{res.choices[0].role}]", res.choices[0].content, "\n"
|
@@ -198,9 +198,9 @@ class LLM::Provider
|
|
198
198
|
|
199
199
|
##
|
200
200
|
# Returns an object that can generate a JSON schema
|
201
|
-
# @return [
|
201
|
+
# @return [LLM::Schema]
|
202
202
|
def schema
|
203
|
-
@schema ||=
|
203
|
+
@schema ||= LLM::Schema.new
|
204
204
|
end
|
205
205
|
|
206
206
|
##
|
@@ -22,6 +22,8 @@ class LLM::Anthropic
|
|
22
22
|
# Raises a subclass of {LLM::Error LLM::Error}
|
23
23
|
def raise_error!
|
24
24
|
case res
|
25
|
+
when Net::HTTPServerError
|
26
|
+
raise LLM::ServerError.new { _1.response = res }, "Server error"
|
25
27
|
when Net::HTTPUnauthorized
|
26
28
|
raise LLM::UnauthorizedError.new { _1.response = res }, "Authentication error"
|
27
29
|
when Net::HTTPTooManyRequests
|
File without changes
|
File without changes
|
@@ -11,7 +11,7 @@ class LLM::Anthropic
|
|
11
11
|
# #!/usr/bin/env ruby
|
12
12
|
# require "llm"
|
13
13
|
#
|
14
|
-
# llm = LLM.anthropic(ENV["KEY"])
|
14
|
+
# llm = LLM.anthropic(key: ENV["KEY"])
|
15
15
|
# res = llm.models.all
|
16
16
|
# res.each do |model|
|
17
17
|
# print "id: ", model.id, "\n"
|
@@ -28,7 +28,7 @@ class LLM::Anthropic
|
|
28
28
|
##
|
29
29
|
# List all models
|
30
30
|
# @example
|
31
|
-
# llm = LLM.anthropic(ENV["KEY"])
|
31
|
+
# llm = LLM.anthropic(key: ENV["KEY"])
|
32
32
|
# res = llm.models.all
|
33
33
|
# res.each do |model|
|
34
34
|
# print "id: ", model.id, "\n"
|
File without changes
|
File without changes
|
@@ -3,7 +3,16 @@
|
|
3
3
|
module LLM
|
4
4
|
##
|
5
5
|
# The Anthropic class implements a provider for
|
6
|
-
# [Anthropic](https://www.anthropic.com)
|
6
|
+
# [Anthropic](https://www.anthropic.com).
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# #!/usr/bin/env ruby
|
10
|
+
# require "llm"
|
11
|
+
#
|
12
|
+
# llm = LLM.anthropic(key: ENV["KEY"])
|
13
|
+
# bot = LLM::Bot.new(llm)
|
14
|
+
# bot.chat ["Tell me about this photo", File.open("/images/dog.jpg", "rb")]
|
15
|
+
# bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
|
7
16
|
class Anthropic < Provider
|
8
17
|
require_relative "anthropic/response/completion"
|
9
18
|
require_relative "anthropic/format"
|
File without changes
|
File without changes
|
@@ -6,8 +6,17 @@ module LLM
|
|
6
6
|
##
|
7
7
|
# The DeepSeek class implements a provider for
|
8
8
|
# [DeepSeek](https://deepseek.com)
|
9
|
-
# through its OpenAI-compatible API
|
9
|
+
# through its OpenAI-compatible API available via
|
10
10
|
# their [web platform](https://platform.deepseek.com).
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# #!/usr/bin/env ruby
|
14
|
+
# require "llm"
|
15
|
+
#
|
16
|
+
# llm = LLM.deepseek(key: ENV["KEY"])
|
17
|
+
# bot = LLM::Bot.new(llm)
|
18
|
+
# bot.chat ["Tell me about this photo", File.open("/images/cat.jpg", "rb")]
|
19
|
+
# bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
|
11
20
|
class DeepSeek < OpenAI
|
12
21
|
require_relative "deepseek/format"
|
13
22
|
include DeepSeek::Format
|
@@ -8,7 +8,7 @@ class LLM::Gemini
|
|
8
8
|
# #!/usr/bin/env ruby
|
9
9
|
# require "llm"
|
10
10
|
#
|
11
|
-
# llm = LLM.gemini(ENV["KEY"])
|
11
|
+
# llm = LLM.gemini(key: ENV["KEY"])
|
12
12
|
# res = llm.audio.create_transcription(input: "/audio/rocket.mp3")
|
13
13
|
# res.text # => "A dog on a rocket to the moon"
|
14
14
|
class Audio
|
@@ -30,7 +30,7 @@ class LLM::Gemini
|
|
30
30
|
##
|
31
31
|
# Create an audio transcription
|
32
32
|
# @example
|
33
|
-
# llm = LLM.gemini(ENV["KEY"])
|
33
|
+
# llm = LLM.gemini(key: ENV["KEY"])
|
34
34
|
# res = llm.audio.create_transcription(file: "/audio/rocket.mp3")
|
35
35
|
# res.text # => "A dog on a rocket to the moon"
|
36
36
|
# @see https://ai.google.dev/gemini-api/docs/audio Gemini docs
|
@@ -52,7 +52,7 @@ class LLM::Gemini
|
|
52
52
|
# Create an audio translation (in English)
|
53
53
|
# @example
|
54
54
|
# # Arabic => English
|
55
|
-
# llm = LLM.gemini(ENV["KEY"])
|
55
|
+
# llm = LLM.gemini(key: ENV["KEY"])
|
56
56
|
# res = llm.audio.create_translation(file: "/audio/bismillah.mp3")
|
57
57
|
# res.text # => "In the name of Allah, the Beneficent, the Merciful."
|
58
58
|
# @see https://ai.google.dev/gemini-api/docs/audio Gemini docs
|