ollama-client 0.2.5 → 0.2.7

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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +336 -91
  4. data/RELEASE_NOTES_v0.2.6.md +41 -0
  5. data/docs/AREAS_FOR_CONSIDERATION.md +325 -0
  6. data/docs/EXAMPLE_REORGANIZATION.md +412 -0
  7. data/docs/FEATURES_ADDED.md +12 -1
  8. data/docs/GETTING_STARTED.md +361 -0
  9. data/docs/INTEGRATION_TESTING.md +170 -0
  10. data/docs/NEXT_STEPS_SUMMARY.md +114 -0
  11. data/docs/PERSONAS.md +383 -0
  12. data/docs/QUICK_START.md +195 -0
  13. data/docs/TESTING.md +392 -170
  14. data/docs/TEST_CHECKLIST.md +450 -0
  15. data/examples/README.md +62 -63
  16. data/examples/basic_chat.rb +33 -0
  17. data/examples/basic_generate.rb +29 -0
  18. data/examples/mcp_executor.rb +39 -0
  19. data/examples/mcp_http_executor.rb +45 -0
  20. data/examples/tool_calling_parsing.rb +59 -0
  21. data/examples/tool_dto_example.rb +0 -0
  22. data/exe/ollama-client +128 -1
  23. data/lib/ollama/agent/planner.rb +7 -2
  24. data/lib/ollama/chat_session.rb +101 -0
  25. data/lib/ollama/client.rb +41 -35
  26. data/lib/ollama/config.rb +9 -4
  27. data/lib/ollama/document_loader.rb +1 -1
  28. data/lib/ollama/embeddings.rb +61 -28
  29. data/lib/ollama/errors.rb +1 -0
  30. data/lib/ollama/mcp/http_client.rb +149 -0
  31. data/lib/ollama/mcp/stdio_client.rb +146 -0
  32. data/lib/ollama/mcp/tools_bridge.rb +72 -0
  33. data/lib/ollama/mcp.rb +31 -0
  34. data/lib/ollama/options.rb +3 -1
  35. data/lib/ollama/personas.rb +287 -0
  36. data/lib/ollama/version.rb +1 -1
  37. data/lib/ollama_client.rb +17 -5
  38. metadata +22 -48
  39. data/examples/advanced_complex_schemas.rb +0 -366
  40. data/examples/advanced_edge_cases.rb +0 -241
  41. data/examples/advanced_error_handling.rb +0 -200
  42. data/examples/advanced_multi_step_agent.rb +0 -341
  43. data/examples/advanced_performance_testing.rb +0 -186
  44. data/examples/chat_console.rb +0 -143
  45. data/examples/complete_workflow.rb +0 -245
  46. data/examples/dhan_console.rb +0 -843
  47. data/examples/dhanhq/README.md +0 -236
  48. data/examples/dhanhq/agents/base_agent.rb +0 -74
  49. data/examples/dhanhq/agents/data_agent.rb +0 -66
  50. data/examples/dhanhq/agents/orchestrator_agent.rb +0 -120
  51. data/examples/dhanhq/agents/technical_analysis_agent.rb +0 -252
  52. data/examples/dhanhq/agents/trading_agent.rb +0 -81
  53. data/examples/dhanhq/analysis/market_structure.rb +0 -138
  54. data/examples/dhanhq/analysis/pattern_recognizer.rb +0 -192
  55. data/examples/dhanhq/analysis/trend_analyzer.rb +0 -88
  56. data/examples/dhanhq/builders/market_context_builder.rb +0 -67
  57. data/examples/dhanhq/dhanhq_agent.rb +0 -829
  58. data/examples/dhanhq/indicators/technical_indicators.rb +0 -158
  59. data/examples/dhanhq/scanners/intraday_options_scanner.rb +0 -492
  60. data/examples/dhanhq/scanners/swing_scanner.rb +0 -247
  61. data/examples/dhanhq/schemas/agent_schemas.rb +0 -61
  62. data/examples/dhanhq/services/base_service.rb +0 -46
  63. data/examples/dhanhq/services/data_service.rb +0 -118
  64. data/examples/dhanhq/services/trading_service.rb +0 -59
  65. data/examples/dhanhq/technical_analysis_agentic_runner.rb +0 -411
  66. data/examples/dhanhq/technical_analysis_runner.rb +0 -420
  67. data/examples/dhanhq/test_tool_calling.rb +0 -538
  68. data/examples/dhanhq/test_tool_calling_verbose.rb +0 -251
  69. data/examples/dhanhq/utils/instrument_helper.rb +0 -32
  70. data/examples/dhanhq/utils/parameter_cleaner.rb +0 -28
  71. data/examples/dhanhq/utils/parameter_normalizer.rb +0 -45
  72. data/examples/dhanhq/utils/rate_limiter.rb +0 -23
  73. data/examples/dhanhq/utils/trading_parameter_normalizer.rb +0 -72
  74. data/examples/dhanhq_agent.rb +0 -964
  75. data/examples/dhanhq_tools.rb +0 -1663
  76. data/examples/multi_step_agent_with_external_data.rb +0 -368
  77. data/examples/structured_outputs_chat.rb +0 -72
  78. data/examples/structured_tools.rb +0 -89
  79. data/examples/test_dhanhq_tool_calling.rb +0 -375
  80. data/examples/test_tool_calling.rb +0 -160
  81. data/examples/tool_calling_direct.rb +0 -124
  82. data/examples/tool_calling_pattern.rb +0 -269
  83. data/exe/dhan_console +0 -4
@@ -1,241 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Advanced Example: Edge Cases and Boundary Testing
5
- # Demonstrates: Empty responses, malformed schemas, extreme values, special characters
6
-
7
- require "json"
8
- require_relative "../lib/ollama_client"
9
-
10
- class EdgeCaseTester
11
- def initialize(client:)
12
- @client = client
13
- end
14
-
15
- def test_empty_prompt
16
- puts "Test 1: Empty prompt"
17
- schema = {
18
- "type" => "object",
19
- "properties" => {
20
- "response" => { "type" => "string" }
21
- }
22
- }
23
-
24
- begin
25
- result = @client.generate(prompt: "", schema: schema)
26
- puts " ✅ Handled empty prompt: #{result.inspect[0..100]}"
27
- rescue Ollama::Error => e
28
- puts " ❌ Error: #{e.class} - #{e.message}"
29
- end
30
- puts
31
- end
32
-
33
- def test_very_long_prompt
34
- puts "Test 2: Very long prompt (10KB+)"
35
- long_prompt = "Repeat this sentence. " * 500
36
- schema = {
37
- "type" => "object",
38
- "properties" => {
39
- "summary" => { "type" => "string" }
40
- }
41
- }
42
-
43
- begin
44
- @client.generate(prompt: long_prompt, schema: schema)
45
- puts " ✅ Handled long prompt (#{long_prompt.length} chars)"
46
- rescue Ollama::Error => e
47
- puts " ❌ Error: #{e.class} - #{e.message}"
48
- end
49
- puts
50
- end
51
-
52
- def test_special_characters
53
- puts "Test 3: Special characters in prompt"
54
- special_prompt = "Analyze: !@#$%^&*()_+-=[]{}|;':\",./<>?`~"
55
- schema = {
56
- "type" => "object",
57
- "properties" => {
58
- "analysis" => { "type" => "string" }
59
- }
60
- }
61
-
62
- begin
63
- @client.generate(prompt: special_prompt, schema: schema)
64
- puts " ✅ Handled special characters"
65
- rescue Ollama::Error => e
66
- puts " ❌ Error: #{e.class} - #{e.message}"
67
- end
68
- puts
69
- end
70
-
71
- def test_unicode_characters
72
- puts "Test 4: Unicode characters"
73
- unicode_prompt = "Analyze: 你好世界 🌍 🚀 émojis and spéciál chäracters"
74
- schema = {
75
- "type" => "object",
76
- "properties" => {
77
- "analysis" => { "type" => "string" }
78
- }
79
- }
80
-
81
- begin
82
- @client.generate(prompt: unicode_prompt, schema: schema)
83
- puts " ✅ Handled unicode characters"
84
- rescue Ollama::Error => e
85
- puts " ❌ Error: #{e.class} - #{e.message}"
86
- end
87
- puts
88
- end
89
-
90
- def test_minimal_schema
91
- puts "Test 5: Minimal schema (no required fields)"
92
- schema = {
93
- "type" => "object",
94
- "additionalProperties" => true
95
- }
96
-
97
- begin
98
- result = @client.generate(
99
- prompt: "Return any JSON object",
100
- schema: schema
101
- )
102
- puts " ✅ Handled minimal schema: #{result.keys.join(', ')}"
103
- rescue Ollama::Error => e
104
- puts " ❌ Error: #{e.class} - #{e.message}"
105
- end
106
- puts
107
- end
108
-
109
- def test_strict_schema
110
- puts "Test 6: Strict schema with many constraints"
111
- strict_schema = {
112
- "type" => "object",
113
- "required" => ["id", "name", "values"],
114
- "properties" => {
115
- "id" => {
116
- "type" => "integer",
117
- "minimum" => 1,
118
- "maximum" => 1000
119
- },
120
- "name" => {
121
- "type" => "string",
122
- "minLength" => 3,
123
- "maxLength" => 20,
124
- "pattern" => "^[A-Za-z0-9_]+$"
125
- },
126
- "values" => {
127
- "type" => "array",
128
- "minItems" => 2,
129
- "maxItems" => 5,
130
- "items" => {
131
- "type" => "number",
132
- "minimum" => 0,
133
- "maximum" => 100
134
- }
135
- }
136
- }
137
- }
138
-
139
- begin
140
- prompt_text = "Generate a valid object with id (1-1000), " \
141
- "name (3-20 alphanumeric chars), " \
142
- "and values (2-5 numbers 0-100)"
143
- result = @client.generate(
144
- prompt: prompt_text,
145
- schema: strict_schema
146
- )
147
- puts " ✅ Handled strict schema"
148
- puts " ID: #{result['id']}, Name: #{result['name']}, Values: #{result['values']}"
149
- rescue Ollama::Error => e
150
- puts " ❌ Error: #{e.class} - #{e.message}"
151
- end
152
- puts
153
- end
154
-
155
- def test_nested_arrays
156
- puts "Test 7: Deeply nested arrays"
157
- nested_schema = {
158
- "type" => "object",
159
- "properties" => {
160
- "matrix" => {
161
- "type" => "array",
162
- "items" => {
163
- "type" => "array",
164
- "items" => {
165
- "type" => "array",
166
- "items" => { "type" => "integer" }
167
- }
168
- }
169
- }
170
- }
171
- }
172
-
173
- begin
174
- @client.generate(
175
- prompt: "Generate a 2x2x2 matrix of integers",
176
- schema: nested_schema
177
- )
178
- puts " ✅ Handled nested arrays"
179
- rescue Ollama::Error => e
180
- puts " ❌ Error: #{e.class} - #{e.message}"
181
- end
182
- puts
183
- end
184
-
185
- def test_enum_constraints
186
- puts "Test 8: Strict enum constraints"
187
- enum_schema = {
188
- "type" => "object",
189
- "required" => ["status", "priority"],
190
- "properties" => {
191
- "status" => {
192
- "type" => "string",
193
- "enum" => ["pending", "in_progress", "completed", "failed"]
194
- },
195
- "priority" => {
196
- "type" => "string",
197
- "enum" => ["low", "medium", "high", "urgent"]
198
- }
199
- }
200
- }
201
-
202
- begin
203
- result = @client.generate(
204
- prompt: "Choose a status and priority from the allowed values",
205
- schema: enum_schema
206
- )
207
- puts " ✅ Handled enum constraints: status=#{result['status']}, priority=#{result['priority']}"
208
- rescue Ollama::Error => e
209
- puts " ❌ Error: #{e.class} - #{e.message}"
210
- end
211
- puts
212
- end
213
-
214
- def run_all_tests
215
- puts "=" * 60
216
- puts "Edge Case Testing Suite"
217
- puts "=" * 60
218
- puts
219
-
220
- test_empty_prompt
221
- test_very_long_prompt
222
- test_special_characters
223
- test_unicode_characters
224
- test_minimal_schema
225
- test_strict_schema
226
- test_nested_arrays
227
- test_enum_constraints
228
-
229
- puts "=" * 60
230
- puts "Edge case testing complete!"
231
- puts "=" * 60
232
- end
233
- end
234
-
235
- # Run tests
236
- if __FILE__ == $PROGRAM_NAME
237
- client = Ollama::Client.new
238
- tester = EdgeCaseTester.new(client: client)
239
- tester.run_all_tests
240
- end
241
-
@@ -1,200 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Advanced Example: Comprehensive Error Handling and Recovery Patterns
5
- # Demonstrates: All error types, retry strategies, fallback mechanisms, observability
6
-
7
- require "json"
8
- require_relative "../lib/ollama_client"
9
-
10
- class ResilientAgent
11
- def initialize(client:)
12
- @client = client
13
- @stats = {
14
- total_calls: 0,
15
- successes: 0,
16
- retries: 0,
17
- failures: 0,
18
- errors_by_type: {}
19
- }
20
- end
21
-
22
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
23
- def execute_with_resilience(prompt:, schema:, max_attempts: 3)
24
- @stats[:total_calls] += 1
25
- attempt = 0
26
-
27
- loop do
28
- attempt += 1
29
- puts "\n📞 Attempt #{attempt}/#{max_attempts}"
30
-
31
- begin
32
- result = @client.generate(prompt: prompt, schema: schema)
33
- @stats[:successes] += 1
34
- puts "✅ Success on attempt #{attempt}"
35
- return { success: true, result: result, attempts: attempt }
36
- rescue Ollama::NotFoundError => e
37
- @stats[:failures] += 1
38
- @stats[:errors_by_type]["NotFoundError"] ||= 0
39
- @stats[:errors_by_type]["NotFoundError"] += 1
40
-
41
- puts "❌ Model not found: #{e.message}"
42
- puts " Suggestions: #{e.suggestions.join(', ')}" if e.suggestions && !e.suggestions.empty?
43
- # Don't retry 404s
44
- return { success: false, error: e, error_type: "NotFoundError", attempts: attempt }
45
- rescue Ollama::HTTPError => e
46
- @stats[:failures] += 1
47
- @stats[:errors_by_type]["HTTPError"] ||= 0
48
- @stats[:errors_by_type]["HTTPError"] += 1
49
-
50
- puts "❌ HTTP Error (#{e.status_code}): #{e.message}"
51
- if e.retryable?
52
- @stats[:retries] += 1
53
- puts " → Retryable, will retry..."
54
- if attempt > max_attempts
55
- return { success: false, error: e, error_type: "HTTPError", retryable: true,
56
- attempts: attempt }
57
- end
58
-
59
- sleep(2**attempt) # Exponential backoff
60
- next
61
- else
62
- puts " → Non-retryable, aborting"
63
- return { success: false, error: e, error_type: "HTTPError", retryable: false, attempts: attempt }
64
- end
65
- rescue Ollama::TimeoutError => e
66
- @stats[:failures] += 1
67
- @stats[:errors_by_type]["TimeoutError"] ||= 0
68
- @stats[:errors_by_type]["TimeoutError"] += 1
69
-
70
- puts "⏱️ Timeout: #{e.message}"
71
- @stats[:retries] += 1
72
- return { success: false, error: e, error_type: "TimeoutError", attempts: attempt } unless attempt < max_attempts
73
-
74
- puts " → Retrying with exponential backoff..."
75
- sleep(2**attempt)
76
- next
77
- rescue Ollama::SchemaViolationError => e
78
- @stats[:failures] += 1
79
- @stats[:errors_by_type]["SchemaViolationError"] ||= 0
80
- @stats[:errors_by_type]["SchemaViolationError"] += 1
81
-
82
- puts "🔴 Schema violation: #{e.message}"
83
- # Schema violations are usually not worth retrying (model issue)
84
- # But we could try with a simpler schema as fallback
85
- unless attempt < max_attempts
86
- return { success: false, error: e, error_type: "SchemaViolationError", attempts: attempt }
87
- end
88
-
89
- puts " → Attempting with simplified schema..."
90
- simplified_schema = create_fallback_schema(schema)
91
- return execute_with_resilience(
92
- prompt: prompt,
93
- schema: simplified_schema,
94
- max_attempts: 1
95
- )
96
- rescue Ollama::InvalidJSONError => e
97
- @stats[:failures] += 1
98
- @stats[:errors_by_type]["InvalidJSONError"] ||= 0
99
- @stats[:errors_by_type]["InvalidJSONError"] += 1
100
-
101
- puts "📄 Invalid JSON: #{e.message}"
102
- @stats[:retries] += 1
103
- unless attempt < max_attempts
104
- return { success: false, error: e, error_type: "InvalidJSONError", attempts: attempt }
105
- end
106
-
107
- puts " → Retrying..."
108
- sleep(1)
109
- next
110
- rescue Ollama::RetryExhaustedError => e
111
- @stats[:failures] += 1
112
- @stats[:errors_by_type]["RetryExhaustedError"] ||= 0
113
- @stats[:errors_by_type]["RetryExhaustedError"] += 1
114
-
115
- puts "🔄 Retries exhausted: #{e.message}"
116
- return { success: false, error: e, error_type: "RetryExhaustedError", attempts: attempt }
117
- rescue Ollama::Error => e
118
- @stats[:failures] += 1
119
- @stats[:errors_by_type]["Error"] ||= 0
120
- @stats[:errors_by_type]["Error"] += 1
121
-
122
- puts "❌ General error: #{e.message}"
123
- return { success: false, error: e, error_type: "Error", attempts: attempt }
124
- rescue StandardError => e
125
- @stats[:failures] += 1
126
- @stats[:errors_by_type]["StandardError"] ||= 0
127
- @stats[:errors_by_type]["StandardError"] += 1
128
-
129
- puts "💥 Unexpected error: #{e.class}: #{e.message}"
130
- return { success: false, error: e, error_type: "StandardError", attempts: attempt }
131
- end
132
- end
133
- end
134
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
135
-
136
- def create_fallback_schema(_original_schema)
137
- # Create a minimal fallback schema
138
- {
139
- "type" => "object",
140
- "additionalProperties" => true
141
- }
142
- end
143
-
144
- def display_stats
145
- puts "\n" + "=" * 60
146
- puts "Execution Statistics"
147
- puts "=" * 60
148
- puts "Total calls: #{@stats[:total_calls]}"
149
- puts "Successes: #{@stats[:successes]}"
150
- puts "Failures: #{@stats[:failures]}"
151
- puts "Retries: #{@stats[:retries]}"
152
- success_rate = @stats[:total_calls].positive? ? (@stats[:successes].to_f / @stats[:total_calls] * 100).round(2) : 0
153
- puts "Success rate: #{success_rate}%"
154
- puts "\nErrors by type:"
155
- @stats[:errors_by_type].each do |type, count|
156
- puts " #{type}: #{count}"
157
- end
158
- end
159
- end
160
-
161
- # Test scenarios
162
- if __FILE__ == $PROGRAM_NAME
163
- client = Ollama::Client.new
164
- agent = ResilientAgent.new(client: client)
165
-
166
- schema = {
167
- "type" => "object",
168
- "required" => ["status", "message"],
169
- "properties" => {
170
- "status" => { "type" => "string" },
171
- "message" => { "type" => "string" }
172
- }
173
- }
174
-
175
- puts "=" * 60
176
- puts "Test 1: Normal execution"
177
- puts "=" * 60
178
- result1 = agent.execute_with_resilience(
179
- prompt: "Respond with status 'ok' and a greeting message",
180
- schema: schema
181
- )
182
- puts "Result: #{result1[:success] ? 'SUCCESS' : 'FAILED'}"
183
-
184
- puts "\n" + "=" * 60
185
- puts "Test 2: Invalid model (should trigger NotFoundError)"
186
- puts "=" * 60
187
- # Temporarily use invalid model
188
- invalid_client = Ollama::Client.new(
189
- config: Ollama::Config.new.tap { |c| c.model = "nonexistent-model:999" }
190
- )
191
- invalid_agent = ResilientAgent.new(client: invalid_client)
192
- result2 = invalid_agent.execute_with_resilience(
193
- prompt: "Test",
194
- schema: schema
195
- )
196
- puts "Result: #{result2[:success] ? 'SUCCESS' : 'FAILED'}"
197
-
198
- agent.display_stats
199
- end
200
-