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,186 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Advanced Example: Performance Testing and Observability
5
+ # Demonstrates: Latency measurement, throughput testing, error rate tracking, concurrent requests
6
+
7
+ require "json"
8
+ require "benchmark"
9
+ require "time"
10
+ require_relative "../lib/ollama_client"
11
+
12
+ class PerformanceMonitor
13
+ def initialize(client:)
14
+ @client = client
15
+ @metrics = {
16
+ calls: [],
17
+ errors: [],
18
+ latencies: []
19
+ }
20
+ end
21
+
22
+ def measure_call(prompt:, schema:)
23
+ start_time = Time.now
24
+
25
+ begin
26
+ result = @client.generate(prompt: prompt, schema: schema)
27
+ latency = (Time.now - start_time) * 1000 # Convert to milliseconds
28
+
29
+ @metrics[:calls] << {
30
+ success: true,
31
+ latency_ms: latency,
32
+ timestamp: Time.now.iso8601
33
+ }
34
+ @metrics[:latencies] << latency
35
+
36
+ { success: true, result: result, latency_ms: latency }
37
+ rescue Ollama::Error => e
38
+ latency = (Time.now - start_time) * 1000
39
+ @metrics[:calls] << {
40
+ success: false,
41
+ latency_ms: latency,
42
+ error: e.class.name,
43
+ timestamp: Time.now.iso8601
44
+ }
45
+ @metrics[:errors] << { error: e.class.name, message: e.message, latency_ms: latency }
46
+
47
+ { success: false, error: e, latency_ms: latency }
48
+ end
49
+ end
50
+
51
+ def run_throughput_test(prompt:, schema:, iterations: 10)
52
+ puts "🚀 Running throughput test (#{iterations} iterations)..."
53
+ results = []
54
+
55
+ total_time = Benchmark.realtime do
56
+ iterations.times do |i|
57
+ print " #{i + 1}/#{iterations}... "
58
+ result = measure_call(prompt: prompt, schema: schema)
59
+ results << result
60
+ puts result[:success] ? "✓" : "✗"
61
+ end
62
+ end
63
+
64
+ {
65
+ total_time: total_time,
66
+ iterations: iterations,
67
+ throughput: iterations / total_time,
68
+ results: results
69
+ }
70
+ end
71
+
72
+ def run_latency_test(prompt:, schema:, iterations: 10)
73
+ puts "⏱️ Running latency test (#{iterations} iterations)..."
74
+ latencies = []
75
+
76
+ iterations.times do |i|
77
+ print " #{i + 1}/#{iterations}... "
78
+ result = measure_call(prompt: prompt, schema: schema)
79
+ if result[:success]
80
+ latencies << result[:latency_ms]
81
+ puts "#{result[:latency_ms].round(2)}ms"
82
+ else
83
+ puts "ERROR"
84
+ end
85
+ end
86
+
87
+ {
88
+ latencies: latencies,
89
+ min: latencies.min,
90
+ max: latencies.max,
91
+ avg: latencies.sum / latencies.length,
92
+ median: latencies.sort[latencies.length / 2],
93
+ p95: latencies.sort[(latencies.length * 0.95).to_i],
94
+ p99: latencies.sort[(latencies.length * 0.99).to_i]
95
+ }
96
+ end
97
+
98
+ def display_metrics
99
+ puts "\n" + "=" * 60
100
+ puts "Performance Metrics"
101
+ puts "=" * 60
102
+
103
+ total_calls = @metrics[:calls].length
104
+ successful = @metrics[:calls].count { |c| c[:success] }
105
+ failed = total_calls - successful
106
+
107
+ puts "Total calls: #{total_calls}"
108
+ puts "Successful: #{successful} (#{(successful.to_f / total_calls * 100).round(2)}%)"
109
+ puts "Failed: #{failed} (#{(failed.to_f / total_calls * 100).round(2)}%)"
110
+
111
+ if @metrics[:latencies].any?
112
+ latencies = @metrics[:latencies]
113
+ puts "\nLatency Statistics (ms):"
114
+ puts " Min: #{latencies.min.round(2)}"
115
+ puts " Max: #{latencies.max.round(2)}"
116
+ puts " Avg: #{(latencies.sum / latencies.length).round(2)}"
117
+ puts " Median: #{latencies.sort[latencies.length / 2].round(2)}"
118
+ puts " P95: #{latencies.sort[(latencies.length * 0.95).to_i].round(2)}"
119
+ puts " P99: #{latencies.sort[(latencies.length * 0.99).to_i].round(2)}"
120
+ end
121
+
122
+ return unless @metrics[:errors].any?
123
+
124
+ puts "\nErrors by type:"
125
+ error_counts = @metrics[:errors].group_by { |e| e[:error] }
126
+ error_counts.each do |error_type, errors|
127
+ puts " #{error_type}: #{errors.length}"
128
+ end
129
+ end
130
+
131
+ def export_metrics(filename: "metrics.json")
132
+ File.write(filename, JSON.pretty_generate(@metrics))
133
+ puts "\n📊 Metrics exported to #{filename}"
134
+ end
135
+ end
136
+
137
+ # Run performance tests
138
+ if __FILE__ == $PROGRAM_NAME
139
+ # Use longer timeout for performance testing
140
+ config = Ollama::Config.new
141
+ config.timeout = 60 # 60 seconds for complex operations
142
+ client = Ollama::Client.new(config: config)
143
+ monitor = PerformanceMonitor.new(client: client)
144
+
145
+ schema = {
146
+ "type" => "object",
147
+ "required" => ["response"],
148
+ "properties" => {
149
+ "response" => { "type" => "string" }
150
+ }
151
+ }
152
+
153
+ puts "=" * 60
154
+ puts "Performance Testing Suite"
155
+ puts "=" * 60
156
+
157
+ # Test 1: Latency
158
+ latency_results = monitor.run_latency_test(
159
+ prompt: "Respond with a simple acknowledgment",
160
+ schema: schema,
161
+ iterations: 5
162
+ )
163
+
164
+ puts "\nLatency Results:"
165
+ puts " Average: #{latency_results[:avg].round(2)}ms"
166
+ puts " P95: #{latency_results[:p95].round(2)}ms"
167
+ puts " P99: #{latency_results[:p99].round(2)}ms"
168
+
169
+ # Test 2: Throughput
170
+ throughput_results = monitor.run_throughput_test(
171
+ prompt: "Count to 5",
172
+ schema: schema,
173
+ iterations: 5
174
+ )
175
+
176
+ puts "\nThroughput Results:"
177
+ puts " Total time: #{throughput_results[:total_time].round(2)}s"
178
+ puts " Throughput: #{throughput_results[:throughput].round(2)} calls/sec"
179
+
180
+ # Display all metrics
181
+ monitor.display_metrics
182
+
183
+ # Export metrics
184
+ monitor.export_metrics
185
+ end
186
+
@@ -0,0 +1,235 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Complete example showing how to use structured outputs in a real workflow
5
+ # This demonstrates the full cycle: schema definition -> LLM call -> using the result
6
+
7
+ require "json"
8
+ require_relative "../lib/ollama_client"
9
+
10
+ # Example: Task Planning Agent
11
+ # The LLM decides what action to take, and we execute it
12
+
13
+ class TaskPlanner
14
+ def initialize(client:)
15
+ @client = client
16
+ @task_schema = {
17
+ "type" => "object",
18
+ "required" => ["action", "reasoning", "confidence", "next_step"],
19
+ "properties" => {
20
+ "action" => {
21
+ "type" => "string",
22
+ "description" => "The action to take",
23
+ "enum" => ["search", "calculate", "store", "retrieve", "finish"]
24
+ },
25
+ "reasoning" => {
26
+ "type" => "string",
27
+ "description" => "Why this action was chosen"
28
+ },
29
+ "confidence" => {
30
+ "type" => "number",
31
+ "minimum" => 0,
32
+ "maximum" => 1,
33
+ "description" => "Confidence in this decision"
34
+ },
35
+ "next_step" => {
36
+ "type" => "string",
37
+ "description" => "What to do next"
38
+ },
39
+ "parameters" => {
40
+ "type" => "object",
41
+ "description" => "Parameters needed for the action"
42
+ }
43
+ }
44
+ }
45
+ end
46
+
47
+ def plan(context:)
48
+ puts "🤔 Planning next action..."
49
+ puts "Context: #{context}\n\n"
50
+
51
+ begin
52
+ result = @client.generate(
53
+ prompt: "Given this context: #{context}\n\nDecide the next action to take.",
54
+ schema: @task_schema
55
+ )
56
+
57
+ # The result is guaranteed to match our schema
58
+ display_decision(result)
59
+ execute_action(result)
60
+
61
+ result
62
+ rescue Ollama::SchemaViolationError => e
63
+ puts "❌ Invalid response structure: #{e.message}"
64
+ puts " This shouldn't happen with format parameter, but we handle it gracefully"
65
+ nil
66
+ rescue Ollama::Error => e
67
+ puts "❌ Error: #{e.message}"
68
+ nil
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def display_decision(result)
75
+ puts "📋 Decision:"
76
+ puts " Action: #{result['action']}"
77
+ puts " Reasoning: #{result['reasoning']}"
78
+ puts " Confidence: #{(result['confidence'] * 100).round}%"
79
+ puts " Next Step: #{result['next_step']}"
80
+ puts " Parameters: #{JSON.pretty_generate(result['parameters'] || {})}\n"
81
+ end
82
+
83
+ def execute_action(result)
84
+ case result["action"]
85
+ when "search"
86
+ query = result.dig("parameters", "query") || "default"
87
+ puts "🔍 Executing search: #{query}"
88
+ # In real code, you'd call your search function here
89
+ puts " → Search results would appear here\n"
90
+
91
+ when "calculate"
92
+ operation = result.dig("parameters", "operation") || "unknown"
93
+ puts "🧮 Executing calculation: #{operation}"
94
+ # In real code, you'd call your calculator here
95
+ puts " → Calculation result would appear here\n"
96
+
97
+ when "store"
98
+ key = result.dig("parameters", "key") || "unknown"
99
+ puts "💾 Storing data with key: #{key}"
100
+ # In real code, you'd save to your storage
101
+ puts " → Data stored successfully\n"
102
+
103
+ when "retrieve"
104
+ key = result.dig("parameters", "key") || "unknown"
105
+ puts "📂 Retrieving data with key: #{key}"
106
+ # In real code, you'd fetch from your storage
107
+ puts " → Data retrieved successfully\n"
108
+
109
+ when "finish"
110
+ puts "✅ Task complete!\n"
111
+
112
+ else
113
+ puts "⚠️ Unknown action: #{result['action']}\n"
114
+ end
115
+ end
116
+ end
117
+
118
+ # Example: Data Analyzer
119
+ # The LLM analyzes data and returns structured insights
120
+
121
+ class DataAnalyzer
122
+ def initialize(client:)
123
+ @client = client
124
+ @analysis_schema = {
125
+ "type" => "object",
126
+ "required" => ["summary", "confidence", "key_points"],
127
+ "properties" => {
128
+ "summary" => {
129
+ "type" => "string",
130
+ "description" => "Brief summary of the analysis"
131
+ },
132
+ "confidence" => {
133
+ "type" => "number",
134
+ "minimum" => 0,
135
+ "maximum" => 1
136
+ },
137
+ "key_points" => {
138
+ "type" => "array",
139
+ "items" => { "type" => "string" },
140
+ "minItems" => 1,
141
+ "maxItems" => 5
142
+ },
143
+ "sentiment" => {
144
+ "type" => "string",
145
+ "enum" => ["positive", "neutral", "negative"]
146
+ },
147
+ "recommendations" => {
148
+ "type" => "array",
149
+ "items" => { "type" => "string" }
150
+ }
151
+ }
152
+ }
153
+ end
154
+
155
+ def analyze(data:)
156
+ puts "📊 Analyzing data..."
157
+ puts "Data: #{data}\n\n"
158
+
159
+ begin
160
+ result = @client.generate(
161
+ prompt: "Analyze this data and provide insights: #{data}",
162
+ schema: @analysis_schema
163
+ )
164
+
165
+ display_analysis(result)
166
+ make_recommendations(result)
167
+
168
+ result
169
+ rescue Ollama::Error => e
170
+ puts "❌ Error: #{e.message}"
171
+ nil
172
+ end
173
+ end
174
+
175
+ private
176
+
177
+ def display_analysis(result)
178
+ puts "📈 Analysis Results:"
179
+ puts " Summary: #{result['summary']}"
180
+ puts " Confidence: #{(result['confidence'] * 100).round}%"
181
+ puts " Sentiment: #{result['sentiment']}"
182
+ puts "\n Key Points:"
183
+ result["key_points"].each_with_index do |point, i|
184
+ puts " #{i + 1}. #{point}"
185
+ end
186
+
187
+ if result["recommendations"] && !result["recommendations"].empty?
188
+ puts "\n Recommendations:"
189
+ result["recommendations"].each_with_index do |rec, i|
190
+ puts " #{i + 1}. #{rec}"
191
+ end
192
+ end
193
+ puts
194
+ end
195
+
196
+ def make_recommendations(result)
197
+ if result["confidence"] > 0.8 && result["sentiment"] == "positive"
198
+ puts "✅ High confidence positive analysis - safe to proceed"
199
+ elsif result["confidence"] < 0.5
200
+ puts "⚠️ Low confidence - manual review recommended"
201
+ elsif result["sentiment"] == "negative"
202
+ puts "⚠️ Negative sentiment detected - investigate further"
203
+ end
204
+ puts
205
+ end
206
+ end
207
+
208
+ # Main execution
209
+ if __FILE__ == $PROGRAM_NAME
210
+ client = Ollama::Client.new
211
+
212
+ puts "=" * 60
213
+ puts "Example 1: Task Planning Agent"
214
+ puts "=" * 60
215
+ puts
216
+
217
+ planner = TaskPlanner.new(client: client)
218
+ planner.plan(context: "User wants to know the weather in Paris")
219
+
220
+ puts "\n" + "=" * 60
221
+ puts "Example 2: Data Analysis"
222
+ puts "=" * 60
223
+ puts
224
+
225
+ analyzer = DataAnalyzer.new(client: client)
226
+ analyzer.analyze(
227
+ data: "Sales increased 25% this quarter. Customer satisfaction is at 4.8/5. " \
228
+ "Revenue: $1.2M. New customers: 150."
229
+ )
230
+
231
+ puts "=" * 60
232
+ puts "Examples complete!"
233
+ puts "=" * 60
234
+ end
235
+