ollama-client 0.2.4 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -1
- data/README.md +560 -106
- data/docs/EXAMPLE_REORGANIZATION.md +412 -0
- data/docs/GETTING_STARTED.md +361 -0
- data/docs/INTEGRATION_TESTING.md +170 -0
- data/docs/NEXT_STEPS_SUMMARY.md +114 -0
- data/docs/PERSONAS.md +383 -0
- data/docs/QUICK_START.md +195 -0
- data/docs/README.md +2 -3
- data/docs/RELEASE_GUIDE.md +376 -0
- data/docs/TESTING.md +392 -170
- data/docs/TEST_CHECKLIST.md +450 -0
- data/docs/ruby_guide.md +6232 -0
- data/examples/README.md +51 -66
- data/examples/basic_chat.rb +33 -0
- data/examples/basic_generate.rb +29 -0
- data/examples/tool_calling_parsing.rb +59 -0
- data/exe/ollama-client +128 -1
- data/lib/ollama/agent/planner.rb +7 -2
- data/lib/ollama/chat_session.rb +101 -0
- data/lib/ollama/client.rb +43 -21
- data/lib/ollama/config.rb +4 -1
- data/lib/ollama/document_loader.rb +163 -0
- data/lib/ollama/embeddings.rb +42 -13
- data/lib/ollama/errors.rb +1 -0
- data/lib/ollama/personas.rb +287 -0
- data/lib/ollama/version.rb +1 -1
- data/lib/ollama_client.rb +8 -0
- metadata +31 -53
- data/docs/GEM_RELEASE_GUIDE.md +0 -794
- data/docs/GET_RUBYGEMS_SECRET.md +0 -151
- data/docs/QUICK_OTP_SETUP.md +0 -80
- data/docs/QUICK_RELEASE.md +0 -106
- data/docs/RUBYGEMS_OTP_SETUP.md +0 -199
- data/examples/advanced_complex_schemas.rb +0 -366
- data/examples/advanced_edge_cases.rb +0 -241
- data/examples/advanced_error_handling.rb +0 -200
- data/examples/advanced_multi_step_agent.rb +0 -341
- data/examples/advanced_performance_testing.rb +0 -186
- data/examples/chat_console.rb +0 -143
- data/examples/complete_workflow.rb +0 -245
- data/examples/dhan_console.rb +0 -843
- data/examples/dhanhq/README.md +0 -236
- data/examples/dhanhq/agents/base_agent.rb +0 -74
- data/examples/dhanhq/agents/data_agent.rb +0 -66
- data/examples/dhanhq/agents/orchestrator_agent.rb +0 -120
- data/examples/dhanhq/agents/technical_analysis_agent.rb +0 -252
- data/examples/dhanhq/agents/trading_agent.rb +0 -81
- data/examples/dhanhq/analysis/market_structure.rb +0 -138
- data/examples/dhanhq/analysis/pattern_recognizer.rb +0 -192
- data/examples/dhanhq/analysis/trend_analyzer.rb +0 -88
- data/examples/dhanhq/builders/market_context_builder.rb +0 -67
- data/examples/dhanhq/dhanhq_agent.rb +0 -829
- data/examples/dhanhq/indicators/technical_indicators.rb +0 -158
- data/examples/dhanhq/scanners/intraday_options_scanner.rb +0 -492
- data/examples/dhanhq/scanners/swing_scanner.rb +0 -247
- data/examples/dhanhq/schemas/agent_schemas.rb +0 -61
- data/examples/dhanhq/services/base_service.rb +0 -46
- data/examples/dhanhq/services/data_service.rb +0 -118
- data/examples/dhanhq/services/trading_service.rb +0 -59
- data/examples/dhanhq/technical_analysis_agentic_runner.rb +0 -411
- data/examples/dhanhq/technical_analysis_runner.rb +0 -420
- data/examples/dhanhq/test_tool_calling.rb +0 -538
- data/examples/dhanhq/test_tool_calling_verbose.rb +0 -251
- data/examples/dhanhq/utils/instrument_helper.rb +0 -32
- data/examples/dhanhq/utils/parameter_cleaner.rb +0 -28
- data/examples/dhanhq/utils/parameter_normalizer.rb +0 -45
- data/examples/dhanhq/utils/rate_limiter.rb +0 -23
- data/examples/dhanhq/utils/trading_parameter_normalizer.rb +0 -72
- data/examples/dhanhq_agent.rb +0 -964
- data/examples/dhanhq_tools.rb +0 -1663
- data/examples/multi_step_agent_with_external_data.rb +0 -368
- data/examples/structured_outputs_chat.rb +0 -72
- data/examples/structured_tools.rb +0 -89
- data/examples/test_dhanhq_tool_calling.rb +0 -375
- data/examples/test_tool_calling.rb +0 -160
- data/examples/tool_calling_direct.rb +0 -124
- data/examples/tool_calling_pattern.rb +0 -269
- 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
|
-
|