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.
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:, model: nil, strict: false, return_meta: false)
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:, model: nil, return_meta: false)
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ollama
4
- VERSION = "0.2.4"
4
+ VERSION = "0.2.5"
5
5
  end
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
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-18 00:00:00.000000000 Z
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/RUBYGEMS_OTP_SETUP.md
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