ollama-ai 1.0.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 +7 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +6 -0
- data/.ruby-version +1 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +62 -0
- data/LICENSE +9 -0
- data/README.md +852 -0
- data/components/errors.rb +24 -0
- data/controllers/client.rb +144 -0
- data/ollama-ai.gemspec +35 -0
- data/ports/dsl/ollama-ai/errors.rb +5 -0
- data/ports/dsl/ollama-ai.rb +14 -0
- data/static/gem.rb +15 -0
- data/tasks/generate-readme.clj +39 -0
- data/template.md +817 -0
- metadata +76 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ollama
|
4
|
+
module Errors
|
5
|
+
class OllamaError < StandardError
|
6
|
+
def initialize(message = nil)
|
7
|
+
super(message)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class BlockWithoutServerSentEventsError < OllamaError; end
|
12
|
+
|
13
|
+
class RequestError < OllamaError
|
14
|
+
attr_reader :request, :payload
|
15
|
+
|
16
|
+
def initialize(message = nil, request: nil, payload: nil)
|
17
|
+
@request = request
|
18
|
+
@payload = payload
|
19
|
+
|
20
|
+
super(message)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
require_relative '../ports/dsl/ollama-ai/errors'
|
7
|
+
|
8
|
+
module Ollama
|
9
|
+
module Controllers
|
10
|
+
class Client
|
11
|
+
DEFAULT_ADDRESS = 'http://localhost:11434'
|
12
|
+
|
13
|
+
ALLOWED_REQUEST_OPTIONS = %i[timeout open_timeout read_timeout write_timeout].freeze
|
14
|
+
|
15
|
+
def initialize(config)
|
16
|
+
@server_sent_events = config.dig(:options, :server_sent_events)
|
17
|
+
|
18
|
+
@address = if config[:credentials][:address].nil? || config[:credentials][:address].to_s.strip.empty?
|
19
|
+
"#{DEFAULT_ADDRESS}/"
|
20
|
+
else
|
21
|
+
"#{config[:credentials][:address].to_s.sub(%r{/$}, '')}/"
|
22
|
+
end
|
23
|
+
|
24
|
+
@request_options = config.dig(:options, :connection, :request)
|
25
|
+
|
26
|
+
@request_options = if @request_options.is_a?(Hash)
|
27
|
+
@request_options.select do |key, _|
|
28
|
+
ALLOWED_REQUEST_OPTIONS.include?(key)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
{}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate(payload, server_sent_events: nil, &callback)
|
36
|
+
request('api/generate', payload, server_sent_events:, &callback)
|
37
|
+
end
|
38
|
+
|
39
|
+
def chat(payload, server_sent_events: nil, &callback)
|
40
|
+
request('api/chat', payload, server_sent_events:, &callback)
|
41
|
+
end
|
42
|
+
|
43
|
+
def create(payload, server_sent_events: nil, &callback)
|
44
|
+
request('api/create', payload, server_sent_events:, &callback)
|
45
|
+
end
|
46
|
+
|
47
|
+
def tags(server_sent_events: nil, &callback)
|
48
|
+
request('api/tags', nil, server_sent_events:, request_method: 'GET', &callback)
|
49
|
+
end
|
50
|
+
|
51
|
+
def show(payload, server_sent_events: nil, &callback)
|
52
|
+
request('api/show', payload, server_sent_events:, &callback)
|
53
|
+
end
|
54
|
+
|
55
|
+
def copy(payload, server_sent_events: nil, &callback)
|
56
|
+
request('api/copy', payload, server_sent_events:, &callback)
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def delete(payload, server_sent_events: nil, &callback)
|
61
|
+
request('api/delete', payload, server_sent_events:, request_method: 'DELETE', &callback)
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
def pull(payload, server_sent_events: nil, &callback)
|
66
|
+
request('api/pull', payload, server_sent_events:, &callback)
|
67
|
+
end
|
68
|
+
|
69
|
+
def push(payload, server_sent_events: nil, &callback)
|
70
|
+
request('api/push', payload, server_sent_events:, &callback)
|
71
|
+
end
|
72
|
+
|
73
|
+
def embeddings(payload, server_sent_events: nil, &callback)
|
74
|
+
request('api/embeddings', payload, server_sent_events:, &callback)
|
75
|
+
end
|
76
|
+
|
77
|
+
def request(path, payload = nil, server_sent_events: nil, request_method: 'POST', &callback)
|
78
|
+
server_sent_events_enabled = server_sent_events.nil? ? @server_sent_events : server_sent_events
|
79
|
+
url = "#{@address}#{path}"
|
80
|
+
|
81
|
+
if !callback.nil? && !server_sent_events_enabled
|
82
|
+
raise BlockWithoutServerSentEventsError,
|
83
|
+
'You are trying to use a block without Server Sent Events (SSE) enabled.'
|
84
|
+
end
|
85
|
+
|
86
|
+
results = []
|
87
|
+
|
88
|
+
method_to_call = request_method.to_s.strip.downcase.to_sym
|
89
|
+
|
90
|
+
partial_json = ''
|
91
|
+
|
92
|
+
response = Faraday.new(request: @request_options) do |faraday|
|
93
|
+
faraday.response :raise_error
|
94
|
+
end.send(method_to_call) do |request|
|
95
|
+
request.url url
|
96
|
+
request.headers['Content-Type'] = 'application/json'
|
97
|
+
|
98
|
+
request.body = payload.to_json unless payload.nil?
|
99
|
+
|
100
|
+
if server_sent_events_enabled
|
101
|
+
request.options.on_data = proc do |chunk, bytes, env|
|
102
|
+
if env && env.status != 200
|
103
|
+
raise_error = Faraday::Response::RaiseError.new
|
104
|
+
raise_error.on_complete(env.merge(body: chunk))
|
105
|
+
end
|
106
|
+
|
107
|
+
partial_json += chunk
|
108
|
+
|
109
|
+
parsed_json = safe_parse_json(partial_json)
|
110
|
+
|
111
|
+
if parsed_json
|
112
|
+
result = { event: parsed_json, raw: { chunk:, bytes:, env: } }
|
113
|
+
|
114
|
+
callback.call(result[:event], result[:raw]) unless callback.nil?
|
115
|
+
|
116
|
+
results << result
|
117
|
+
|
118
|
+
partial_json = ''
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
return safe_parse_jsonl(response.body) unless server_sent_events_enabled
|
125
|
+
|
126
|
+
results.map { |result| result[:event] }
|
127
|
+
rescue Faraday::Error => e
|
128
|
+
raise RequestError.new(e.message, request: e, payload:)
|
129
|
+
end
|
130
|
+
|
131
|
+
def safe_parse_json(raw)
|
132
|
+
raw.to_s.lstrip.start_with?('{', '[') ? JSON.parse(raw) : nil
|
133
|
+
rescue JSON::ParserError
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
|
137
|
+
def safe_parse_jsonl(raw)
|
138
|
+
raw.to_s.lstrip.start_with?('{', '[') ? raw.split("\n").map { |line| JSON.parse(line) } : raw
|
139
|
+
rescue JSON::ParserError
|
140
|
+
raw
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/ollama-ai.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'static/gem'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = Ollama::GEM[:name]
|
7
|
+
spec.version = Ollama::GEM[:version]
|
8
|
+
spec.authors = [Ollama::GEM[:author]]
|
9
|
+
|
10
|
+
spec.summary = Ollama::GEM[:summary]
|
11
|
+
spec.description = Ollama::GEM[:description]
|
12
|
+
|
13
|
+
spec.homepage = Ollama::GEM[:github]
|
14
|
+
|
15
|
+
spec.license = Ollama::GEM[:license]
|
16
|
+
|
17
|
+
spec.required_ruby_version = Gem::Requirement.new(">= #{Ollama::GEM[:ruby]}")
|
18
|
+
|
19
|
+
spec.metadata['allowed_push_host'] = Ollama::GEM[:gem_server]
|
20
|
+
|
21
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
22
|
+
spec.metadata['source_code_uri'] = Ollama::GEM[:github]
|
23
|
+
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
f.match(%r{\A(?:test|spec|features)/})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
spec.require_paths = ['ports/dsl']
|
31
|
+
|
32
|
+
spec.add_dependency 'faraday', '~> 2.8'
|
33
|
+
|
34
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
35
|
+
end
|
data/static/gem.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ollama
|
4
|
+
GEM = {
|
5
|
+
name: 'ollama-ai',
|
6
|
+
version: '1.0.0',
|
7
|
+
author: 'gbaptista',
|
8
|
+
summary: 'Interact with Ollama API to run open source AI models locally.',
|
9
|
+
description: "A Ruby gem for interacting with Ollama's API that allows you to run open source AI LLMs (Large Language Models) locally.",
|
10
|
+
github: 'https://github.com/gbaptista/ollama-ai',
|
11
|
+
gem_server: 'https://rubygems.org',
|
12
|
+
license: 'MIT',
|
13
|
+
ruby: '3.1.0'
|
14
|
+
}.freeze
|
15
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
(require '[clojure.string :as str])
|
2
|
+
|
3
|
+
(defn slugify [text]
|
4
|
+
(-> text
|
5
|
+
(clojure.string/lower-case)
|
6
|
+
(clojure.string/replace " " "-")
|
7
|
+
(clojure.string/replace #"[^a-z0-9\-_]" "")))
|
8
|
+
|
9
|
+
(defn remove-code-blocks [content]
|
10
|
+
(let [code-block-regex #"(?s)```.*?```"]
|
11
|
+
(clojure.string/replace content code-block-regex "")))
|
12
|
+
|
13
|
+
(defn process-line [line]
|
14
|
+
(when-let [[_ hashes title] (re-find #"^(\#{2,}) (.+)" line)]
|
15
|
+
(let [link (slugify title)]
|
16
|
+
{:level (count hashes) :title title :link link})))
|
17
|
+
|
18
|
+
(defn create-index [content]
|
19
|
+
(let [processed-content (remove-code-blocks content)
|
20
|
+
processed-lines (->> processed-content
|
21
|
+
clojure.string/split-lines
|
22
|
+
(map process-line)
|
23
|
+
(remove nil?))]
|
24
|
+
(->> processed-lines
|
25
|
+
(map (fn [{:keys [level title link]}]
|
26
|
+
(str (apply str (repeat (* 4 (- level 2)) " "))
|
27
|
+
"- ["
|
28
|
+
title
|
29
|
+
"](#"
|
30
|
+
link
|
31
|
+
")")))
|
32
|
+
(clojure.string/join "\n"))))
|
33
|
+
|
34
|
+
|
35
|
+
(let [content (slurp "template.md")
|
36
|
+
index (create-index content)
|
37
|
+
updated-content (clojure.string/replace content "{index}" index)]
|
38
|
+
(spit "README.md" updated-content)
|
39
|
+
(println "README.md successfully generated."))
|