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
@@ -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"
@@ -17,22 +17,10 @@ class LLM::Gemini
17
17
  # #!/usr/bin/env ruby
18
18
  # require "llm"
19
19
  #
20
- # llm = LLM.gemini(ENV["KEY"])
20
+ # llm = LLM.gemini(key: ENV["KEY"])
21
21
  # bot = LLM::Bot.new(llm)
22
- # file = llm.files.create file: "/audio/haiku.mp3"
23
- # bot.chat(file)
24
- # bot.chat("Describe the audio file I sent to you")
25
- # bot.chat("The audio file is the first message I sent to you.")
26
- # bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
27
- #
28
- # @example example #2
29
- # #!/usr/bin/env ruby
30
- # require "llm"
31
- #
32
- # llm = LLM.gemini(ENV["KEY"])
33
- # bot = LLM::Bot.new(llm)
34
- # file = llm.files.create file: "/audio/haiku.mp3"
35
- # bot.chat(["Describe the audio file I sent to you", file])
22
+ # file = llm.files.create(file: "/audio/haiku.mp3")
23
+ # bot.chat ["Tell me about this file", file]
36
24
  # bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
37
25
  class Files
38
26
  require_relative "response/file"
@@ -48,7 +36,7 @@ class LLM::Gemini
48
36
  ##
49
37
  # List all files
50
38
  # @example
51
- # llm = LLM.gemini(ENV["KEY"])
39
+ # llm = LLM.gemini(key: ENV["KEY"])
52
40
  # res = llm.files.all
53
41
  # res.each do |file|
54
42
  # print "name: ", file.name, "\n"
@@ -67,8 +55,8 @@ class LLM::Gemini
67
55
  ##
68
56
  # Create a file
69
57
  # @example
70
- # llm = LLM.gemini(ENV["KEY"])
71
- # res = llm.files.create file: "/audio/haiku.mp3"
58
+ # llm = LLM.gemini(key: ENV["KEY"])
59
+ # res = llm.files.create(file: "/audio/haiku.mp3")
72
60
  # @see https://ai.google.dev/gemini-api/docs/files Gemini docs
73
61
  # @param [String, LLM::File] file The file
74
62
  # @param [Hash] params Other parameters (see Gemini docs)
@@ -90,7 +78,7 @@ class LLM::Gemini
90
78
  ##
91
79
  # Get a file
92
80
  # @example
93
- # llm = LLM.gemini(ENV["KEY"])
81
+ # llm = LLM.gemini(key: ENV["KEY"])
94
82
  # res = llm.files.get(file: "files/1234567890")
95
83
  # print "name: ", res.name, "\n"
96
84
  # @see https://ai.google.dev/gemini-api/docs/files Gemini docs
@@ -109,7 +97,7 @@ class LLM::Gemini
109
97
  ##
110
98
  # Delete a file
111
99
  # @example
112
- # llm = LLM.gemini(ENV["KEY"])
100
+ # llm = LLM.gemini(key: ENV["KEY"])
113
101
  # res = llm.files.delete(file: "files/1234567890")
114
102
  # @see https://ai.google.dev/gemini-api/docs/files Gemini docs
115
103
  # @param [#name, String] file The file to delete
@@ -59,8 +59,8 @@ module LLM::Gemini::Format
59
59
  end
60
60
 
61
61
  def prompt_error!(object)
62
- raise LLM::PromptError, "The given object (an instance of #{object.class}) " \
63
- "is not supported by the Gemini API"
62
+ raise LLM::PromptError, "The given object (an instance of #{object.class}) " \
63
+ "is not supported by the Gemini API"
64
64
  end
65
65
 
66
66
  def message = @message
File without changes
@@ -11,7 +11,7 @@ class LLM::Gemini
11
11
  # #!/usr/bin/env ruby
12
12
  # require "llm"
13
13
  #
14
- # llm = LLM.gemini(ENV["KEY"])
14
+ # llm = LLM.gemini(key: ENV["KEY"])
15
15
  # res = llm.images.create prompt: "A dog on a rocket to the moon"
16
16
  # IO.copy_stream res.images[0], "rocket.png"
17
17
  class Images
@@ -29,7 +29,7 @@ class LLM::Gemini
29
29
  ##
30
30
  # Create an image
31
31
  # @example
32
- # llm = LLM.gemini(ENV["KEY"])
32
+ # llm = LLM.gemini(key: ENV["KEY"])
33
33
  # res = llm.images.create prompt: "A dog on a rocket to the moon"
34
34
  # IO.copy_stream res.images[0], "rocket.png"
35
35
  # @see https://ai.google.dev/gemini-api/docs/image-generation Gemini docs
@@ -55,8 +55,8 @@ class LLM::Gemini
55
55
  ##
56
56
  # Edit an image
57
57
  # @example
58
- # llm = LLM.gemini(ENV["KEY"])
59
- # res = llm.images.edit image: LLM::File("cat.png"), prompt: "Add a hat to the cat"
58
+ # llm = LLM.gemini(key: ENV["KEY"])
59
+ # res = llm.images.edit image: "cat.png", prompt: "Add a hat to the cat"
60
60
  # IO.copy_stream res.images[0], "hatoncat.png"
61
61
  # @see https://ai.google.dev/gemini-api/docs/image-generation Gemini docs
62
62
  # @param [String, LLM::File] image The image to edit
@@ -11,7 +11,7 @@ class LLM::Gemini
11
11
  # #!/usr/bin/env ruby
12
12
  # require "llm"
13
13
  #
14
- # llm = LLM.gemini(ENV["KEY"])
14
+ # llm = LLM.gemini(key: ENV["KEY"])
15
15
  # res = llm.models.all
16
16
  # res.each do |model|
17
17
  # print "id: ", model.id, "\n"
@@ -30,7 +30,7 @@ class LLM::Gemini
30
30
  ##
31
31
  # List all models
32
32
  # @example
33
- # llm = LLM.gemini(ENV["KEY"])
33
+ # llm = LLM.gemini(key: ENV["KEY"])
34
34
  # res = llm.models.all
35
35
  # res.each do |model|
36
36
  # print "id: ", model.id, "\n"
@@ -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
@@ -5,4 +5,4 @@ module LLM::Gemini::Response
5
5
  def model = "text-embedding-004"
6
6
  def embeddings = body.dig("embedding", "values")
7
7
  end
8
- end
8
+ end
File without changes
File without changes
File without changes
@@ -3,30 +3,19 @@
3
3
  module LLM
4
4
  ##
5
5
  # The Gemini class implements a provider for
6
- # [Gemini](https://ai.google.dev/).
6
+ # [Gemini](https://ai.google.dev/). The Gemini provider
7
+ # can accept multiple inputs (text, images, audio, and video).
8
+ # The inputs can be provided inline via the prompt for files
9
+ # under 20MB or via the Gemini Files API for files
10
+ # that are over 20MB.
7
11
  #
8
- # The Gemini provider can accept multiple inputs (text, images,
9
- # audio, and video). The inputs can be provided inline via the
10
- # prompt for files under 20MB or via the Gemini Files API for
11
- # files that are over 20MB
12
- #
13
- # @example example #1
14
- # #!/usr/bin/env ruby
15
- # require "llm"
16
- #
17
- # llm = LLM.gemini(ENV["KEY"])
18
- # bot = LLM::Bot.new(llm)
19
- # bot.chat LLM.File("/images/capybara.png")
20
- # bot.chat "Describe the image"
21
- # bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
22
- #
23
- # @example example #2
12
+ # @example
24
13
  # #!/usr/bin/env ruby
25
14
  # require "llm"
26
15
  #
27
- # llm = LLM.gemini(ENV["KEY"])
16
+ # llm = LLM.gemini(key: ENV["KEY"])
28
17
  # bot = LLM::Bot.new(llm)
29
- # bot.chat ["Describe the image", LLM::File("/images/capybara.png")]
18
+ # bot.chat ["Tell me about this photo", File.open("/images/horse.jpg", "rb")]
30
19
  # bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
31
20
  class Gemini < Provider
32
21
  require_relative "gemini/response/embedding"
@@ -55,7 +44,7 @@ module LLM
55
44
  # @param model (see LLM::Provider#embed)
56
45
  # @param params (see LLM::Provider#embed)
57
46
  # @raise (see LLM::Provider#request)
58
- # @return (see LLM::Provider#embed)
47
+ # @return [LLM::Response]
59
48
  def embed(input, model: "text-embedding-004", **params)
60
49
  model = model.respond_to?(:id) ? model.id : model
61
50
  path = ["/v1beta/models/#{model}", "embedContent?key=#{@key}"].join(":")
@@ -74,7 +63,7 @@ module LLM
74
63
  # @raise (see LLM::Provider#request)
75
64
  # @raise [LLM::PromptError]
76
65
  # When given an object a provider does not understand
77
- # @return (see LLM::Provider#complete)
66
+ # @return [LLM::Response]
78
67
  def complete(prompt, params = {})
79
68
  params = {role: :user, model: default_model}.merge!(params)
80
69
  params = [params, format_schema(params), format_tools(params)].inject({}, &:merge!).compact
@@ -93,6 +82,7 @@ module LLM
93
82
  ##
94
83
  # Provides an interface to Gemini's audio API
95
84
  # @see https://ai.google.dev/gemini-api/docs/audio Gemini docs
85
+ # @return [LLM::Gemini::Audio]
96
86
  def audio
97
87
  LLM::Gemini::Audio.new(self)
98
88
  end
@@ -108,6 +98,7 @@ module LLM
108
98
  ##
109
99
  # Provides an interface to Gemini's file management API
110
100
  # @see https://ai.google.dev/gemini-api/docs/files Gemini docs
101
+ # @return [LLM::Gemini::Files]
111
102
  def files
112
103
  LLM::Gemini::Files.new(self)
113
104
  end
@@ -115,6 +106,7 @@ module LLM
115
106
  ##
116
107
  # Provides an interface to Gemini's models API
117
108
  # @see https://ai.google.dev/gemini-api/docs/models Gemini docs
109
+ # @return [LLM::Gemini::Models]
118
110
  def models
119
111
  LLM::Gemini::Models.new(self)
120
112
  end
@@ -7,7 +7,18 @@ module LLM
7
7
  # The LlamaCpp class implements a provider for
8
8
  # [llama.cpp](https://github.com/ggml-org/llama.cpp)
9
9
  # through the OpenAI-compatible API provided by the
10
- # llama-server binary.
10
+ # llama-server binary. Similar to the ollama provider,
11
+ # this provider supports a wide range of models and
12
+ # is straightforward to run on your own hardware.
13
+ #
14
+ # @example
15
+ # #!/usr/bin/env ruby
16
+ # require "llm"
17
+ #
18
+ # llm = LLM.llamacpp(key: nil)
19
+ # bot = LLM::Bot.new(llm)
20
+ # bot.chat ["Tell me about this photo", File.open("/images/frog.jpg", "rb")]
21
+ # bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
11
22
  class LlamaCpp < OpenAI
12
23
  ##
13
24
  # @param (see LLM::Provider#initialize)
@@ -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
File without changes
File without changes
File without changes
File without changes
@@ -1,4 +1,3 @@
1
-
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module LLM::Ollama::Response
@@ -7,4 +6,4 @@ module LLM::Ollama::Response
7
6
  def prompt_tokens = body.dig("usage", "prompt_tokens") || 0
8
7
  def total_tokens = body.dig("usage", "total_tokens") || 0
9
8
  end
10
- end
9
+ end
File without changes
@@ -2,21 +2,18 @@
2
2
 
3
3
  module LLM
4
4
  ##
5
- # The Ollama class implements a provider for [Ollama](https://ollama.ai/).
6
- #
7
- # This provider supports a wide range of models, it is relatively
8
- # straight forward to run on your own hardware, and includes multi-modal
9
- # models that can process images and text. See the example for a demonstration
10
- # of a multi-modal model by the name `llava`
5
+ # The Ollama class implements a provider for [Ollama](https://ollama.ai/) &ndash;
6
+ # and the provider supports a wide range of models. It is straight forward
7
+ # to run on your own hardware, and there are a number of multi-modal models
8
+ # that can process both images and text.
11
9
  #
12
10
  # @example
13
11
  # #!/usr/bin/env ruby
14
12
  # require "llm"
15
13
  #
16
- # llm = LLM.ollama(nil)
14
+ # llm = LLM.ollama(key: nil)
17
15
  # bot = LLM::Bot.new(llm, model: "llava")
18
- # bot.chat LLM::File("/images/capybara.png")
19
- # bot.chat "Describe the image"
16
+ # bot.chat ["Tell me about this image", File.open("/images/parrot.png", "rb")]
20
17
  # bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
21
18
  class Ollama < Provider
22
19
  require_relative "ollama/response/embedding"
@@ -42,7 +39,7 @@ module LLM
42
39
  # @param model (see LLM::Provider#embed)
43
40
  # @param params (see LLM::Provider#embed)
44
41
  # @raise (see LLM::Provider#request)
45
- # @return (see LLM::Provider#embed)
42
+ # @return [LLM::Response]
46
43
  def embed(input, model: default_model, **params)
47
44
  params = {model:}.merge!(params)
48
45
  req = Net::HTTP::Post.new("/v1/embeddings", headers)
@@ -60,7 +57,7 @@ module LLM
60
57
  # @raise (see LLM::Provider#request)
61
58
  # @raise [LLM::PromptError]
62
59
  # When given an object a provider does not understand
63
- # @return (see LLM::Provider#complete)
60
+ # @return [LLM::Response]
64
61
  def complete(prompt, params = {})
65
62
  params = {role: :user, model: default_model, stream: true}.merge!(params)
66
63
  params = [params, {format: params[:schema]}, format_tools(params)].inject({}, &:merge!).compact
@@ -5,7 +5,7 @@ class LLM::OpenAI
5
5
  # The {LLM::OpenAI::Audio LLM::OpenAI::Audio} class provides an audio
6
6
  # object for interacting with [OpenAI's audio API](https://platform.openai.com/docs/api-reference/audio/createSpeech).
7
7
  # @example
8
- # llm = LLM.openai(ENV["KEY"])
8
+ # llm = LLM.openai(key: ENV["KEY"])
9
9
  # res = llm.audio.create_speech(input: "A dog on a rocket to the moon")
10
10
  # IO.copy_stream res.audio, "rocket.mp3"
11
11
  class Audio
@@ -20,7 +20,7 @@ class LLM::OpenAI
20
20
  ##
21
21
  # Create an audio track
22
22
  # @example
23
- # llm = LLM.openai(ENV["KEY"])
23
+ # llm = LLM.openai(key: ENV["KEY"])
24
24
  # res = llm.images.create_speech(input: "A dog on a rocket to the moon")
25
25
  # File.binwrite("rocket.mp3", res.audio.string)
26
26
  # @see https://platform.openai.com/docs/api-reference/audio/createSpeech OpenAI docs
@@ -42,7 +42,7 @@ class LLM::OpenAI
42
42
  ##
43
43
  # Create an audio transcription
44
44
  # @example
45
- # llm = LLM.openai(ENV["KEY"])
45
+ # llm = LLM.openai(key: ENV["KEY"])
46
46
  # res = llm.audio.create_transcription(file: "/audio/rocket.mp3")
47
47
  # res.text # => "A dog on a rocket to the moon"
48
48
  # @see https://platform.openai.com/docs/api-reference/audio/createTranscription OpenAI docs
@@ -64,7 +64,7 @@ class LLM::OpenAI
64
64
  # Create an audio translation (in English)
65
65
  # @example
66
66
  # # Arabic => English
67
- # llm = LLM.openai(ENV["KEY"])
67
+ # llm = LLM.openai(key: ENV["KEY"])
68
68
  # res = llm.audio.create_translation(file: "/audio/bismillah.mp3")
69
69
  # res.text # => "In the name of Allah, the Beneficent, the Merciful."
70
70
  # @see https://platform.openai.com/docs/api-reference/audio/createTranslation OpenAI docs
@@ -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
@@ -12,21 +12,10 @@ class LLM::OpenAI
12
12
  # #!/usr/bin/env ruby
13
13
  # require "llm"
14
14
  #
15
- # llm = LLM.openai(ENV["KEY"])
15
+ # llm = LLM.openai(key: ENV["KEY"])
16
16
  # bot = LLM::Bot.new(llm)
17
- # file = llm.files.create file: "/documents/freebsd.pdf"
18
- # bot.chat(file)
19
- # bot.chat("Describe the document")
20
- # bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
21
- #
22
- # @example example #2
23
- # #!/usr/bin/env ruby
24
- # require "llm"
25
- #
26
- # llm = LLM.openai(ENV["KEY"])
27
- # bot = LLM::Bot.new(llm)
28
- # file = llm.files.create file: "/documents/openbsd.pdf"
29
- # bot.chat(["Describe the document I sent to you", file])
17
+ # file = llm.files.create file: "/books/goodread.pdf"
18
+ # bot.chat ["Tell me about this PDF", file]
30
19
  # bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
31
20
  class Files
32
21
  require_relative "response/file"
@@ -42,7 +31,7 @@ class LLM::OpenAI
42
31
  ##
43
32
  # List all files
44
33
  # @example
45
- # llm = LLM.openai(ENV["KEY"])
34
+ # llm = LLM.openai(key: ENV["KEY"])
46
35
  # res = llm.files.all
47
36
  # res.each do |file|
48
37
  # print "id: ", file.id, "\n"
@@ -61,7 +50,7 @@ class LLM::OpenAI
61
50
  ##
62
51
  # Create a file
63
52
  # @example
64
- # llm = LLM.openai(ENV["KEY"])
53
+ # llm = LLM.openai(key: ENV["KEY"])
65
54
  # res = llm.files.create file: "/documents/haiku.txt"
66
55
  # @see https://platform.openai.com/docs/api-reference/files/create OpenAI docs
67
56
  # @param [File, LLM::File, String] file The file
@@ -81,7 +70,7 @@ class LLM::OpenAI
81
70
  ##
82
71
  # Get a file
83
72
  # @example
84
- # llm = LLM.openai(ENV["KEY"])
73
+ # llm = LLM.openai(key: ENV["KEY"])
85
74
  # res = llm.files.get(file: "file-1234567890")
86
75
  # print "id: ", res.id, "\n"
87
76
  # @see https://platform.openai.com/docs/api-reference/files/get OpenAI docs
@@ -100,7 +89,7 @@ class LLM::OpenAI
100
89
  ##
101
90
  # Download the content of a file
102
91
  # @example
103
- # llm = LLM.openai(ENV["KEY"])
92
+ # llm = LLM.openai(key: ENV["KEY"])
104
93
  # res = llm.files.download(file: "file-1234567890")
105
94
  # File.binwrite "haiku1.txt", res.file.read
106
95
  # print res.file.read, "\n"
@@ -121,7 +110,7 @@ class LLM::OpenAI
121
110
  ##
122
111
  # Delete a file
123
112
  # @example
124
- # llm = LLM.openai(ENV["KEY"])
113
+ # llm = LLM.openai(key: ENV["KEY"])
125
114
  # res = llm.files.delete(file: "file-1234567890")
126
115
  # print res.deleted, "\n"
127
116
  # @see https://platform.openai.com/docs/api-reference/files/delete OpenAI docs
File without changes
File without changes
File without changes
File without changes
@@ -2,27 +2,27 @@
2
2
 
3
3
  class LLM::OpenAI
4
4
  ##
5
- # The {LLM::OpenAI::Images LLM::OpenAI::Images} class provides an images
6
- # object for interacting with [OpenAI's images API](https://platform.openai.com/docs/api-reference/images).
5
+ # The {LLM::OpenAI::Images LLM::OpenAI::Images} class provides an interface
6
+ # for [OpenAI's images API](https://platform.openai.com/docs/api-reference/images).
7
7
  # OpenAI supports multiple response formats: temporary URLs, or binary strings
8
8
  # encoded in base64. The default is to return temporary URLs.
9
9
  #
10
- # @example example #1
10
+ # @example Temporary URLs
11
11
  # #!/usr/bin/env ruby
12
12
  # require "llm"
13
13
  # require "open-uri"
14
14
  # require "fileutils"
15
15
  #
16
- # llm = LLM.openai(ENV["KEY"])
16
+ # llm = LLM.openai(key: ENV["KEY"])
17
17
  # res = llm.images.create prompt: "A dog on a rocket to the moon"
18
18
  # FileUtils.mv OpenURI.open_uri(res.urls[0]).path,
19
19
  # "rocket.png"
20
20
  #
21
- # @example example #2
21
+ # @example Binary strings
22
22
  # #!/usr/bin/env ruby
23
23
  # require "llm"
24
24
  #
25
- # llm = LLM.openai(ENV["KEY"])
25
+ # llm = LLM.openai(key: ENV["KEY"])
26
26
  # res = llm.images.create prompt: "A dog on a rocket to the moon",
27
27
  # response_format: "b64_json"
28
28
  # IO.copy_stream res.images[0], "rocket.png"
@@ -39,9 +39,9 @@ class LLM::OpenAI
39
39
  ##
40
40
  # Create an image
41
41
  # @example
42
- # llm = LLM.openai(ENV["KEY"])
42
+ # llm = LLM.openai(key: ENV["KEY"])
43
43
  # res = llm.images.create prompt: "A dog on a rocket to the moon"
44
- # p res.urls
44
+ # res.urls.each { print _1, "\n" }
45
45
  # @see https://platform.openai.com/docs/api-reference/images/create OpenAI docs
46
46
  # @param [String] prompt The prompt
47
47
  # @param [String] model The model to use
@@ -58,7 +58,7 @@ class LLM::OpenAI
58
58
  ##
59
59
  # Create image variations
60
60
  # @example
61
- # llm = LLM.openai(ENV["KEY"])
61
+ # llm = LLM.openai(key: ENV["KEY"])
62
62
  # res = llm.images.create_variation(image: "/images/hat.png", n: 5)
63
63
  # p res.urls
64
64
  # @see https://platform.openai.com/docs/api-reference/images/createVariation OpenAI docs
@@ -80,7 +80,7 @@ class LLM::OpenAI
80
80
  ##
81
81
  # Edit an image
82
82
  # @example
83
- # llm = LLM.openai(ENV["KEY"])
83
+ # llm = LLM.openai(key: ENV["KEY"])
84
84
  # res = llm.images.edit(image: "/images/hat.png", prompt: "A cat wearing this hat")
85
85
  # p res.urls
86
86
  # @see https://platform.openai.com/docs/api-reference/images/createEdit OpenAI docs
@@ -11,7 +11,7 @@ class LLM::OpenAI
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
  # res = llm.models.all
16
16
  # res.each do |model|
17
17
  # print "id: ", model.id, "\n"
@@ -28,7 +28,7 @@ class LLM::OpenAI
28
28
  ##
29
29
  # List all models
30
30
  # @example
31
- # llm = LLM.openai(ENV["KEY"])
31
+ # llm = LLM.openai(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
@@ -16,7 +16,7 @@ module LLM::OpenAI::Response
16
16
  end
17
17
  end
18
18
  alias_method :messages, :choices
19
-
19
+
20
20
  def model = body.model
21
21
  def prompt_tokens = body.usage&.prompt_tokens
22
22
  def completion_tokens = body.usage&.completion_tokens
@@ -36,4 +36,4 @@ module LLM::OpenAI::Response
36
36
  end
37
37
  end
38
38
  end
39
- end
39
+ end
@@ -1,9 +1,9 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
2
 
3
- module LLM::OpenAI::Response
3
+ module LLM::OpenAI::Response
4
4
  module Embedding
5
5
  def embeddings = data.map { _1["embedding"] }
6
6
  def prompt_tokens = data.dig(0, "usage", "prompt_tokens")
7
7
  def total_tokens = data.dig(0, "usage", "total_tokens")
8
8
  end
9
- end
9
+ end
File without changes
File without changes
File without changes
@@ -2,7 +2,6 @@
2
2
 
3
3
  module LLM::OpenAI::Response
4
4
  module Responds
5
-
6
5
  def outputs = [format_message]
7
6
  def choices = body.output
8
7
  def tools = output.select { _1.type == "function_call" }