llm.rb 0.2.0 → 0.3.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/README.md +264 -110
- data/lib/llm/buffer.rb +83 -0
- data/lib/llm/chat.rb +131 -0
- data/lib/llm/file.rb +26 -40
- data/lib/llm/http_client.rb +10 -5
- data/lib/llm/message.rb +14 -8
- data/lib/llm/mime.rb +54 -0
- data/lib/llm/multipart.rb +98 -0
- data/lib/llm/provider.rb +116 -12
- data/lib/llm/providers/anthropic/error_handler.rb +2 -0
- data/lib/llm/providers/anthropic/format.rb +9 -1
- data/lib/llm/providers/anthropic/response_parser.rb +3 -1
- data/lib/llm/providers/anthropic.rb +14 -5
- data/lib/llm/providers/gemini/audio.rb +77 -0
- data/lib/llm/providers/gemini/error_handler.rb +2 -0
- data/lib/llm/providers/gemini/files.rb +160 -0
- data/lib/llm/providers/gemini/format.rb +19 -7
- data/lib/llm/providers/gemini/images.rb +99 -0
- data/lib/llm/providers/gemini/response_parser.rb +27 -1
- data/lib/llm/providers/gemini.rb +62 -6
- data/lib/llm/providers/ollama/error_handler.rb +2 -0
- data/lib/llm/providers/ollama/format.rb +18 -4
- data/lib/llm/providers/ollama/response_parser.rb +3 -1
- data/lib/llm/providers/ollama.rb +30 -7
- data/lib/llm/providers/openai/audio.rb +97 -0
- data/lib/llm/providers/openai/error_handler.rb +2 -0
- data/lib/llm/providers/openai/files.rb +148 -0
- data/lib/llm/providers/openai/format.rb +26 -7
- data/lib/llm/providers/openai/images.rb +109 -0
- data/lib/llm/providers/openai/response_parser.rb +58 -5
- data/lib/llm/providers/openai/responses.rb +78 -0
- data/lib/llm/providers/openai.rb +52 -6
- data/lib/llm/providers/voyageai.rb +2 -2
- data/lib/llm/response/audio.rb +13 -0
- data/lib/llm/response/audio_transcription.rb +14 -0
- data/lib/llm/response/audio_translation.rb +14 -0
- data/lib/llm/response/download_file.rb +15 -0
- data/lib/llm/response/file.rb +42 -0
- data/lib/llm/response/filelist.rb +18 -0
- data/lib/llm/response/image.rb +29 -0
- data/lib/llm/response/output.rb +56 -0
- data/lib/llm/response.rb +18 -6
- data/lib/llm/utils.rb +19 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +5 -2
- data/llm.gemspec +1 -6
- data/spec/anthropic/completion_spec.rb +1 -1
- data/spec/gemini/completion_spec.rb +22 -1
- data/spec/gemini/conversation_spec.rb +31 -0
- data/spec/gemini/files_spec.rb +124 -0
- data/spec/gemini/images_spec.rb +47 -0
- data/spec/llm/conversation_spec.rb +133 -1
- data/spec/ollama/completion_spec.rb +1 -1
- data/spec/ollama/conversation_spec.rb +31 -0
- data/spec/openai/audio_spec.rb +55 -0
- data/spec/openai/completion_spec.rb +22 -1
- data/spec/openai/files_spec.rb +150 -0
- data/spec/openai/images_spec.rb +95 -0
- data/spec/openai/responses_spec.rb +51 -0
- data/spec/setup.rb +8 -0
- metadata +31 -51
- data/LICENSE.txt +0 -21
- data/lib/llm/conversation.rb +0 -50
- data/lib/llm/lazy_conversation.rb +0 -51
- data/lib/llm/message_queue.rb +0 -47
- data/spec/llm/lazy_conversation_spec.rb +0 -92
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LLM
|
4
|
+
##
|
5
|
+
# The {LLM::Response::Audio LLM::Response::Audio} class represents an
|
6
|
+
# audio file that has been returned by a provider. It wraps an IO object
|
7
|
+
# that can be used to read the contents of an audio stream (as binary data).
|
8
|
+
class Response::Audio < Response
|
9
|
+
##
|
10
|
+
# @return [StringIO]
|
11
|
+
attr_accessor :audio
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LLM
|
4
|
+
##
|
5
|
+
# The {LLM::Response::AudioTranscription LLM::Response::AudioTranscription}
|
6
|
+
# class represents an audio transcription that has been returned by
|
7
|
+
# a provider (eg OpenAI, Gemini, etc)
|
8
|
+
class Response::AudioTranscription < Response
|
9
|
+
##
|
10
|
+
# Returns the text of the transcription
|
11
|
+
# @return [String]
|
12
|
+
attr_accessor :text
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LLM
|
4
|
+
##
|
5
|
+
# The {LLM::Response::AudioTranslation LLM::Response::AudioTranslation}
|
6
|
+
# class represents an audio translation that has been returned by
|
7
|
+
# a provider (eg OpenAI, Gemini, etc)
|
8
|
+
class Response::AudioTranslation < Response
|
9
|
+
##
|
10
|
+
# Returns the text of the translation
|
11
|
+
# @return [String]
|
12
|
+
attr_accessor :text
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LLM
|
4
|
+
##
|
5
|
+
# The {LLM::Response::DownloadFile LLM::Response::DownloadFile} class
|
6
|
+
# represents the contents of a file that has been returned by a
|
7
|
+
# provider. It wraps an IO object that can be used to read the file
|
8
|
+
# contents.
|
9
|
+
class Response::DownloadFile < Response
|
10
|
+
##
|
11
|
+
# Returns a StringIO object
|
12
|
+
# @return [StringIO]
|
13
|
+
attr_accessor :file
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LLM
|
4
|
+
##
|
5
|
+
# The {LLM::Response::File LLM::Response::File} class represents a file
|
6
|
+
# that has been uploaded to a provider. Its properties are delegated
|
7
|
+
# to the underlying response body, and vary by provider.
|
8
|
+
class Response::File < Response
|
9
|
+
##
|
10
|
+
# Returns a normalized response body
|
11
|
+
# @return [Hash]
|
12
|
+
def body
|
13
|
+
@_body ||= if super["file"]
|
14
|
+
super["file"].transform_keys { snakecase(_1) }
|
15
|
+
else
|
16
|
+
super.transform_keys { snakecase(_1) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# @return [String]
|
22
|
+
def inspect
|
23
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} body=#{body}>"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
include LLM::Utils
|
29
|
+
|
30
|
+
def respond_to_missing?(m, _)
|
31
|
+
body.key?(m.to_s) || super
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_missing(m, *args, &block)
|
35
|
+
if body.key?(m.to_s)
|
36
|
+
body[m.to_s]
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LLM
|
4
|
+
##
|
5
|
+
# The {LLM::Response::FileList LLM::Response::FileList} class represents a
|
6
|
+
# list of file objects that are returned by a provider. It is an Enumerable
|
7
|
+
# object, and can be used to iterate over the file objects in a way that is
|
8
|
+
# similar to an array. Each element is an instance of OpenStruct.
|
9
|
+
class Response::FileList < Response
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
attr_accessor :files
|
13
|
+
|
14
|
+
def each(&)
|
15
|
+
@files.each(&)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LLM
|
4
|
+
##
|
5
|
+
# The {LLM::Response::Image LLM::Response::Image} class represents
|
6
|
+
# an image response. An image response might encapsulate one or more
|
7
|
+
# URLs, or a base64 encoded image -- depending on the provider.
|
8
|
+
class Response::Image < Response
|
9
|
+
##
|
10
|
+
# Returns one or more image objects, or nil
|
11
|
+
# @return [Array<OpenStruct>, nil]
|
12
|
+
def images
|
13
|
+
parsed[:images].any? ? parsed[:images] : nil
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Returns one or more image URLs, or nil
|
18
|
+
# @return [Array<String>, nil]
|
19
|
+
def urls
|
20
|
+
parsed[:urls].any? ? parsed[:urls] : nil
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def parsed
|
26
|
+
@parsed ||= parse_image(body)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LLM
|
4
|
+
class Response::Output < Response
|
5
|
+
##
|
6
|
+
# @return [String]
|
7
|
+
# Returns the id of the response
|
8
|
+
def id
|
9
|
+
parsed[:id]
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# @return [String]
|
14
|
+
# Returns the model name
|
15
|
+
def model
|
16
|
+
parsed[:model]
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# @return [Array<LLM::Message>]
|
21
|
+
def outputs
|
22
|
+
parsed[:outputs]
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# @return [Integer]
|
27
|
+
# Returns the input token count
|
28
|
+
def input_tokens
|
29
|
+
parsed[:input_tokens]
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# @return [Integer]
|
34
|
+
# Returns the output token count
|
35
|
+
def output_tokens
|
36
|
+
parsed[:output_tokens]
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# @return [Integer]
|
41
|
+
# Returns the total count of tokens
|
42
|
+
def total_tokens
|
43
|
+
parsed[:total_tokens]
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
##
|
49
|
+
# @private
|
50
|
+
# @return [Hash]
|
51
|
+
# Returns the parsed response from the provider
|
52
|
+
def parsed
|
53
|
+
@parsed ||= parse_output_response(body)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/llm/response.rb
CHANGED
@@ -5,11 +5,14 @@ module LLM
|
|
5
5
|
require "json"
|
6
6
|
require_relative "response/completion"
|
7
7
|
require_relative "response/embedding"
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
require_relative "response/output"
|
9
|
+
require_relative "response/image"
|
10
|
+
require_relative "response/audio"
|
11
|
+
require_relative "response/audio_transcription"
|
12
|
+
require_relative "response/audio_translation"
|
13
|
+
require_relative "response/file"
|
14
|
+
require_relative "response/filelist"
|
15
|
+
require_relative "response/download_file"
|
13
16
|
|
14
17
|
##
|
15
18
|
# @param [Net::HTTPResponse] res
|
@@ -18,7 +21,16 @@ module LLM
|
|
18
21
|
# Returns an instance of LLM::Response
|
19
22
|
def initialize(res)
|
20
23
|
@res = res
|
21
|
-
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Returns the response body
|
28
|
+
# @return [Hash, String]
|
29
|
+
def body
|
30
|
+
@body ||= case @res["content-type"]
|
31
|
+
when %r|\Aapplication/json\s*| then JSON.parse(@res.body)
|
32
|
+
else @res.body
|
33
|
+
end
|
22
34
|
end
|
23
35
|
end
|
24
36
|
end
|
data/lib/llm/utils.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# @private
|
5
|
+
module LLM::Utils
|
6
|
+
def camelcase(key)
|
7
|
+
key.to_s
|
8
|
+
.split("_")
|
9
|
+
.map.with_index { (_2 > 0) ? _1.capitalize : _1 }
|
10
|
+
.join
|
11
|
+
end
|
12
|
+
|
13
|
+
def snakecase(key)
|
14
|
+
key
|
15
|
+
.split(/([A-Z])/)
|
16
|
+
.map { (_1.size == 1) ? "_#{_1.downcase}" : _1 }
|
17
|
+
.join
|
18
|
+
end
|
19
|
+
end
|
data/lib/llm/version.rb
CHANGED
data/lib/llm.rb
CHANGED
@@ -2,14 +2,17 @@
|
|
2
2
|
|
3
3
|
module LLM
|
4
4
|
require_relative "llm/version"
|
5
|
+
require_relative "llm/utils"
|
5
6
|
require_relative "llm/error"
|
6
7
|
require_relative "llm/message"
|
7
8
|
require_relative "llm/response"
|
9
|
+
require_relative "llm/mime"
|
10
|
+
require_relative "llm/multipart"
|
8
11
|
require_relative "llm/file"
|
9
12
|
require_relative "llm/model"
|
10
13
|
require_relative "llm/provider"
|
11
|
-
require_relative "llm/
|
12
|
-
require_relative "llm/
|
14
|
+
require_relative "llm/chat"
|
15
|
+
require_relative "llm/buffer"
|
13
16
|
require_relative "llm/core_ext/ostruct"
|
14
17
|
|
15
18
|
module_function
|
data/llm.gemspec
CHANGED
@@ -14,12 +14,11 @@ Gem::Specification.new do |spec|
|
|
14
14
|
"flexible, and easy to use."
|
15
15
|
spec.description = spec.summary
|
16
16
|
spec.homepage = "https://github.com/llmrb/llm"
|
17
|
-
spec.license = "
|
17
|
+
spec.license = "0BSDL"
|
18
18
|
spec.required_ruby_version = ">= 3.0.0"
|
19
19
|
|
20
20
|
spec.metadata["homepage_uri"] = spec.homepage
|
21
21
|
spec.metadata["source_code_uri"] = "https://github.com/llmrb/llm"
|
22
|
-
spec.metadata["changelog_uri"] = "https://github.com/llmrb/llm/blob/main/CHANGELOG.md"
|
23
22
|
|
24
23
|
spec.files = Dir[
|
25
24
|
"README.md", "LICENSE.txt",
|
@@ -29,10 +28,6 @@ Gem::Specification.new do |spec|
|
|
29
28
|
]
|
30
29
|
spec.require_paths = ["lib"]
|
31
30
|
|
32
|
-
spec.add_runtime_dependency "net-http", "~> 0.6.0"
|
33
|
-
spec.add_runtime_dependency "json"
|
34
|
-
spec.add_runtime_dependency "yaml"
|
35
|
-
|
36
31
|
spec.add_development_dependency "webmock", "~> 3.24.0"
|
37
32
|
spec.add_development_dependency "yard", "~> 0.9.37"
|
38
33
|
spec.add_development_dependency "kramdown", "~> 2.4"
|
@@ -41,11 +41,32 @@ RSpec.describe "LLM::Gemini: completions" do
|
|
41
41
|
end
|
42
42
|
|
43
43
|
it "includes the response" do
|
44
|
-
expect(choice.extra[:
|
44
|
+
expect(choice.extra[:response]).to eq(response)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
+
context "when given a thread of messages",
|
50
|
+
vcr: {cassette_name: "gemini/completions/successful_response_thread"} do
|
51
|
+
subject(:response) do
|
52
|
+
gemini.complete "What is your name? What age are you?", :user, messages: [
|
53
|
+
{role: "user", content: "Answer all of my questions"},
|
54
|
+
{role: "user", content: "Your name is Pablo, you are 25 years old and you are my amigo"}
|
55
|
+
]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "has choices" do
|
59
|
+
expect(response).to have_attributes(
|
60
|
+
choices: [
|
61
|
+
have_attributes(
|
62
|
+
role: "model",
|
63
|
+
content: "My name is Pablo, and I am 25 years old. ¡Amigo!\n"
|
64
|
+
)
|
65
|
+
]
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
49
70
|
context "when given an unauthorized response",
|
50
71
|
vcr: {cassette_name: "gemini/completions/unauthorized_response"} do
|
51
72
|
subject(:response) { gemini.complete("Hello!", :user) }
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "setup"
|
4
|
+
|
5
|
+
RSpec.describe "LLM::Chat: gemini" do
|
6
|
+
let(:described_class) { LLM::Chat }
|
7
|
+
let(:provider) { LLM.gemini(token) }
|
8
|
+
let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
|
9
|
+
let(:conversation) { described_class.new(provider, **params).lazy }
|
10
|
+
|
11
|
+
context "when asked to describe an image",
|
12
|
+
vcr: {cassette_name: "gemini/conversations/multimodal_response"} do
|
13
|
+
subject { conversation.last_message }
|
14
|
+
|
15
|
+
let(:params) { {} }
|
16
|
+
let(:image) { LLM::File("spec/fixtures/images/bluebook.png") }
|
17
|
+
|
18
|
+
before do
|
19
|
+
conversation.chat(image, :user)
|
20
|
+
conversation.chat("Describe the image with a short sentance", :user)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "describes the image" do
|
24
|
+
is_expected.to have_attributes(
|
25
|
+
role: "model",
|
26
|
+
content: "That's a simple illustration of a book " \
|
27
|
+
"resting on a blue, X-shaped book stand.\n"
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "setup"
|
4
|
+
|
5
|
+
RSpec.describe "LLM::Gemini::Files" do
|
6
|
+
let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
|
7
|
+
let(:provider) { LLM.gemini(token) }
|
8
|
+
|
9
|
+
context "when given a successful create operation (bismillah.mp3)",
|
10
|
+
vcr: {cassette_name: "gemini/files/successful_create_bismillah"} do
|
11
|
+
subject(:file) { provider.files.create(file: LLM::File("spec/fixtures/audio/bismillah.mp3")) }
|
12
|
+
after { provider.files.delete(file:) }
|
13
|
+
|
14
|
+
it "is successful" do
|
15
|
+
expect(file).to be_instance_of(LLM::Response::File)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "returns a file object" do
|
19
|
+
expect(file).to have_attributes(
|
20
|
+
name: instance_of(String),
|
21
|
+
display_name: "bismillah.mp3"
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when given a successful delete operation (bismillah.mp3)",
|
27
|
+
vcr: {cassette_name: "gemini/files/successful_delete_bismillah"} do
|
28
|
+
let(:file) { provider.files.create(file: LLM::File("spec/fixtures/audio/bismillah.mp3")) }
|
29
|
+
subject { provider.files.delete(file:) }
|
30
|
+
|
31
|
+
it "is successful" do
|
32
|
+
is_expected.to be_instance_of(Net::HTTPOK)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when given a successful get operation (bismillah.mp3)",
|
37
|
+
vcr: {cassette_name: "gemini/files/successful_get_bismillah"} do
|
38
|
+
let(:file) { provider.files.create(file: LLM::File("spec/fixtures/audio/bismillah.mp3")) }
|
39
|
+
subject { provider.files.get(file:) }
|
40
|
+
after { provider.files.delete(file:) }
|
41
|
+
|
42
|
+
it "is successful" do
|
43
|
+
is_expected.to be_instance_of(LLM::Response::File)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns a file object" do
|
47
|
+
is_expected.to have_attributes(
|
48
|
+
name: instance_of(String),
|
49
|
+
display_name: "bismillah.mp3"
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when given a successful translation operation (bismillah.mp3)",
|
55
|
+
vcr: {cassette_name: "gemini/files/successful_translation_bismillah"} do
|
56
|
+
subject { bot.last_message.content }
|
57
|
+
let(:file) { provider.files.create(file: LLM::File("spec/fixtures/audio/bismillah.mp3")) }
|
58
|
+
let(:bot) { LLM::Chat.new(provider).lazy }
|
59
|
+
after { provider.files.delete(file:) }
|
60
|
+
|
61
|
+
before do
|
62
|
+
bot.chat file
|
63
|
+
bot.chat "Translate the contents of the audio file into English"
|
64
|
+
bot.chat "The audio is referenced in the first message I sent to you"
|
65
|
+
bot.chat "Provide no other content except the translation"
|
66
|
+
end
|
67
|
+
|
68
|
+
it "translates the audio clip" do
|
69
|
+
is_expected.to eq("In the name of God, the Most Gracious, the Most Merciful.\n")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "when given a successful translation operation (alhamdullilah.mp3)",
|
74
|
+
vcr: {cassette_name: "gemini/files/successful_translation_alhamdullilah"} do
|
75
|
+
subject { bot.last_message.content }
|
76
|
+
let(:file) { provider.files.create(file: LLM::File("spec/fixtures/audio/alhamdullilah.mp3")) }
|
77
|
+
let(:bot) { LLM::Chat.new(provider).lazy }
|
78
|
+
after { provider.files.delete(file:) }
|
79
|
+
|
80
|
+
before do
|
81
|
+
bot.chat [
|
82
|
+
"Translate the contents of the audio file into English",
|
83
|
+
"Provide no other content except the translation",
|
84
|
+
file
|
85
|
+
]
|
86
|
+
end
|
87
|
+
|
88
|
+
it "translates the audio clip" do
|
89
|
+
is_expected.to eq("All praise is due to Allah, Lord of the Worlds.\n")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "when given a successful all operation",
|
94
|
+
vcr: {cassette_name: "gemini/files/successful_all"} do
|
95
|
+
let!(:files) do
|
96
|
+
[
|
97
|
+
provider.files.create(file: LLM::File("spec/fixtures/audio/bismillah.mp3")),
|
98
|
+
provider.files.create(file: LLM::File("spec/fixtures/audio/alhamdullilah.mp3"))
|
99
|
+
]
|
100
|
+
end
|
101
|
+
|
102
|
+
subject(:response) { provider.files.all }
|
103
|
+
after { files.each { |file| provider.files.delete(file:) } }
|
104
|
+
|
105
|
+
it "is successful" do
|
106
|
+
expect(response).to be_instance_of(LLM::Response::FileList)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "returns an array of file objects" do
|
110
|
+
expect(response).to match_array(
|
111
|
+
[
|
112
|
+
have_attributes(
|
113
|
+
name: instance_of(String),
|
114
|
+
display_name: "bismillah.mp3"
|
115
|
+
),
|
116
|
+
have_attributes(
|
117
|
+
name: instance_of(String),
|
118
|
+
display_name: "alhamdullilah.mp3"
|
119
|
+
)
|
120
|
+
]
|
121
|
+
)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "setup"
|
4
|
+
|
5
|
+
RSpec.describe "LLM::Gemini::Images" do
|
6
|
+
let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
|
7
|
+
let(:provider) { LLM.gemini(token) }
|
8
|
+
|
9
|
+
context "when given a successful create operation",
|
10
|
+
vcr: {cassette_name: "gemini/images/successful_create"} do
|
11
|
+
subject(:response) { provider.images.create(prompt: "A dog on a rocket to the moon") }
|
12
|
+
|
13
|
+
it "is successful" do
|
14
|
+
expect(response).to be_instance_of(LLM::Response::Image)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "returns an encoded string" do
|
18
|
+
expect(response.images[0].encoded).to be_instance_of(String)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns a binary string" do
|
22
|
+
expect(response.images[0].binary).to be_instance_of(String)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when given a successful edit operation",
|
27
|
+
vcr: {cassette_name: "gemini/images/successful_edit"} do
|
28
|
+
subject(:response) do
|
29
|
+
provider.images.edit(
|
30
|
+
image: LLM::File("spec/fixtures/images/bluebook.png"),
|
31
|
+
prompt: "Book is floating in the clouds"
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "is successful" do
|
36
|
+
expect(response).to be_instance_of(LLM::Response::Image)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns data" do
|
40
|
+
expect(response.images[0].encoded).to be_instance_of(String)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns a url" do
|
44
|
+
expect(response.images[0].binary).to be_instance_of(String)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|