ollama-ruby 1.21.0 → 1.22.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/CHANGES.md +39 -0
- data/README.md +31 -0
- data/lib/ollama/client/command.rb +8 -4
- data/lib/ollama/client.rb +76 -0
- data/lib/ollama/commands/blob_exists.rb +73 -0
- data/lib/ollama/commands/push_blob.rb +74 -0
- data/lib/ollama/digester.rb +53 -0
- data/lib/ollama/handlers/concern.rb +4 -10
- data/lib/ollama/handlers/progress.rb +3 -2
- data/lib/ollama/version.rb +1 -1
- data/lib/ollama.rb +3 -0
- data/ollama-ruby.gemspec +7 -7
- data/spec/ollama/client_spec.rb +94 -0
- data/spec/ollama/commands/blob_exists_spec.rb +49 -0
- data/spec/ollama/commands/push_blob_spec.rb +42 -0
- data/spec/ollama/commands/show_spec.rb +1 -1
- data/spec/ollama/digester_spec.rb +62 -0
- metadata +16 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e4e3eda370660a57c7382a3548702359bdb3ea37afc2c785a26fc0ef1f199cd7
|
|
4
|
+
data.tar.gz: 77f392bc3c35948514cd78543560f3057b7b921487abd5803cad6f6f1cea9ac7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c7633fbcb2ed35181d8ffbdf129f4e151f1328ade92df8279a7f62ced065e3abfd05f0c5c93ac06871dc7421b5567d9c2b963a41a97ddd4e8fe51e8bd7ca7aeb
|
|
7
|
+
data.tar.gz: 93bdea4a001a9fb416100b6e5230c8a11d5524fe036b6216ccecc58e49584a703a1249e24510f1d66871866bcdd6483b703a4f47d0386253b0c5c3db613837d7
|
data/CHANGES.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-06-21 v1.22.0
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- Added support for blob uploads and management:
|
|
8
|
+
- Implemented `Ollama::Client#upload_file` using `Excon` `:request_block`
|
|
9
|
+
for memory-efficient streaming.
|
|
10
|
+
- Introduced `Ollama::Commands::PushBlob` for binary data uploads with
|
|
11
|
+
automatic SHA256 calculation.
|
|
12
|
+
- Added `Ollama::Client#blob_exists?` and `Ollama::Commands::BlobExists` to
|
|
13
|
+
verify blobs via the API.
|
|
14
|
+
- Introduced `Ollama::Digester` for SHA256 binary hashing, featuring
|
|
15
|
+
`compute_digest` for IO streams and a `prefix_sha256` utility for conditional
|
|
16
|
+
digest formatting.
|
|
17
|
+
- Added visual progress tracking to digest computation using `infobar`, IEC
|
|
18
|
+
units, and braille spinner frames.
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
|
|
22
|
+
- Added a "Importing Custom Models" section to the `README.md`, documenting the
|
|
23
|
+
workflow using `push_blob` and `create` with Ruby **3** shorthand syntax
|
|
24
|
+
examples.
|
|
25
|
+
- Enhanced YARD documentation for `prefix_sha256` and internal command options.
|
|
26
|
+
|
|
27
|
+
### Improvements & Refactors
|
|
28
|
+
|
|
29
|
+
- Mapped network exceptions (`Excon::Errors::SocketError`,
|
|
30
|
+
`Excon::Errors::Timeout`, and `Excon::Error`) to internal `Ollama::Errors`.
|
|
31
|
+
- Updated `Ollama::Handlers::Concern` to change `result` from an `attr_reader`
|
|
32
|
+
to an `attr_accessor`.
|
|
33
|
+
- Added a `skip_doc` option to `Ollama::Client::Command.command` to suppress
|
|
34
|
+
automatic documentation for internal commands.
|
|
35
|
+
- Fixed formatting in `show_spec.rb`.
|
|
36
|
+
|
|
37
|
+
### Dependencies
|
|
38
|
+
|
|
39
|
+
- Updated `rubygems_version` in the gemspec to **4.0.10**.
|
|
40
|
+
- Bumped minimum `gem_hadar` development dependency to **2.17.1**.
|
|
41
|
+
|
|
3
42
|
## 2026-03-20 v1.21.0
|
|
4
43
|
|
|
5
44
|
- Added a template accessor to Ollama::Commands::Create, exposing a new
|
data/README.md
CHANGED
|
@@ -69,6 +69,37 @@ messages = Message.new(role: 'user', content: 'Why is the sky blue?')
|
|
|
69
69
|
ollama.chat(model: 'llama3.1', stream: true, messages:, &Print)
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
### Importing Custom Models
|
|
73
|
+
|
|
74
|
+
You can import your own model files (e.g., `.gguf`) directly into Ollama. This
|
|
75
|
+
is a two-step process: first, upload the binary file to obtain a SHA256 digest,
|
|
76
|
+
and then create the model using that digest as a reference.
|
|
77
|
+
|
|
78
|
+
This workflow is particularly convenient in the `ollama_console`:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
# 1. Upload the binary blob to get its digest
|
|
82
|
+
# This will show a progress bar for both hashing and uploading
|
|
83
|
+
digest = push_blob(body: File.new('my-model-q4_0.gguf', 'rb'))
|
|
84
|
+
|
|
85
|
+
# 2. Define the model configuration
|
|
86
|
+
template = "<|system|>\n{{ .System }}</s>\n<|user|>\n{{ .Prompt }}</s>\n<|assistant|>\n"
|
|
87
|
+
system = "You are a helpful AI assistant."
|
|
88
|
+
parameters = { stop: ["<|system|>", "<|user|>", "<|assistant|>", "</s>"] }
|
|
89
|
+
|
|
90
|
+
# 3. Create the model using the digest (using Ruby 3 shorthand)
|
|
91
|
+
create(
|
|
92
|
+
model: 'my-username/my-model:latest',
|
|
93
|
+
files: { 'my-model-q4_0.gguf' => digest },
|
|
94
|
+
template:,
|
|
95
|
+
system:,
|
|
96
|
+
parameters:
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
This method is highly efficient as it uses streaming uploads and avoids
|
|
101
|
+
redundant data transfers if the blob already exists on the server.
|
|
102
|
+
|
|
72
103
|
## Try out things in ollama\_console
|
|
73
104
|
|
|
74
105
|
This is an interactive console where you can try out the different commands
|
|
@@ -23,16 +23,20 @@ module Ollama::Client::Command
|
|
|
23
23
|
# the command supports streaming and the presence of an explicit handler.
|
|
24
24
|
#
|
|
25
25
|
# @param name [ Symbol ] the name of the command to define
|
|
26
|
-
# @param default_handler [ Class ] the default handler class to use when no
|
|
27
|
-
#
|
|
26
|
+
# @param default_handler [ Class ] the default handler class to use when no
|
|
27
|
+
# explicit handler is provided
|
|
28
|
+
# @param stream_handler [ Class, nil ] the handler class to use for
|
|
29
|
+
# streaming responses, if applicable
|
|
30
|
+
# @param skip_doc [ Boolean ] whether to omit this command from the
|
|
31
|
+
# client's documented commands list, defaults to false
|
|
28
32
|
#
|
|
29
33
|
# @note Create Command `name`, if `stream` was true, set `stream_handler`
|
|
30
34
|
# as default, otherwise `default_handler`.
|
|
31
35
|
#
|
|
32
36
|
# @return [ self ] returns the receiver after defining the command method
|
|
33
|
-
def command(name, default_handler:, stream_handler: nil)
|
|
37
|
+
def command(name, default_handler:, stream_handler: nil, skip_doc: false)
|
|
34
38
|
klass = Ollama::Commands.const_get(name.to_s.camelize)
|
|
35
|
-
doc Ollama::Client::Doc.new(name)
|
|
39
|
+
skip_doc or doc Ollama::Client::Doc.new(name)
|
|
36
40
|
define_method(name) do |**parameters, &handler|
|
|
37
41
|
instance = klass.new(**parameters)
|
|
38
42
|
instance.client = self
|
data/lib/ollama/client.rb
CHANGED
|
@@ -123,6 +123,10 @@ class Ollama::Client
|
|
|
123
123
|
|
|
124
124
|
command(:version, default_handler: Single)
|
|
125
125
|
|
|
126
|
+
command(:blob_exists, default_handler: NOP, skip_doc: true)
|
|
127
|
+
|
|
128
|
+
command(:push_blob, default_handler: Progress, skip_doc: true)
|
|
129
|
+
|
|
126
130
|
# The commands method retrieves and sorts the documented commands available
|
|
127
131
|
# in the client.
|
|
128
132
|
#
|
|
@@ -199,6 +203,78 @@ class Ollama::Client
|
|
|
199
203
|
raise Ollama::Errors::Error, "Caught #{e.class} #{e.message.inspect} for #{url.to_s.inspect}"
|
|
200
204
|
end
|
|
201
205
|
|
|
206
|
+
# The upload_file method handles the streaming upload of a file to the server.
|
|
207
|
+
#
|
|
208
|
+
# This method opens the specified file in binary mode and utilizes Excon's
|
|
209
|
+
# :request_block to stream the content in chunks. It simultaneously updates
|
|
210
|
+
# the provided progress handler with current upload stats.
|
|
211
|
+
#
|
|
212
|
+
# @param command [ Ollama::Commands::PushBlob ] the command instance containing path logic
|
|
213
|
+
# @param body [ IO ] the body to be uploaded
|
|
214
|
+
# @param handler [ Ollama::Handler ] the handler object responsible for processing API responses
|
|
215
|
+
#
|
|
216
|
+
# @return [ Ollama::Client ] returns the client instance itself after initiating the request
|
|
217
|
+
def upload_file(path:, body:, handler:)
|
|
218
|
+
body.binmode
|
|
219
|
+
body.rewind
|
|
220
|
+
total_size = body.size
|
|
221
|
+
bytes_sent = 0
|
|
222
|
+
new_headers = {
|
|
223
|
+
'Content-Type' => 'application/octet-stream',
|
|
224
|
+
'Content-Length' => total_size,
|
|
225
|
+
}
|
|
226
|
+
request_block = -> do
|
|
227
|
+
chunk = body.read(Excon.defaults[:chunk_size]).to_s
|
|
228
|
+
bytes_sent += chunk.bytesize
|
|
229
|
+
fake_response = Ollama::Response.new(
|
|
230
|
+
completed: bytes_sent,
|
|
231
|
+
total: total_size,
|
|
232
|
+
status: 'Uploading…'
|
|
233
|
+
)
|
|
234
|
+
handler.call(fake_response)
|
|
235
|
+
chunk
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
url = @base_url + path
|
|
239
|
+
response = excon(url).post(
|
|
240
|
+
headers: headers.merge(new_headers),
|
|
241
|
+
request_block:
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
case response.status
|
|
245
|
+
when 200, 201
|
|
246
|
+
# Success
|
|
247
|
+
else
|
|
248
|
+
raise Ollama::Errors::Error, "#{response.status} #{response.body.inspect}"
|
|
249
|
+
end
|
|
250
|
+
self
|
|
251
|
+
rescue Excon::Errors::SocketError => e
|
|
252
|
+
raise Ollama::Errors::SocketError, "Caught #{e.class} #{e.message.inspect} for #{url.to_s.inspect}"
|
|
253
|
+
rescue Excon::Errors::Timeout => e
|
|
254
|
+
raise Ollama::Errors::TimeoutError, "Caught #{e.class} #{e.message.inspect} for #{url.to_s.inspect}"
|
|
255
|
+
rescue Excon::Error => e
|
|
256
|
+
raise Ollama::Errors::Error, "Caught #{e.class} #{e.message.inspect} for #{url.to_s.inspect}"
|
|
257
|
+
ensure
|
|
258
|
+
body.rewind
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# The blob_exists? method verifies if a binary blob exists on the Ollama
|
|
262
|
+
# server.
|
|
263
|
+
#
|
|
264
|
+
# This convenience method checks for the existence of a blob identified by
|
|
265
|
+
# its digest and returns a boolean value, simplifying the process of
|
|
266
|
+
# verifying blobs before attempting an upload.
|
|
267
|
+
#
|
|
268
|
+
# @param digest [ String ] the SHA256 digest of the blob (e.g., 'sha256:...')
|
|
269
|
+
# @return [ TrueClass, FalseClass ] true if the blob exists on the server,
|
|
270
|
+
# false otherwise
|
|
271
|
+
def blob_exists?(digest)
|
|
272
|
+
blob_exists(digest:)
|
|
273
|
+
true
|
|
274
|
+
rescue Ollama::Errors::NotFoundError
|
|
275
|
+
false
|
|
276
|
+
end
|
|
277
|
+
|
|
202
278
|
# The inspect method returns a string representation of the client instance.
|
|
203
279
|
#
|
|
204
280
|
# This method provides a human-readable description of the client object,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require 'ollama/digester'
|
|
2
|
+
|
|
3
|
+
# A command class that represents the blob existence check API endpoint for
|
|
4
|
+
# Ollama.
|
|
5
|
+
#
|
|
6
|
+
# This class is used to interact with the Ollama API's blobs endpoint using a
|
|
7
|
+
# HEAD request to verify if a specific binary blob (identified by its digest)
|
|
8
|
+
# already exists on the server. This is particularly useful for optimizing
|
|
9
|
+
# model uploads by avoiding redundant transfers of large files.
|
|
10
|
+
#
|
|
11
|
+
# @example Checking if a blob exists
|
|
12
|
+
# exists = ollama.blob_exists?(digest: 'sha256:...')
|
|
13
|
+
class Ollama::Commands::BlobExists
|
|
14
|
+
include Ollama::Digester
|
|
15
|
+
|
|
16
|
+
# The initialize method sets up a new instance with the target digest.
|
|
17
|
+
#
|
|
18
|
+
# This method initializes the command object with the specific digest of the
|
|
19
|
+
# blob being checked and explicitly disables streaming as HEAD requests
|
|
20
|
+
# are inherently non-streaming.
|
|
21
|
+
#
|
|
22
|
+
# @param digest [ String, nil ] the SHA256 digest of the blob (e.g., 'sha256:...')
|
|
23
|
+
# @param blob [ IO, nil ] the binary data stream to be hashed if no digest is
|
|
24
|
+
# provided
|
|
25
|
+
def initialize(digest: nil, blob: nil)
|
|
26
|
+
digest = prefix_sha256(digest)
|
|
27
|
+
if digest.nil? && blob
|
|
28
|
+
digest = compute_digest(blob)
|
|
29
|
+
end
|
|
30
|
+
digest or raise ArgumentError, 'require digest or blob to perform'
|
|
31
|
+
@digest, @stream = digest, false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The digest attribute reader returns the target blob digest.
|
|
35
|
+
#
|
|
36
|
+
# @return [ String ] the SHA256 digest of the blob
|
|
37
|
+
attr_reader :digest
|
|
38
|
+
|
|
39
|
+
# The stream attribute reader returns the streaming behavior setting.
|
|
40
|
+
#
|
|
41
|
+
# @return [ FalseClass ] the streaming behavior flag (always false for this command)
|
|
42
|
+
attr_reader :stream
|
|
43
|
+
|
|
44
|
+
# The client attribute writer allows setting the client instance associated
|
|
45
|
+
# with the object.
|
|
46
|
+
#
|
|
47
|
+
# @attr_writer [ Ollama::Client ] the assigned client instance
|
|
48
|
+
attr_writer :client
|
|
49
|
+
|
|
50
|
+
# The path method constructs the API endpoint URL for a specific binary blob.
|
|
51
|
+
# It interpolates the target blob's digest into the path string to create the
|
|
52
|
+
# correct resource identifier for the Ollama API.
|
|
53
|
+
#
|
|
54
|
+
# @return [ String ] the API endpoint path for the blob
|
|
55
|
+
def path
|
|
56
|
+
"/api/blobs/#{digest}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The perform method executes the existence check request using a HEAD method.
|
|
60
|
+
#
|
|
61
|
+
# This method initiates a HEAD request to the '/api/blobs/:digest' endpoint.
|
|
62
|
+
# A successful response (typically 200 OK) indicates the blob exists, while
|
|
63
|
+
# a 404 Not Found indicates it does not.
|
|
64
|
+
#
|
|
65
|
+
# @param handler [ Ollama::Handler ] the handler object responsible for
|
|
66
|
+
# processing API responses
|
|
67
|
+
#
|
|
68
|
+
# @return [ self ] returns the current instance after initiating the request
|
|
69
|
+
def perform(handler)
|
|
70
|
+
@client.request(method: :head, path:, stream:, handler:)
|
|
71
|
+
self
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require 'ollama/digester'
|
|
2
|
+
|
|
3
|
+
# A command class that represents the push blob API endpoint for Ollama.
|
|
4
|
+
#
|
|
5
|
+
# This class is used to upload raw binary files (blobs) to the Ollama server.
|
|
6
|
+
# It accepts the content to be uploaded and automatically calculates the
|
|
7
|
+
# required SHA256 digest to target the correct API endpoint.
|
|
8
|
+
#
|
|
9
|
+
# @example Preparing a push blob command with a file
|
|
10
|
+
# command = Ollama::Commands::PushBlob.new(body: File.open('model.gguf', 'rb'))
|
|
11
|
+
class Ollama::Commands::PushBlob
|
|
12
|
+
include Ollama::Digester
|
|
13
|
+
|
|
14
|
+
# The initialize method sets up a new instance and calculates the content
|
|
15
|
+
# digest.
|
|
16
|
+
#
|
|
17
|
+
# This method ensures the body is an IO-like object and computes the SHA256
|
|
18
|
+
# hash of the content to be used as the target endpoint path.
|
|
19
|
+
#
|
|
20
|
+
# @param body [ IO, String ] the binary content or file handle to upload
|
|
21
|
+
# @param digest [ String, nil ] optional precomputed SHA256 digest. If provided,
|
|
22
|
+
# it takes precedence over computing one from the body.
|
|
23
|
+
def initialize(body:, digest: nil)
|
|
24
|
+
@body = body.respond_to?(:read) ? body : StringIO.new(body.to_str)
|
|
25
|
+
digest = prefix_sha256(digest)
|
|
26
|
+
@digest = digest || compute_digest(@body)
|
|
27
|
+
@stream = false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The body attribute reader returns the binary content to be uploaded to the
|
|
31
|
+
# Ollama server.
|
|
32
|
+
#
|
|
33
|
+
# @attr_reader [ IO, String ] the raw image or model data used for the blob upload
|
|
34
|
+
attr_reader :body
|
|
35
|
+
|
|
36
|
+
# The digest attribute reader returns the target blob digest.
|
|
37
|
+
#
|
|
38
|
+
# @return [ String ] the SHA256 digest of the blob
|
|
39
|
+
attr_reader :digest
|
|
40
|
+
|
|
41
|
+
# The stream attribute reader returns the streaming behavior setting.
|
|
42
|
+
#
|
|
43
|
+
# @return [ FalseClass ] the streaming behavior flag (always false for this command)
|
|
44
|
+
attr_reader :stream
|
|
45
|
+
|
|
46
|
+
# The client attribute writer allows setting the client instance associated
|
|
47
|
+
# with the object.
|
|
48
|
+
#
|
|
49
|
+
# @attr_writer [ Ollama::Client ] the assigned client instance
|
|
50
|
+
attr_writer :client
|
|
51
|
+
|
|
52
|
+
# The path method returns the API endpoint path for pushing blobs.
|
|
53
|
+
#
|
|
54
|
+
# This method interpolates the digest into the URL to target a specific blob.
|
|
55
|
+
#
|
|
56
|
+
# @return [ String ] the API endpoint path '/api/blobs/<digest>'
|
|
57
|
+
def path
|
|
58
|
+
"/api/blobs/#{digest}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The perform method is not used directly for file uploads in this
|
|
62
|
+
# implementation, as the client uses a specialized streaming request method
|
|
63
|
+
# to handle IO.
|
|
64
|
+
# However, it's kept for interface consistency.
|
|
65
|
+
#
|
|
66
|
+
# @param handler [ Ollama::Handler ] the handler object responsible for processing API responses
|
|
67
|
+
#
|
|
68
|
+
# @return [ self ] returns the current instance after initiating the request
|
|
69
|
+
def perform(handler)
|
|
70
|
+
@client.blob_exists?(digest) or @client.upload_file(path:, body:, handler:)
|
|
71
|
+
handler.result = digest
|
|
72
|
+
self
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'digest'
|
|
2
|
+
|
|
3
|
+
# Provides utility methods for computing SHA256 digests of binary streams.
|
|
4
|
+
# This module is designed as a mixin to provide consistent hashing logic across
|
|
5
|
+
# different components, ensuring that IO objects are handled safely and
|
|
6
|
+
# efficiently.
|
|
7
|
+
module Ollama::Digester
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
# Calculates the SHA256 checksum of a given IO object.
|
|
11
|
+
#
|
|
12
|
+
# The method ensures the stream is reset both before and after processing to
|
|
13
|
+
# maintain the state of the IO object. It reads the content in chunks to
|
|
14
|
+
# minimize memory footprint when dealing with large binary files.
|
|
15
|
+
#
|
|
16
|
+
# @param io [ IO ] the binary data stream to be hashed
|
|
17
|
+
# @param chunk_size [ Integer ] the size of chunks to read from the IO stream (default: 16384)
|
|
18
|
+
#
|
|
19
|
+
# @return [ String ] the resulting SHA256 digest formatted as 'sha256:<hex>'
|
|
20
|
+
def compute_digest(io, chunk_size: 1 << 16)
|
|
21
|
+
io.binmode
|
|
22
|
+
io.rewind
|
|
23
|
+
label = 'Computing Digest…'
|
|
24
|
+
total = io.size
|
|
25
|
+
message = {
|
|
26
|
+
format: '%l %c/%t in %te, ETA %e @%E %s',
|
|
27
|
+
'%c' => { format: '%.2f %U', unit: ?B, prefix: :iec_uc },
|
|
28
|
+
'%t' => { format: '%2.2f %U', unit: ?B, prefix: :iec_uc },
|
|
29
|
+
'%s' => { frames: :braille181 },
|
|
30
|
+
}
|
|
31
|
+
infobar.(total:, label:, message:)
|
|
32
|
+
digest = Digest::SHA256.new
|
|
33
|
+
until io.eof?
|
|
34
|
+
infobar.progress(by: chunk_size)
|
|
35
|
+
digest << io.read(chunk_size)
|
|
36
|
+
end
|
|
37
|
+
infobar.newline
|
|
38
|
+
'sha256:%s' % digest.hexdigest
|
|
39
|
+
ensure
|
|
40
|
+
io.rewind
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Ensures a SHA256 digest is prefixed with 'sha256:'.
|
|
44
|
+
#
|
|
45
|
+
# If the provided string consists of exactly 64 hexadecimal characters,
|
|
46
|
+
# the prefix is prepended. Otherwise, the original string is returned.
|
|
47
|
+
#
|
|
48
|
+
# @param digest [ String, nil ] the digest to be prefixed
|
|
49
|
+
# @return [ String, nil ] the prefixed digest or the original value
|
|
50
|
+
def prefix_sha256(digest)
|
|
51
|
+
digest&.sub(/\A(?=\h{64}\z)/, 'sha256:')
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -27,17 +27,11 @@ module Ollama::Handlers::Concern
|
|
|
27
27
|
# messages are written
|
|
28
28
|
attr_reader :output
|
|
29
29
|
|
|
30
|
-
# The result
|
|
30
|
+
# The result attribute allows reading and setting the final outcome of a
|
|
31
|
+
# command operation.
|
|
31
32
|
#
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
# Single handlers.
|
|
35
|
-
#
|
|
36
|
-
# @return [ Ollama::Response, Array<Ollama::Response>, nil ] the result of the handler operation,
|
|
37
|
-
# which may be a single response, an array of responses, or nil
|
|
38
|
-
# depending on the handler type and the command execution
|
|
39
|
-
attr_reader :result
|
|
40
|
-
|
|
33
|
+
# @attr [ Object ] the processed result or return value generated by the handler
|
|
34
|
+
attr_accessor :result
|
|
41
35
|
|
|
42
36
|
# The implement :call, :subclass line enforces that any class including this
|
|
43
37
|
# concern must implement a `call` instance method. This creates a contract
|
|
@@ -76,8 +76,9 @@ class Ollama::Handlers::Progress
|
|
|
76
76
|
# @param current [ Integer ] the current progress value
|
|
77
77
|
# @param total [ Integer ] the total progress value
|
|
78
78
|
#
|
|
79
|
-
# @return [
|
|
80
|
-
# time information, and estimated completion details
|
|
79
|
+
# @return [ Hash ] a formatted progress message containing current status,
|
|
80
|
+
# time information, and estimated completion details, with a braille
|
|
81
|
+
# spinner.
|
|
81
82
|
def message(current, total)
|
|
82
83
|
progress = '%s/%s' % [ current, total ].map {
|
|
83
84
|
Tins::Unit.format(_1, format: '%.2f %U', unit: ?B, prefix: :iec_uc)
|
data/lib/ollama/version.rb
CHANGED
data/lib/ollama.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'json'
|
|
2
|
+
require 'uri'
|
|
2
3
|
require 'excon'
|
|
3
4
|
require 'tins'
|
|
4
5
|
require 'tins/xt/full'
|
|
@@ -66,5 +67,7 @@ require 'ollama/commands/embed'
|
|
|
66
67
|
require 'ollama/commands/embeddings'
|
|
67
68
|
require 'ollama/commands/ps'
|
|
68
69
|
require 'ollama/commands/version'
|
|
70
|
+
require 'ollama/commands/blob_exists'
|
|
71
|
+
require 'ollama/commands/push_blob'
|
|
69
72
|
|
|
70
73
|
require 'ollama/client'
|
data/ollama-ruby.gemspec
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: ollama-ruby 1.
|
|
2
|
+
# stub: ollama-ruby 1.22.0 ruby lib
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = "ollama-ruby".freeze
|
|
6
|
-
s.version = "1.
|
|
6
|
+
s.version = "1.22.0".freeze
|
|
7
7
|
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
|
9
9
|
s.require_paths = ["lib".freeze]
|
|
@@ -12,19 +12,19 @@ Gem::Specification.new do |s|
|
|
|
12
12
|
s.description = "Library that allows interacting with the Ollama API".freeze
|
|
13
13
|
s.email = "flori@ping.de".freeze
|
|
14
14
|
s.executables = ["ollama_console".freeze, "ollama_update".freeze, "ollama_cli".freeze, "ollama_browse".freeze, "ollama_ps".freeze]
|
|
15
|
-
s.extra_rdoc_files = ["README.md".freeze, "lib/ollama.rb".freeze, "lib/ollama/client.rb".freeze, "lib/ollama/client/command.rb".freeze, "lib/ollama/client/configuration/config.rb".freeze, "lib/ollama/client/doc.rb".freeze, "lib/ollama/commands/chat.rb".freeze, "lib/ollama/commands/copy.rb".freeze, "lib/ollama/commands/create.rb".freeze, "lib/ollama/commands/delete.rb".freeze, "lib/ollama/commands/embed.rb".freeze, "lib/ollama/commands/embeddings.rb".freeze, "lib/ollama/commands/generate.rb".freeze, "lib/ollama/commands/ps.rb".freeze, "lib/ollama/commands/pull.rb".freeze, "lib/ollama/commands/push.rb".freeze, "lib/ollama/commands/show.rb".freeze, "lib/ollama/commands/tags.rb".freeze, "lib/ollama/commands/version.rb".freeze, "lib/ollama/dto.rb".freeze, "lib/ollama/errors.rb".freeze, "lib/ollama/handlers.rb".freeze, "lib/ollama/handlers/collector.rb".freeze, "lib/ollama/handlers/concern.rb".freeze, "lib/ollama/handlers/dump_json.rb".freeze, "lib/ollama/handlers/dump_yaml.rb".freeze, "lib/ollama/handlers/markdown.rb".freeze, "lib/ollama/handlers/nop.rb".freeze, "lib/ollama/handlers/print.rb".freeze, "lib/ollama/handlers/progress.rb".freeze, "lib/ollama/handlers/say.rb".freeze, "lib/ollama/handlers/single.rb".freeze, "lib/ollama/image.rb".freeze, "lib/ollama/json_loader.rb".freeze, "lib/ollama/message.rb".freeze, "lib/ollama/options.rb".freeze, "lib/ollama/response.rb".freeze, "lib/ollama/tool.rb".freeze, "lib/ollama/tool/function.rb".freeze, "lib/ollama/tool/function/parameters.rb".freeze, "lib/ollama/tool/function/parameters/property.rb".freeze, "lib/ollama/version.rb".freeze]
|
|
16
|
-
s.files = [".envrc".freeze, ".utilsrc".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/ollama_browse".freeze, "bin/ollama_cli".freeze, "bin/ollama_console".freeze, "bin/ollama_ps".freeze, "bin/ollama_update".freeze, "lib/ollama.rb".freeze, "lib/ollama/client.rb".freeze, "lib/ollama/client/command.rb".freeze, "lib/ollama/client/configuration/config.rb".freeze, "lib/ollama/client/doc.rb".freeze, "lib/ollama/commands/chat.rb".freeze, "lib/ollama/commands/copy.rb".freeze, "lib/ollama/commands/create.rb".freeze, "lib/ollama/commands/delete.rb".freeze, "lib/ollama/commands/embed.rb".freeze, "lib/ollama/commands/embeddings.rb".freeze, "lib/ollama/commands/generate.rb".freeze, "lib/ollama/commands/ps.rb".freeze, "lib/ollama/commands/pull.rb".freeze, "lib/ollama/commands/push.rb".freeze, "lib/ollama/commands/show.rb".freeze, "lib/ollama/commands/tags.rb".freeze, "lib/ollama/commands/version.rb".freeze, "lib/ollama/dto.rb".freeze, "lib/ollama/errors.rb".freeze, "lib/ollama/handlers.rb".freeze, "lib/ollama/handlers/collector.rb".freeze, "lib/ollama/handlers/concern.rb".freeze, "lib/ollama/handlers/dump_json.rb".freeze, "lib/ollama/handlers/dump_yaml.rb".freeze, "lib/ollama/handlers/markdown.rb".freeze, "lib/ollama/handlers/nop.rb".freeze, "lib/ollama/handlers/print.rb".freeze, "lib/ollama/handlers/progress.rb".freeze, "lib/ollama/handlers/say.rb".freeze, "lib/ollama/handlers/single.rb".freeze, "lib/ollama/image.rb".freeze, "lib/ollama/json_loader.rb".freeze, "lib/ollama/message.rb".freeze, "lib/ollama/options.rb".freeze, "lib/ollama/response.rb".freeze, "lib/ollama/tool.rb".freeze, "lib/ollama/tool/function.rb".freeze, "lib/ollama/tool/function/parameters.rb".freeze, "lib/ollama/tool/function/parameters/property.rb".freeze, "lib/ollama/version.rb".freeze, "ollama-ruby.gemspec".freeze, "spec/assets/client.json".freeze, "spec/assets/kitten.jpg".freeze, "spec/assets/options.json".freeze, "spec/ollama/client/doc_spec.rb".freeze, "spec/ollama/client_spec.rb".freeze, "spec/ollama/commands/chat_spec.rb".freeze, "spec/ollama/commands/copy_spec.rb".freeze, "spec/ollama/commands/create_spec.rb".freeze, "spec/ollama/commands/delete_spec.rb".freeze, "spec/ollama/commands/embed_spec.rb".freeze, "spec/ollama/commands/embeddings_spec.rb".freeze, "spec/ollama/commands/generate_spec.rb".freeze, "spec/ollama/commands/ps_spec.rb".freeze, "spec/ollama/commands/pull_spec.rb".freeze, "spec/ollama/commands/push_spec.rb".freeze, "spec/ollama/commands/show_spec.rb".freeze, "spec/ollama/commands/tags_spec.rb".freeze, "spec/ollama/commands/version_spec.rb".freeze, "spec/ollama/handlers/collector_spec.rb".freeze, "spec/ollama/handlers/dump_json_spec.rb".freeze, "spec/ollama/handlers/dump_yaml_spec.rb".freeze, "spec/ollama/handlers/markdown_spec.rb".freeze, "spec/ollama/handlers/nop_spec.rb".freeze, "spec/ollama/handlers/print_spec.rb".freeze, "spec/ollama/handlers/progress_spec.rb".freeze, "spec/ollama/handlers/say_spec.rb".freeze, "spec/ollama/handlers/single_spec.rb".freeze, "spec/ollama/image_spec.rb".freeze, "spec/ollama/message_spec.rb".freeze, "spec/ollama/options_spec.rb".freeze, "spec/ollama/tool_spec.rb".freeze, "spec/spec_helper.rb".freeze, "tmp/.keep".freeze]
|
|
15
|
+
s.extra_rdoc_files = ["README.md".freeze, "lib/ollama.rb".freeze, "lib/ollama/client.rb".freeze, "lib/ollama/client/command.rb".freeze, "lib/ollama/client/configuration/config.rb".freeze, "lib/ollama/client/doc.rb".freeze, "lib/ollama/commands/blob_exists.rb".freeze, "lib/ollama/commands/chat.rb".freeze, "lib/ollama/commands/copy.rb".freeze, "lib/ollama/commands/create.rb".freeze, "lib/ollama/commands/delete.rb".freeze, "lib/ollama/commands/embed.rb".freeze, "lib/ollama/commands/embeddings.rb".freeze, "lib/ollama/commands/generate.rb".freeze, "lib/ollama/commands/ps.rb".freeze, "lib/ollama/commands/pull.rb".freeze, "lib/ollama/commands/push.rb".freeze, "lib/ollama/commands/push_blob.rb".freeze, "lib/ollama/commands/show.rb".freeze, "lib/ollama/commands/tags.rb".freeze, "lib/ollama/commands/version.rb".freeze, "lib/ollama/digester.rb".freeze, "lib/ollama/dto.rb".freeze, "lib/ollama/errors.rb".freeze, "lib/ollama/handlers.rb".freeze, "lib/ollama/handlers/collector.rb".freeze, "lib/ollama/handlers/concern.rb".freeze, "lib/ollama/handlers/dump_json.rb".freeze, "lib/ollama/handlers/dump_yaml.rb".freeze, "lib/ollama/handlers/markdown.rb".freeze, "lib/ollama/handlers/nop.rb".freeze, "lib/ollama/handlers/print.rb".freeze, "lib/ollama/handlers/progress.rb".freeze, "lib/ollama/handlers/say.rb".freeze, "lib/ollama/handlers/single.rb".freeze, "lib/ollama/image.rb".freeze, "lib/ollama/json_loader.rb".freeze, "lib/ollama/message.rb".freeze, "lib/ollama/options.rb".freeze, "lib/ollama/response.rb".freeze, "lib/ollama/tool.rb".freeze, "lib/ollama/tool/function.rb".freeze, "lib/ollama/tool/function/parameters.rb".freeze, "lib/ollama/tool/function/parameters/property.rb".freeze, "lib/ollama/version.rb".freeze]
|
|
16
|
+
s.files = [".envrc".freeze, ".utilsrc".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/ollama_browse".freeze, "bin/ollama_cli".freeze, "bin/ollama_console".freeze, "bin/ollama_ps".freeze, "bin/ollama_update".freeze, "lib/ollama.rb".freeze, "lib/ollama/client.rb".freeze, "lib/ollama/client/command.rb".freeze, "lib/ollama/client/configuration/config.rb".freeze, "lib/ollama/client/doc.rb".freeze, "lib/ollama/commands/blob_exists.rb".freeze, "lib/ollama/commands/chat.rb".freeze, "lib/ollama/commands/copy.rb".freeze, "lib/ollama/commands/create.rb".freeze, "lib/ollama/commands/delete.rb".freeze, "lib/ollama/commands/embed.rb".freeze, "lib/ollama/commands/embeddings.rb".freeze, "lib/ollama/commands/generate.rb".freeze, "lib/ollama/commands/ps.rb".freeze, "lib/ollama/commands/pull.rb".freeze, "lib/ollama/commands/push.rb".freeze, "lib/ollama/commands/push_blob.rb".freeze, "lib/ollama/commands/show.rb".freeze, "lib/ollama/commands/tags.rb".freeze, "lib/ollama/commands/version.rb".freeze, "lib/ollama/digester.rb".freeze, "lib/ollama/dto.rb".freeze, "lib/ollama/errors.rb".freeze, "lib/ollama/handlers.rb".freeze, "lib/ollama/handlers/collector.rb".freeze, "lib/ollama/handlers/concern.rb".freeze, "lib/ollama/handlers/dump_json.rb".freeze, "lib/ollama/handlers/dump_yaml.rb".freeze, "lib/ollama/handlers/markdown.rb".freeze, "lib/ollama/handlers/nop.rb".freeze, "lib/ollama/handlers/print.rb".freeze, "lib/ollama/handlers/progress.rb".freeze, "lib/ollama/handlers/say.rb".freeze, "lib/ollama/handlers/single.rb".freeze, "lib/ollama/image.rb".freeze, "lib/ollama/json_loader.rb".freeze, "lib/ollama/message.rb".freeze, "lib/ollama/options.rb".freeze, "lib/ollama/response.rb".freeze, "lib/ollama/tool.rb".freeze, "lib/ollama/tool/function.rb".freeze, "lib/ollama/tool/function/parameters.rb".freeze, "lib/ollama/tool/function/parameters/property.rb".freeze, "lib/ollama/version.rb".freeze, "ollama-ruby.gemspec".freeze, "spec/assets/client.json".freeze, "spec/assets/kitten.jpg".freeze, "spec/assets/options.json".freeze, "spec/ollama/client/doc_spec.rb".freeze, "spec/ollama/client_spec.rb".freeze, "spec/ollama/commands/blob_exists_spec.rb".freeze, "spec/ollama/commands/chat_spec.rb".freeze, "spec/ollama/commands/copy_spec.rb".freeze, "spec/ollama/commands/create_spec.rb".freeze, "spec/ollama/commands/delete_spec.rb".freeze, "spec/ollama/commands/embed_spec.rb".freeze, "spec/ollama/commands/embeddings_spec.rb".freeze, "spec/ollama/commands/generate_spec.rb".freeze, "spec/ollama/commands/ps_spec.rb".freeze, "spec/ollama/commands/pull_spec.rb".freeze, "spec/ollama/commands/push_blob_spec.rb".freeze, "spec/ollama/commands/push_spec.rb".freeze, "spec/ollama/commands/show_spec.rb".freeze, "spec/ollama/commands/tags_spec.rb".freeze, "spec/ollama/commands/version_spec.rb".freeze, "spec/ollama/digester_spec.rb".freeze, "spec/ollama/handlers/collector_spec.rb".freeze, "spec/ollama/handlers/dump_json_spec.rb".freeze, "spec/ollama/handlers/dump_yaml_spec.rb".freeze, "spec/ollama/handlers/markdown_spec.rb".freeze, "spec/ollama/handlers/nop_spec.rb".freeze, "spec/ollama/handlers/print_spec.rb".freeze, "spec/ollama/handlers/progress_spec.rb".freeze, "spec/ollama/handlers/say_spec.rb".freeze, "spec/ollama/handlers/single_spec.rb".freeze, "spec/ollama/image_spec.rb".freeze, "spec/ollama/message_spec.rb".freeze, "spec/ollama/options_spec.rb".freeze, "spec/ollama/tool_spec.rb".freeze, "spec/spec_helper.rb".freeze, "tmp/.keep".freeze]
|
|
17
17
|
s.homepage = "https://github.com/flori/ollama-ruby".freeze
|
|
18
18
|
s.licenses = ["MIT".freeze]
|
|
19
19
|
s.rdoc_options = ["--title".freeze, "Ollama-ruby - Interacting with the Ollama API".freeze, "--main".freeze, "README.md".freeze]
|
|
20
20
|
s.required_ruby_version = Gem::Requirement.new(">= 3.1".freeze)
|
|
21
|
-
s.rubygems_version = "4.0.
|
|
21
|
+
s.rubygems_version = "4.0.10".freeze
|
|
22
22
|
s.summary = "Interacting with the Ollama API".freeze
|
|
23
|
-
s.test_files = ["spec/ollama/client/doc_spec.rb".freeze, "spec/ollama/client_spec.rb".freeze, "spec/ollama/commands/chat_spec.rb".freeze, "spec/ollama/commands/copy_spec.rb".freeze, "spec/ollama/commands/create_spec.rb".freeze, "spec/ollama/commands/delete_spec.rb".freeze, "spec/ollama/commands/embed_spec.rb".freeze, "spec/ollama/commands/embeddings_spec.rb".freeze, "spec/ollama/commands/generate_spec.rb".freeze, "spec/ollama/commands/ps_spec.rb".freeze, "spec/ollama/commands/pull_spec.rb".freeze, "spec/ollama/commands/push_spec.rb".freeze, "spec/ollama/commands/show_spec.rb".freeze, "spec/ollama/commands/tags_spec.rb".freeze, "spec/ollama/commands/version_spec.rb".freeze, "spec/ollama/handlers/collector_spec.rb".freeze, "spec/ollama/handlers/dump_json_spec.rb".freeze, "spec/ollama/handlers/dump_yaml_spec.rb".freeze, "spec/ollama/handlers/markdown_spec.rb".freeze, "spec/ollama/handlers/nop_spec.rb".freeze, "spec/ollama/handlers/print_spec.rb".freeze, "spec/ollama/handlers/progress_spec.rb".freeze, "spec/ollama/handlers/say_spec.rb".freeze, "spec/ollama/handlers/single_spec.rb".freeze, "spec/ollama/image_spec.rb".freeze, "spec/ollama/message_spec.rb".freeze, "spec/ollama/options_spec.rb".freeze, "spec/ollama/tool_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
|
23
|
+
s.test_files = ["spec/ollama/client/doc_spec.rb".freeze, "spec/ollama/client_spec.rb".freeze, "spec/ollama/commands/blob_exists_spec.rb".freeze, "spec/ollama/commands/chat_spec.rb".freeze, "spec/ollama/commands/copy_spec.rb".freeze, "spec/ollama/commands/create_spec.rb".freeze, "spec/ollama/commands/delete_spec.rb".freeze, "spec/ollama/commands/embed_spec.rb".freeze, "spec/ollama/commands/embeddings_spec.rb".freeze, "spec/ollama/commands/generate_spec.rb".freeze, "spec/ollama/commands/ps_spec.rb".freeze, "spec/ollama/commands/pull_spec.rb".freeze, "spec/ollama/commands/push_blob_spec.rb".freeze, "spec/ollama/commands/push_spec.rb".freeze, "spec/ollama/commands/show_spec.rb".freeze, "spec/ollama/commands/tags_spec.rb".freeze, "spec/ollama/commands/version_spec.rb".freeze, "spec/ollama/digester_spec.rb".freeze, "spec/ollama/handlers/collector_spec.rb".freeze, "spec/ollama/handlers/dump_json_spec.rb".freeze, "spec/ollama/handlers/dump_yaml_spec.rb".freeze, "spec/ollama/handlers/markdown_spec.rb".freeze, "spec/ollama/handlers/nop_spec.rb".freeze, "spec/ollama/handlers/print_spec.rb".freeze, "spec/ollama/handlers/progress_spec.rb".freeze, "spec/ollama/handlers/say_spec.rb".freeze, "spec/ollama/handlers/single_spec.rb".freeze, "spec/ollama/image_spec.rb".freeze, "spec/ollama/message_spec.rb".freeze, "spec/ollama/options_spec.rb".freeze, "spec/ollama/tool_spec.rb".freeze, "spec/spec_helper.rb".freeze]
|
|
24
24
|
|
|
25
25
|
s.specification_version = 4
|
|
26
26
|
|
|
27
|
-
s.add_development_dependency(%q<gem_hadar>.freeze, [">= 2.17.
|
|
27
|
+
s.add_development_dependency(%q<gem_hadar>.freeze, [">= 2.17.1".freeze])
|
|
28
28
|
s.add_development_dependency(%q<all_images>.freeze, ["~> 0.12".freeze])
|
|
29
29
|
s.add_development_dependency(%q<rspec>.freeze, ["~> 3.2".freeze])
|
|
30
30
|
s.add_development_dependency(%q<kramdown>.freeze, ["~> 2.0".freeze])
|
data/spec/ollama/client_spec.rb
CHANGED
|
@@ -193,4 +193,98 @@ describe Ollama::Client do
|
|
|
193
193
|
expect($stdout).to receive(:puts).with(/Commands:.*?chat/)
|
|
194
194
|
ollama.help
|
|
195
195
|
end
|
|
196
|
+
|
|
197
|
+
context 'blobs' do
|
|
198
|
+
it 'can verify that a blob exists' do
|
|
199
|
+
expect(excon).to receive(:head).
|
|
200
|
+
and_return(double('Response', status: 200, body: ''))
|
|
201
|
+
exists = ollama.blob_exists?(
|
|
202
|
+
'sha256:5ee4f07cdb9beadbbb293e85803c569b01bd37ed059d2715faa7bb405f31caa6'
|
|
203
|
+
)
|
|
204
|
+
expect(exists).to eq true
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it 'can falsify that a blob exists' do
|
|
208
|
+
expect(excon).to receive(:head).and_raise Ollama::Errors::NotFoundError
|
|
209
|
+
exists = ollama.blob_exists?(
|
|
210
|
+
'sha256:5ee4f07cdb9beadbbb293e85803c569b01bd37ed059d2715faa7bb405f31ca6a'
|
|
211
|
+
)
|
|
212
|
+
expect(exists).to eq false
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'can upload a file and return self on success' do
|
|
216
|
+
body = StringIO.new('dummy content')
|
|
217
|
+
handler = double('Handler', call: nil)
|
|
218
|
+
|
|
219
|
+
expect(excon).to receive(:post).with(
|
|
220
|
+
headers: hash_including('Content-Type' => 'application/octet-stream'),
|
|
221
|
+
request_block: an_instance_of(Proc)
|
|
222
|
+
).and_return(double(status: 201, body: ''))
|
|
223
|
+
|
|
224
|
+
expect(ollama.upload_file(path: '/api/blobs/abc', body: body, handler: handler)).to eq ollama
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it 'can report progress via the handler' do
|
|
228
|
+
content = 'Hello Ruby!'
|
|
229
|
+
body = StringIO.new(content)
|
|
230
|
+
handler = double('Handler')
|
|
231
|
+
|
|
232
|
+
# We capture the block to simulate Excon calling it
|
|
233
|
+
captured_block = nil
|
|
234
|
+
expect(excon).to receive(:post) do |args|
|
|
235
|
+
captured_block = args[:request_block]
|
|
236
|
+
double(status: 201, body: '')
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
ollama.upload_file(path: '/api/blobs/abc', body: body, handler: handler)
|
|
240
|
+
|
|
241
|
+
# Execute the block and verify the fake response is passed to the handler
|
|
242
|
+
expect(handler).to receive(:call).with(an_instance_of(Ollama::Response))
|
|
243
|
+
captured_block.call
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
it 'can raise error on non-success status' do
|
|
247
|
+
body = StringIO.new('dummy content')
|
|
248
|
+
handler = double('Handler', call: nil)
|
|
249
|
+
|
|
250
|
+
expect(excon).to receive(:post).and_return(double(status: 400, body: 'Bad Request'))
|
|
251
|
+
|
|
252
|
+
expect {
|
|
253
|
+
ollama.upload_file(path: '/api/blobs/abc', body: body, handler: handler)
|
|
254
|
+
}.to raise_error(Ollama::Errors::Error, /400 "Bad Request"/)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
it 'can map Excon socket errors to Ollama::Errors::SocketError' do
|
|
258
|
+
body = StringIO.new('dummy content')
|
|
259
|
+
handler = double('Handler', call: nil)
|
|
260
|
+
|
|
261
|
+
expect(excon).to receive(:post).and_raise(Excon::Errors::SocketError)
|
|
262
|
+
|
|
263
|
+
expect {
|
|
264
|
+
ollama.upload_file(path: '/api/blobs/abc', body: body, handler: handler)
|
|
265
|
+
}.to raise_error(Ollama::Errors::SocketError)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it 'can map Excon timeout errors to Ollama::Errors::TimeoutError' do
|
|
269
|
+
body = StringIO.new('dummy content')
|
|
270
|
+
handler = double('Handler', call: nil)
|
|
271
|
+
|
|
272
|
+
expect(excon).to receive(:post).and_raise(Excon::Errors::Timeout)
|
|
273
|
+
|
|
274
|
+
expect {
|
|
275
|
+
ollama.upload_file(path: '/api/blobs/abc', body: body, handler: handler)
|
|
276
|
+
}.to raise_error(Ollama::Errors::TimeoutError)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
it 'can map generic Excon errors to Ollama::Errors::Error' do
|
|
280
|
+
body = StringIO.new('dummy content')
|
|
281
|
+
handler = double('Handler', call: nil)
|
|
282
|
+
|
|
283
|
+
expect(excon).to receive(:post).and_raise(Excon::Error)
|
|
284
|
+
|
|
285
|
+
expect {
|
|
286
|
+
ollama.upload_file(path: '/api/blobs/abc', body: body, handler: handler)
|
|
287
|
+
}.to raise_error(Ollama::Errors::Error)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
196
290
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
describe Ollama::Commands::BlobExists do
|
|
2
|
+
let :hex_digest do
|
|
3
|
+
'5ee4f07cdb9beadbbb293e85803c569b01bd37ed059d2715faa7bb405f31caa6'
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
let :digest do
|
|
7
|
+
"sha256:#{hex_digest}"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
let :blob_exists do
|
|
11
|
+
described_class.new(digest:)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'can be instantiated with a full digest' do
|
|
15
|
+
expect(blob_exists).to be_a described_class
|
|
16
|
+
expect(blob_exists.digest).to eq digest
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'automatically prefixes 64-char hex digests' do
|
|
20
|
+
be = described_class.new(digest: hex_digest)
|
|
21
|
+
expect(be.digest).to eq "sha256:#{hex_digest}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'computes digest when provided with a blob' do
|
|
25
|
+
blob = StringIO.new('hello world')
|
|
26
|
+
be = described_class.new(blob: blob)
|
|
27
|
+
expect(be.digest).to start_with('sha256:')
|
|
28
|
+
expect(be.digest).not_to be_nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'raises ArgumentError if neither digest nor blob is provided' do
|
|
32
|
+
expect { described_class.new }.to\
|
|
33
|
+
raise_error(ArgumentError, 'require digest or blob to perform')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'cannot be converted to JSON' do
|
|
37
|
+
expect(blob_exists).not_to respond_to(:as_json)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'can perform' do
|
|
41
|
+
blob_exists.client = ollama = double('Ollama::Client')
|
|
42
|
+
expect(ollama).to receive(:request).
|
|
43
|
+
with(
|
|
44
|
+
method: :head, path: "/api/blobs/#{digest}", handler: Ollama::Handlers::NOP,
|
|
45
|
+
stream: false
|
|
46
|
+
)
|
|
47
|
+
blob_exists.perform(Ollama::Handlers::NOP)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
describe Ollama::Commands::PushBlob do
|
|
2
|
+
let :body do
|
|
3
|
+
'test blob content'
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
let :push_blob do
|
|
7
|
+
described_class.new(body: body)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'can be instantiated with a string' do
|
|
11
|
+
expect(push_blob).to be_a described_class
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'can be instantiated with an IO object' do
|
|
15
|
+
io = StringIO.new(body)
|
|
16
|
+
push_blob_io = described_class.new(body: io)
|
|
17
|
+
expect(push_blob_io).to be_a described_class
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'cannot be converted to JSON' do
|
|
21
|
+
expect(push_blob).not_to respond_to(:as_json)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'calculates the correct digest and path' do
|
|
25
|
+
expected_digest = 'sha256:dccfe42873d40807d0da4be11f3a412e4914f1315288d3c6e8cf0a19a8928feb'
|
|
26
|
+
expect(push_blob.digest).to eq expected_digest
|
|
27
|
+
expect(push_blob.path).to eq "/api/blobs/#{expected_digest}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'can perform' do
|
|
31
|
+
handler = double('Ollama::Handlers::NOP').as_null_object
|
|
32
|
+
push_blob.client = ollama = double('Ollama::Client')
|
|
33
|
+
|
|
34
|
+
expect(handler).to receive(:result=).with(push_blob.digest)
|
|
35
|
+
expect(ollama).to receive(:blob_exists?).and_return false
|
|
36
|
+
expect(ollama).to receive(:upload_file).with(
|
|
37
|
+
path: push_blob.path, body: push_blob.body, handler: handler
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
push_blob.perform(handler)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -18,7 +18,7 @@ describe Ollama::Commands::Show do
|
|
|
18
18
|
show = described_class.new(model: 'llama3.1')
|
|
19
19
|
show.client = ollama = double('Ollama::Client')
|
|
20
20
|
expect(ollama).to receive(:request).with(
|
|
21
|
-
method: :post, path: '/api/show', handler: Ollama::Handlers::NOP
|
|
21
|
+
method: :post, path: '/api/show', handler: Ollama::Handlers::NOP, stream: false,
|
|
22
22
|
body: '{"model":"llama3.1","stream":false}'
|
|
23
23
|
)
|
|
24
24
|
show.perform(Ollama::Handlers::NOP)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require 'stringio'
|
|
2
|
+
|
|
3
|
+
describe Ollama::Digester do
|
|
4
|
+
# Since compute_digest is private, we create a test class to include it
|
|
5
|
+
let :test_class do
|
|
6
|
+
Class.new do
|
|
7
|
+
include Ollama::Digester
|
|
8
|
+
|
|
9
|
+
def digest(io, **args)
|
|
10
|
+
compute_digest(io, **args)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
let(:digester) { test_class.new }
|
|
16
|
+
|
|
17
|
+
let(:content) { 'Hello, Ruby happiness! 🍓' }
|
|
18
|
+
|
|
19
|
+
let(:io) { StringIO.new(content) }
|
|
20
|
+
|
|
21
|
+
describe '#compute_digest' do
|
|
22
|
+
it 'calculates the correct SHA256 digest with the required prefix' do
|
|
23
|
+
expected_hash = "sha256:#{Digest::SHA256.hexdigest(content)}"
|
|
24
|
+
expect(digester.digest(io)).to eq(expected_hash)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'rewinds the IO object before starting if it is not at the beginning' do
|
|
28
|
+
io.seek(content.length / 2) # Move pointer to the middle
|
|
29
|
+
|
|
30
|
+
# If it didn't rewind, it would only hash the second half
|
|
31
|
+
expected_hash = "sha256:#{Digest::SHA256.hexdigest(content)}"
|
|
32
|
+
expect(digester.digest(io)).to eq(expected_hash)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'leaves the IO object rewound after processing' do
|
|
36
|
+
digester.digest(io)
|
|
37
|
+
expect(io.pos).to eq(0)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'works correctly when using a very small chunk size' do
|
|
41
|
+
# Force many iterations of the loop by using 1-byte chunks
|
|
42
|
+
expected_hash = "sha256:#{Digest::SHA256.hexdigest(content)}"
|
|
43
|
+
expect(digester.digest(io, chunk_size: 1)).to eq(expected_hash)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'handles empty streams correctly' do
|
|
47
|
+
empty_io = StringIO.new('')
|
|
48
|
+
expected_hash = "sha256:#{Digest::SHA256.hexdigest('')}"
|
|
49
|
+
expect(digester.digest(empty_io)).to eq(expected_hash)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'ensures the IO is rewound even if an error occurs during hashing' do
|
|
53
|
+
io.seek(content.length / 2) # Move pointer to the middle
|
|
54
|
+
|
|
55
|
+
# Mock Digest to raise an error
|
|
56
|
+
allow(Digest::SHA256).to receive(:new).and_raise(StandardError, 'Hashing failed')
|
|
57
|
+
|
|
58
|
+
expect { digester.digest(io) }.to raise_error(StandardError, 'Hashing failed')
|
|
59
|
+
expect(io.pos).to eq(0)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ollama-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.22.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Florian Frank
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 2.17.
|
|
18
|
+
version: 2.17.1
|
|
19
19
|
type: :development
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 2.17.
|
|
25
|
+
version: 2.17.1
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: all_images
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -269,6 +269,7 @@ extra_rdoc_files:
|
|
|
269
269
|
- lib/ollama/client/command.rb
|
|
270
270
|
- lib/ollama/client/configuration/config.rb
|
|
271
271
|
- lib/ollama/client/doc.rb
|
|
272
|
+
- lib/ollama/commands/blob_exists.rb
|
|
272
273
|
- lib/ollama/commands/chat.rb
|
|
273
274
|
- lib/ollama/commands/copy.rb
|
|
274
275
|
- lib/ollama/commands/create.rb
|
|
@@ -279,9 +280,11 @@ extra_rdoc_files:
|
|
|
279
280
|
- lib/ollama/commands/ps.rb
|
|
280
281
|
- lib/ollama/commands/pull.rb
|
|
281
282
|
- lib/ollama/commands/push.rb
|
|
283
|
+
- lib/ollama/commands/push_blob.rb
|
|
282
284
|
- lib/ollama/commands/show.rb
|
|
283
285
|
- lib/ollama/commands/tags.rb
|
|
284
286
|
- lib/ollama/commands/version.rb
|
|
287
|
+
- lib/ollama/digester.rb
|
|
285
288
|
- lib/ollama/dto.rb
|
|
286
289
|
- lib/ollama/errors.rb
|
|
287
290
|
- lib/ollama/handlers.rb
|
|
@@ -323,6 +326,7 @@ files:
|
|
|
323
326
|
- lib/ollama/client/command.rb
|
|
324
327
|
- lib/ollama/client/configuration/config.rb
|
|
325
328
|
- lib/ollama/client/doc.rb
|
|
329
|
+
- lib/ollama/commands/blob_exists.rb
|
|
326
330
|
- lib/ollama/commands/chat.rb
|
|
327
331
|
- lib/ollama/commands/copy.rb
|
|
328
332
|
- lib/ollama/commands/create.rb
|
|
@@ -333,9 +337,11 @@ files:
|
|
|
333
337
|
- lib/ollama/commands/ps.rb
|
|
334
338
|
- lib/ollama/commands/pull.rb
|
|
335
339
|
- lib/ollama/commands/push.rb
|
|
340
|
+
- lib/ollama/commands/push_blob.rb
|
|
336
341
|
- lib/ollama/commands/show.rb
|
|
337
342
|
- lib/ollama/commands/tags.rb
|
|
338
343
|
- lib/ollama/commands/version.rb
|
|
344
|
+
- lib/ollama/digester.rb
|
|
339
345
|
- lib/ollama/dto.rb
|
|
340
346
|
- lib/ollama/errors.rb
|
|
341
347
|
- lib/ollama/handlers.rb
|
|
@@ -365,6 +371,7 @@ files:
|
|
|
365
371
|
- spec/assets/options.json
|
|
366
372
|
- spec/ollama/client/doc_spec.rb
|
|
367
373
|
- spec/ollama/client_spec.rb
|
|
374
|
+
- spec/ollama/commands/blob_exists_spec.rb
|
|
368
375
|
- spec/ollama/commands/chat_spec.rb
|
|
369
376
|
- spec/ollama/commands/copy_spec.rb
|
|
370
377
|
- spec/ollama/commands/create_spec.rb
|
|
@@ -374,10 +381,12 @@ files:
|
|
|
374
381
|
- spec/ollama/commands/generate_spec.rb
|
|
375
382
|
- spec/ollama/commands/ps_spec.rb
|
|
376
383
|
- spec/ollama/commands/pull_spec.rb
|
|
384
|
+
- spec/ollama/commands/push_blob_spec.rb
|
|
377
385
|
- spec/ollama/commands/push_spec.rb
|
|
378
386
|
- spec/ollama/commands/show_spec.rb
|
|
379
387
|
- spec/ollama/commands/tags_spec.rb
|
|
380
388
|
- spec/ollama/commands/version_spec.rb
|
|
389
|
+
- spec/ollama/digester_spec.rb
|
|
381
390
|
- spec/ollama/handlers/collector_spec.rb
|
|
382
391
|
- spec/ollama/handlers/dump_json_spec.rb
|
|
383
392
|
- spec/ollama/handlers/dump_yaml_spec.rb
|
|
@@ -415,12 +424,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
415
424
|
- !ruby/object:Gem::Version
|
|
416
425
|
version: '0'
|
|
417
426
|
requirements: []
|
|
418
|
-
rubygems_version: 4.0.
|
|
427
|
+
rubygems_version: 4.0.10
|
|
419
428
|
specification_version: 4
|
|
420
429
|
summary: Interacting with the Ollama API
|
|
421
430
|
test_files:
|
|
422
431
|
- spec/ollama/client/doc_spec.rb
|
|
423
432
|
- spec/ollama/client_spec.rb
|
|
433
|
+
- spec/ollama/commands/blob_exists_spec.rb
|
|
424
434
|
- spec/ollama/commands/chat_spec.rb
|
|
425
435
|
- spec/ollama/commands/copy_spec.rb
|
|
426
436
|
- spec/ollama/commands/create_spec.rb
|
|
@@ -430,10 +440,12 @@ test_files:
|
|
|
430
440
|
- spec/ollama/commands/generate_spec.rb
|
|
431
441
|
- spec/ollama/commands/ps_spec.rb
|
|
432
442
|
- spec/ollama/commands/pull_spec.rb
|
|
443
|
+
- spec/ollama/commands/push_blob_spec.rb
|
|
433
444
|
- spec/ollama/commands/push_spec.rb
|
|
434
445
|
- spec/ollama/commands/show_spec.rb
|
|
435
446
|
- spec/ollama/commands/tags_spec.rb
|
|
436
447
|
- spec/ollama/commands/version_spec.rb
|
|
448
|
+
- spec/ollama/digester_spec.rb
|
|
437
449
|
- spec/ollama/handlers/collector_spec.rb
|
|
438
450
|
- spec/ollama/handlers/dump_json_spec.rb
|
|
439
451
|
- spec/ollama/handlers/dump_yaml_spec.rb
|