llm.rb 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +17 -0
- data/README.md +3 -5
- data/lib/json/schema/array.rb +1 -2
- data/lib/json/schema/leaf.rb +27 -0
- data/lib/json/schema/version.rb +6 -0
- data/lib/json/schema.rb +24 -13
- data/lib/llm/version.rb +1 -1
- data/llm.gemspec +2 -3
- metadata +4 -25
- data/spec/anthropic/completion_spec.rb +0 -96
- data/spec/anthropic/embedding_spec.rb +0 -25
- data/spec/anthropic/models_spec.rb +0 -21
- data/spec/gemini/completion_spec.rb +0 -85
- data/spec/gemini/conversation_spec.rb +0 -31
- data/spec/gemini/embedding_spec.rb +0 -25
- data/spec/gemini/files_spec.rb +0 -124
- data/spec/gemini/images_spec.rb +0 -39
- data/spec/gemini/models_spec.rb +0 -21
- data/spec/llm/conversation_spec.rb +0 -261
- data/spec/ollama/completion_spec.rb +0 -43
- data/spec/ollama/conversation_spec.rb +0 -31
- data/spec/ollama/embedding_spec.rb +0 -24
- data/spec/ollama/models_spec.rb +0 -20
- data/spec/openai/audio_spec.rb +0 -55
- data/spec/openai/completion_spec.rb +0 -116
- data/spec/openai/embedding_spec.rb +0 -25
- data/spec/openai/files_spec.rb +0 -204
- data/spec/openai/images_spec.rb +0 -91
- data/spec/openai/models_spec.rb +0 -21
- data/spec/openai/responses_spec.rb +0 -51
- data/spec/readme_spec.rb +0 -61
- data/spec/setup.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ae98573410bfc74a61f0bf15f811c30800b6567c9e5fc9e4cbe50f4994a200f
|
4
|
+
data.tar.gz: 9776839912017f5f3bac8452670c60b3bc9eabecebe11c402458a36b78d958e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4252d2d861d409428067415340c2d36f2e75f34f3be9905a576a63455902d1faf4100d8c8c7060e683fe922ff095cc00fd56fd135b9589a746d9de716c66e5e5
|
7
|
+
data.tar.gz: 819676961e401aa5e27678e3d36af406362f0a68b0243e1e1f71f5f135286830dd1c009c47c69c26b6789a771da6a1fe72eb023f0f1833c20ffadac37f58ed1b
|
data/LICENSE
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Copyright (C) 2025
|
2
|
+
Antar Azri <azantar@proton.me>
|
3
|
+
0x1eef <0x1eef@proton.me>
|
4
|
+
|
5
|
+
Permission to use, copy, modify, and/or distribute this
|
6
|
+
software for any purpose with or without fee is hereby
|
7
|
+
granted.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS
|
10
|
+
ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
11
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
|
12
|
+
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
13
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
14
|
+
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
15
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
16
|
+
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
17
|
+
OF THIS SOFTWARE.
|
data/README.md
CHANGED
@@ -133,7 +133,7 @@ The interface is designed so you could drop in any other library in its place:
|
|
133
133
|
require "llm"
|
134
134
|
|
135
135
|
llm = LLM.openai(ENV["KEY"])
|
136
|
-
schema = llm.schema.object({os: llm.schema.string.enum("OpenBSD", "FreeBSD", "NetBSD")
|
136
|
+
schema = llm.schema.object({os: llm.schema.string.enum("OpenBSD", "FreeBSD", "NetBSD")})
|
137
137
|
bot = LLM::Chat.new(llm, schema:)
|
138
138
|
bot.chat "You secretly love NetBSD", :system
|
139
139
|
bot.chat "What operating system is the best?", :user
|
@@ -364,7 +364,7 @@ bot.messages.select(&:assistant?).each { print "[#{_1.role}] ", _1.content, "\n"
|
|
364
364
|
Generally all providers accept text prompts but some providers can
|
365
365
|
also understand URLs, and various file types (eg images, audio, video,
|
366
366
|
etc). The llm.rb approach to multimodal prompts is to let you pass `URI`
|
367
|
-
objects to describe links, `LLM::File`
|
367
|
+
objects to describe links, `LLM::File` | `LLM::Response::File` objects
|
368
368
|
to describe files, `String` objects to describe text blobs, or an array
|
369
369
|
of the aforementioned objects to describe multiple objects in a single
|
370
370
|
prompt. Each object is a first class citizen that can be passed directly
|
@@ -372,9 +372,7 @@ to a prompt.
|
|
372
372
|
|
373
373
|
For more depth and examples on how to use the multimodal API, please see
|
374
374
|
the [provider-specific documentation](https://0x1eef.github.io/x/llm.rb/)
|
375
|
-
for more provider-specific examples
|
376
|
-
between providers and even between APIs from the same provider that are
|
377
|
-
not covered in the README:
|
375
|
+
for more provider-specific examples:
|
378
376
|
|
379
377
|
```ruby
|
380
378
|
#!/usr/bin/env ruby
|
data/lib/json/schema/array.rb
CHANGED
data/lib/json/schema/leaf.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class JSON::Schema
|
4
|
+
##
|
5
|
+
# The {JSON::Schema::Leaf JSON::Schema::Leaf} class is the
|
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}
|
9
|
+
# through its subclasses.
|
4
10
|
class Leaf
|
5
11
|
def initialize
|
6
12
|
@description = nil
|
@@ -9,30 +15,51 @@ class JSON::Schema
|
|
9
15
|
@required = nil
|
10
16
|
end
|
11
17
|
|
18
|
+
##
|
19
|
+
# Set the description of a leaf
|
20
|
+
# @param [String] str The description
|
21
|
+
# @return [JSON::Schema::Leaf]
|
12
22
|
def description(str)
|
13
23
|
tap { @description = str }
|
14
24
|
end
|
15
25
|
|
26
|
+
##
|
27
|
+
# Set the default value of a leaf
|
28
|
+
# @param [Object] value The default value
|
29
|
+
# @return [JSON::Schema::Leaf]
|
16
30
|
def default(value)
|
17
31
|
tap { @default = value }
|
18
32
|
end
|
19
33
|
|
34
|
+
##
|
35
|
+
# Set the allowed values of a leaf
|
36
|
+
# @param [Array] values The allowed values
|
37
|
+
# @return [JSON::Schema::Leaf]
|
20
38
|
def enum(*values)
|
21
39
|
tap { @enum = values }
|
22
40
|
end
|
23
41
|
|
42
|
+
##
|
43
|
+
# Denote a leaf as required
|
44
|
+
# @return [JSON::Schema::Leaf]
|
24
45
|
def required
|
25
46
|
tap { @required = true }
|
26
47
|
end
|
27
48
|
|
49
|
+
##
|
50
|
+
# @return [Hash]
|
28
51
|
def to_h
|
29
52
|
{description: @description, default: @default, enum: @enum}.compact
|
30
53
|
end
|
31
54
|
|
55
|
+
##
|
56
|
+
# @return [String]
|
32
57
|
def to_json(options = {})
|
33
58
|
to_h.to_json(options)
|
34
59
|
end
|
35
60
|
|
61
|
+
##
|
62
|
+
# @return [Boolean]
|
36
63
|
def required?
|
37
64
|
@required
|
38
65
|
end
|
data/lib/json/schema.rb
CHANGED
@@ -3,7 +3,25 @@
|
|
3
3
|
module JSON
|
4
4
|
end unless defined?(JSON)
|
5
5
|
|
6
|
+
##
|
7
|
+
# The {JSON::Schema JSON::Schema} class represents a JSON schema,
|
8
|
+
# and provides methods that let you describe and produce a schema
|
9
|
+
# that can be used in various contexts that include the validation
|
10
|
+
# and generation of JSON data.
|
11
|
+
#
|
12
|
+
# @see https://json-schema.org/ JSON Schema Specification
|
13
|
+
# @see https://tour.json-schema.org/ JSON Schema Tour
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# schema = JSON::Schema.new
|
17
|
+
# schema.object({
|
18
|
+
# name: schema.string.enum("John", "Jane").required,
|
19
|
+
# age: schema.integer.required,
|
20
|
+
# hobbies: schema.array(schema.string, schema.null).required,
|
21
|
+
# address: schema.object({street: schema.string}).required,
|
22
|
+
# })
|
6
23
|
class JSON::Schema
|
24
|
+
require_relative "schema/version"
|
7
25
|
require_relative "schema/leaf"
|
8
26
|
require_relative "schema/object"
|
9
27
|
require_relative "schema/array"
|
@@ -15,25 +33,22 @@ class JSON::Schema
|
|
15
33
|
|
16
34
|
##
|
17
35
|
# Returns an object
|
18
|
-
# @param
|
19
|
-
# @param rest [Hash] Any other options
|
36
|
+
# @param [Hash] properties A hash of properties
|
20
37
|
# @return [JSON::Schema::Object]
|
21
|
-
def object(properties
|
22
|
-
Object.new(properties
|
38
|
+
def object(properties)
|
39
|
+
Object.new(properties)
|
23
40
|
end
|
24
41
|
|
25
42
|
##
|
26
43
|
# Returns an array
|
27
|
-
# @param
|
28
|
-
# @param rest [Hash] Any other options
|
44
|
+
# @param [Array] items An array of items
|
29
45
|
# @return [JSON::Schema::Array]
|
30
|
-
def array(items
|
31
|
-
Array.new(items
|
46
|
+
def array(*items)
|
47
|
+
Array.new(*items)
|
32
48
|
end
|
33
49
|
|
34
50
|
##
|
35
51
|
# Returns a string
|
36
|
-
# @param rest [Hash] Any other options
|
37
52
|
# @return [JSON::Schema::String]
|
38
53
|
def string(...)
|
39
54
|
String.new(...)
|
@@ -41,7 +56,6 @@ class JSON::Schema
|
|
41
56
|
|
42
57
|
##
|
43
58
|
# Returns a number
|
44
|
-
# @param rest [Hash] Any other options
|
45
59
|
# @return [JSON::Schema::Number] a number
|
46
60
|
def number(...)
|
47
61
|
Number.new(...)
|
@@ -49,7 +63,6 @@ class JSON::Schema
|
|
49
63
|
|
50
64
|
##
|
51
65
|
# Returns an integer
|
52
|
-
# @param rest [Hash] Any other options
|
53
66
|
# @return [JSON::Schema::Integer]
|
54
67
|
def integer(...)
|
55
68
|
Integer.new(...)
|
@@ -57,7 +70,6 @@ class JSON::Schema
|
|
57
70
|
|
58
71
|
##
|
59
72
|
# Returns a boolean
|
60
|
-
# @param rest [Hash] Any other options
|
61
73
|
# @return [JSON::Schema::Boolean]
|
62
74
|
def boolean(...)
|
63
75
|
Boolean.new(...)
|
@@ -65,7 +77,6 @@ class JSON::Schema
|
|
65
77
|
|
66
78
|
##
|
67
79
|
# Returns null
|
68
|
-
# @param rest [Hash] Any other options
|
69
80
|
# @return [JSON::Schema::Null]
|
70
81
|
def null(...)
|
71
82
|
Null.new(...)
|
data/lib/llm/version.rb
CHANGED
data/llm.gemspec
CHANGED
@@ -21,10 +21,9 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.metadata["source_code_uri"] = "https://github.com/llmrb/llm"
|
22
22
|
|
23
23
|
spec.files = Dir[
|
24
|
-
"README.md", "LICENSE
|
24
|
+
"README.md", "LICENSE",
|
25
25
|
"lib/*.rb", "lib/**/*.rb",
|
26
|
-
"
|
27
|
-
"share/llm/models/*.yml", "llm.gemspec"
|
26
|
+
"llm.gemspec"
|
28
27
|
]
|
29
28
|
spec.require_paths = ["lib"]
|
30
29
|
|
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.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Antar Azri
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2025-04-
|
12
|
+
date: 2025-04-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: webmock
|
@@ -147,6 +147,7 @@ executables: []
|
|
147
147
|
extensions: []
|
148
148
|
extra_rdoc_files: []
|
149
149
|
files:
|
150
|
+
- LICENSE
|
150
151
|
- README.md
|
151
152
|
- lib/json/schema.rb
|
152
153
|
- lib/json/schema/array.rb
|
@@ -157,6 +158,7 @@ files:
|
|
157
158
|
- lib/json/schema/number.rb
|
158
159
|
- lib/json/schema/object.rb
|
159
160
|
- lib/json/schema/string.rb
|
161
|
+
- lib/json/schema/version.rb
|
160
162
|
- lib/llm.rb
|
161
163
|
- lib/llm/buffer.rb
|
162
164
|
- lib/llm/chat.rb
|
@@ -213,29 +215,6 @@ files:
|
|
213
215
|
- lib/llm/utils.rb
|
214
216
|
- lib/llm/version.rb
|
215
217
|
- llm.gemspec
|
216
|
-
- spec/anthropic/completion_spec.rb
|
217
|
-
- spec/anthropic/embedding_spec.rb
|
218
|
-
- spec/anthropic/models_spec.rb
|
219
|
-
- spec/gemini/completion_spec.rb
|
220
|
-
- spec/gemini/conversation_spec.rb
|
221
|
-
- spec/gemini/embedding_spec.rb
|
222
|
-
- spec/gemini/files_spec.rb
|
223
|
-
- spec/gemini/images_spec.rb
|
224
|
-
- spec/gemini/models_spec.rb
|
225
|
-
- spec/llm/conversation_spec.rb
|
226
|
-
- spec/ollama/completion_spec.rb
|
227
|
-
- spec/ollama/conversation_spec.rb
|
228
|
-
- spec/ollama/embedding_spec.rb
|
229
|
-
- spec/ollama/models_spec.rb
|
230
|
-
- spec/openai/audio_spec.rb
|
231
|
-
- spec/openai/completion_spec.rb
|
232
|
-
- spec/openai/embedding_spec.rb
|
233
|
-
- spec/openai/files_spec.rb
|
234
|
-
- spec/openai/images_spec.rb
|
235
|
-
- spec/openai/models_spec.rb
|
236
|
-
- spec/openai/responses_spec.rb
|
237
|
-
- spec/readme_spec.rb
|
238
|
-
- spec/setup.rb
|
239
218
|
homepage: https://github.com/llmrb/llm
|
240
219
|
licenses:
|
241
220
|
- 0BSDL
|
@@ -1,96 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "setup"
|
4
|
-
|
5
|
-
RSpec.describe "LLM::Anthropic: completions" do
|
6
|
-
subject(:anthropic) { LLM.anthropic(token) }
|
7
|
-
let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
|
8
|
-
|
9
|
-
context "when given a successful response",
|
10
|
-
vcr: {cassette_name: "anthropic/completions/successful_response"} do
|
11
|
-
subject(:response) { anthropic.complete("Hello, world", :user) }
|
12
|
-
|
13
|
-
it "returns a completion" do
|
14
|
-
expect(response).to be_a(LLM::Response::Completion)
|
15
|
-
end
|
16
|
-
|
17
|
-
it "returns a model" do
|
18
|
-
expect(response.model).to eq("claude-3-5-sonnet-20240620")
|
19
|
-
end
|
20
|
-
|
21
|
-
it "includes token usage" do
|
22
|
-
expect(response).to have_attributes(
|
23
|
-
prompt_tokens: 10,
|
24
|
-
completion_tokens: 30,
|
25
|
-
total_tokens: 40
|
26
|
-
)
|
27
|
-
end
|
28
|
-
|
29
|
-
context "with a choice" do
|
30
|
-
subject(:choice) { response.choices[0] }
|
31
|
-
|
32
|
-
it "has choices" do
|
33
|
-
expect(choice).to have_attributes(
|
34
|
-
role: "assistant",
|
35
|
-
content: "Hello! How can I assist you today? Feel free to ask me any questions or let me know if you need help with anything."
|
36
|
-
)
|
37
|
-
end
|
38
|
-
|
39
|
-
it "includes the response" do
|
40
|
-
expect(choice.extra[:response]).to eq(response)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
context "when given a URI to an image",
|
46
|
-
vcr: {cassette_name: "anthropic/completions/successful_response_uri_image"} do
|
47
|
-
subject { response.choices[0].content.downcase[0..2] }
|
48
|
-
let(:response) do
|
49
|
-
anthropic.complete([
|
50
|
-
"Is this image the flag of brazil ? ",
|
51
|
-
"Answer with yes or no. ",
|
52
|
-
"Nothing else.",
|
53
|
-
uri
|
54
|
-
], :user)
|
55
|
-
end
|
56
|
-
let(:uri) { URI("https://upload.wikimedia.org/wikipedia/en/thumb/0/05/Flag_of_Brazil.svg/250px-Flag_of_Brazil.svg.png") }
|
57
|
-
|
58
|
-
it "describes the image" do
|
59
|
-
is_expected.to eq("yes")
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
context "when given a local reference to an image",
|
64
|
-
vcr: {cassette_name: "anthropic/completions/successful_response_file_image"} do
|
65
|
-
subject { response.choices[0].content.downcase[0..2] }
|
66
|
-
let(:response) do
|
67
|
-
anthropic.complete([
|
68
|
-
"Is this image a representation of a blue book ?",
|
69
|
-
"Answer with yes or no.",
|
70
|
-
"Nothing else.",
|
71
|
-
file
|
72
|
-
], :user)
|
73
|
-
end
|
74
|
-
let(:file) { LLM::File("spec/fixtures/images/bluebook.png") }
|
75
|
-
|
76
|
-
it "describes the image" do
|
77
|
-
is_expected.to eq("yes")
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
context "when given an unauthorized response",
|
82
|
-
vcr: {cassette_name: "anthropic/completions/unauthorized_response"} do
|
83
|
-
subject(:response) { anthropic.complete("Hello", :user) }
|
84
|
-
let(:token) { "BADTOKEN" }
|
85
|
-
|
86
|
-
it "raises an error" do
|
87
|
-
expect { response }.to raise_error(LLM::Error::Unauthorized)
|
88
|
-
end
|
89
|
-
|
90
|
-
it "includes the response" do
|
91
|
-
response
|
92
|
-
rescue LLM::Error::Unauthorized => ex
|
93
|
-
expect(ex.response).to be_kind_of(Net::HTTPResponse)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "setup"
|
4
|
-
|
5
|
-
RSpec.describe "LLM::Anthropic: embeddings" do
|
6
|
-
let(:anthropic) { LLM.anthropic(token) }
|
7
|
-
let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
|
8
|
-
|
9
|
-
context "when given a successful response",
|
10
|
-
vcr: {cassette_name: "anthropic/embeddings/successful_response"} do
|
11
|
-
subject(:response) { anthropic.embed("Hello, world", token:) }
|
12
|
-
|
13
|
-
it "returns an embedding" do
|
14
|
-
expect(response).to be_instance_of(LLM::Response::Embedding)
|
15
|
-
end
|
16
|
-
|
17
|
-
it "returns a model" do
|
18
|
-
expect(response.model).to eq("voyage-2")
|
19
|
-
end
|
20
|
-
|
21
|
-
it "has embeddings" do
|
22
|
-
expect(response.embeddings).to be_instance_of(Array)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "setup"
|
4
|
-
|
5
|
-
RSpec.describe "LLM::Anthropic::Models" do
|
6
|
-
let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
|
7
|
-
let(:provider) { LLM.anthropic(token) }
|
8
|
-
|
9
|
-
context "when given a successful list operation",
|
10
|
-
vcr: {cassette_name: "anthropic/models/successful_list"} do
|
11
|
-
subject { provider.models.all }
|
12
|
-
|
13
|
-
it "is successful" do
|
14
|
-
is_expected.to be_instance_of(LLM::Response::ModelList)
|
15
|
-
end
|
16
|
-
|
17
|
-
it "returns a list of models" do
|
18
|
-
expect(subject.models).to all(be_a(LLM::Model))
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,85 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "setup"
|
4
|
-
|
5
|
-
RSpec.describe "LLM::Gemini: completions" do
|
6
|
-
subject(:gemini) { LLM.gemini(token) }
|
7
|
-
let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
|
8
|
-
|
9
|
-
context "when given a successful response",
|
10
|
-
vcr: {cassette_name: "gemini/completions/successful_response"} do
|
11
|
-
subject(:response) { gemini.complete("Hello!", :user) }
|
12
|
-
|
13
|
-
it "returns a completion" do
|
14
|
-
expect(response).to be_a(LLM::Response::Completion)
|
15
|
-
end
|
16
|
-
|
17
|
-
it "returns a model" do
|
18
|
-
expect(response.model).to eq("gemini-1.5-flash")
|
19
|
-
end
|
20
|
-
|
21
|
-
it "includes token usage" do
|
22
|
-
expect(response).to have_attributes(
|
23
|
-
prompt_tokens: 2,
|
24
|
-
completion_tokens: 11,
|
25
|
-
total_tokens: 13
|
26
|
-
)
|
27
|
-
end
|
28
|
-
|
29
|
-
context "with a choice" do
|
30
|
-
subject(:choice) { response.choices[0] }
|
31
|
-
|
32
|
-
it "has choices" do
|
33
|
-
expect(response).to be_a(LLM::Response::Completion).and have_attributes(
|
34
|
-
choices: [
|
35
|
-
have_attributes(
|
36
|
-
role: "model",
|
37
|
-
content: "Hello there! How can I help you today?\n"
|
38
|
-
)
|
39
|
-
]
|
40
|
-
)
|
41
|
-
end
|
42
|
-
|
43
|
-
it "includes the response" do
|
44
|
-
expect(choice.extra[:response]).to eq(response)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
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
|
-
|
70
|
-
context "when given an unauthorized response",
|
71
|
-
vcr: {cassette_name: "gemini/completions/unauthorized_response"} do
|
72
|
-
subject(:response) { gemini.complete("Hello!", :user) }
|
73
|
-
let(:token) { "BADTOKEN" }
|
74
|
-
|
75
|
-
it "raises an error" do
|
76
|
-
expect { response }.to raise_error(LLM::Error::Unauthorized)
|
77
|
-
end
|
78
|
-
|
79
|
-
it "includes a response" do
|
80
|
-
response
|
81
|
-
rescue LLM::Error::Unauthorized => ex
|
82
|
-
expect(ex.response).to be_kind_of(Net::HTTPResponse)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
@@ -1,31 +0,0 @@
|
|
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
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "setup"
|
4
|
-
|
5
|
-
RSpec.describe "LLM::OpenAI: embeddings" do
|
6
|
-
let(:gemini) { LLM.gemini(token) }
|
7
|
-
let(:token) { ENV["LLM_SECRET"] || "TOKEN" }
|
8
|
-
|
9
|
-
context "when given a successful response",
|
10
|
-
vcr: {cassette_name: "gemini/embeddings/successful_response"} do
|
11
|
-
subject(:response) { gemini.embed("Hello, world") }
|
12
|
-
|
13
|
-
it "returns an embedding" do
|
14
|
-
expect(response).to be_instance_of(LLM::Response::Embedding)
|
15
|
-
end
|
16
|
-
|
17
|
-
it "returns a model" do
|
18
|
-
expect(response.model).to eq("text-embedding-004")
|
19
|
-
end
|
20
|
-
|
21
|
-
it "has embeddings" do
|
22
|
-
expect(response.embeddings).to be_instance_of(Array)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|