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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -1
  3. data/README.md +560 -106
  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/README.md +2 -3
  11. data/docs/RELEASE_GUIDE.md +376 -0
  12. data/docs/TESTING.md +392 -170
  13. data/docs/TEST_CHECKLIST.md +450 -0
  14. data/docs/ruby_guide.md +6232 -0
  15. data/examples/README.md +51 -66
  16. data/examples/basic_chat.rb +33 -0
  17. data/examples/basic_generate.rb +29 -0
  18. data/examples/tool_calling_parsing.rb +59 -0
  19. data/exe/ollama-client +128 -1
  20. data/lib/ollama/agent/planner.rb +7 -2
  21. data/lib/ollama/chat_session.rb +101 -0
  22. data/lib/ollama/client.rb +43 -21
  23. data/lib/ollama/config.rb +4 -1
  24. data/lib/ollama/document_loader.rb +163 -0
  25. data/lib/ollama/embeddings.rb +42 -13
  26. data/lib/ollama/errors.rb +1 -0
  27. data/lib/ollama/personas.rb +287 -0
  28. data/lib/ollama/version.rb +1 -1
  29. data/lib/ollama_client.rb +8 -0
  30. metadata +31 -53
  31. data/docs/GEM_RELEASE_GUIDE.md +0 -794
  32. data/docs/GET_RUBYGEMS_SECRET.md +0 -151
  33. data/docs/QUICK_OTP_SETUP.md +0 -80
  34. data/docs/QUICK_RELEASE.md +0 -106
  35. data/docs/RUBYGEMS_OTP_SETUP.md +0 -199
  36. data/examples/advanced_complex_schemas.rb +0 -366
  37. data/examples/advanced_edge_cases.rb +0 -241
  38. data/examples/advanced_error_handling.rb +0 -200
  39. data/examples/advanced_multi_step_agent.rb +0 -341
  40. data/examples/advanced_performance_testing.rb +0 -186
  41. data/examples/chat_console.rb +0 -143
  42. data/examples/complete_workflow.rb +0 -245
  43. data/examples/dhan_console.rb +0 -843
  44. data/examples/dhanhq/README.md +0 -236
  45. data/examples/dhanhq/agents/base_agent.rb +0 -74
  46. data/examples/dhanhq/agents/data_agent.rb +0 -66
  47. data/examples/dhanhq/agents/orchestrator_agent.rb +0 -120
  48. data/examples/dhanhq/agents/technical_analysis_agent.rb +0 -252
  49. data/examples/dhanhq/agents/trading_agent.rb +0 -81
  50. data/examples/dhanhq/analysis/market_structure.rb +0 -138
  51. data/examples/dhanhq/analysis/pattern_recognizer.rb +0 -192
  52. data/examples/dhanhq/analysis/trend_analyzer.rb +0 -88
  53. data/examples/dhanhq/builders/market_context_builder.rb +0 -67
  54. data/examples/dhanhq/dhanhq_agent.rb +0 -829
  55. data/examples/dhanhq/indicators/technical_indicators.rb +0 -158
  56. data/examples/dhanhq/scanners/intraday_options_scanner.rb +0 -492
  57. data/examples/dhanhq/scanners/swing_scanner.rb +0 -247
  58. data/examples/dhanhq/schemas/agent_schemas.rb +0 -61
  59. data/examples/dhanhq/services/base_service.rb +0 -46
  60. data/examples/dhanhq/services/data_service.rb +0 -118
  61. data/examples/dhanhq/services/trading_service.rb +0 -59
  62. data/examples/dhanhq/technical_analysis_agentic_runner.rb +0 -411
  63. data/examples/dhanhq/technical_analysis_runner.rb +0 -420
  64. data/examples/dhanhq/test_tool_calling.rb +0 -538
  65. data/examples/dhanhq/test_tool_calling_verbose.rb +0 -251
  66. data/examples/dhanhq/utils/instrument_helper.rb +0 -32
  67. data/examples/dhanhq/utils/parameter_cleaner.rb +0 -28
  68. data/examples/dhanhq/utils/parameter_normalizer.rb +0 -45
  69. data/examples/dhanhq/utils/rate_limiter.rb +0 -23
  70. data/examples/dhanhq/utils/trading_parameter_normalizer.rb +0 -72
  71. data/examples/dhanhq_agent.rb +0 -964
  72. data/examples/dhanhq_tools.rb +0 -1663
  73. data/examples/multi_step_agent_with_external_data.rb +0 -368
  74. data/examples/structured_outputs_chat.rb +0 -72
  75. data/examples/structured_tools.rb +0 -89
  76. data/examples/test_dhanhq_tool_calling.rb +0 -375
  77. data/examples/test_tool_calling.rb +0 -160
  78. data/examples/tool_calling_direct.rb +0 -124
  79. data/examples/tool_calling_pattern.rb +0 -269
  80. data/exe/dhan_console +0 -4
@@ -0,0 +1,450 @@
1
+ # Test Checklist: `ollama-client` Client-Only Testing
2
+
3
+ This checklist ensures comprehensive testing of the `ollama-client` transport layer without leaking agent concerns.
4
+
5
+ ## Category A: `/generate` Mode Tests (Stateless, Deterministic)
6
+
7
+ ### ✅ G1 — Basic Generate
8
+ - [ ] Response is a Hash
9
+ - [ ] JSON is parsed correctly
10
+ - [ ] No `tool_calls` present
11
+ - [ ] No streaming artifacts
12
+ - [ ] Schema validation passes (if schema provided)
13
+
14
+ **Test File:** `spec/ollama/client_generate_spec.rb`
15
+
16
+ **Example Test:**
17
+ ```ruby
18
+ it "parses JSON response from generate endpoint" do
19
+ stub_request(:post, "http://localhost:11434/api/generate")
20
+ .to_return(
21
+ status: 200,
22
+ body: { response: '{"status":"ok"}' }.to_json
23
+ )
24
+
25
+ result = client.generate(
26
+ prompt: "Output a JSON object with a single key 'status' and value 'ok'.",
27
+ schema: { "type" => "object", "required" => ["status"] }
28
+ )
29
+
30
+ expect(result).to be_a(Hash)
31
+ expect(result["status"]).to eq("ok")
32
+ expect(result).not_to have_key("tool_calls")
33
+ end
34
+ ```
35
+
36
+ ---
37
+
38
+ ### ✅ G2 — Strict Schema Enforcement
39
+ - [ ] Raises error if schema violated
40
+ - [ ] Rejects extra fields (if strict mode enabled)
41
+ - [ ] Validates required fields
42
+ - [ ] Validates type constraints
43
+ - [ ] Validates enum constraints
44
+ - [ ] Validates number min/max
45
+ - [ ] Validates string minLength/maxLength
46
+
47
+ **Test File:** `spec/ollama/client_generate_spec.rb` or `spec/ollama/schema_validator_spec.rb`
48
+
49
+ **Example Test:**
50
+ ```ruby
51
+ it "rejects responses that violate schema" do
52
+ stub_request(:post, "http://localhost:11434/api/generate")
53
+ .to_return(
54
+ status: 200,
55
+ body: { response: '{"count":"not-a-number"}' }.to_json
56
+ )
57
+
58
+ schema = {
59
+ "type" => "object",
60
+ "required" => ["count"],
61
+ "properties" => {
62
+ "count" => { "type" => "number" }
63
+ }
64
+ }
65
+
66
+ expect do
67
+ client.generate(prompt: "Output JSON with key 'count' as a number.", schema: schema)
68
+ end.to raise_error(Ollama::SchemaViolationError)
69
+ end
70
+ ```
71
+
72
+ ---
73
+
74
+ ### ❌ G3 — Tool Attempt in Generate (Must Fail)
75
+ - [ ] No `tool_calls` parsed
76
+ - [ ] No silent acceptance of tool intent
77
+ - [ ] Either ignored or explicit error
78
+ - [ ] `/generate` remains non-agentic
79
+
80
+ **Test File:** `spec/ollama/client_generate_spec.rb`
81
+
82
+ **Example Test:**
83
+ ```ruby
84
+ it "ignores tool calls in generate mode" do
85
+ stub_request(:post, "http://localhost:11434/api/generate")
86
+ .to_return(
87
+ status: 200,
88
+ body: { response: '{"action":"call read_file tool on foo.rb"}' }.to_json
89
+ )
90
+
91
+ result = client.generate(
92
+ prompt: "Call the read_file tool on foo.rb",
93
+ schema: { "type" => "object" }
94
+ )
95
+
96
+ expect(result).not_to have_key("tool_calls")
97
+ expect(result).not_to have_key("tool_use")
98
+ end
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Category B: `/chat` Mode Tests (Stateful, Tool-Aware)
104
+
105
+ ### ✅ C1 — Simple Chat
106
+ - [ ] Response contains assistant message
107
+ - [ ] Message history preserved in request
108
+ - [ ] Role is correct
109
+ - [ ] Content is parsed correctly
110
+
111
+ **Test File:** `spec/ollama/client_chat_spec.rb` or `spec/ollama/client_chat_raw_spec.rb`
112
+
113
+ **Example Test:**
114
+ ```ruby
115
+ it "handles simple chat messages" do
116
+ stub_request(:post, "http://localhost:11434/api/chat")
117
+ .to_return(
118
+ status: 200,
119
+ body: {
120
+ message: { role: "assistant", content: "Hello!" }
121
+ }.to_json
122
+ )
123
+
124
+ response = client.chat_raw(
125
+ messages: [{ role: "user", content: "Say hello." }],
126
+ allow_chat: true
127
+ )
128
+
129
+ expect(response.message.content).to eq("Hello!")
130
+ expect(response.message.role).to eq("assistant")
131
+ end
132
+ ```
133
+
134
+ ---
135
+
136
+ ### ✅ C2 — Tool-Call Parsing (Critical)
137
+ - [ ] `tool_calls` extracted correctly
138
+ - [ ] Tool name parsed
139
+ - [ ] Arguments parsed as hash
140
+ - [ ] **No execution happens** (client must not execute tools)
141
+ - [ ] Multiple tool calls handled
142
+ - [ ] Tool call ID preserved (if present)
143
+
144
+ **Test File:** `spec/ollama/client_chat_raw_spec.rb`
145
+
146
+ **Example Test:**
147
+ ```ruby
148
+ it "extracts tool calls from chat response" do
149
+ stub_request(:post, "http://localhost:11434/api/chat")
150
+ .to_return(
151
+ status: 200,
152
+ body: {
153
+ message: {
154
+ role: "assistant",
155
+ content: "I'll call the ping tool.",
156
+ tool_calls: [
157
+ {
158
+ type: "function",
159
+ function: {
160
+ name: "ping",
161
+ arguments: { "x" => 1 }.to_json
162
+ }
163
+ }
164
+ ]
165
+ }
166
+ }.to_json
167
+ )
168
+
169
+ response = client.chat_raw(
170
+ messages: [{ role: "user", content: "If a tool named 'ping' exists, call it with { 'x': 1 }." }],
171
+ tools: [tool_definition],
172
+ allow_chat: true
173
+ )
174
+
175
+ tool_calls = response.message.tool_calls
176
+ expect(tool_calls).not_to be_empty
177
+ expect(tool_calls.first["function"]["name"]).to eq("ping")
178
+ expect(JSON.parse(tool_calls.first["function"]["arguments"])).to eq("x" => 1)
179
+ end
180
+ ```
181
+
182
+ ---
183
+
184
+ ### ✅ C3 — Tool Result Round-Trip Formatting
185
+ - [ ] Client serializes tool message correctly
186
+ - [ ] Ollama accepts tool result message
187
+ - [ ] Response parsed cleanly after tool result
188
+ - [ ] Tool name preserved
189
+ - [ ] Tool content serialized as JSON string
190
+
191
+ **Test File:** `spec/ollama/client_chat_raw_spec.rb`
192
+
193
+ **Example Test:**
194
+ ```ruby
195
+ it "serializes tool result messages correctly" do
196
+ messages = [
197
+ { role: "user", content: "Call ping tool" },
198
+ { role: "assistant", content: "", tool_calls: [...] },
199
+ { role: "tool", name: "ping", content: { ok: true }.to_json }
200
+ ]
201
+
202
+ stub_request(:post, "http://localhost:11434/api/chat")
203
+ .with(body: hash_including(messages: messages))
204
+ .to_return(
205
+ status: 200,
206
+ body: { message: { role: "assistant", content: "Done!" } }.to_json
207
+ )
208
+
209
+ response = client.chat_raw(messages: messages, allow_chat: true)
210
+ expect(response.message.content).to eq("Done!")
211
+ end
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Category C: Protocol Adapters (Anthropic / Native)
217
+
218
+ ### ✅ A1 — Anthropic Message Shape
219
+ - [ ] Messages serialized as content blocks
220
+ - [ ] Tool calls emitted as `tool_use` (if Anthropic mode)
221
+ - [ ] Tool results serialized as `tool_result`
222
+ - [ ] Request payload matches Anthropic format
223
+
224
+ **Test File:** `spec/ollama/client_chat_raw_spec.rb` or new `spec/ollama/protocol_adapter_spec.rb`
225
+
226
+ **Example Test:**
227
+ ```ruby
228
+ it "serializes messages in Anthropic format" do
229
+ stub_request(:post, "http://localhost:11434/api/chat")
230
+ .with do |req|
231
+ body = JSON.parse(req.body)
232
+ expect(body["messages"]).to be_an(Array)
233
+ expect(body["messages"].first).to include("role", "content")
234
+ end
235
+ .to_return(status: 200, body: { message: {} }.to_json)
236
+
237
+ client.chat_raw(
238
+ messages: [{ role: "user", content: "Test" }],
239
+ allow_chat: true
240
+ )
241
+ end
242
+ ```
243
+
244
+ ---
245
+
246
+ ### ✅ A2 — Anthropic Response Parsing
247
+ - [ ] Client normalizes Anthropic format into internal `tool_calls`
248
+ - [ ] Protocol adapter correctness
249
+ - [ ] Tool use ID preserved
250
+ - [ ] Tool input parsed correctly
251
+
252
+ **Test File:** `spec/ollama/client_chat_raw_spec.rb` or new `spec/ollama/protocol_adapter_spec.rb`
253
+
254
+ **Example Test:**
255
+ ```ruby
256
+ it "normalizes Anthropic-style responses into internal format" do
257
+ anthropic_response = {
258
+ content: [
259
+ {
260
+ type: "tool_use",
261
+ id: "call_123",
262
+ name: "search",
263
+ input: { q: "foo" }
264
+ }
265
+ ]
266
+ }
267
+
268
+ stub_request(:post, "http://localhost:11434/api/chat")
269
+ .to_return(status: 200, body: anthropic_response.to_json)
270
+
271
+ response = client.chat_raw(
272
+ messages: [{ role: "user", content: "Search for foo" }],
273
+ allow_chat: true
274
+ )
275
+
276
+ tool_calls = response.message.tool_calls
277
+ expect(tool_calls).not_to be_empty
278
+ expect(tool_calls.first["function"]["name"]).to eq("search")
279
+ end
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Category D: Failure Modes (Non-Negotiable)
285
+
286
+ ### ✅ F1 — Ollama Down
287
+ - [ ] Connection refused raises correct exception
288
+ - [ ] No hangs
289
+ - [ ] Retries handled correctly (if retryable)
290
+ - [ ] Error message is clear
291
+
292
+ **Test File:** `spec/ollama/errors_spec.rb` or `spec/ollama/client_spec.rb`
293
+
294
+ **Example Test:**
295
+ ```ruby
296
+ it "handles connection refused gracefully" do
297
+ stub_request(:post, "http://localhost:11434/api/generate")
298
+ .to_raise(Errno::ECONNREFUSED)
299
+
300
+ start_time = Time.now
301
+ expect do
302
+ client.generate(prompt: "test", schema: schema)
303
+ end.to raise_error(Ollama::Error)
304
+
305
+ # Verify no hangs
306
+ expect(Time.now - start_time).to be < 5
307
+ end
308
+ ```
309
+
310
+ ---
311
+
312
+ ### ✅ F2 — Invalid JSON from Model
313
+ - [ ] Client raises parse error
314
+ - [ ] Does not silently continue
315
+ - [ ] Retries handled (if retryable)
316
+ - [ ] Error message includes context
317
+
318
+ **Test File:** `spec/ollama/errors_spec.rb`
319
+
320
+ **Example Test:**
321
+ ```ruby
322
+ it "raises error on invalid JSON response" do
323
+ stub_request(:post, "http://localhost:11434/api/generate")
324
+ .to_return(status: 200, body: { response: "not json at all" }.to_json)
325
+
326
+ expect do
327
+ client.generate(prompt: "test", schema: schema)
328
+ end.to raise_error(Ollama::InvalidJSONError)
329
+ end
330
+ ```
331
+
332
+ ---
333
+
334
+ ### ✅ F3 — Streaming Interruption
335
+ - [ ] Partial stream handled
336
+ - [ ] Client terminates cleanly
337
+ - [ ] No corrupted state
338
+ - [ ] Error raised appropriately
339
+
340
+ **Test File:** `spec/ollama/client_chat_raw_spec.rb` (if streaming tests exist)
341
+
342
+ **Example Test:**
343
+ ```ruby
344
+ it "handles partial stream gracefully" do
345
+ stub_request(:post, "http://localhost:11434/api/chat")
346
+ .to_return(
347
+ status: 200,
348
+ body: "data: {\"message\":{\"content\":\"partial\"}}\n",
349
+ headers: { "Content-Type" => "text/event-stream" }
350
+ )
351
+
352
+ # Simulate stream interruption
353
+ expect do
354
+ client.chat_raw(messages: [{ role: "user", content: "test" }], allow_chat: true)
355
+ end.to raise_error(Ollama::Error)
356
+ end
357
+ ```
358
+
359
+ ---
360
+
361
+ ## Additional Test Areas
362
+
363
+ ### Retry Logic
364
+ - [ ] Retries up to `config.retries` times
365
+ - [ ] Only retries retryable errors (5xx, 408, 429)
366
+ - [ ] Raises `RetryExhaustedError` after max retries
367
+ - [ ] Succeeds if retry succeeds
368
+ - [ ] Non-retryable errors (400, 404) fail immediately
369
+
370
+ ### Error Handling
371
+ - [ ] **404 (NotFoundError)**: Model not found, no retries, includes suggestions
372
+ - [ ] **500 (HTTPError)**: Retryable, retries up to config limit
373
+ - [ ] **400 (HTTPError)**: Non-retryable, fails immediately
374
+ - [ ] **TimeoutError**: Retries on timeout
375
+ - [ ] **InvalidJSONError**: Retries on JSON parse errors
376
+ - [ ] **SchemaViolationError**: Retries on schema validation failures
377
+ - [ ] **Connection Errors**: Retries on network failures
378
+
379
+ ### Edge Cases
380
+ - [ ] JSON wrapped in markdown code blocks
381
+ - [ ] Plain JSON responses
382
+ - [ ] Empty model lists
383
+ - [ ] Missing response fields
384
+ - [ ] Malformed JSON
385
+ - [ ] Empty prompts
386
+ - [ ] Very long prompts
387
+ - [ ] Special characters in prompts
388
+
389
+ ### Model Suggestions
390
+ - [ ] Suggests similar models on 404
391
+ - [ ] Fuzzy matching on model names
392
+ - [ ] Limits suggestions to 5 models
393
+ - [ ] Handles model listing failures gracefully
394
+
395
+ ---
396
+
397
+ ## What NOT to Test (Agent Concerns)
398
+
399
+ ❌ **Do not test:**
400
+ - Infinite loops
401
+ - Retries based on content
402
+ - Agent stopping behavior
403
+ - Tool side effects
404
+ - Correctness of answers
405
+ - Agent convergence logic
406
+ - Policy decisions
407
+ - Tool execution
408
+ - Agent state management
409
+
410
+ **Those belong to `agent-runtime` and app repos.**
411
+
412
+ ---
413
+
414
+ ## Test Coverage Goals
415
+
416
+ - **Transport layer**: 100% coverage
417
+ - **Protocol parsing**: 100% coverage
418
+ - **Error handling**: 100% coverage
419
+ - **Schema validation**: 100% coverage
420
+ - **Tool-call parsing**: 100% coverage
421
+
422
+ **Note:** We don't aim for 100% line coverage of agent logic because agent logic doesn't belong in this gem.
423
+
424
+ ---
425
+
426
+ ## Running the Checklist
427
+
428
+ 1. Review each category
429
+ 2. Mark tests as complete when implemented
430
+ 3. Add test file references
431
+ 4. Update this checklist as new test categories are identified
432
+ 5. Remove tests that leak agent concerns
433
+
434
+ ---
435
+
436
+ ## Test File Organization
437
+
438
+ ```
439
+ spec/
440
+ ├── ollama/
441
+ │ ├── client_spec.rb # Basic initialization, config
442
+ │ ├── client_generate_spec.rb # Category A (G1-G3)
443
+ │ ├── client_chat_spec.rb # Category B (C1-C3) - basic chat
444
+ │ ├── client_chat_raw_spec.rb # Category B (C1-C3) - tool calls
445
+ │ ├── protocol_adapter_spec.rb # Category C (A1-A2) - if needed
446
+ │ ├── errors_spec.rb # Category D (F1-F3)
447
+ │ ├── schema_validator_spec.rb # Category A (G2)
448
+ │ ├── client_list_models_spec.rb # Model listing
449
+ │ └── client_model_suggestions_spec.rb # Model suggestions
450
+ ```