ollama-client 0.2.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/CHANGELOG.md +12 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/CONTRIBUTING.md +36 -0
- data/LICENSE.txt +21 -0
- data/PRODUCTION_FIXES.md +172 -0
- data/README.md +690 -0
- data/Rakefile +12 -0
- data/TESTING.md +286 -0
- data/examples/advanced_complex_schemas.rb +363 -0
- data/examples/advanced_edge_cases.rb +241 -0
- data/examples/advanced_error_handling.rb +200 -0
- data/examples/advanced_multi_step_agent.rb +258 -0
- data/examples/advanced_performance_testing.rb +186 -0
- data/examples/complete_workflow.rb +235 -0
- data/examples/dhanhq_agent.rb +752 -0
- data/examples/dhanhq_tools.rb +563 -0
- data/examples/structured_outputs_chat.rb +72 -0
- data/examples/tool_calling_pattern.rb +266 -0
- data/exe/ollama-client +4 -0
- data/lib/ollama/agent/executor.rb +157 -0
- data/lib/ollama/agent/messages.rb +31 -0
- data/lib/ollama/agent/planner.rb +47 -0
- data/lib/ollama/client.rb +775 -0
- data/lib/ollama/config.rb +29 -0
- data/lib/ollama/errors.rb +54 -0
- data/lib/ollama/schema_validator.rb +79 -0
- data/lib/ollama/schemas/base.json +5 -0
- data/lib/ollama/streaming_observer.rb +22 -0
- data/lib/ollama/version.rb +5 -0
- data/lib/ollama_client.rb +46 -0
- data/sig/ollama/client.rbs +6 -0
- metadata +108 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ollama
|
|
4
|
+
# Configuration class with safe defaults for agent-grade usage
|
|
5
|
+
#
|
|
6
|
+
# ⚠️ THREAD SAFETY WARNING:
|
|
7
|
+
# Global configuration via OllamaClient.configure is NOT thread-safe.
|
|
8
|
+
# For concurrent agents or multi-threaded applications, use per-client
|
|
9
|
+
# configuration instead:
|
|
10
|
+
#
|
|
11
|
+
# config = Ollama::Config.new
|
|
12
|
+
# config.model = "llama3.1"
|
|
13
|
+
# client = Ollama::Client.new(config: config)
|
|
14
|
+
#
|
|
15
|
+
class Config
|
|
16
|
+
attr_accessor :base_url, :model, :timeout, :retries, :temperature, :top_p, :num_ctx, :on_response
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@base_url = "http://localhost:11434"
|
|
20
|
+
@model = "llama3.1:8b"
|
|
21
|
+
@timeout = 20
|
|
22
|
+
@retries = 2
|
|
23
|
+
@temperature = 0.2
|
|
24
|
+
@top_p = 0.9
|
|
25
|
+
@num_ctx = 8192
|
|
26
|
+
@on_response = nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ollama
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
class TimeoutError < Error; end
|
|
6
|
+
class InvalidJSONError < Error; end
|
|
7
|
+
class SchemaViolationError < Error; end
|
|
8
|
+
class RetryExhaustedError < Error; end
|
|
9
|
+
|
|
10
|
+
# HTTP error with retry logic
|
|
11
|
+
class HTTPError < Error
|
|
12
|
+
attr_reader :status_code
|
|
13
|
+
|
|
14
|
+
def initialize(message, status_code = nil)
|
|
15
|
+
super(message)
|
|
16
|
+
@status_code = status_code
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def retryable?
|
|
20
|
+
# Explicit retry policy:
|
|
21
|
+
# - Retry: 408 (Request Timeout), 429 (Too Many Requests),
|
|
22
|
+
# 500 (Internal Server Error), 502 (Bad Gateway), 503 (Service Unavailable)
|
|
23
|
+
# - Never retry: 400-407, 409-428, 430-499 (client errors)
|
|
24
|
+
# - Never retry: 501, 504-599 (other server errors - may indicate permanent issues)
|
|
25
|
+
return true if @status_code.nil? # Unknown status - retry for safety
|
|
26
|
+
return true if [408, 429, 500, 502, 503].include?(@status_code)
|
|
27
|
+
|
|
28
|
+
false
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Specific error for 404 Not Found responses
|
|
33
|
+
class NotFoundError < HTTPError
|
|
34
|
+
attr_reader :requested_model, :suggestions
|
|
35
|
+
|
|
36
|
+
def initialize(message = "Resource not found", requested_model: nil, suggestions: [])
|
|
37
|
+
super("HTTP 404: #{message}", 404)
|
|
38
|
+
@requested_model = requested_model
|
|
39
|
+
@suggestions = suggestions
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def retryable?
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_s
|
|
47
|
+
msg = super
|
|
48
|
+
return msg unless @requested_model && !@suggestions.empty?
|
|
49
|
+
|
|
50
|
+
suggestion_text = @suggestions.map { |m| " - #{m}" }.join("\n")
|
|
51
|
+
"#{msg}\n\nModel '#{@requested_model}' not found. Did you mean one of these?\n#{suggestion_text}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json-schema"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
|
|
6
|
+
module Ollama
|
|
7
|
+
# Validates JSON data against JSON Schema
|
|
8
|
+
#
|
|
9
|
+
# For agent-grade usage, enforces strict schemas by default:
|
|
10
|
+
# - additionalProperties: false (unless explicitly set)
|
|
11
|
+
# - Prevents LLMs from adding unexpected fields
|
|
12
|
+
class SchemaValidator
|
|
13
|
+
def self.validate!(data, schema)
|
|
14
|
+
JSON::Validator.validate!(prepare_schema(schema), data)
|
|
15
|
+
rescue JSON::Schema::ValidationError => e
|
|
16
|
+
raise SchemaViolationError, e.message
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# JSON Schema defaults to allowing additional properties unless
|
|
20
|
+
# `additionalProperties: false` is specified. For agent-grade contracts,
|
|
21
|
+
# we want the stricter default, while still allowing callers to override
|
|
22
|
+
# it explicitly on any object schema.
|
|
23
|
+
def self.prepare_schema(schema)
|
|
24
|
+
enforce_no_additional_properties(schema)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
28
|
+
def self.enforce_no_additional_properties(node)
|
|
29
|
+
case node
|
|
30
|
+
when Array
|
|
31
|
+
node.map { |v| enforce_no_additional_properties(v) }
|
|
32
|
+
when Hash
|
|
33
|
+
h = node.dup
|
|
34
|
+
|
|
35
|
+
# Recurse into common schema composition keywords
|
|
36
|
+
%w[anyOf oneOf allOf].each do |k|
|
|
37
|
+
h[k] = h[k].map { |v| enforce_no_additional_properties(v) } if h[k].is_a?(Array)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Recurse into nested schemas
|
|
41
|
+
h["not"] = enforce_no_additional_properties(h["not"]) if h["not"].is_a?(Hash)
|
|
42
|
+
|
|
43
|
+
if h["properties"].is_a?(Hash)
|
|
44
|
+
h["properties"] = h["properties"].transform_values { |v| enforce_no_additional_properties(v) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if h["patternProperties"].is_a?(Hash)
|
|
48
|
+
h["patternProperties"] = h["patternProperties"].transform_values { |v| enforce_no_additional_properties(v) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
h["items"] = enforce_no_additional_properties(h["items"]) if h["items"]
|
|
52
|
+
|
|
53
|
+
h["additionalItems"] = enforce_no_additional_properties(h["additionalItems"]) if h["additionalItems"]
|
|
54
|
+
|
|
55
|
+
# JSON Schema draft variants
|
|
56
|
+
if h["definitions"].is_a?(Hash)
|
|
57
|
+
h["definitions"] = h["definitions"].transform_values { |v| enforce_no_additional_properties(v) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
h["$defs"] = h["$defs"].transform_values { |v| enforce_no_additional_properties(v) } if h["$defs"].is_a?(Hash)
|
|
61
|
+
|
|
62
|
+
# Enforce strict object shape by default.
|
|
63
|
+
is_objectish =
|
|
64
|
+
h["type"] == "object" ||
|
|
65
|
+
h.key?("properties") ||
|
|
66
|
+
h.key?("patternProperties")
|
|
67
|
+
|
|
68
|
+
h["additionalProperties"] = false if is_objectish && !h.key?("additionalProperties")
|
|
69
|
+
|
|
70
|
+
h
|
|
71
|
+
else
|
|
72
|
+
node
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
76
|
+
|
|
77
|
+
private_class_method :prepare_schema, :enforce_no_additional_properties
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ollama
|
|
4
|
+
# Presentation-only streaming observer for agent loops.
|
|
5
|
+
# This object must never control tool execution or loop termination.
|
|
6
|
+
class StreamingObserver
|
|
7
|
+
Event = Struct.new(:type, :text, :name, :state, :data, keyword_init: true)
|
|
8
|
+
|
|
9
|
+
def initialize(&block)
|
|
10
|
+
@block = block
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def emit(type, text: nil, name: nil, state: nil, data: nil)
|
|
14
|
+
return unless @block
|
|
15
|
+
|
|
16
|
+
@block.call(Event.new(type: type, text: text, name: name, state: state, data: data))
|
|
17
|
+
rescue StandardError
|
|
18
|
+
# Observers must never break control flow.
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ollama/config"
|
|
4
|
+
require_relative "ollama/errors"
|
|
5
|
+
require_relative "ollama/schema_validator"
|
|
6
|
+
require_relative "ollama/client"
|
|
7
|
+
require_relative "ollama/streaming_observer"
|
|
8
|
+
require_relative "ollama/agent/messages"
|
|
9
|
+
require_relative "ollama/agent/planner"
|
|
10
|
+
require_relative "ollama/agent/executor"
|
|
11
|
+
|
|
12
|
+
# Main entry point for OllamaClient gem
|
|
13
|
+
#
|
|
14
|
+
# ⚠️ THREAD SAFETY WARNING:
|
|
15
|
+
# Global configuration via OllamaClient.configure is NOT thread-safe.
|
|
16
|
+
# For concurrent agents or multi-threaded applications, use per-client
|
|
17
|
+
# configuration instead:
|
|
18
|
+
#
|
|
19
|
+
# config = Ollama::Config.new
|
|
20
|
+
# config.model = "llama3.1"
|
|
21
|
+
# client = Ollama::Client.new(config: config)
|
|
22
|
+
#
|
|
23
|
+
module OllamaClient
|
|
24
|
+
@config_mutex = Mutex.new
|
|
25
|
+
@warned_thread_config = false
|
|
26
|
+
|
|
27
|
+
def self.config
|
|
28
|
+
@config_mutex.synchronize do
|
|
29
|
+
@config ||= Ollama::Config.new
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.configure
|
|
34
|
+
if Thread.current != Thread.main && !@warned_thread_config
|
|
35
|
+
@warned_thread_config = true
|
|
36
|
+
msg = "[ollama-client] Global OllamaClient.configure is not thread-safe. " \
|
|
37
|
+
"Prefer per-client config (Ollama::Client.new(config: ...))."
|
|
38
|
+
warn(msg)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@config_mutex.synchronize do
|
|
42
|
+
@config ||= Ollama::Config.new
|
|
43
|
+
yield(@config)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ollama-client
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Shubham Taywade
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bigdecimal
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: json-schema
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '4.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '4.0'
|
|
40
|
+
description: A production-ready, agent-first Ruby client for the Ollama API with schema
|
|
41
|
+
validation, bounded retries, and explicit safety defaults. Includes a minimal agent
|
|
42
|
+
layer (Ollama::Agent::Planner for deterministic /api/generate, and Ollama::Agent::Executor
|
|
43
|
+
for stateful /api/chat tool loops with disciplined, observer-only streaming). Not
|
|
44
|
+
a chatbot UI and not a promise of full Ollama endpoint coverage.
|
|
45
|
+
email:
|
|
46
|
+
- shubhamtaywade82@gmail.com
|
|
47
|
+
executables:
|
|
48
|
+
- ollama-client
|
|
49
|
+
extensions: []
|
|
50
|
+
extra_rdoc_files: []
|
|
51
|
+
files:
|
|
52
|
+
- CHANGELOG.md
|
|
53
|
+
- CODE_OF_CONDUCT.md
|
|
54
|
+
- CONTRIBUTING.md
|
|
55
|
+
- LICENSE.txt
|
|
56
|
+
- PRODUCTION_FIXES.md
|
|
57
|
+
- README.md
|
|
58
|
+
- Rakefile
|
|
59
|
+
- TESTING.md
|
|
60
|
+
- examples/advanced_complex_schemas.rb
|
|
61
|
+
- examples/advanced_edge_cases.rb
|
|
62
|
+
- examples/advanced_error_handling.rb
|
|
63
|
+
- examples/advanced_multi_step_agent.rb
|
|
64
|
+
- examples/advanced_performance_testing.rb
|
|
65
|
+
- examples/complete_workflow.rb
|
|
66
|
+
- examples/dhanhq_agent.rb
|
|
67
|
+
- examples/dhanhq_tools.rb
|
|
68
|
+
- examples/structured_outputs_chat.rb
|
|
69
|
+
- examples/tool_calling_pattern.rb
|
|
70
|
+
- exe/ollama-client
|
|
71
|
+
- lib/ollama/agent/executor.rb
|
|
72
|
+
- lib/ollama/agent/messages.rb
|
|
73
|
+
- lib/ollama/agent/planner.rb
|
|
74
|
+
- lib/ollama/client.rb
|
|
75
|
+
- lib/ollama/config.rb
|
|
76
|
+
- lib/ollama/errors.rb
|
|
77
|
+
- lib/ollama/schema_validator.rb
|
|
78
|
+
- lib/ollama/schemas/base.json
|
|
79
|
+
- lib/ollama/streaming_observer.rb
|
|
80
|
+
- lib/ollama/version.rb
|
|
81
|
+
- lib/ollama_client.rb
|
|
82
|
+
- sig/ollama/client.rbs
|
|
83
|
+
homepage: https://github.com/shubhamtaywade82/ollama-client
|
|
84
|
+
licenses:
|
|
85
|
+
- MIT
|
|
86
|
+
metadata:
|
|
87
|
+
homepage_uri: https://github.com/shubhamtaywade82/ollama-client
|
|
88
|
+
source_code_uri: https://github.com/shubhamtaywade82/ollama-client
|
|
89
|
+
changelog_uri: https://github.com/shubhamtaywade82/ollama-client/blob/main/CHANGELOG.md
|
|
90
|
+
rubygems_mfa_required: 'true'
|
|
91
|
+
rdoc_options: []
|
|
92
|
+
require_paths:
|
|
93
|
+
- lib
|
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
95
|
+
requirements:
|
|
96
|
+
- - ">="
|
|
97
|
+
- !ruby/object:Gem::Version
|
|
98
|
+
version: 3.2.0
|
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
requirements: []
|
|
105
|
+
rubygems_version: 4.0.3
|
|
106
|
+
specification_version: 4
|
|
107
|
+
summary: An agent-first Ruby client for Ollama (planner/executor + safe tool loops)
|
|
108
|
+
test_files: []
|