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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +12 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/CONTRIBUTING.md +36 -0
- data/LICENSE.txt +21 -0
- data/PRODUCTION_FIXES.md +172 -0
- data/README.md +690 -0
- data/Rakefile +12 -0
- data/TESTING.md +286 -0
- data/examples/advanced_complex_schemas.rb +363 -0
- data/examples/advanced_edge_cases.rb +241 -0
- data/examples/advanced_error_handling.rb +200 -0
- data/examples/advanced_multi_step_agent.rb +258 -0
- data/examples/advanced_performance_testing.rb +186 -0
- data/examples/complete_workflow.rb +235 -0
- data/examples/dhanhq_agent.rb +752 -0
- data/examples/dhanhq_tools.rb +563 -0
- data/examples/structured_outputs_chat.rb +72 -0
- data/examples/tool_calling_pattern.rb +266 -0
- data/exe/ollama-client +4 -0
- data/lib/ollama/agent/executor.rb +157 -0
- data/lib/ollama/agent/messages.rb +31 -0
- data/lib/ollama/agent/planner.rb +47 -0
- data/lib/ollama/client.rb +775 -0
- data/lib/ollama/config.rb +29 -0
- data/lib/ollama/errors.rb +54 -0
- data/lib/ollama/schema_validator.rb +79 -0
- data/lib/ollama/schemas/base.json +5 -0
- data/lib/ollama/streaming_observer.rb +22 -0
- data/lib/ollama/version.rb +5 -0
- data/lib/ollama_client.rb +46 -0
- data/sig/ollama/client.rbs +6 -0
- metadata +108 -0
data/TESTING.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# Testing Guide
|
|
2
|
+
|
|
3
|
+
This document explains how to test the `ollama-client` gem comprehensively.
|
|
4
|
+
|
|
5
|
+
## Test Structure
|
|
6
|
+
|
|
7
|
+
The test suite is organized into focused spec files:
|
|
8
|
+
|
|
9
|
+
- `spec/ollama/client_spec.rb` - Basic client initialization and parameter validation
|
|
10
|
+
- `spec/ollama/client_generate_spec.rb` - Comprehensive tests for `generate()` method
|
|
11
|
+
- `spec/ollama/client_chat_spec.rb` - Comprehensive tests for `chat()` method
|
|
12
|
+
- `spec/ollama/client_list_models_spec.rb` - Tests for `list_models()` method
|
|
13
|
+
- `spec/ollama/client_model_suggestions_spec.rb` - Tests for model suggestion feature
|
|
14
|
+
- `spec/ollama/errors_spec.rb` - Tests for all error classes
|
|
15
|
+
- `spec/ollama/config_spec.rb` - Config class tests (in client_spec.rb)
|
|
16
|
+
- `spec/ollama/schema_validator_spec.rb` - Schema validation tests (in client_spec.rb)
|
|
17
|
+
|
|
18
|
+
## Running Tests
|
|
19
|
+
|
|
20
|
+
### Run All Tests
|
|
21
|
+
```bash
|
|
22
|
+
bundle exec rspec
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Run Specific Test File
|
|
26
|
+
```bash
|
|
27
|
+
bundle exec rspec spec/ollama/client_generate_spec.rb
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Run with Documentation Format
|
|
31
|
+
```bash
|
|
32
|
+
bundle exec rspec --format documentation
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Run Specific Test
|
|
36
|
+
```bash
|
|
37
|
+
bundle exec rspec spec/ollama/client_generate_spec.rb:45
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Run Tests Matching a Pattern
|
|
41
|
+
```bash
|
|
42
|
+
bundle exec rspec -e "retry"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Testing Strategy
|
|
46
|
+
|
|
47
|
+
### 1. HTTP Mocking with WebMock
|
|
48
|
+
|
|
49
|
+
All HTTP requests are mocked using [WebMock](https://github.com/bblimke/webmock). This allows us to:
|
|
50
|
+
- Test without a real Ollama server
|
|
51
|
+
- Test error scenarios reliably
|
|
52
|
+
- Test retry logic deterministically
|
|
53
|
+
- Run tests in CI/CD without external dependencies
|
|
54
|
+
|
|
55
|
+
**Example:**
|
|
56
|
+
```ruby
|
|
57
|
+
stub_request(:post, "http://localhost:11434/api/generate")
|
|
58
|
+
.to_return(status: 200, body: { response: '{"test":"value"}' }.to_json)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 2. Test Coverage Areas
|
|
62
|
+
|
|
63
|
+
#### ✅ Success Cases
|
|
64
|
+
- Successful API calls return parsed JSON
|
|
65
|
+
- Schema validation passes
|
|
66
|
+
- Config defaults are applied correctly
|
|
67
|
+
- Model overrides work
|
|
68
|
+
- Options are merged correctly
|
|
69
|
+
|
|
70
|
+
#### ✅ Error Handling
|
|
71
|
+
- **404 (NotFoundError)**: Model not found, no retries, includes suggestions
|
|
72
|
+
- **500 (HTTPError)**: Retryable, retries up to config limit
|
|
73
|
+
- **400 (HTTPError)**: Non-retryable, fails immediately
|
|
74
|
+
- **TimeoutError**: Retries on timeout
|
|
75
|
+
- **InvalidJSONError**: Retries on JSON parse errors
|
|
76
|
+
- **SchemaViolationError**: Retries on schema validation failures
|
|
77
|
+
- **Connection Errors**: Retries on network failures
|
|
78
|
+
|
|
79
|
+
#### ✅ Retry Logic
|
|
80
|
+
- Retries up to `config.retries` times
|
|
81
|
+
- Only retries retryable errors (5xx, 408, 429)
|
|
82
|
+
- Raises `RetryExhaustedError` after max retries
|
|
83
|
+
- Succeeds if retry succeeds
|
|
84
|
+
|
|
85
|
+
#### ✅ Edge Cases
|
|
86
|
+
- JSON wrapped in markdown code blocks
|
|
87
|
+
- Plain JSON responses
|
|
88
|
+
- Empty model lists
|
|
89
|
+
- Missing response fields
|
|
90
|
+
- Malformed JSON
|
|
91
|
+
|
|
92
|
+
#### ✅ Model Suggestions
|
|
93
|
+
- Suggests similar models on 404
|
|
94
|
+
- Fuzzy matching on model names
|
|
95
|
+
- Limits suggestions to 5 models
|
|
96
|
+
- Handles model listing failures gracefully
|
|
97
|
+
|
|
98
|
+
## Writing New Tests
|
|
99
|
+
|
|
100
|
+
### Basic Test Structure
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
RSpec.describe Ollama::Client, "#method_name" do
|
|
104
|
+
let(:client) { described_class.new(config: config) }
|
|
105
|
+
let(:config) do
|
|
106
|
+
Ollama::Config.new.tap do |c|
|
|
107
|
+
c.base_url = "http://localhost:11434"
|
|
108
|
+
c.model = "test-model"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
before do
|
|
113
|
+
WebMock.disable_net_connect!(allow_localhost: false)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
after do
|
|
117
|
+
WebMock.reset!
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "does something" do
|
|
121
|
+
stub_request(:post, "http://localhost:11434/api/generate")
|
|
122
|
+
.to_return(status: 200, body: { response: '{}' }.to_json)
|
|
123
|
+
|
|
124
|
+
result = client.generate(prompt: "test", schema: { "type" => "object" })
|
|
125
|
+
expect(result).to eq({})
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Testing Retry Logic
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
it "retries on 500 errors" do
|
|
134
|
+
stub_request(:post, "http://localhost:11434/api/generate")
|
|
135
|
+
.to_return(status: 500, body: "Internal Server Error")
|
|
136
|
+
.times(config.retries + 1)
|
|
137
|
+
|
|
138
|
+
expect do
|
|
139
|
+
client.generate(prompt: "test", schema: schema)
|
|
140
|
+
end.to raise_error(Ollama::RetryExhaustedError)
|
|
141
|
+
|
|
142
|
+
expect(WebMock).to have_requested(:post, "http://localhost:11434/api/generate")
|
|
143
|
+
.times(config.retries + 1)
|
|
144
|
+
end
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Testing Success After Retry
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
it "succeeds on retry" do
|
|
151
|
+
stub_request(:post, "http://localhost:11434/api/generate")
|
|
152
|
+
.to_return(
|
|
153
|
+
{ status: 500, body: "Internal Server Error" },
|
|
154
|
+
{ status: 200, body: { response: '{"test":"value"}' }.to_json }
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
result = client.generate(prompt: "test", schema: schema)
|
|
158
|
+
expect(result).to eq("test" => "value")
|
|
159
|
+
expect(WebMock).to have_requested(:post, "http://localhost:11434/api/generate").twice
|
|
160
|
+
end
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Testing Error Details
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
it "raises error with correct details" do
|
|
167
|
+
stub_request(:post, "http://localhost:11434/api/generate")
|
|
168
|
+
.to_return(status: 404, body: "Not Found")
|
|
169
|
+
|
|
170
|
+
expect do
|
|
171
|
+
client.generate(prompt: "test", schema: schema)
|
|
172
|
+
end.to raise_error(Ollama::NotFoundError) do |error|
|
|
173
|
+
expect(error.requested_model).to eq("test-model")
|
|
174
|
+
expect(error.status_code).to eq(404)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Integration Tests (Optional)
|
|
180
|
+
|
|
181
|
+
For integration tests that hit a real Ollama server, create a separate spec file:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
# spec/integration/ollama_client_integration_spec.rb
|
|
185
|
+
RSpec.describe "Ollama Client Integration", :integration do
|
|
186
|
+
# Skip if OLLAMA_URL is not set
|
|
187
|
+
before(:all) do
|
|
188
|
+
skip "Set OLLAMA_URL environment variable to run integration tests" unless ENV["OLLAMA_URL"]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
let(:client) do
|
|
192
|
+
config = Ollama::Config.new
|
|
193
|
+
config.base_url = ENV["OLLAMA_URL"] || "http://localhost:11434"
|
|
194
|
+
Ollama::Client.new(config: config)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it "can generate structured output" do
|
|
198
|
+
schema = {
|
|
199
|
+
"type" => "object",
|
|
200
|
+
"required" => ["test"],
|
|
201
|
+
"properties" => { "test" => { "type" => "string" } }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
result = client.generate(
|
|
205
|
+
prompt: "Return a JSON object with test='hello'",
|
|
206
|
+
schema: schema
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
expect(result["test"]).to eq("hello")
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Run integration tests separately:
|
|
215
|
+
```bash
|
|
216
|
+
bundle exec rspec --tag integration
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Test Coverage Metrics
|
|
220
|
+
|
|
221
|
+
To check test coverage, add `simplecov`:
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
# spec/spec_helper.rb
|
|
225
|
+
require "simplecov"
|
|
226
|
+
SimpleCov.start
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Then run:
|
|
230
|
+
```bash
|
|
231
|
+
bundle exec rspec
|
|
232
|
+
open coverage/index.html
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Continuous Integration
|
|
236
|
+
|
|
237
|
+
The test suite is designed to run in CI without external dependencies:
|
|
238
|
+
- All tests use WebMock (no real Ollama server needed)
|
|
239
|
+
- Tests are deterministic and fast
|
|
240
|
+
- No flaky network-dependent tests
|
|
241
|
+
|
|
242
|
+
## Best Practices
|
|
243
|
+
|
|
244
|
+
1. **Always mock HTTP requests** - Don't make real network calls in unit tests
|
|
245
|
+
2. **Test error paths** - Ensure all error scenarios are covered
|
|
246
|
+
3. **Test retry logic** - Verify retries work correctly
|
|
247
|
+
4. **Test edge cases** - JSON parsing, empty responses, etc.
|
|
248
|
+
5. **Keep tests focused** - One assertion per test when possible
|
|
249
|
+
6. **Use descriptive test names** - "it 'retries on 500 errors'"
|
|
250
|
+
7. **Reset WebMock** - Always reset in `after` blocks
|
|
251
|
+
|
|
252
|
+
## Debugging Tests
|
|
253
|
+
|
|
254
|
+
### See WebMock Requests
|
|
255
|
+
```ruby
|
|
256
|
+
puts WebMock::RequestRegistry.instance.requested_signatures
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Inspect Stubbed Requests
|
|
260
|
+
```ruby
|
|
261
|
+
stub = stub_request(:post, "http://localhost:11434/api/generate")
|
|
262
|
+
.with { |req| puts req.body }
|
|
263
|
+
.to_return(status: 200, body: { response: '{}' }.to_json)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Allow Real Requests (for debugging)
|
|
267
|
+
```ruby
|
|
268
|
+
WebMock.allow_net_connect!
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Common Issues
|
|
272
|
+
|
|
273
|
+
### "Real HTTP connections are disabled"
|
|
274
|
+
- Make sure `WebMock.disable_net_connect!` is called in `before` block
|
|
275
|
+
- Check that all requests are properly stubbed
|
|
276
|
+
|
|
277
|
+
### "Unregistered request"
|
|
278
|
+
- The request URL or method doesn't match the stub
|
|
279
|
+
- Check the exact URL being called
|
|
280
|
+
- Use `WebMock.allow_net_connect!` temporarily to see the real request
|
|
281
|
+
|
|
282
|
+
### Tests are flaky
|
|
283
|
+
- Ensure WebMock is reset in `after` blocks
|
|
284
|
+
- Don't share state between tests
|
|
285
|
+
- Use `let` instead of instance variables
|
|
286
|
+
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Advanced Example: Complex Nested Schemas and Validation
|
|
5
|
+
# Demonstrates: Deep nesting, arrays of objects, conditional validation, real-world data structures
|
|
6
|
+
|
|
7
|
+
require "json"
|
|
8
|
+
require_relative "../lib/ollama_client"
|
|
9
|
+
|
|
10
|
+
# Example 1: Financial Analysis Schema
|
|
11
|
+
class FinancialAnalyzer
|
|
12
|
+
def initialize(client:)
|
|
13
|
+
@client = client
|
|
14
|
+
@schema = {
|
|
15
|
+
"type" => "object",
|
|
16
|
+
"required" => ["analysis_date", "summary", "metrics", "recommendations"],
|
|
17
|
+
"properties" => {
|
|
18
|
+
"analysis_date" => {
|
|
19
|
+
"type" => "string",
|
|
20
|
+
"format" => "date-time"
|
|
21
|
+
},
|
|
22
|
+
"summary" => {
|
|
23
|
+
"type" => "string",
|
|
24
|
+
"minLength" => 50,
|
|
25
|
+
"maxLength" => 500
|
|
26
|
+
},
|
|
27
|
+
"metrics" => {
|
|
28
|
+
"type" => "object",
|
|
29
|
+
"required" => ["revenue", "profit_margin", "growth_rate"],
|
|
30
|
+
"properties" => {
|
|
31
|
+
"revenue" => {
|
|
32
|
+
"type" => "number",
|
|
33
|
+
"minimum" => 0
|
|
34
|
+
},
|
|
35
|
+
"profit_margin" => {
|
|
36
|
+
"type" => "number",
|
|
37
|
+
"minimum" => 0,
|
|
38
|
+
"maximum" => 100
|
|
39
|
+
},
|
|
40
|
+
"growth_rate" => {
|
|
41
|
+
"type" => "number"
|
|
42
|
+
},
|
|
43
|
+
"trend" => {
|
|
44
|
+
"type" => "string",
|
|
45
|
+
"enum" => ["increasing", "stable", "decreasing"]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"recommendations" => {
|
|
50
|
+
"type" => "array",
|
|
51
|
+
"minItems" => 1,
|
|
52
|
+
"maxItems" => 10,
|
|
53
|
+
"items" => {
|
|
54
|
+
"type" => "object",
|
|
55
|
+
"required" => ["action", "priority", "rationale"],
|
|
56
|
+
"properties" => {
|
|
57
|
+
"action" => {
|
|
58
|
+
"type" => "string"
|
|
59
|
+
},
|
|
60
|
+
"priority" => {
|
|
61
|
+
"type" => "string",
|
|
62
|
+
"enum" => ["low", "medium", "high", "critical"]
|
|
63
|
+
},
|
|
64
|
+
"rationale" => {
|
|
65
|
+
"type" => "string"
|
|
66
|
+
},
|
|
67
|
+
"estimated_impact" => {
|
|
68
|
+
"type" => "object",
|
|
69
|
+
"properties" => {
|
|
70
|
+
"revenue_impact" => {
|
|
71
|
+
"type" => "number"
|
|
72
|
+
},
|
|
73
|
+
"risk_level" => {
|
|
74
|
+
"type" => "string",
|
|
75
|
+
"enum" => ["low", "medium", "high"]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"risk_factors" => {
|
|
83
|
+
"type" => "array",
|
|
84
|
+
"items" => {
|
|
85
|
+
"type" => "object",
|
|
86
|
+
"required" => ["factor", "severity"],
|
|
87
|
+
"properties" => {
|
|
88
|
+
"factor" => { "type" => "string" },
|
|
89
|
+
"severity" => {
|
|
90
|
+
"type" => "string",
|
|
91
|
+
"enum" => ["low", "medium", "high", "critical"]
|
|
92
|
+
},
|
|
93
|
+
"mitigation" => { "type" => "string" }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def analyze(data:)
|
|
102
|
+
prompt = <<~PROMPT
|
|
103
|
+
Analyze this financial data: #{data}
|
|
104
|
+
|
|
105
|
+
Return JSON with: summary (50-500 chars), metrics (revenue, profit_margin, growth_rate, trend),
|
|
106
|
+
recommendations array (action, priority, rationale), and optional risk_factors array.
|
|
107
|
+
PROMPT
|
|
108
|
+
|
|
109
|
+
@client.generate(prompt: prompt, schema: @schema)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Example 2: Code Review Schema
|
|
114
|
+
class CodeReviewer
|
|
115
|
+
def initialize(client:)
|
|
116
|
+
@client = client
|
|
117
|
+
@schema = {
|
|
118
|
+
"type" => "object",
|
|
119
|
+
"required" => ["overall_score", "issues", "suggestions"],
|
|
120
|
+
"properties" => {
|
|
121
|
+
"overall_score" => {
|
|
122
|
+
"type" => "integer",
|
|
123
|
+
"minimum" => 0,
|
|
124
|
+
"maximum" => 100
|
|
125
|
+
},
|
|
126
|
+
"issues" => {
|
|
127
|
+
"type" => "array",
|
|
128
|
+
"items" => {
|
|
129
|
+
"type" => "object",
|
|
130
|
+
"required" => ["type", "severity", "location", "description"],
|
|
131
|
+
"properties" => {
|
|
132
|
+
"type" => {
|
|
133
|
+
"type" => "string",
|
|
134
|
+
"enum" => ["bug", "security", "performance", "style", "maintainability"]
|
|
135
|
+
},
|
|
136
|
+
"severity" => {
|
|
137
|
+
"type" => "string",
|
|
138
|
+
"enum" => ["low", "medium", "high", "critical"]
|
|
139
|
+
},
|
|
140
|
+
"location" => {
|
|
141
|
+
"type" => "object",
|
|
142
|
+
"properties" => {
|
|
143
|
+
"file" => { "type" => "string" },
|
|
144
|
+
"line" => { "type" => "integer" },
|
|
145
|
+
"column" => { "type" => "integer" }
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"description" => { "type" => "string" },
|
|
149
|
+
"suggestion" => { "type" => "string" }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
"suggestions" => {
|
|
154
|
+
"type" => "array",
|
|
155
|
+
"items" => {
|
|
156
|
+
"type" => "object",
|
|
157
|
+
"required" => ["category", "description"],
|
|
158
|
+
"properties" => {
|
|
159
|
+
"category" => {
|
|
160
|
+
"type" => "string",
|
|
161
|
+
"enum" => ["refactoring", "optimization", "documentation", "testing"]
|
|
162
|
+
},
|
|
163
|
+
"description" => { "type" => "string" },
|
|
164
|
+
"priority" => {
|
|
165
|
+
"type" => "string",
|
|
166
|
+
"enum" => ["low", "medium", "high"]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"strengths" => {
|
|
172
|
+
"type" => "array",
|
|
173
|
+
"items" => { "type" => "string" }
|
|
174
|
+
},
|
|
175
|
+
"estimated_effort" => {
|
|
176
|
+
"type" => "object",
|
|
177
|
+
"properties" => {
|
|
178
|
+
"hours" => { "type" => "number", "minimum" => 0 },
|
|
179
|
+
"complexity" => {
|
|
180
|
+
"type" => "string",
|
|
181
|
+
"enum" => ["simple", "moderate", "complex"]
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def review(code:)
|
|
190
|
+
prompt = <<~PROMPT
|
|
191
|
+
Review this Ruby code: #{code}
|
|
192
|
+
|
|
193
|
+
Return JSON with: overall_score (0-100), issues array (type, severity, location, description),
|
|
194
|
+
suggestions array (category, description, priority), optional strengths array, optional estimated_effort.
|
|
195
|
+
PROMPT
|
|
196
|
+
|
|
197
|
+
@client.generate(prompt: prompt, schema: @schema)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Example 3: Research Paper Analysis Schema
|
|
202
|
+
class ResearchAnalyzer
|
|
203
|
+
def initialize(client:)
|
|
204
|
+
@client = client
|
|
205
|
+
@schema = {
|
|
206
|
+
"type" => "object",
|
|
207
|
+
"required" => ["title", "key_findings", "methodology", "citations"],
|
|
208
|
+
"properties" => {
|
|
209
|
+
"title" => { "type" => "string" },
|
|
210
|
+
"key_findings" => {
|
|
211
|
+
"type" => "array",
|
|
212
|
+
"minItems" => 3,
|
|
213
|
+
"maxItems" => 10,
|
|
214
|
+
"items" => {
|
|
215
|
+
"type" => "object",
|
|
216
|
+
"required" => ["finding", "significance"],
|
|
217
|
+
"properties" => {
|
|
218
|
+
"finding" => { "type" => "string" },
|
|
219
|
+
"significance" => {
|
|
220
|
+
"type" => "string",
|
|
221
|
+
"enum" => ["low", "medium", "high", "breakthrough"]
|
|
222
|
+
},
|
|
223
|
+
"evidence" => { "type" => "string" }
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
"methodology" => {
|
|
228
|
+
"type" => "object",
|
|
229
|
+
"required" => ["type", "description"],
|
|
230
|
+
"properties" => {
|
|
231
|
+
"type" => {
|
|
232
|
+
"type" => "string",
|
|
233
|
+
"enum" => ["experimental", "observational", "theoretical", "computational", "mixed"]
|
|
234
|
+
},
|
|
235
|
+
"description" => { "type" => "string" },
|
|
236
|
+
"limitations" => {
|
|
237
|
+
"type" => "array",
|
|
238
|
+
"items" => { "type" => "string" }
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
"citations" => {
|
|
243
|
+
"type" => "array",
|
|
244
|
+
"items" => {
|
|
245
|
+
"type" => "object",
|
|
246
|
+
"required" => ["author", "title", "year"],
|
|
247
|
+
"properties" => {
|
|
248
|
+
"author" => { "type" => "string" },
|
|
249
|
+
"title" => { "type" => "string" },
|
|
250
|
+
"year" => {
|
|
251
|
+
"type" => "integer",
|
|
252
|
+
"minimum" => 1900,
|
|
253
|
+
"maximum" => 2100
|
|
254
|
+
},
|
|
255
|
+
"relevance" => {
|
|
256
|
+
"type" => "string",
|
|
257
|
+
"enum" => ["low", "medium", "high"]
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
"reproducibility_score" => {
|
|
263
|
+
"type" => "number",
|
|
264
|
+
"minimum" => 0,
|
|
265
|
+
"maximum" => 1
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def analyze(paper_text:)
|
|
272
|
+
prompt = <<~PROMPT
|
|
273
|
+
Analyze this research paper: #{paper_text}
|
|
274
|
+
|
|
275
|
+
Return JSON with: title, key_findings array (3-10 items: finding, significance, evidence),
|
|
276
|
+
methodology (type, description, limitations), citations array (author, title, year, relevance),
|
|
277
|
+
optional reproducibility_score (0-1).
|
|
278
|
+
PROMPT
|
|
279
|
+
|
|
280
|
+
@client.generate(prompt: prompt, schema: @schema)
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Run examples
|
|
285
|
+
if __FILE__ == $PROGRAM_NAME
|
|
286
|
+
# Use longer timeout for complex schemas
|
|
287
|
+
config = Ollama::Config.new
|
|
288
|
+
config.timeout = 60 # 60 seconds for complex operations
|
|
289
|
+
client = Ollama::Client.new(config: config)
|
|
290
|
+
|
|
291
|
+
puts "=" * 60
|
|
292
|
+
puts "Example 1: Financial Analysis"
|
|
293
|
+
puts "=" * 60
|
|
294
|
+
financial_data = <<~DATA
|
|
295
|
+
Q4 2024 Financial Report:
|
|
296
|
+
- Revenue: $2.5M (up 15% from Q3)
|
|
297
|
+
- Operating expenses: $1.8M
|
|
298
|
+
- Net profit: $700K
|
|
299
|
+
- Customer base: 5,000 (up 20%)
|
|
300
|
+
- Churn rate: 2% (down from 3%)
|
|
301
|
+
DATA
|
|
302
|
+
|
|
303
|
+
analyzer = FinancialAnalyzer.new(client: client)
|
|
304
|
+
begin
|
|
305
|
+
puts "⏳ Analyzing financial data (this may take 30-60 seconds)..."
|
|
306
|
+
result = analyzer.analyze(data: financial_data)
|
|
307
|
+
puts JSON.pretty_generate(result)
|
|
308
|
+
rescue Ollama::TimeoutError => e
|
|
309
|
+
puts "⏱️ Timeout: #{e.message}"
|
|
310
|
+
puts " Try increasing timeout or using a faster model"
|
|
311
|
+
rescue Ollama::Error => e
|
|
312
|
+
puts "❌ Error: #{e.message}"
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
puts "\n" + "=" * 60
|
|
316
|
+
puts "Example 2: Code Review"
|
|
317
|
+
puts "=" * 60
|
|
318
|
+
code_sample = <<~RUBY
|
|
319
|
+
def calculate_total(items)
|
|
320
|
+
total = 0
|
|
321
|
+
items.each do |item|
|
|
322
|
+
total += item.price
|
|
323
|
+
end
|
|
324
|
+
total
|
|
325
|
+
end
|
|
326
|
+
RUBY
|
|
327
|
+
|
|
328
|
+
reviewer = CodeReviewer.new(client: client)
|
|
329
|
+
begin
|
|
330
|
+
puts "⏳ Reviewing code (this may take 30-60 seconds)..."
|
|
331
|
+
result = reviewer.review(code: code_sample)
|
|
332
|
+
puts JSON.pretty_generate(result)
|
|
333
|
+
rescue Ollama::TimeoutError => e
|
|
334
|
+
puts "⏱️ Timeout: #{e.message}"
|
|
335
|
+
puts " Try increasing timeout or using a faster model"
|
|
336
|
+
rescue Ollama::Error => e
|
|
337
|
+
puts "❌ Error: #{e.message}"
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
puts "\n" + "=" * 60
|
|
341
|
+
puts "Example 3: Research Paper Analysis"
|
|
342
|
+
puts "=" * 60
|
|
343
|
+
paper_abstract = <<~TEXT
|
|
344
|
+
This study investigates the impact of machine learning on financial forecasting.
|
|
345
|
+
We analyzed 10 years of market data using neural networks and found a 23% improvement
|
|
346
|
+
in prediction accuracy. The methodology involved training on historical data and
|
|
347
|
+
validating on out-of-sample periods. Key limitations include data quality and
|
|
348
|
+
model interpretability challenges.
|
|
349
|
+
TEXT
|
|
350
|
+
|
|
351
|
+
research_analyzer = ResearchAnalyzer.new(client: client)
|
|
352
|
+
begin
|
|
353
|
+
puts "⏳ Analyzing research paper (this may take 30-60 seconds)..."
|
|
354
|
+
result = research_analyzer.analyze(paper_text: paper_abstract)
|
|
355
|
+
puts JSON.pretty_generate(result)
|
|
356
|
+
rescue Ollama::TimeoutError => e
|
|
357
|
+
puts "⏱️ Timeout: #{e.message}"
|
|
358
|
+
puts " Try increasing timeout or using a faster model"
|
|
359
|
+
rescue Ollama::Error => e
|
|
360
|
+
puts "❌ Error: #{e.message}"
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|