ollama-client 0.2.5 → 0.2.6

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/README.md +138 -76
  4. data/docs/EXAMPLE_REORGANIZATION.md +412 -0
  5. data/docs/GETTING_STARTED.md +361 -0
  6. data/docs/INTEGRATION_TESTING.md +170 -0
  7. data/docs/NEXT_STEPS_SUMMARY.md +114 -0
  8. data/docs/PERSONAS.md +383 -0
  9. data/docs/QUICK_START.md +195 -0
  10. data/docs/TESTING.md +392 -170
  11. data/docs/TEST_CHECKLIST.md +450 -0
  12. data/examples/README.md +51 -66
  13. data/examples/basic_chat.rb +33 -0
  14. data/examples/basic_generate.rb +29 -0
  15. data/examples/tool_calling_parsing.rb +59 -0
  16. data/exe/ollama-client +128 -1
  17. data/lib/ollama/agent/planner.rb +7 -2
  18. data/lib/ollama/chat_session.rb +101 -0
  19. data/lib/ollama/client.rb +41 -35
  20. data/lib/ollama/config.rb +4 -1
  21. data/lib/ollama/document_loader.rb +1 -1
  22. data/lib/ollama/embeddings.rb +41 -26
  23. data/lib/ollama/errors.rb +1 -0
  24. data/lib/ollama/personas.rb +287 -0
  25. data/lib/ollama/version.rb +1 -1
  26. data/lib/ollama_client.rb +7 -0
  27. metadata +14 -48
  28. data/examples/advanced_complex_schemas.rb +0 -366
  29. data/examples/advanced_edge_cases.rb +0 -241
  30. data/examples/advanced_error_handling.rb +0 -200
  31. data/examples/advanced_multi_step_agent.rb +0 -341
  32. data/examples/advanced_performance_testing.rb +0 -186
  33. data/examples/chat_console.rb +0 -143
  34. data/examples/complete_workflow.rb +0 -245
  35. data/examples/dhan_console.rb +0 -843
  36. data/examples/dhanhq/README.md +0 -236
  37. data/examples/dhanhq/agents/base_agent.rb +0 -74
  38. data/examples/dhanhq/agents/data_agent.rb +0 -66
  39. data/examples/dhanhq/agents/orchestrator_agent.rb +0 -120
  40. data/examples/dhanhq/agents/technical_analysis_agent.rb +0 -252
  41. data/examples/dhanhq/agents/trading_agent.rb +0 -81
  42. data/examples/dhanhq/analysis/market_structure.rb +0 -138
  43. data/examples/dhanhq/analysis/pattern_recognizer.rb +0 -192
  44. data/examples/dhanhq/analysis/trend_analyzer.rb +0 -88
  45. data/examples/dhanhq/builders/market_context_builder.rb +0 -67
  46. data/examples/dhanhq/dhanhq_agent.rb +0 -829
  47. data/examples/dhanhq/indicators/technical_indicators.rb +0 -158
  48. data/examples/dhanhq/scanners/intraday_options_scanner.rb +0 -492
  49. data/examples/dhanhq/scanners/swing_scanner.rb +0 -247
  50. data/examples/dhanhq/schemas/agent_schemas.rb +0 -61
  51. data/examples/dhanhq/services/base_service.rb +0 -46
  52. data/examples/dhanhq/services/data_service.rb +0 -118
  53. data/examples/dhanhq/services/trading_service.rb +0 -59
  54. data/examples/dhanhq/technical_analysis_agentic_runner.rb +0 -411
  55. data/examples/dhanhq/technical_analysis_runner.rb +0 -420
  56. data/examples/dhanhq/test_tool_calling.rb +0 -538
  57. data/examples/dhanhq/test_tool_calling_verbose.rb +0 -251
  58. data/examples/dhanhq/utils/instrument_helper.rb +0 -32
  59. data/examples/dhanhq/utils/parameter_cleaner.rb +0 -28
  60. data/examples/dhanhq/utils/parameter_normalizer.rb +0 -45
  61. data/examples/dhanhq/utils/rate_limiter.rb +0 -23
  62. data/examples/dhanhq/utils/trading_parameter_normalizer.rb +0 -72
  63. data/examples/dhanhq_agent.rb +0 -964
  64. data/examples/dhanhq_tools.rb +0 -1663
  65. data/examples/multi_step_agent_with_external_data.rb +0 -368
  66. data/examples/structured_outputs_chat.rb +0 -72
  67. data/examples/structured_tools.rb +0 -89
  68. data/examples/test_dhanhq_tool_calling.rb +0 -375
  69. data/examples/test_tool_calling.rb +0 -160
  70. data/examples/tool_calling_direct.rb +0 -124
  71. data/examples/tool_calling_pattern.rb +0 -269
  72. data/exe/dhan_console +0 -4
@@ -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
-
@@ -1,341 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Advanced Example: Multi-Step Agent with Complex Decision Making
5
- # Demonstrates: Nested schemas, state management, error recovery, confidence thresholds
6
-
7
- require "json"
8
- require_relative "../lib/ollama_client"
9
-
10
- class MultiStepAgent
11
- def initialize(client:)
12
- @client = client
13
- @state = {
14
- steps_completed: [],
15
- data_collected: {},
16
- errors: []
17
- }
18
-
19
- # Complex nested schema for decision making
20
- @decision_schema = {
21
- "type" => "object",
22
- "required" => ["step", "action", "reasoning", "confidence", "next_steps"],
23
- "properties" => {
24
- "step" => {
25
- "type" => "integer",
26
- "description" => "Current step number in the workflow"
27
- },
28
- "action" => {
29
- "type" => "object",
30
- "required" => ["type", "parameters"],
31
- "properties" => {
32
- "type" => {
33
- "type" => "string",
34
- "enum" => ["collect", "analyze", "transform", "validate", "complete"]
35
- },
36
- "parameters" => {
37
- "type" => "object",
38
- "additionalProperties" => true
39
- }
40
- }
41
- },
42
- "reasoning" => {
43
- "type" => "string",
44
- "description" => "Why this action was chosen"
45
- },
46
- "confidence" => {
47
- "type" => "number",
48
- "minimum" => 0,
49
- "maximum" => 1,
50
- "description" => "Confidence level (0.0 to 1.0, where 1.0 is 100% confident)"
51
- },
52
- "next_steps" => {
53
- "type" => "array",
54
- "items" => {
55
- "type" => "string"
56
- },
57
- "minItems" => 0,
58
- "maxItems" => 5
59
- },
60
- "risk_assessment" => {
61
- "type" => "object",
62
- "properties" => {
63
- "level" => {
64
- "type" => "string",
65
- "enum" => ["low", "medium", "high"]
66
- },
67
- "factors" => {
68
- "type" => "array",
69
- "items" => { "type" => "string" }
70
- }
71
- }
72
- }
73
- }
74
- }
75
- end
76
-
77
- def execute_workflow(goal:)
78
- puts "🚀 Starting multi-step workflow"
79
- puts "Goal: #{goal}\n\n"
80
-
81
- max_steps = 10
82
- step_count = 0
83
-
84
- loop do
85
- step_count += 1
86
- break if step_count > max_steps
87
-
88
- puts "─" * 60
89
- puts "Step #{step_count}/#{max_steps}"
90
- puts "─" * 60
91
-
92
- context = build_context(goal: goal)
93
-
94
- begin
95
- decision = @client.generate(
96
- prompt: build_prompt(goal: goal, context: context),
97
- schema: @decision_schema
98
- )
99
-
100
- # Validate confidence threshold
101
- if decision["confidence"] < 0.5
102
- puts "⚠️ Low confidence (#{(decision["confidence"] * 100).round}%) - requesting manual review"
103
- break
104
- end
105
-
106
- display_decision(decision)
107
- result = execute_action(decision)
108
-
109
- # Update state
110
- @state[:steps_completed] << {
111
- step: decision["step"],
112
- action: decision["action"]["type"],
113
- result: result
114
- }
115
-
116
- # Check if workflow is complete
117
- if decision["action"]["type"] == "complete"
118
- puts "\n✅ Workflow completed successfully!"
119
- break
120
- end
121
-
122
- # Prevent infinite loops - if we've done the same action 3+ times, force progression
123
- recent_actions = @state[:steps_completed].last(3).map { |s| s[:action] }
124
- if recent_actions.length == 3 && recent_actions.uniq.length == 1
125
- puts "⚠️ Detected repetitive actions - forcing workflow progression"
126
- # Force next phase
127
- case recent_actions.first
128
- when "collect"
129
- puts " → Moving to analysis phase"
130
- decision["action"]["type"] = "analyze"
131
- decision["action"]["parameters"] = { "target" => "collected_data" }
132
- result = execute_action(decision)
133
- @state[:steps_completed] << {
134
- step: decision["step"],
135
- action: "analyze",
136
- result: result
137
- }
138
- when "analyze"
139
- puts " → Moving to validation phase"
140
- decision["action"]["type"] = "validate"
141
- decision["action"]["parameters"] = { "type" => "results" }
142
- result = execute_action(decision)
143
- @state[:steps_completed] << {
144
- step: decision["step"],
145
- action: "validate",
146
- result: result
147
- }
148
- when "validate"
149
- puts " → Completing workflow"
150
- decision["action"]["type"] = "complete"
151
- result = execute_action(decision)
152
- @state[:steps_completed] << {
153
- step: decision["step"],
154
- action: "complete",
155
- result: result
156
- }
157
- puts "\n✅ Workflow completed successfully!"
158
- break
159
- end
160
- end
161
-
162
- # Handle risk
163
- if decision["risk_assessment"] && decision["risk_assessment"]["level"] == "high"
164
- puts "⚠️ High risk detected - proceeding with caution"
165
- end
166
- rescue Ollama::SchemaViolationError => e
167
- puts "❌ Schema violation: #{e.message}"
168
- @state[:errors] << { step: step_count, error: "schema_violation", message: e.message }
169
- break
170
- rescue Ollama::RetryExhaustedError => e
171
- puts "❌ Retries exhausted: #{e.message}"
172
- @state[:errors] << { step: step_count, error: "retry_exhausted", message: e.message }
173
- break
174
- rescue Ollama::Error => e
175
- puts "❌ Error: #{e.message}"
176
- @state[:errors] << { step: step_count, error: "general", message: e.message }
177
- # Try to recover or break
178
- break if step_count > 3 # Don't loop forever
179
- end
180
-
181
- puts
182
- sleep 0.5 # Small delay for readability
183
- end
184
-
185
- display_summary
186
- @state
187
- end
188
-
189
- private
190
-
191
- def build_context(goal:) # rubocop:disable Lint/UnusedMethodArgument
192
- {
193
- steps_completed: @state[:steps_completed].map { |s| s[:action] },
194
- data_collected: @state[:data_collected].keys,
195
- error_count: @state[:errors].length,
196
- step_count: @state[:steps_completed].length
197
- }
198
- end
199
-
200
- def build_prompt(goal:, context:)
201
- step_count = context[:step_count]
202
- completed_actions = context[:steps_completed]
203
- collected_data = context[:data_collected]
204
-
205
- # Determine current phase based on what's been done
206
- phase = if completed_actions.empty?
207
- "collection"
208
- elsif completed_actions.include?("collect") && !completed_actions.include?("analyze")
209
- "analysis"
210
- elsif completed_actions.include?("analyze") && !completed_actions.include?("validate")
211
- "validation"
212
- else
213
- "completion"
214
- end
215
-
216
- <<~PROMPT
217
- Goal: #{goal}
218
-
219
- Current Phase: #{phase}
220
- Steps completed: #{step_count}
221
- Actions taken: #{completed_actions.join(", ") || "none"}
222
- Data collected: #{collected_data.join(", ") || "none"}
223
- Errors encountered: #{context[:error_count]}
224
-
225
- Workflow Phases (in order):
226
- 1. COLLECTION: Collect initial data (user data, patterns, etc.)
227
- 2. ANALYSIS: Analyze collected data for patterns and insights
228
- 3. VALIDATION: Validate the analysis results
229
- 4. COMPLETION: Finish the workflow
230
-
231
- Current State Analysis:
232
- - You are in the #{phase} phase
233
- - You have completed #{step_count} steps
234
- - You have collected: #{collected_data.any? ? collected_data.join(", ") : "nothing yet"}
235
-
236
- Decision Guidelines:
237
- - If in COLLECTION phase and no data collected: use action "collect" with specific data_type (e.g., "user_data", "patterns")
238
- - If data collected but not analyzed: use action "analyze" with target
239
- - If analyzed but not validated: use action "validate"
240
- - If all phases done: use action "complete"
241
- - AVOID repeating the same action multiple times unless necessary
242
- - Progress through phases: collect → analyze → validate → complete
243
-
244
- Provide a structured decision with high confidence (>0.7) if possible.
245
- Set step number to #{step_count + 1}.
246
- PROMPT
247
- end
248
-
249
- def display_decision(decision)
250
- puts "\n📋 Decision:"
251
- puts " Step: #{decision['step']}"
252
- puts " Action: #{decision['action']['type']}"
253
- puts " Reasoning: #{decision['reasoning']}"
254
- puts " Confidence: #{(decision['confidence'] * 100).round}%"
255
- if decision["risk_assessment"]
256
- puts " Risk Level: #{decision['risk_assessment']['level']}"
257
- if decision["risk_assessment"]["factors"]
258
- puts " Risk Factors: #{decision['risk_assessment']['factors'].join(', ')}"
259
- end
260
- end
261
- return unless decision["next_steps"] && !decision["next_steps"].empty?
262
-
263
- puts " Next Steps: #{decision['next_steps'].join(' → ')}"
264
- end
265
-
266
- def execute_action(decision)
267
- action_type = decision["action"]["type"]
268
- params = decision["action"]["parameters"] || {}
269
-
270
- case action_type
271
- when "collect"
272
- data_key = params["data_type"] || params["key"] || "user_data"
273
- # Prevent collecting the same generic data repeatedly
274
- data_key = "user_data" if should_collect_user_data?(data_key)
275
- puts " 📥 Collecting: #{data_key}"
276
- @state[:data_collected][data_key] = "collected_at_#{Time.now.to_i}"
277
- { status: "collected", key: data_key }
278
-
279
- when "analyze"
280
- target = params["target"] || "collected_data"
281
- puts " 🔍 Analyzing: #{target}"
282
- # Mark that analysis has been done
283
- @state[:data_collected]["analysis_complete"] = true
284
- { status: "analyzed", target: target, insights: "Patterns identified in collected data" }
285
-
286
- when "transform"
287
- transformation = params["type"] || "default"
288
- puts " 🔄 Transforming: #{transformation}"
289
- { status: "transformed", type: transformation }
290
-
291
- when "validate"
292
- validation_type = params["type"] || "results"
293
- puts " ✓ Validating: #{validation_type}"
294
- # Mark that validation has been done
295
- @state[:data_collected]["validation_complete"] = true
296
- { status: "validated", type: validation_type, result: "All checks passed" }
297
-
298
- when "complete"
299
- puts " ✅ Completing workflow"
300
- { status: "complete" }
301
-
302
- else
303
- { status: "unknown_action" }
304
- end
305
- end
306
-
307
- def should_collect_user_data?(data_key)
308
- @state[:data_collected].key?(data_key) &&
309
- data_key.match?(/^(missing|unknown|data)$/i) &&
310
- !@state[:data_collected].key?("user_data")
311
- end
312
-
313
- def display_summary
314
- puts "\n" + "=" * 60
315
- puts "Workflow Summary"
316
- puts "=" * 60
317
- puts "Steps completed: #{@state[:steps_completed].length}"
318
- puts "Data collected: #{@state[:data_collected].keys.join(', ') || 'none'}"
319
- puts "Errors: #{@state[:errors].length}"
320
- return unless @state[:errors].any?
321
-
322
- puts "\nErrors:"
323
- @state[:errors].each do |error|
324
- puts " Step #{error[:step]}: #{error[:error]} - #{error[:message]}"
325
- end
326
- end
327
- end
328
-
329
- # Run example
330
- if __FILE__ == $PROGRAM_NAME
331
- # Use longer timeout for multi-step workflows
332
- config = Ollama::Config.new
333
- config.timeout = 60 # 60 seconds for complex operations
334
- client = Ollama::Client.new(config: config)
335
-
336
- agent = MultiStepAgent.new(client: client)
337
- agent.execute_workflow(
338
- goal: "Collect user data, analyze patterns, validate results, and generate report"
339
- )
340
- end
341
-