llm.rb 0.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2cd935a4ccd3b911e92b5ff54335cfc143247cbb5fe55214fd563551f7349da4
4
- data.tar.gz: c76b36f2877c0cec7fdde54471a81ae19a4ec044158077742eba9acd26cd1483
3
+ metadata.gz: f83248fa3bce285d75952b726cc7d1b097e61b61fb77edd55f39168922a8614c
4
+ data.tar.gz: 52fa691fad7eaa2e77e24245aba97ea469688f8307f869d533fac47707aa6caf
5
5
  SHA512:
6
- metadata.gz: f654d042a6f44cba15b2dc0049d3933aa442f631293be446486e524773ff01bfc0f13f89ecbf09659a175c3ff9f7c6512ae8bde5716f6935c8d3d05528d3e4e9
7
- data.tar.gz: 73945113e01d89188301a1a7db921c8c07ac19a847fa71528bc2e62eb5162ac656aca02a5f17e02bc4918dc973c298f0021afbb14a4cb38e7f81705950c4ed5b
6
+ metadata.gz: d17e104601295bbbd8dd5bd34fe5378e96367aacb832ddb81b7f0fdfd3fa54f39d98dff6568f3190a4d979de4382a6ebd2eba8ee2fb2cce0e67db2478be9a1df
7
+ data.tar.gz: d2e6bb0f507bdcf140bd7f44f9edd40c057f061a9b4bc7123a87bdf6fa40a6f16a99642965bdaf81aaaeab5e995a01587c79cd47e19c6a2b5f86e456fa4813a9
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  llm.rb is a zero-dependency Ruby toolkit for Large Language Models that
4
4
  includes OpenAI, Gemini, Anthropic, xAI (grok), DeepSeek, Ollama, and
5
5
  LlamaCpp. The toolkit includes full support for chat, streaming, tool calling,
6
- audio, images, files, and JSON Schema generation.
6
+ audio, images, files, and structured outputs (JSON Schema).
7
7
 
8
8
  ## Features
9
9
 
@@ -22,7 +22,7 @@ audio, 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
27
  #### Embeddings
28
28
  - 🧮 Text embeddings and vector support
@@ -47,9 +47,9 @@ breaks things down by provider, so you can see exactly what’s supported where.
47
47
  | **Audio (TTS / Transcribe / Translate)** | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
48
48
  | **Image Generation & Editing** | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ |
49
49
  | **File Uploads** | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
50
- | **Multimodal Prompts** *(text+image)* | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
50
+ | **Multimodal Prompts** *(text, documents, audio, images, videos, URLs, etc)* | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
51
51
  | **Embeddings** | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
52
- | **Models API** g| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
52
+ | **Models API** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
53
53
  | **Local Model Support** | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
54
54
  | **Vector Stores (RAG)** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
55
55
  | **Responses** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
@@ -110,12 +110,12 @@ require "llm"
110
110
 
111
111
  llm = LLM.openai(key: ENV["KEY"])
112
112
  bot = LLM::Bot.new(llm)
113
- 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"
114
114
  msgs = bot.chat do |prompt|
115
115
  prompt.system "Your task is to answer all user queries"
116
116
  prompt.user ["Tell me about this URL", URI(url)]
117
- prompt.user ["Tell me about this pdf", File.open("spec/fixtures/documents/freebsd.sysctl.pdf", "rb")]
118
- 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?"
119
119
  end
120
120
 
121
121
  # At this point, we execute a single request
@@ -142,12 +142,12 @@ require "llm"
142
142
 
143
143
  llm = LLM.openai(key: ENV["KEY"])
144
144
  bot = LLM::Bot.new(llm)
145
- 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"
146
146
  bot.chat(stream: $stdout) do |prompt|
147
147
  prompt.system "Your task is to answer all user queries"
148
148
  prompt.user ["Tell me about this URL", URI(url)]
149
- prompt.user ["Tell me about this pdf", File.open("spec/fixtures/documents/freebsd.sysctl.pdf", "rb")]
150
- 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?"
151
151
  end.to_a
152
152
  ```
153
153
 
@@ -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
@@ -13,12 +13,12 @@ module LLM
13
13
  #
14
14
  # llm = LLM.openai(key: ENV["KEY"])
15
15
  # bot = LLM::Bot.new(llm)
16
- # url = "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Cognac_glass.jpg/500px-Cognac_glass.jpg"
16
+ # url = "https://en.wikipedia.org/wiki/Special:FilePath/Cognac_glass.jpg"
17
17
  # msgs = bot.chat do |prompt|
18
18
  # prompt.system "Your task is to answer all user queries"
19
19
  # prompt.user ["Tell me about this URL", URI(url)]
20
- # prompt.user ["Tell me about this pdf", File.open("freebsd_book.pdf", "rb")]
21
- # prompt.user "Is the URL and PDF similar to each other?"
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?"
22
22
  # end
23
23
  #
24
24
  # # At this point, we execute a single request
data/lib/llm/buffer.rb CHANGED
@@ -55,7 +55,7 @@ module LLM
55
55
  end
56
56
 
57
57
  ##
58
- # @param [[LLM::Message, Hash]] item
58
+ # @param [[LLM::Message, Hash, Symbol]] item
59
59
  # A message and its parameters
60
60
  # @return [void]
61
61
  def <<(item)
@@ -80,6 +80,13 @@ module LLM
80
80
  "completed_count=#{@completed.size} pending_count=#{@pending.size}>"
81
81
  end
82
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
+
83
90
  private
84
91
 
85
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)
data/lib/llm/function.rb CHANGED
@@ -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/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/provider.rb CHANGED
@@ -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
@@ -22,6 +22,8 @@ class LLM::Gemini
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::HTTPBadRequest
26
28
  reason = body.dig("error", "details", 0, "reason")
27
29
  if reason == "API_KEY_INVALID"
@@ -28,5 +28,7 @@ module LLM::Gemini::Response
28
28
  LLM::Object.new(function)
29
29
  end
30
30
  end
31
+
32
+ def candidates = body.candidates || []
31
33
  end
32
34
  end
@@ -22,6 +22,8 @@ class LLM::Ollama
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
@@ -22,13 +22,25 @@ class LLM::OpenAI
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
28
30
  raise LLM::RateLimitError.new { _1.response = res }, "Too many requests"
29
31
  else
30
- raise LLM::ResponseError.new { _1.response = res }, "Unexpected response"
32
+ error = body["error"] || {}
33
+ case error["type"]
34
+ when "server_error" then raise LLM::ServerError.new { _1.response = res }, error["message"]
35
+ else raise LLM::ResponseError.new { _1.response = res }, error["message"] || "Unexpected response"
36
+ end
31
37
  end
32
38
  end
39
+
40
+ private
41
+
42
+ def body
43
+ @body ||= JSON.parse(res.body)
44
+ end
33
45
  end
34
46
  end
@@ -39,7 +39,7 @@ class LLM::XAI
39
39
  # @param [Hash] params Other parameters (see xAI docs)
40
40
  # @raise (see LLM::Provider#request)
41
41
  # @return [LLM::Response]
42
- def create(model: "grok-2-image-1212", **)
42
+ def create(prompt:, model: "grok-2-image-1212", **params)
43
43
  super
44
44
  end
45
45
 
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class JSON::Schema
3
+ class LLM::Schema
4
4
  ##
5
- # The {JSON::Schema::Array JSON::Schema::Array} class represents an
5
+ # The {LLM::Schema::Array LLM::Schema::Array} class represents an
6
6
  # array value in a JSON schema. It is a subclass of
7
- # {JSON::Schema::Leaf JSON::Schema::Leaf} and provides methods that
7
+ # {LLM::Schema::Leaf LLM::Schema::Leaf} and provides methods that
8
8
  # can act as constraints.
9
9
  class Array < Leaf
10
10
  def initialize(items)
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class JSON::Schema
3
+ class LLM::Schema
4
4
  ##
5
- # The {JSON::Schema::Boolean JSON::Schema::Boolean} class represents a
5
+ # The {LLM::Schema::Boolean LLM::Schema::Boolean} class represents a
6
6
  # boolean value in a JSON schema. It is a subclass of
7
- # {JSON::Schema::Leaf JSON::Schema::Leaf}.
7
+ # {LLM::Schema::Leaf LLM::Schema::Leaf}.
8
8
  class Boolean < Leaf
9
9
  def to_h
10
10
  super.merge!({type: "boolean"})
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class JSON::Schema
3
+ class LLM::Schema
4
4
  ##
5
- # The {JSON::Schema::Integer JSON::Schema::Integer} class represents a
5
+ # The {LLM::Schema::Integer LLM::Schema::Integer} class represents a
6
6
  # whole number value in a JSON schema. It is a subclass of
7
- # {JSON::Schema::Leaf JSON::Schema::Leaf} and provides methods that
7
+ # {LLM::Schema::Leaf LLM::Schema::Leaf} and provides methods that
8
8
  # can act as constraints.
9
9
  class Integer < Leaf
10
10
  ##
11
11
  # Constrain the number to a minimum value
12
12
  # @param [Integer] i The minimum value
13
- # @return [JSON::Schema::Number] Returns self
13
+ # @return [LLM::Schema::Number] Returns self
14
14
  def min(i)
15
15
  tap { @minimum = i }
16
16
  end
@@ -18,7 +18,7 @@ class JSON::Schema
18
18
  ##
19
19
  # Constrain the number to a maximum value
20
20
  # @param [Integer] i The maximum value
21
- # @return [JSON::Schema::Number] Returns self
21
+ # @return [LLM::Schema::Number] Returns self
22
22
  def max(i)
23
23
  tap { @maximum = i }
24
24
  end
@@ -26,7 +26,7 @@ class JSON::Schema
26
26
  ##
27
27
  # Constrain the number to a multiple of a given value
28
28
  # @param [Integer] i The multiple
29
- # @return [JSON::Schema::Number] Returns self
29
+ # @return [LLM::Schema::Number] Returns self
30
30
  def multiple_of(i)
31
31
  tap { @multiple_of = i }
32
32
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class JSON::Schema
3
+ class LLM::Schema
4
4
  ##
5
- # The {JSON::Schema::Leaf JSON::Schema::Leaf} class is the
5
+ # The {LLM::Schema::Leaf LLM::Schema::Leaf} class is the
6
6
  # superclass of all values that can appear in a JSON schema.
7
- # See the instance methods of {JSON::Schema JSON::Schema} for
8
- # an example of how to create instances of {JSON::Schema::Leaf JSON::Schema::Leaf}
7
+ # See the instance methods of {LLM::Schema LLM::Schema} for
8
+ # an example of how to create instances of {LLM::Schema::Leaf LLM::Schema::Leaf}
9
9
  # through its subclasses.
10
10
  class Leaf
11
11
  def initialize
@@ -19,7 +19,7 @@ class JSON::Schema
19
19
  ##
20
20
  # Set the description of a leaf
21
21
  # @param [String] str The description
22
- # @return [JSON::Schema::Leaf]
22
+ # @return [LLM::Schema::Leaf]
23
23
  def description(str)
24
24
  tap { @description = str }
25
25
  end
@@ -27,7 +27,7 @@ class JSON::Schema
27
27
  ##
28
28
  # Set the default value of a leaf
29
29
  # @param [Object] value The default value
30
- # @return [JSON::Schema::Leaf]
30
+ # @return [LLM::Schema::Leaf]
31
31
  def default(value)
32
32
  tap { @default = value }
33
33
  end
@@ -36,7 +36,7 @@ class JSON::Schema
36
36
  # Set the allowed values of a leaf
37
37
  # @see https://tour.json-schema.org/content/02-Primitive-Types/07-Enumerated-Values-II Enumerated Values
38
38
  # @param [Array] values The allowed values
39
- # @return [JSON::Schema::Leaf]
39
+ # @return [LLM::Schema::Leaf]
40
40
  def enum(*values)
41
41
  tap { @enum = values }
42
42
  end
@@ -45,14 +45,14 @@ class JSON::Schema
45
45
  # Set the value of a leaf to be a constant value
46
46
  # @see https://tour.json-schema.org/content/02-Primitive-Types/08-Defining-Constant-Values Constant Values
47
47
  # @param [Object] value The constant value
48
- # @return [JSON::Schema::Leaf]
48
+ # @return [LLM::Schema::Leaf]
49
49
  def const(value)
50
50
  tap { @const = value }
51
51
  end
52
52
 
53
53
  ##
54
54
  # Denote a leaf as required
55
- # @return [JSON::Schema::Leaf]
55
+ # @return [LLM::Schema::Leaf]
56
56
  def required
57
57
  tap { @required = true }
58
58
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class JSON::Schema
3
+ class LLM::Schema
4
4
  ##
5
- # The {JSON::Schema::Null JSON::Schema::Null} class represents a
5
+ # The {LLM::Schema::Null LLM::Schema::Null} class represents a
6
6
  # null value in a JSON schema. It is a subclass of
7
- # {JSON::Schema::Leaf JSON::Schema::Leaf}.
7
+ # {LLM::Schema::Leaf LLM::Schema::Leaf}.
8
8
  class Null < Leaf
9
9
  def to_h
10
10
  super.merge!({type: "null"})
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class JSON::Schema
3
+ class LLM::Schema
4
4
  ##
5
- # The {JSON::Schema::Number JSON::Schema::Number} class represents a
5
+ # The {LLM::Schema::Number LLM::Schema::Number} class represents a
6
6
  # a number (either whole or decimal) value in a JSON schema. It is a
7
- # subclass of {JSON::Schema::Leaf JSON::Schema::Leaf} and provides
7
+ # subclass of {LLM::Schema::Leaf LLM::Schema::Leaf} and provides
8
8
  # methods that can act as constraints.
9
9
  class Number < Leaf
10
10
  ##
11
11
  # Constrain the number to a minimum value
12
12
  # @param [Integer, Float] i The minimum value
13
- # @return [JSON::Schema::Number] Returns self
13
+ # @return [LLM::Schema::Number] Returns self
14
14
  def min(i)
15
15
  tap { @minimum = i }
16
16
  end
@@ -18,7 +18,7 @@ class JSON::Schema
18
18
  ##
19
19
  # Constrain the number to a maximum value
20
20
  # @param [Integer, Float] i The maximum value
21
- # @return [JSON::Schema::Number] Returns self
21
+ # @return [LLM::Schema::Number] Returns self
22
22
  def max(i)
23
23
  tap { @maximum = i }
24
24
  end
@@ -26,7 +26,7 @@ class JSON::Schema
26
26
  ##
27
27
  # Constrain the number to a multiple of a given value
28
28
  # @param [Integer, Float] i The multiple
29
- # @return [JSON::Schema::Number] Returns self
29
+ # @return [LLM::Schema::Number] Returns self
30
30
  def multiple_of(i)
31
31
  tap { @multiple_of = i }
32
32
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class JSON::Schema
3
+ class LLM::Schema
4
4
  ##
5
- # The {JSON::Schema::Object JSON::Schema::Object} class represents an
5
+ # The {LLM::Schema::Object LLM::Schema::Object} class represents an
6
6
  # object value in a JSON schema. It is a subclass of
7
- # {JSON::Schema::Leaf JSON::Schema::Leaf} and provides methods that
7
+ # {LLM::Schema::Leaf LLM::Schema::Leaf} and provides methods that
8
8
  # can act as constraints.
9
9
  class Object < Leaf
10
10
  def initialize(properties)
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class JSON::Schema
3
+ class LLM::Schema
4
4
  ##
5
- # The {JSON::Schema::String JSON::Schema::String} class represents a
5
+ # The {LLM::Schema::String LLM::Schema::String} class represents a
6
6
  # string value in a JSON schema. It is a subclass of
7
- # {JSON::Schema::Leaf JSON::Schema::Leaf} and provides methods that
7
+ # {LLM::Schema::Leaf LLM::Schema::Leaf} and provides methods that
8
8
  # can act as constraints.
9
9
  class String < Leaf
10
10
  ##
11
11
  # Constrain the string to a minimum length
12
12
  # @param [Integer] i The minimum length
13
- # @return [JSON::Schema::String] Returns self
13
+ # @return [LLM::Schema::String] Returns self
14
14
  def min(i)
15
15
  tap { @minimum = i }
16
16
  end
@@ -18,7 +18,7 @@ class JSON::Schema
18
18
  ##
19
19
  # Constrain the string to a maximum length
20
20
  # @param [Integer] i The maximum length
21
- # @return [JSON::Schema::String] Returns self
21
+ # @return [LLM::Schema::String] Returns self
22
22
  def max(i)
23
23
  tap { @maximum = i }
24
24
  end
@@ -3,6 +3,6 @@
3
3
  module JSON
4
4
  end unless defined?(JSON)
5
5
 
6
- class JSON::Schema
6
+ class LLM::Schema
7
7
  VERSION = "0.1.0"
8
8
  end
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module JSON
4
- end unless defined?(JSON)
5
-
6
3
  ##
7
- # The {JSON::Schema JSON::Schema} class represents a JSON schema,
4
+ # The {LLM::Schema LLM::Schema} class represents a JSON schema,
8
5
  # and provides methods that let you describe and produce a schema
9
6
  # that can be used in various contexts that include the validation
10
7
  # and generation of JSON data.
@@ -13,14 +10,14 @@ end unless defined?(JSON)
13
10
  # @see https://tour.json-schema.org/ JSON Schema Tour
14
11
  #
15
12
  # @example
16
- # schema = JSON::Schema.new
13
+ # schema = LLM::Schema.new
17
14
  # schema.object({
18
15
  # name: schema.string.enum("John", "Jane").required,
19
16
  # age: schema.integer.required,
20
17
  # hobbies: schema.array(schema.string, schema.null).required,
21
18
  # address: schema.object({street: schema.string}).required,
22
19
  # })
23
- class JSON::Schema
20
+ class LLM::Schema
24
21
  require_relative "schema/version"
25
22
  require_relative "schema/leaf"
26
23
  require_relative "schema/object"
@@ -34,7 +31,7 @@ class JSON::Schema
34
31
  ##
35
32
  # Returns an object
36
33
  # @param [Hash] properties A hash of properties
37
- # @return [JSON::Schema::Object]
34
+ # @return [LLM::Schema::Object]
38
35
  def object(properties)
39
36
  Object.new(properties)
40
37
  end
@@ -42,42 +39,42 @@ class JSON::Schema
42
39
  ##
43
40
  # Returns an array
44
41
  # @param [Array] items An array of items
45
- # @return [JSON::Schema::Array]
42
+ # @return [LLM::Schema::Array]
46
43
  def array(*items)
47
44
  Array.new(*items)
48
45
  end
49
46
 
50
47
  ##
51
48
  # Returns a string
52
- # @return [JSON::Schema::String]
49
+ # @return [LLM::Schema::String]
53
50
  def string
54
51
  String.new
55
52
  end
56
53
 
57
54
  ##
58
55
  # Returns a number
59
- # @return [JSON::Schema::Number] a number
56
+ # @return [LLM::Schema::Number] a number
60
57
  def number
61
58
  Number.new
62
59
  end
63
60
 
64
61
  ##
65
62
  # Returns an integer
66
- # @return [JSON::Schema::Integer]
63
+ # @return [LLM::Schema::Integer]
67
64
  def integer
68
65
  Integer.new
69
66
  end
70
67
 
71
68
  ##
72
69
  # Returns a boolean
73
- # @return [JSON::Schema::Boolean]
70
+ # @return [LLM::Schema::Boolean]
74
71
  def boolean
75
72
  Boolean.new
76
73
  end
77
74
 
78
75
  ##
79
76
  # Returns null
80
- # @return [JSON::Schema::Null]
77
+ # @return [LLM::Schema::Null]
81
78
  def null
82
79
  Null.new
83
80
  end
data/lib/llm/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LLM
4
- VERSION = "0.12.0"
4
+ VERSION = "0.13.0"
5
5
  end
data/lib/llm.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module LLM
4
4
  require "stringio"
5
- require_relative "llm/json/schema"
5
+ require_relative "llm/schema"
6
6
  require_relative "llm/object"
7
7
  require_relative "llm/version"
8
8
  require_relative "llm/utils"
data/llm.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  llm.rb is a zero-dependency Ruby toolkit for Large Language Models that
13
13
  includes OpenAI, Gemini, Anthropic, xAI (grok), DeepSeek, Ollama, and
14
14
  LlamaCpp. The toolkit includes full support for chat, streaming, tool calling,
15
- audio, images, files, and JSON Schema generation.
15
+ audio, images, files, and structured outputs (JSON Schema).
16
16
  SUMMARY
17
17
 
18
18
  spec.description = spec.summary
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antar Azri
@@ -153,7 +153,7 @@ dependencies:
153
153
  description: llm.rb is a zero-dependency Ruby toolkit for Large Language Models that
154
154
  includes OpenAI, Gemini, Anthropic, xAI (grok), DeepSeek, Ollama, and LlamaCpp.
155
155
  The toolkit includes full support for chat, streaming, tool calling, audio, images,
156
- files, and JSON Schema generation.
156
+ files, and structured outputs (JSON Schema).
157
157
  email:
158
158
  - azantar@proton.me
159
159
  - 0x1eef@proton.me
@@ -177,16 +177,6 @@ files:
177
177
  - lib/llm/eventstream/parser.rb
178
178
  - lib/llm/file.rb
179
179
  - lib/llm/function.rb
180
- - lib/llm/json/schema.rb
181
- - lib/llm/json/schema/array.rb
182
- - lib/llm/json/schema/boolean.rb
183
- - lib/llm/json/schema/integer.rb
184
- - lib/llm/json/schema/leaf.rb
185
- - lib/llm/json/schema/null.rb
186
- - lib/llm/json/schema/number.rb
187
- - lib/llm/json/schema/object.rb
188
- - lib/llm/json/schema/string.rb
189
- - lib/llm/json/schema/version.rb
190
180
  - lib/llm/message.rb
191
181
  - lib/llm/mime.rb
192
182
  - lib/llm/multipart.rb
@@ -250,6 +240,16 @@ files:
250
240
  - lib/llm/providers/xai.rb
251
241
  - lib/llm/providers/xai/images.rb
252
242
  - lib/llm/response.rb
243
+ - lib/llm/schema.rb
244
+ - lib/llm/schema/array.rb
245
+ - lib/llm/schema/boolean.rb
246
+ - lib/llm/schema/integer.rb
247
+ - lib/llm/schema/leaf.rb
248
+ - lib/llm/schema/null.rb
249
+ - lib/llm/schema/number.rb
250
+ - lib/llm/schema/object.rb
251
+ - lib/llm/schema/string.rb
252
+ - lib/llm/schema/version.rb
253
253
  - lib/llm/utils.rb
254
254
  - lib/llm/version.rb
255
255
  - llm.gemspec
@@ -278,5 +278,5 @@ specification_version: 4
278
278
  summary: llm.rb is a zero-dependency Ruby toolkit for Large Language Models that includes
279
279
  OpenAI, Gemini, Anthropic, xAI (grok), DeepSeek, Ollama, and LlamaCpp. The toolkit
280
280
  includes full support for chat, streaming, tool calling, audio, images, files, and
281
- JSON Schema generation.
281
+ structured outputs (JSON Schema).
282
282
  test_files: []