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.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +0 -0
  3. data/README.md +56 -19
  4. data/lib/llm/bot/builder.rb +0 -0
  5. data/lib/llm/bot/conversable.rb +12 -4
  6. data/lib/llm/bot/prompt/completion.rb +18 -0
  7. data/lib/llm/bot/prompt/respond.rb +9 -0
  8. data/lib/llm/bot.rb +10 -17
  9. data/lib/llm/buffer.rb +15 -1
  10. data/lib/llm/error.rb +4 -0
  11. data/lib/llm/{event_handler.rb → eventhandler.rb} +0 -0
  12. data/lib/llm/eventstream/event.rb +0 -0
  13. data/lib/llm/eventstream/parser.rb +0 -0
  14. data/lib/llm/eventstream.rb +0 -0
  15. data/lib/llm/file.rb +4 -3
  16. data/lib/llm/function.rb +4 -4
  17. data/lib/llm/message.rb +0 -0
  18. data/lib/llm/mime.rb +88 -6
  19. data/lib/llm/multipart.rb +0 -1
  20. data/lib/llm/object/builder.rb +0 -0
  21. data/lib/llm/object/kernel.rb +0 -0
  22. data/lib/llm/object.rb +2 -3
  23. data/lib/llm/provider.rb +3 -3
  24. data/lib/llm/providers/anthropic/error_handler.rb +2 -0
  25. data/lib/llm/providers/anthropic/format/completion_format.rb +0 -0
  26. data/lib/llm/providers/anthropic/format.rb +0 -0
  27. data/lib/llm/providers/anthropic/models.rb +2 -2
  28. data/lib/llm/providers/anthropic/response/completion.rb +0 -0
  29. data/lib/llm/providers/anthropic/stream_parser.rb +0 -0
  30. data/lib/llm/providers/anthropic.rb +10 -1
  31. data/lib/llm/providers/deepseek/format/completion_format.rb +0 -0
  32. data/lib/llm/providers/deepseek/format.rb +0 -0
  33. data/lib/llm/providers/deepseek.rb +10 -1
  34. data/lib/llm/providers/gemini/audio.rb +3 -3
  35. data/lib/llm/providers/gemini/error_handler.rb +2 -0
  36. data/lib/llm/providers/gemini/files.rb +8 -20
  37. data/lib/llm/providers/gemini/format/completion_format.rb +2 -2
  38. data/lib/llm/providers/gemini/format.rb +0 -0
  39. data/lib/llm/providers/gemini/images.rb +4 -4
  40. data/lib/llm/providers/gemini/models.rb +2 -2
  41. data/lib/llm/providers/gemini/response/completion.rb +2 -0
  42. data/lib/llm/providers/gemini/response/embedding.rb +1 -1
  43. data/lib/llm/providers/gemini/response/file.rb +0 -0
  44. data/lib/llm/providers/gemini/response/image.rb +0 -0
  45. data/lib/llm/providers/gemini/stream_parser.rb +0 -0
  46. data/lib/llm/providers/gemini.rb +13 -21
  47. data/lib/llm/providers/llamacpp.rb +12 -1
  48. data/lib/llm/providers/ollama/error_handler.rb +2 -0
  49. data/lib/llm/providers/ollama/format/completion_format.rb +0 -0
  50. data/lib/llm/providers/ollama/format.rb +0 -0
  51. data/lib/llm/providers/ollama/models.rb +0 -0
  52. data/lib/llm/providers/ollama/response/completion.rb +0 -0
  53. data/lib/llm/providers/ollama/response/embedding.rb +1 -2
  54. data/lib/llm/providers/ollama/stream_parser.rb +0 -0
  55. data/lib/llm/providers/ollama.rb +8 -11
  56. data/lib/llm/providers/openai/audio.rb +4 -4
  57. data/lib/llm/providers/openai/error_handler.rb +13 -1
  58. data/lib/llm/providers/openai/files.rb +8 -19
  59. data/lib/llm/providers/openai/format/completion_format.rb +0 -0
  60. data/lib/llm/providers/openai/format/moderation_format.rb +0 -0
  61. data/lib/llm/providers/openai/format/respond_format.rb +0 -0
  62. data/lib/llm/providers/openai/format.rb +0 -0
  63. data/lib/llm/providers/openai/images.rb +10 -10
  64. data/lib/llm/providers/openai/models.rb +2 -2
  65. data/lib/llm/providers/openai/moderations.rb +0 -0
  66. data/lib/llm/providers/openai/response/audio.rb +0 -0
  67. data/lib/llm/providers/openai/response/completion.rb +2 -2
  68. data/lib/llm/providers/openai/response/embedding.rb +3 -3
  69. data/lib/llm/providers/openai/response/file.rb +0 -0
  70. data/lib/llm/providers/openai/response/image.rb +0 -0
  71. data/lib/llm/providers/openai/response/moderations.rb +0 -0
  72. data/lib/llm/providers/openai/response/responds.rb +0 -1
  73. data/lib/llm/providers/openai/responses.rb +6 -25
  74. data/lib/llm/providers/openai/stream_parser.rb +1 -0
  75. data/lib/llm/providers/openai/vector_stores.rb +85 -3
  76. data/lib/llm/providers/openai.rb +10 -1
  77. data/lib/llm/providers/xai/images.rb +58 -0
  78. data/lib/llm/providers/xai.rb +72 -0
  79. data/lib/llm/response.rb +5 -0
  80. data/lib/llm/{json/schema → schema}/array.rb +3 -3
  81. data/lib/llm/{json/schema → schema}/boolean.rb +3 -3
  82. data/lib/llm/{json/schema → schema}/integer.rb +6 -6
  83. data/lib/llm/{json/schema → schema}/leaf.rb +9 -9
  84. data/lib/llm/{json/schema → schema}/null.rb +3 -3
  85. data/lib/llm/{json/schema → schema}/number.rb +6 -6
  86. data/lib/llm/{json/schema → schema}/object.rb +3 -3
  87. data/lib/llm/{json/schema → schema}/string.rb +5 -5
  88. data/lib/llm/{json/schema → schema}/version.rb +1 -1
  89. data/lib/llm/{json/schema.rb → schema.rb} +10 -13
  90. data/lib/llm/utils.rb +0 -0
  91. data/lib/llm/version.rb +1 -1
  92. data/lib/llm.rb +11 -2
  93. data/llm.gemspec +4 -4
  94. metadata +22 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce6a4e56ce25f397337733009249edb930cade1641bd84d7939ef0d349c6e92a
4
- data.tar.gz: b9506c346dd19af655f342e9b6a64aed25cd3f4ecffb6bb32cb36e1221e71c6a
3
+ metadata.gz: f83248fa3bce285d75952b726cc7d1b097e61b61fb77edd55f39168922a8614c
4
+ data.tar.gz: 52fa691fad7eaa2e77e24245aba97ea469688f8307f869d533fac47707aa6caf
5
5
  SHA512:
6
- metadata.gz: a4a53b8e5aeaae2cd26f5303366ce1974ecfdb4176a76c5aa01f46cb9997d10a402de782b0b14f97bf3c65dc4c200b976c1279a037ce29ae44ab64fd1799ffd9
7
- data.tar.gz: 96128a756147d8cad13e85f91247fdb43b6bf5c469348318c27f27518670c61b6466546d65bf1f553b086b2a42b15a5225e0ac5355b680ba46214b32bc20d754
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 LlamaCpp. The
5
- toolkit includes full support for chat, streaming, tool calling, audio,
6
- images, files, and JSON Schema generation.
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, PDFs, URLs, files)
25
+ - 💡 Multimodal prompts (text, documents, audio, images, videos, URLs, etc)
26
26
 
27
- #### Miscellaneous
27
+ #### Embeddings
28
28
  - 🧮 Text embeddings and vector support
29
- - 🔌 Retrieve models dynamically for introspection and selection
30
- - 🧱 Includes support for OpenAI's responses, moderations, and vector stores APIs
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["OPENAI_SECRET"])
111
+ llm = LLM.openai(key: ENV["KEY"])
82
112
  bot = LLM::Bot.new(llm)
83
- url = "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Cognac_glass.jpg/500px-Cognac_glass.jpg"
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 pdf", File.open("spec/fixtures/documents/freebsd.sysctl.pdf", "r")]
88
- prompt.user "Is the URL and PDF similar to each other?"
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["OPENAI_SECRET"])
143
+ llm = LLM.openai(key: ENV["KEY"])
114
144
  bot = LLM::Bot.new(llm)
115
- url = "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Cognac_glass.jpg/500px-Cognac_glass.jpg"
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 pdf", File.open("spec/fixtures/documents/freebsd.sysctl.pdf", "r")]
120
- prompt.user "Is the URL and PDF similar to each other?"
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(answer: llm.schema.integer.required)
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
- The [docs/](docs/) directory contains some additional documentation that
467
- didn't quite make it into the README.
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
 
File without changes
@@ -12,8 +12,12 @@ class LLM::Bot
12
12
  # @param [Hash] params
13
13
  # @return [void]
14
14
  def async_response(prompt, params = {})
15
- role = params.delete(:role)
16
- @messages << [LLM::Message.new(role, prompt), @params.merge(params), :respond]
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
- role = params.delete(:role)
26
- @messages.push [LLM::Message.new(role, prompt), @params.merge(params), :complete]
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
- # a conversation. A conversation can use the chat completions API
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 example #1
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.user "What programming language should I learn next ?"
18
- # prompt.user "Can you recommend a good book ?"
19
- # prompt.user "Can you suggest a fun project to practice ?"
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
- # @example example #2
24
- # #!/usr/bin/env ruby
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
- # @param [[LLM::Message, Hash]] item
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
@@ -31,6 +31,10 @@ module LLM
31
31
  # HTTPTooManyRequests
32
32
  RateLimitError = Class.new(ResponseError)
33
33
 
34
+ ##
35
+ # HTTPServerError
36
+ ServerError = Class.new(ResponseError)
37
+
34
38
  ##
35
39
  # When an given an input object that is not understood
36
40
  FormatError = Class.new(Error)
File without changes
File without changes
File without changes
File without changes
data/lib/llm/file.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  ##
4
- # The {LLM::File LLM::File} class represents a local file. It can
5
- # be used as a prompt with certain providers (eg: Ollama, Gemini),
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
- # local function that can be called by an LLM.
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 = JSON::Schema.new
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 [JSON::Schema] schema The schema object
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
- if key.respond_to?(:path)
11
- types[File.extname(key.path)]
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
@@ -4,7 +4,6 @@
4
4
  ##
5
5
  # @private
6
6
  class LLM::Multipart
7
- require "llm"
8
7
  require "securerandom"
9
8
 
10
9
  ##
File without changes
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, and it
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 (and not a default gem) in Ruby 3.5.
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 [JSON::Schema]
201
+ # @return [LLM::Schema]
202
202
  def schema
203
- @schema ||= JSON::Schema.new
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
@@ -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
@@ -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 provided via
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