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.
@@ -0,0 +1,241 @@
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
+
@@ -0,0 +1,200 @@
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
+
@@ -0,0 +1,258 @@
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
+ },
51
+ "next_steps" => {
52
+ "type" => "array",
53
+ "items" => {
54
+ "type" => "string"
55
+ },
56
+ "minItems" => 0,
57
+ "maxItems" => 5
58
+ },
59
+ "risk_assessment" => {
60
+ "type" => "object",
61
+ "properties" => {
62
+ "level" => {
63
+ "type" => "string",
64
+ "enum" => ["low", "medium", "high"]
65
+ },
66
+ "factors" => {
67
+ "type" => "array",
68
+ "items" => { "type" => "string" }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ end
75
+
76
+ def execute_workflow(goal:)
77
+ puts "🚀 Starting multi-step workflow"
78
+ puts "Goal: #{goal}\n\n"
79
+
80
+ max_steps = 10
81
+ step_count = 0
82
+
83
+ loop do
84
+ step_count += 1
85
+ break if step_count > max_steps
86
+
87
+ puts "─" * 60
88
+ puts "Step #{step_count}/#{max_steps}"
89
+ puts "─" * 60
90
+
91
+ context = build_context(goal: goal)
92
+
93
+ begin
94
+ decision = @client.generate(
95
+ prompt: build_prompt(goal: goal, context: context),
96
+ schema: @decision_schema
97
+ )
98
+
99
+ # Validate confidence threshold
100
+ if decision["confidence"] < 0.5
101
+ puts "⚠️ Low confidence (#{(decision["confidence"] * 100).round}%) - requesting manual review"
102
+ break
103
+ end
104
+
105
+ display_decision(decision)
106
+ result = execute_action(decision)
107
+
108
+ # Update state
109
+ @state[:steps_completed] << {
110
+ step: decision["step"],
111
+ action: decision["action"]["type"],
112
+ result: result
113
+ }
114
+
115
+ # Check if workflow is complete
116
+ if decision["action"]["type"] == "complete"
117
+ puts "\n✅ Workflow completed successfully!"
118
+ break
119
+ end
120
+
121
+ # Handle risk
122
+ if decision["risk_assessment"] && decision["risk_assessment"]["level"] == "high"
123
+ puts "⚠️ High risk detected - proceeding with caution"
124
+ end
125
+ rescue Ollama::SchemaViolationError => e
126
+ puts "❌ Schema violation: #{e.message}"
127
+ @state[:errors] << { step: step_count, error: "schema_violation", message: e.message }
128
+ break
129
+ rescue Ollama::RetryExhaustedError => e
130
+ puts "❌ Retries exhausted: #{e.message}"
131
+ @state[:errors] << { step: step_count, error: "retry_exhausted", message: e.message }
132
+ break
133
+ rescue Ollama::Error => e
134
+ puts "❌ Error: #{e.message}"
135
+ @state[:errors] << { step: step_count, error: "general", message: e.message }
136
+ # Try to recover or break
137
+ break if step_count > 3 # Don't loop forever
138
+ end
139
+
140
+ puts
141
+ sleep 0.5 # Small delay for readability
142
+ end
143
+
144
+ display_summary
145
+ @state
146
+ end
147
+
148
+ private
149
+
150
+ def build_context(_goal:)
151
+ {
152
+ steps_completed: @state[:steps_completed].map { |s| s[:action] },
153
+ data_collected: @state[:data_collected].keys,
154
+ error_count: @state[:errors].length
155
+ }
156
+ end
157
+
158
+ def build_prompt(goal:, context:)
159
+ <<~PROMPT
160
+ Goal: #{goal}
161
+
162
+ Workflow State:
163
+ - Steps completed: #{context[:steps_completed].join(", ") || "none"}
164
+ - Data collected: #{context[:data_collected].join(", ") || "none"}
165
+ - Errors encountered: #{context[:error_count]}
166
+
167
+ Analyze the current state and decide the next action.
168
+ Consider:
169
+ 1. What data still needs to be collected?
170
+ 2. What analysis is needed?
171
+ 3. What validation is required?
172
+ 4. When should the workflow complete?
173
+
174
+ Provide a structured decision with high confidence (>0.7) if possible.
175
+ PROMPT
176
+ end
177
+
178
+ def display_decision(decision)
179
+ puts "\n📋 Decision:"
180
+ puts " Step: #{decision['step']}"
181
+ puts " Action: #{decision['action']['type']}"
182
+ puts " Reasoning: #{decision['reasoning']}"
183
+ puts " Confidence: #{(decision['confidence'] * 100).round}%"
184
+ if decision["risk_assessment"]
185
+ puts " Risk Level: #{decision['risk_assessment']['level']}"
186
+ if decision["risk_assessment"]["factors"]
187
+ puts " Risk Factors: #{decision['risk_assessment']['factors'].join(', ')}"
188
+ end
189
+ end
190
+ return unless decision["next_steps"] && !decision["next_steps"].empty?
191
+
192
+ puts " Next Steps: #{decision['next_steps'].join(' → ')}"
193
+ end
194
+
195
+ def execute_action(decision)
196
+ action_type = decision["action"]["type"]
197
+ params = decision["action"]["parameters"] || {}
198
+
199
+ case action_type
200
+ when "collect"
201
+ data_key = params["data_type"] || "unknown"
202
+ puts " 📥 Collecting: #{data_key}"
203
+ @state[:data_collected][data_key] = "collected_at_#{Time.now.to_i}"
204
+ { status: "collected", key: data_key }
205
+
206
+ when "analyze"
207
+ target = params["target"] || "data"
208
+ puts " 🔍 Analyzing: #{target}"
209
+ { status: "analyzed", target: target, insights: "analysis_complete" }
210
+
211
+ when "transform"
212
+ transformation = params["type"] || "default"
213
+ puts " 🔄 Transforming: #{transformation}"
214
+ { status: "transformed", type: transformation }
215
+
216
+ when "validate"
217
+ validation_type = params["type"] || "general"
218
+ puts " ✓ Validating: #{validation_type}"
219
+ { status: "validated", type: validation_type }
220
+
221
+ when "complete"
222
+ puts " ✅ Completing workflow"
223
+ { status: "complete" }
224
+
225
+ else
226
+ { status: "unknown_action" }
227
+ end
228
+ end
229
+
230
+ def display_summary
231
+ puts "\n" + "=" * 60
232
+ puts "Workflow Summary"
233
+ puts "=" * 60
234
+ puts "Steps completed: #{@state[:steps_completed].length}"
235
+ puts "Data collected: #{@state[:data_collected].keys.join(', ') || 'none'}"
236
+ puts "Errors: #{@state[:errors].length}"
237
+ return unless @state[:errors].any?
238
+
239
+ puts "\nErrors:"
240
+ @state[:errors].each do |error|
241
+ puts " Step #{error[:step]}: #{error[:error]} - #{error[:message]}"
242
+ end
243
+ end
244
+ end
245
+
246
+ # Run example
247
+ if __FILE__ == $PROGRAM_NAME
248
+ # Use longer timeout for multi-step workflows
249
+ config = Ollama::Config.new
250
+ config.timeout = 60 # 60 seconds for complex operations
251
+ client = Ollama::Client.new(config: config)
252
+
253
+ agent = MultiStepAgent.new(client: client)
254
+ agent.execute_workflow(
255
+ goal: "Collect user data, analyze patterns, validate results, and generate report"
256
+ )
257
+ end
258
+