ollama-client 0.2.4 → 0.2.5
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/CHANGELOG.md +8 -1
- data/README.md +431 -39
- data/docs/README.md +2 -3
- data/docs/RELEASE_GUIDE.md +376 -0
- data/docs/ruby_guide.md +6232 -0
- data/lib/ollama/client.rb +18 -2
- data/lib/ollama/document_loader.rb +163 -0
- data/lib/ollama/embeddings.rb +14 -0
- data/lib/ollama/version.rb +1 -1
- data/lib/ollama_client.rb +1 -0
- metadata +19 -7
- data/docs/GEM_RELEASE_GUIDE.md +0 -794
- data/docs/GET_RUBYGEMS_SECRET.md +0 -151
- data/docs/QUICK_OTP_SETUP.md +0 -80
- data/docs/QUICK_RELEASE.md +0 -106
- data/docs/RUBYGEMS_OTP_SETUP.md +0 -199
data/lib/ollama/client.rb
CHANGED
|
@@ -206,7 +206,7 @@ module Ollama
|
|
|
206
206
|
end
|
|
207
207
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/ParameterLists
|
|
208
208
|
|
|
209
|
-
def generate(prompt:, schema
|
|
209
|
+
def generate(prompt:, schema: nil, model: nil, strict: false, return_meta: false)
|
|
210
210
|
attempts = 0
|
|
211
211
|
@current_schema = schema # Store for prompt enhancement
|
|
212
212
|
started_at = monotonic_time
|
|
@@ -227,6 +227,22 @@ module Ollama
|
|
|
227
227
|
}
|
|
228
228
|
)
|
|
229
229
|
|
|
230
|
+
# If no schema provided, return plain text/markdown
|
|
231
|
+
unless schema
|
|
232
|
+
return raw unless return_meta
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
"data" => raw,
|
|
236
|
+
"meta" => {
|
|
237
|
+
"endpoint" => "/api/generate",
|
|
238
|
+
"model" => model || @config.model,
|
|
239
|
+
"attempts" => attempts,
|
|
240
|
+
"latency_ms" => elapsed_ms(started_at)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Schema provided - parse and validate JSON
|
|
230
246
|
parsed = parse_json_response(raw)
|
|
231
247
|
|
|
232
248
|
# CRITICAL: If schema is provided, free-text output is forbidden
|
|
@@ -267,7 +283,7 @@ module Ollama
|
|
|
267
283
|
end
|
|
268
284
|
# rubocop:enable Metrics/MethodLength
|
|
269
285
|
|
|
270
|
-
def generate_strict!(prompt:, schema
|
|
286
|
+
def generate_strict!(prompt:, schema: nil, model: nil, return_meta: false)
|
|
271
287
|
generate(prompt: prompt, schema: schema, model: model, strict: true, return_meta: return_meta)
|
|
272
288
|
end
|
|
273
289
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "csv"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Ollama
|
|
7
|
+
# Document loader for RAG (Retrieval-Augmented Generation)
|
|
8
|
+
#
|
|
9
|
+
# Loads files from a directory and provides them as context for LLM queries.
|
|
10
|
+
# Supports: .txt, .md, .csv, .json files
|
|
11
|
+
#
|
|
12
|
+
# Example:
|
|
13
|
+
# loader = Ollama::DocumentLoader.new("docs/")
|
|
14
|
+
# context = loader.load_all
|
|
15
|
+
# result = client.generate(
|
|
16
|
+
# prompt: "Context: #{context}\n\nQuestion: What is Ruby?",
|
|
17
|
+
# schema: {...}
|
|
18
|
+
# )
|
|
19
|
+
class DocumentLoader
|
|
20
|
+
SUPPORTED_EXTENSIONS = %w[.txt .md .markdown .csv .json].freeze
|
|
21
|
+
|
|
22
|
+
def initialize(directory, extensions: SUPPORTED_EXTENSIONS)
|
|
23
|
+
@directory = File.expand_path(directory)
|
|
24
|
+
@extensions = extensions
|
|
25
|
+
@documents = {}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Load all supported files from the directory
|
|
29
|
+
#
|
|
30
|
+
# @param recursive [Boolean] Load files from subdirectories
|
|
31
|
+
# @return [Hash] Hash of filename => content
|
|
32
|
+
def load_all(recursive: false)
|
|
33
|
+
raise Error, "Directory not found: #{@directory}" unless Dir.exist?(@directory)
|
|
34
|
+
|
|
35
|
+
pattern = recursive ? "**/*" : "*"
|
|
36
|
+
files = Dir.glob(File.join(@directory, pattern)).select { |f| File.file?(f) }
|
|
37
|
+
|
|
38
|
+
loaded_count = 0
|
|
39
|
+
files.each do |file|
|
|
40
|
+
next unless supported_file?(file)
|
|
41
|
+
|
|
42
|
+
filename = File.basename(file)
|
|
43
|
+
content = load_file(file)
|
|
44
|
+
next if content.nil?
|
|
45
|
+
|
|
46
|
+
@documents[filename] = content
|
|
47
|
+
loaded_count += 1
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
@documents
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Load a specific file
|
|
54
|
+
#
|
|
55
|
+
# @param filename [String] Name of the file to load (can be relative or absolute)
|
|
56
|
+
# @return [String] File content as text, or nil if file not found
|
|
57
|
+
def load_file(filename)
|
|
58
|
+
# Handle both relative and absolute paths
|
|
59
|
+
full_path = if File.absolute_path?(filename)
|
|
60
|
+
filename
|
|
61
|
+
else
|
|
62
|
+
File.join(@directory, filename)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
return nil unless File.exist?(full_path)
|
|
66
|
+
|
|
67
|
+
# Store with basename for consistency
|
|
68
|
+
basename = File.basename(filename)
|
|
69
|
+
|
|
70
|
+
ext = File.extname(filename).downcase
|
|
71
|
+
content = File.read(full_path)
|
|
72
|
+
|
|
73
|
+
parsed_content = case ext
|
|
74
|
+
when ".csv"
|
|
75
|
+
parse_csv(content)
|
|
76
|
+
when ".json"
|
|
77
|
+
parse_json(content)
|
|
78
|
+
else
|
|
79
|
+
# .txt, .md, .markdown, or any other text file
|
|
80
|
+
content
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Store in documents hash
|
|
84
|
+
@documents[basename] = parsed_content
|
|
85
|
+
parsed_content
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Get all loaded documents as a single context string
|
|
89
|
+
#
|
|
90
|
+
# @param separator [String] Separator between documents
|
|
91
|
+
# @return [String] Combined context from all documents
|
|
92
|
+
def to_context(separator: "\n\n---\n\n")
|
|
93
|
+
@documents.map do |filename, content|
|
|
94
|
+
"File: #{filename}\n#{content}"
|
|
95
|
+
end.join(separator)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Get documents matching a pattern
|
|
99
|
+
#
|
|
100
|
+
# @param pattern [String, Regexp] Pattern to match filenames
|
|
101
|
+
# @return [Hash] Matching documents
|
|
102
|
+
def select(pattern)
|
|
103
|
+
if pattern.is_a?(Regexp)
|
|
104
|
+
@documents.select { |filename, _| filename.match?(pattern) }
|
|
105
|
+
else
|
|
106
|
+
@documents.select { |filename, _| filename.include?(pattern.to_s) }
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Get a specific document by filename
|
|
111
|
+
#
|
|
112
|
+
# @param filename [String] Name of the document
|
|
113
|
+
# @return [String, nil] Document content or nil if not found
|
|
114
|
+
def [](filename)
|
|
115
|
+
@documents[filename]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# List all loaded document names
|
|
119
|
+
#
|
|
120
|
+
# @return [Array<String>] Array of filenames
|
|
121
|
+
def files
|
|
122
|
+
@documents.keys
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Check if any documents are loaded
|
|
126
|
+
#
|
|
127
|
+
# @return [Boolean] True if documents are loaded
|
|
128
|
+
def empty?
|
|
129
|
+
@documents.empty?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def supported_file?(file)
|
|
135
|
+
ext = File.extname(file).downcase
|
|
136
|
+
@extensions.include?(ext)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def parse_csv(content)
|
|
140
|
+
rows = CSV.parse(content, headers: true)
|
|
141
|
+
return content if rows.empty?
|
|
142
|
+
|
|
143
|
+
# Convert CSV to readable text format
|
|
144
|
+
headers = rows.headers || []
|
|
145
|
+
text_rows = rows.map do |row|
|
|
146
|
+
if headers.any?
|
|
147
|
+
headers.map { |h| "#{h}: #{row[h]}" }.join(", ")
|
|
148
|
+
else
|
|
149
|
+
row.fields.join(", ")
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
"CSV Data:\n" + text_rows.join("\n")
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def parse_json(content)
|
|
157
|
+
parsed = JSON.parse(content)
|
|
158
|
+
JSON.pretty_generate(parsed)
|
|
159
|
+
rescue JSON::ParserError
|
|
160
|
+
content
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
data/lib/ollama/embeddings.rb
CHANGED
|
@@ -44,6 +44,20 @@ module Ollama
|
|
|
44
44
|
response_body = JSON.parse(res.body)
|
|
45
45
|
embedding = response_body["embedding"]
|
|
46
46
|
|
|
47
|
+
if embedding.nil?
|
|
48
|
+
raise Error,
|
|
49
|
+
"Embedding not found in response. Response keys: #{response_body.keys.join(", ")}. Full response: #{response_body.inspect[0..200]}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if embedding.is_a?(Array) && embedding.empty?
|
|
53
|
+
error_msg = "Empty embedding returned. This usually means:\n"
|
|
54
|
+
error_msg += " 1. The model may not be properly loaded - try: ollama pull #{model}\n"
|
|
55
|
+
error_msg += " 2. The model may not support embeddings - verify it's an embedding model\n"
|
|
56
|
+
error_msg += " 3. Check if the model is working: curl http://localhost:11434/api/embeddings -d '{\"model\":\"#{model}\",\"input\":\"test\"}'\n"
|
|
57
|
+
error_msg += "Response: #{response_body.inspect[0..300]}"
|
|
58
|
+
raise Error, error_msg
|
|
59
|
+
end
|
|
60
|
+
|
|
47
61
|
# Return single array for single input, or array of arrays for multiple inputs
|
|
48
62
|
if input.is_a?(Array)
|
|
49
63
|
# Ollama returns single embedding array even for multiple inputs
|
data/lib/ollama/version.rb
CHANGED
data/lib/ollama_client.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative "ollama/options"
|
|
|
7
7
|
require_relative "ollama/response"
|
|
8
8
|
require_relative "ollama/tool"
|
|
9
9
|
require_relative "ollama/client"
|
|
10
|
+
require_relative "ollama/document_loader"
|
|
10
11
|
require_relative "ollama/streaming_observer"
|
|
11
12
|
require_relative "ollama/agent/messages"
|
|
12
13
|
require_relative "ollama/agent/planner"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ollama-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shubham Taywade
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-01-
|
|
11
|
+
date: 2026-01-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bigdecimal
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: csv
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: json-schema
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -60,17 +74,14 @@ files:
|
|
|
60
74
|
- docs/CLOUD.md
|
|
61
75
|
- docs/CONSOLE_IMPROVEMENTS.md
|
|
62
76
|
- docs/FEATURES_ADDED.md
|
|
63
|
-
- docs/GEM_RELEASE_GUIDE.md
|
|
64
|
-
- docs/GET_RUBYGEMS_SECRET.md
|
|
65
77
|
- docs/HANDLERS_ANALYSIS.md
|
|
66
78
|
- docs/PRODUCTION_FIXES.md
|
|
67
|
-
- docs/QUICK_OTP_SETUP.md
|
|
68
|
-
- docs/QUICK_RELEASE.md
|
|
69
79
|
- docs/README.md
|
|
70
|
-
- docs/
|
|
80
|
+
- docs/RELEASE_GUIDE.md
|
|
71
81
|
- docs/SCHEMA_FIXES.md
|
|
72
82
|
- docs/TESTING.md
|
|
73
83
|
- docs/TEST_UPDATES.md
|
|
84
|
+
- docs/ruby_guide.md
|
|
74
85
|
- examples/README.md
|
|
75
86
|
- examples/advanced_complex_schemas.rb
|
|
76
87
|
- examples/advanced_edge_cases.rb
|
|
@@ -124,6 +135,7 @@ files:
|
|
|
124
135
|
- lib/ollama/agent/planner.rb
|
|
125
136
|
- lib/ollama/client.rb
|
|
126
137
|
- lib/ollama/config.rb
|
|
138
|
+
- lib/ollama/document_loader.rb
|
|
127
139
|
- lib/ollama/dto.rb
|
|
128
140
|
- lib/ollama/embeddings.rb
|
|
129
141
|
- lib/ollama/errors.rb
|