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
data/README.md CHANGED
@@ -26,11 +26,15 @@ Domain tools and application logic live **outside** this gem. For convenience, i
26
26
 
27
27
  ## 🚫 What This Gem IS NOT
28
28
 
29
- * Domain tool implementations
30
- * ❌ Domain logic
31
- * ❌ Memory store
32
- * ❌ Chat UI
33
- * ❌ A promise of full Ollama API coverage (it focuses on agent workflows)
29
+ This gem is **NOT**:
30
+ * ❌ A chatbot UI framework
31
+ * ❌ A domain-specific agent implementation
32
+ * ❌ A tool execution engine
33
+ * ❌ A memory store
34
+ * ❌ A promise of full Ollama API coverage (focuses on agent workflows)
35
+ * ❌ An agent runtime (it provides transport + protocol, not agent logic)
36
+
37
+ **Domain tools and application logic live outside this gem.**
34
38
 
35
39
  This keeps it **clean and future-proof**.
36
40
 
@@ -74,24 +78,226 @@ gem install ollama-client
74
78
 
75
79
  ### Primary API: `generate()`
76
80
 
77
- **`generate(prompt:, schema:)`** is the **primary and recommended method** for agent-grade usage:
81
+ **`generate(prompt:, schema: nil, allow_plain_text: false)`** is the **primary and recommended method** for agent-grade usage:
78
82
 
79
83
  - ✅ Stateless, explicit state injection
80
84
  - ✅ Uses `/api/generate` endpoint
81
85
  - ✅ Ideal for: agent planning, tool routing, one-shot analysis, classification, extraction
82
86
  - ✅ No implicit memory or conversation history
87
+ - ✅ Supports both structured JSON (with schema) and plain text/markdown (with `allow_plain_text: true`)
83
88
 
84
89
  **This is the method you should use for hybrid agents.**
85
90
 
91
+ **Usage:**
92
+ - **With schema** (structured JSON): `generate(prompt: "...", schema: {...})` - returns Hash
93
+ - **Without schema** (plain text): `generate(prompt: "...", allow_plain_text: true)` - returns String
94
+
86
95
  ### Choosing the Correct API (generate vs chat)
87
96
 
88
97
  - **Use `/api/generate`** (via `Ollama::Client#generate` or `Ollama::Agent::Planner`) for **stateless planner/router** steps where you want strict, deterministic structured outputs.
89
98
  - **Use `/api/chat`** (via `Ollama::Agent::Executor`) for **stateful tool-using** workflows where the model may request tool calls across multiple turns.
90
99
 
91
100
  **Warnings:**
92
- - Dont use `generate()` for tool-calling loops (youll end up re-implementing message/tool lifecycles).
93
- - Dont use `chat()` for deterministic planners unless youre intentionally managing conversation state.
94
- - Dont let streaming output drive decisions (streaming is presentation-only).
101
+ - Don't use `generate()` for tool-calling loops (you'll end up re-implementing message/tool lifecycles).
102
+ - Don't use `chat()` for deterministic planners unless you're intentionally managing conversation state.
103
+ - Don't let streaming output drive decisions (streaming is presentation-only).
104
+
105
+ ### Providing Context to Queries
106
+
107
+ You can provide context to your queries in several ways:
108
+
109
+ **Option 1: Include context directly in the prompt (generate)**
110
+
111
+ ```ruby
112
+ require "ollama_client"
113
+
114
+ client = Ollama::Client.new
115
+
116
+ # Build prompt with context
117
+ context = "User's previous actions: search, calculate, validate"
118
+ user_query = "What should I do next?"
119
+
120
+ full_prompt = "Given this context: #{context}\n\nUser asks: #{user_query}"
121
+
122
+ result = client.generate(
123
+ prompt: full_prompt,
124
+ schema: {
125
+ "type" => "object",
126
+ "required" => ["action"],
127
+ "properties" => {
128
+ "action" => { "type" => "string" }
129
+ }
130
+ }
131
+ )
132
+ ```
133
+
134
+ **Option 2: Use system messages (chat/chat_raw)**
135
+
136
+ ```ruby
137
+ require "ollama_client"
138
+
139
+ client = Ollama::Client.new
140
+
141
+ # Provide context via system message
142
+ context = "You are analyzing market data. Current market status: Bullish. Key indicators: RSI 65, MACD positive."
143
+
144
+ response = client.chat_raw(
145
+ messages: [
146
+ { role: "system", content: context },
147
+ { role: "user", content: "What's the next trading action?" }
148
+ ],
149
+ allow_chat: true
150
+ )
151
+
152
+ puts response.message.content
153
+ ```
154
+
155
+ **Option 3: Use Planner with context parameter**
156
+
157
+ ```ruby
158
+ require "ollama_client"
159
+
160
+ client = Ollama::Client.new
161
+ planner = Ollama::Agent::Planner.new(client)
162
+
163
+ context = {
164
+ previous_actions: ["search", "calculate"],
165
+ user_preferences: "prefers conservative strategies"
166
+ }
167
+
168
+ plan = planner.run(
169
+ prompt: "Decide the next action",
170
+ context: context
171
+ )
172
+ ```
173
+
174
+ **Option 4: Load documents from directory (DocumentLoader)**
175
+
176
+ ```ruby
177
+ require "ollama_client"
178
+
179
+ client = Ollama::Client.new
180
+
181
+ # Load all documents from a directory (supports .txt, .md, .csv, .json)
182
+ loader = Ollama::DocumentLoader.new("docs/")
183
+ loader.load_all # Loads all supported files
184
+
185
+ # Get all documents as context
186
+ context = loader.to_context
187
+
188
+ # Use in your query
189
+ result = client.generate(
190
+ prompt: "Context from documents:\n#{context}\n\nQuestion: What is Ruby?",
191
+ schema: {
192
+ "type" => "object",
193
+ "required" => ["answer"],
194
+ "properties" => {
195
+ "answer" => { "type" => "string" }
196
+ }
197
+ }
198
+ )
199
+
200
+ # Or load specific files
201
+ loader.load_file("ruby_guide.md")
202
+ ruby_context = loader["ruby_guide.md"]
203
+
204
+ result = client.generate(
205
+ prompt: "Based on this documentation:\n#{ruby_context}\n\nExplain Ruby's key features."
206
+ )
207
+ ```
208
+
209
+ **Option 5: RAG-style context injection (using embeddings + DocumentLoader)**
210
+
211
+ ```ruby
212
+ require "ollama_client"
213
+
214
+ client = Ollama::Client.new
215
+
216
+ # 1. Load documents
217
+ loader = Ollama::DocumentLoader.new("docs/")
218
+ loader.load_all
219
+
220
+ # 2. When querying, find relevant context using embeddings
221
+ query = "What is Ruby?"
222
+ # (In real RAG, you'd compute embeddings and find similar docs)
223
+
224
+ # 3. Inject relevant context into prompt
225
+ relevant_context = loader["ruby_guide.md"] # Or find via similarity search
226
+
227
+ result = client.generate(
228
+ prompt: "Context: #{relevant_context}\n\nQuestion: #{query}\n\nAnswer based on the context:"
229
+ )
230
+ ```
231
+
232
+ **Option 5: Multi-turn conversation with accumulated context**
233
+
234
+ ```ruby
235
+ require "ollama_client"
236
+
237
+ client = Ollama::Client.new
238
+
239
+ messages = [
240
+ { role: "system", content: "You are a helpful assistant with access to context." },
241
+ { role: "user", content: "What is Ruby?" }
242
+ ]
243
+
244
+ # First response
245
+ response1 = client.chat_raw(messages: messages, allow_chat: true)
246
+ puts response1.message.content
247
+
248
+ # Add context and continue conversation
249
+ messages << { role: "assistant", content: response1.message.content }
250
+ messages << { role: "user", content: "Tell me more about its use cases" }
251
+
252
+ response2 = client.chat_raw(messages: messages, allow_chat: true)
253
+ puts response2.message.content
254
+ ```
255
+
256
+ ### Plain Text / Markdown Responses (No JSON Schema)
257
+
258
+ For simple text or markdown responses without JSON validation, you can use either `generate()` or `chat_raw()`:
259
+
260
+ **Option 1: Using `generate()` (recommended for simple queries)**
261
+
262
+ ```ruby
263
+ require "ollama_client"
264
+
265
+ client = Ollama::Client.new
266
+
267
+ # Get plain text/markdown response (use allow_plain_text: true to skip schema)
268
+ text_response = client.generate(
269
+ prompt: "Explain Ruby in simple terms",
270
+ allow_plain_text: true
271
+ )
272
+
273
+ puts text_response
274
+ # Output: Plain text or markdown explanation (String)
275
+ ```
276
+
277
+ **Option 2: Using `chat_raw()` (for multi-turn conversations)**
278
+
279
+ ```ruby
280
+ require "ollama_client"
281
+
282
+ client = Ollama::Client.new
283
+
284
+ # Get plain text/markdown response (no format required)
285
+ response = client.chat_raw(
286
+ messages: [{ role: "user", content: "Explain Ruby in simple terms" }],
287
+ allow_chat: true
288
+ )
289
+
290
+ # Access the plain text content
291
+ text_response = response.message.content
292
+ puts text_response
293
+ # Output: Plain text or markdown explanation
294
+ ```
295
+
296
+ **When to use which:**
297
+ - **`generate()` with `allow_plain_text: true`** - Simple one-shot queries, explanations, text generation
298
+ - **`generate()` with schema** - Structured JSON outputs for agents (default, recommended)
299
+ - **`chat_raw()` without format** - Multi-turn conversations with plain text
300
+ - **`chat_raw()` with format** - Multi-turn conversations with structured outputs
95
301
 
96
302
  ### Scope / endpoint coverage
97
303
 
@@ -114,6 +320,40 @@ Within `Ollama::Agent`:
114
320
  ```ruby
115
321
  require "ollama_client"
116
322
 
323
+ client = Ollama::Client.new
324
+
325
+ # Option 1: With schema (recommended for structured outputs)
326
+ DECISION_SCHEMA = {
327
+ "type" => "object",
328
+ "required" => ["action", "reasoning"],
329
+ "properties" => {
330
+ "action" => {
331
+ "type" => "string",
332
+ "enum" => ["search", "calculate", "store", "retrieve", "finish"]
333
+ },
334
+ "reasoning" => {
335
+ "type" => "string"
336
+ }
337
+ }
338
+ }
339
+
340
+ planner = Ollama::Agent::Planner.new(client)
341
+
342
+ plan = planner.run(
343
+ prompt: "Given the user request, decide the next action.",
344
+ schema: DECISION_SCHEMA,
345
+ context: { user_request: "Plan a weekend trip to Rome" }
346
+ )
347
+
348
+ puts plan["action"] # => "search" (or one of the enum values)
349
+ puts plan["reasoning"] # => Explanation string
350
+ ```
351
+
352
+ **Option 2: Without schema (returns any JSON)**
353
+
354
+ ```ruby
355
+ require "ollama_client"
356
+
117
357
  client = Ollama::Client.new
118
358
  planner = Ollama::Agent::Planner.new(client)
119
359
 
@@ -125,7 +365,7 @@ plan = planner.run(
125
365
  context: { user_request: "Plan a weekend trip to Rome" }
126
366
  )
127
367
 
128
- puts plan
368
+ puts plan # => Any valid JSON structure
129
369
  ```
130
370
 
131
371
  ### Executor Agent (tool loop, /api/chat)
@@ -200,16 +440,36 @@ Use structured tools when you need:
200
440
  All Tool classes support serialization and deserialization:
201
441
 
202
442
  ```ruby
443
+ # Create a tool
444
+ tool = Ollama::Tool.new(
445
+ type: "function",
446
+ function: Ollama::Tool::Function.new(
447
+ name: "fetch_weather",
448
+ description: "Get weather for a city",
449
+ parameters: Ollama::Tool::Function::Parameters.new(
450
+ type: "object",
451
+ properties: {
452
+ city: Ollama::Tool::Function::Parameters::Property.new(
453
+ type: "string",
454
+ description: "The city name"
455
+ )
456
+ },
457
+ required: %w[city]
458
+ )
459
+ )
460
+ )
461
+
203
462
  # Serialize to JSON
204
463
  json = tool.to_json
205
464
 
206
465
  # Deserialize from hash
207
- tool = Ollama::Tool.from_hash(JSON.parse(json))
466
+ tool2 = Ollama::Tool.from_hash(JSON.parse(json))
208
467
 
209
468
  # Equality comparison
210
- tool1 == tool2 # Compares hash representations
469
+ tool == tool2 # Compares hash representations (returns true)
211
470
 
212
471
  # Empty check
472
+ params = Ollama::Tool::Function::Parameters.new(type: "object", properties: {})
213
473
  params.empty? # True if no properties/required fields
214
474
  ```
215
475
 
@@ -267,7 +527,23 @@ end
267
527
 
268
528
  ### Quick Start Pattern
269
529
 
270
- The basic pattern for using structured outputs:
530
+ **Option 1: Plain text/markdown (no schema)**
531
+
532
+ ```ruby
533
+ require "ollama_client"
534
+
535
+ client = Ollama::Client.new
536
+
537
+ # Simple text response - no schema needed
538
+ response = client.generate(
539
+ prompt: "Explain Ruby programming in one sentence"
540
+ )
541
+
542
+ puts response
543
+ # Output: Plain text explanation
544
+ ```
545
+
546
+ **Option 2: Structured JSON (with schema)**
271
547
 
272
548
  ```ruby
273
549
  require "ollama_client"
@@ -288,7 +564,7 @@ schema = {
288
564
  begin
289
565
  result = client.generate(
290
566
  model: "llama3.1:8b",
291
- prompt: "Your prompt here",
567
+ prompt: "Return a JSON object with field1 as a string and field2 as a number. Example: field1 could be 'example' and field2 could be 42.",
292
568
  schema: schema
293
569
  )
294
570
 
@@ -400,7 +676,18 @@ end
400
676
  **For agents, prefer `generate()` with explicit state injection:**
401
677
 
402
678
  ```ruby
679
+ # Define decision schema
680
+ decision_schema = {
681
+ "type" => "object",
682
+ "required" => ["action", "reasoning"],
683
+ "properties" => {
684
+ "action" => { "type" => "string" },
685
+ "reasoning" => { "type" => "string" }
686
+ }
687
+ }
688
+
403
689
  # ✅ GOOD: Explicit state in prompt
690
+ actions = ["search", "calculate", "validate"]
404
691
  context = "Previous actions: #{actions.join(', ')}"
405
692
  result = client.generate(
406
693
  prompt: "Given context: #{context}. Decide next action.",
@@ -408,10 +695,74 @@ result = client.generate(
408
695
  )
409
696
 
410
697
  # ❌ AVOID: Implicit conversation history
411
- messages = [{ role: "user", content: "..." }]
412
- result = client.chat(messages: messages, format: schema, allow_chat: true) # History grows silently
698
+ messages = [{ role: "user", content: "Decide the next action based on previous actions: search, calculate, validate" }]
699
+ result = client.chat(messages: messages, format: decision_schema, allow_chat: true)
700
+
701
+ # Problem: History grows silently - you must manually manage it
702
+ messages << { role: "assistant", content: result.to_json }
703
+ messages << { role: "user", content: "Now do the next step" }
704
+ result2 = client.chat(messages: messages, format: decision_schema, allow_chat: true)
705
+ # messages.size is now 3, and will keep growing with each turn
706
+ # You must manually track what's in the history
707
+ # Schema validation can become weaker with accumulated context
708
+ # Harder to reason about state in agent systems
709
+ ```
710
+
711
+ ### Decision Table: `generate()` vs `chat()` vs `ChatSession`
712
+
713
+ > **Use `generate()` for systems. Use `chat()` or `ChatSession` for humans.**
714
+
715
+ | Use Case | Method | Schema Guarantees | Streaming | Memory | When to Use |
716
+ |----------|--------|-------------------|-----------|--------|-------------|
717
+ | **Agent planning/routing** | `generate()` | ✅ Strong | ❌ No | ❌ Stateless | Default for agents |
718
+ | **Structured extraction** | `generate()` | ✅ Strong | ❌ No | ❌ Stateless | Data extraction, classification |
719
+ | **Tool-calling loops** | `chat_raw()` | ⚠️ Weaker | ✅ Yes | ✅ Stateful | Executor agent internals |
720
+ | **UI chat interface** | `ChatSession` | ⚠️ Best-effort | ✅ Yes | ✅ Stateful | Human-facing assistants |
721
+ | **Multi-turn conversations** | `ChatSession` | ⚠️ Best-effort | ✅ Yes | ✅ Stateful | Interactive chat |
722
+
723
+ **Core Rule:** Chat must be a feature flag, not default behavior.
724
+
725
+ ### Using `ChatSession` for Human-Facing Chat
726
+
727
+ For UI assistants and interactive chat, use `ChatSession` to manage conversation state:
728
+
729
+ ```ruby
730
+ require "ollama_client"
731
+
732
+ # Enable chat in config
733
+ config = Ollama::Config.new
734
+ config.allow_chat = true
735
+ config.streaming_enabled = true
736
+
737
+ client = Ollama::Client.new(config: config)
738
+
739
+ # Create streaming observer for presentation
740
+ observer = Ollama::StreamingObserver.new do |event|
741
+ case event.type
742
+ when :token
743
+ print event.text
744
+ when :final
745
+ puts "\n--- DONE ---"
746
+ end
747
+ end
748
+
749
+ # Create chat session with system message
750
+ chat = Ollama::ChatSession.new(
751
+ client,
752
+ system: "You are a helpful assistant",
753
+ stream: observer
754
+ )
755
+
756
+ # Send messages (history is managed automatically)
757
+ chat.say("Hello")
758
+ chat.say("Explain Ruby blocks")
759
+
760
+ # Clear history if needed (keeps system message)
761
+ chat.clear
413
762
  ```
414
763
 
764
+ **Important:** Schema validation in chat is **best-effort** for formatting, not correctness. Never use chat+schema for agent control flow.
765
+
415
766
  ### Example: Chat API (Advanced Use Case)
416
767
 
417
768
  ```ruby
@@ -567,7 +918,7 @@ data = "Sales increased 25% this quarter, customer satisfaction is at 4.8/5"
567
918
 
568
919
  begin
569
920
  result = client.generate(
570
- prompt: "Analyze this data: #{data}",
921
+ prompt: "Analyze this data: #{data}. Return confidence as a decimal between 0 and 1 (e.g., 0.85 for 85% confidence).",
571
922
  schema: analysis_schema
572
923
  )
573
924
 
@@ -589,7 +940,8 @@ begin
589
940
 
590
941
  rescue Ollama::SchemaViolationError => e
591
942
  puts "Analysis failed validation: #{e.message}"
592
- # Could retry or use fallback logic
943
+ puts "The LLM response didn't match the schema constraints."
944
+ # Could retry with a clearer prompt or use fallback logic
593
945
  rescue Ollama::TimeoutError => e
594
946
  puts "Request timed out: #{e.message}"
595
947
  rescue Ollama::Error => e
@@ -631,6 +983,63 @@ models = client.list_models
631
983
  puts "Available models: #{models.join(', ')}"
632
984
  ```
633
985
 
986
+ ### Loading Documents from Directory (DocumentLoader)
987
+
988
+ Load files from a directory and use them as context for your queries. Supports `.txt`, `.md`, `.csv`, and `.json` files:
989
+
990
+ ```ruby
991
+ require "ollama_client"
992
+
993
+ client = Ollama::Client.new
994
+
995
+ # Load all documents from a directory
996
+ loader = Ollama::DocumentLoader.new("docs/")
997
+ loader.load_all # Loads all .txt, .md, .csv, .json files
998
+
999
+ # Get all documents as a single context string
1000
+ context = loader.to_context
1001
+
1002
+ # Use in your query
1003
+ result = client.generate(
1004
+ prompt: "Context from documents:\n#{context}\n\nQuestion: What is Ruby?",
1005
+ schema: {
1006
+ "type" => "object",
1007
+ "required" => ["answer"],
1008
+ "properties" => {
1009
+ "answer" => { "type" => "string" }
1010
+ }
1011
+ }
1012
+ )
1013
+
1014
+ # Load specific file
1015
+ ruby_guide = loader.load_file("ruby_guide.md")
1016
+
1017
+ # Access loaded documents
1018
+ all_files = loader.files # ["ruby_guide.md", "python_intro.txt", ...]
1019
+ specific_doc = loader["ruby_guide.md"]
1020
+
1021
+ # Load recursively from subdirectories
1022
+ loader.load_all(recursive: true)
1023
+
1024
+ # Select documents by pattern
1025
+ ruby_docs = loader.select(/ruby/)
1026
+ ```
1027
+
1028
+ **Supported file types:**
1029
+ - **`.txt`** - Plain text files
1030
+ - **`.md`, `.markdown`** - Markdown files
1031
+ - **`.csv`** - CSV files (converted to readable text format)
1032
+ - **`.json`** - JSON files (pretty-printed)
1033
+
1034
+ **Example directory structure:**
1035
+ ```
1036
+ docs/
1037
+ ├── ruby_guide.md
1038
+ ├── python_intro.txt
1039
+ ├── data.csv
1040
+ └── config.json
1041
+ ```
1042
+
634
1043
  ### Embeddings for RAG/Semantic Search
635
1044
 
636
1045
  Use embeddings for building knowledge bases and semantic search in agents:
@@ -640,21 +1049,55 @@ require "ollama_client"
640
1049
 
641
1050
  client = Ollama::Client.new
642
1051
 
643
- # Single text embedding
644
- embedding = client.embeddings.embed(
645
- model: "all-minilm",
646
- input: "What is Ruby programming?"
647
- )
648
- # Returns: [0.123, -0.456, ...] (array of floats)
1052
+ # Note: You need an embedding model installed in Ollama
1053
+ # Common models: nomic-embed-text, all-minilm, mxbai-embed-large
1054
+ # Check available models: client.list_models
649
1055
 
650
- # Multiple texts
651
- embeddings = client.embeddings.embed(
652
- model: "all-minilm",
653
- input: ["What is Ruby?", "What is Python?", "What is JavaScript?"]
654
- )
655
- # Returns: [[...], [...], [...]] (array of embedding arrays)
1056
+ begin
1057
+ # Single text embedding
1058
+ # Note: Use the full model name with tag if needed (e.g., "nomic-embed-text:latest")
1059
+ embedding = client.embeddings.embed(
1060
+ model: "nomic-embed-text:latest", # Use an available embedding model
1061
+ input: "What is Ruby programming?"
1062
+ )
1063
+ # Returns: [0.123, -0.456, ...] (array of floats)
1064
+ if embedding.empty?
1065
+ puts "Warning: Empty embedding returned. Check model compatibility."
1066
+ else
1067
+ puts "Embedding dimension: #{embedding.length}"
1068
+ puts "First few values: #{embedding.first(5).map { |v| v.round(4) }}"
1069
+ end
1070
+
1071
+ # Multiple texts
1072
+ embeddings = client.embeddings.embed(
1073
+ model: "nomic-embed-text:latest",
1074
+ input: ["What is Ruby?", "What is Python?", "What is JavaScript?"]
1075
+ )
1076
+ # Returns: [[...], [...], [...]] (array of embedding arrays)
1077
+ if embeddings.is_a?(Array) && embeddings.first.is_a?(Array)
1078
+ puts "Number of embeddings: #{embeddings.length}"
1079
+ puts "Each embedding dimension: #{embeddings.first.length}"
1080
+ else
1081
+ puts "Unexpected response format: #{embeddings.class}"
1082
+ end
1083
+
1084
+ rescue Ollama::NotFoundError => e
1085
+ puts "Model not found. Install an embedding model first:"
1086
+ puts " ollama pull nomic-embed-text"
1087
+ puts "Or check available models: client.list_models"
1088
+ puts "Note: Use the full model name with tag (e.g., 'nomic-embed-text:latest')"
1089
+ rescue Ollama::Error => e
1090
+ puts "Error: #{e.message}"
1091
+ end
656
1092
 
657
1093
  # Use for semantic similarity in agents
1094
+ def cosine_similarity(vec1, vec2)
1095
+ dot_product = vec1.zip(vec2).sum { |a, b| a * b }
1096
+ magnitude1 = Math.sqrt(vec1.sum { |x| x * x })
1097
+ magnitude2 = Math.sqrt(vec2.sum { |x| x * x })
1098
+ dot_product / (magnitude1 * magnitude2)
1099
+ end
1100
+
658
1101
  def find_similar(query_embedding, document_embeddings, threshold: 0.7)
659
1102
  document_embeddings.select do |doc_emb|
660
1103
  cosine_similarity(query_embedding, doc_emb) > threshold
@@ -668,18 +1111,28 @@ Load configuration from JSON files for production deployments:
668
1111
 
669
1112
  ```ruby
670
1113
  require "ollama_client"
1114
+ require "json"
671
1115
 
672
- # config.json:
673
- # {
674
- # "base_url": "http://localhost:11434",
675
- # "model": "llama3.1:8b",
676
- # "timeout": 30,
677
- # "retries": 3,
678
- # "temperature": 0.2
679
- # }
1116
+ # Create config.json file (or use an existing one)
1117
+ config_data = {
1118
+ "base_url" => "http://localhost:11434",
1119
+ "model" => "llama3.1:8b",
1120
+ "timeout" => 30,
1121
+ "retries" => 3,
1122
+ "temperature" => 0.2
1123
+ }
680
1124
 
681
- config = Ollama::Config.load_from_json("config.json")
682
- client = Ollama::Client.new(config: config)
1125
+ # Write config file
1126
+ File.write("config.json", JSON.pretty_generate(config_data))
1127
+
1128
+ # Load configuration from file
1129
+ begin
1130
+ config = Ollama::Config.load_from_json("config.json")
1131
+ client = Ollama::Client.new(config: config)
1132
+ puts "Client configured from config.json"
1133
+ rescue Ollama::Error => e
1134
+ puts "Error loading config: #{e.message}"
1135
+ end
683
1136
  ```
684
1137
 
685
1138
  ### Type-Safe Model Options
@@ -689,6 +1142,17 @@ Use the `Options` class for type-checked model parameters:
689
1142
  ```ruby
690
1143
  require "ollama_client"
691
1144
 
1145
+ client = Ollama::Client.new
1146
+
1147
+ # Define schema
1148
+ analysis_schema = {
1149
+ "type" => "object",
1150
+ "required" => ["summary"],
1151
+ "properties" => {
1152
+ "summary" => { "type" => "string" }
1153
+ }
1154
+ }
1155
+
692
1156
  # Options with validation
693
1157
  options = Ollama::Options.new(
694
1158
  temperature: 0.7,
@@ -701,11 +1165,19 @@ options = Ollama::Options.new(
701
1165
  # Will raise ArgumentError if values are out of range
702
1166
  # options.temperature = 3.0 # Error: temperature must be between 0.0 and 2.0
703
1167
 
704
- client.generate(
705
- prompt: "Analyze this data",
706
- schema: analysis_schema,
707
- options: options.to_h
1168
+ # Use with chat() - chat() accepts options parameter
1169
+ client.chat(
1170
+ messages: [{ role: "user", content: "Analyze this data" }],
1171
+ format: analysis_schema,
1172
+ options: options.to_h,
1173
+ allow_chat: true
708
1174
  )
1175
+
1176
+ # Note: generate() doesn't accept options parameter
1177
+ # For generate(), set options in config instead:
1178
+ # config = Ollama::Config.new
1179
+ # config.temperature = 0.7
1180
+ # client = Ollama::Client.new(config: config)
709
1181
  ```
710
1182
 
711
1183
  ### Error Handling
@@ -713,8 +1185,22 @@ client.generate(
713
1185
  ```ruby
714
1186
  require "ollama_client"
715
1187
 
1188
+ client = Ollama::Client.new
1189
+ schema = {
1190
+ "type" => "object",
1191
+ "required" => ["result"],
1192
+ "properties" => {
1193
+ "result" => { "type" => "string" }
1194
+ }
1195
+ }
1196
+
716
1197
  begin
717
- result = client.generate(prompt: prompt, schema: schema)
1198
+ result = client.generate(
1199
+ prompt: "Return a simple result",
1200
+ schema: schema
1201
+ )
1202
+ # Success - use the result
1203
+ puts "Result: #{result['result']}"
718
1204
  rescue Ollama::NotFoundError => e
719
1205
  # 404 Not Found - model or endpoint doesn't exist
720
1206
  # The error message automatically suggests similar model names if available
@@ -812,67 +1298,35 @@ end
812
1298
 
813
1299
  This keeps the `ollama-client` gem **domain-agnostic** and **reusable** across any project.
814
1300
 
815
- **See `examples/tool_calling_pattern.rb` for a working implementation of this pattern.**
816
-
817
- ## Advanced Examples
818
-
819
- The `examples/` directory contains advanced examples demonstrating production-grade patterns:
820
-
821
- ### `tool_calling_pattern.rb`
822
- **Working implementation of the ToolRouter pattern from the Architecture section:**
823
- - Tool registry and routing
824
- - LLM outputs intent, agent executes tools
825
- - Demonstrates the correct separation of concerns
826
- - Matches the pattern shown in README.md lines 430-500
827
-
828
- ### `dhanhq_trading_agent.rb`
829
- **Real-world integration: Ollama (reasoning) + DhanHQ (execution):**
830
- - Ollama analyzes market data and makes trading decisions
831
- - DhanHQ executes trades (place orders, check positions, etc.)
832
- - Demonstrates proper separation: LLM = reasoning, DhanHQ = execution
833
- - Shows risk management with super orders (SL/TP)
834
- - Perfect example of agent-grade tool calling pattern
835
-
836
- ### `advanced_multi_step_agent.rb`
837
- Multi-step agent workflow with:
838
- - Complex nested schemas
839
- - State management across steps
840
- - Confidence thresholds
841
- - Risk assessment
842
- - Error recovery
843
-
844
- ### `advanced_error_handling.rb`
845
- Comprehensive error handling patterns:
846
- - All error types (NotFoundError, HTTPError, TimeoutError, etc.)
847
- - Retry strategies with exponential backoff
848
- - Fallback mechanisms
849
- - Error statistics and observability
850
-
851
- ### `advanced_complex_schemas.rb`
852
- Real-world complex schemas:
853
- - Financial analysis (nested metrics, recommendations, risk factors)
854
- - Code review (issues, suggestions, effort estimation)
855
- - Research paper analysis (findings, methodology, citations)
856
-
857
- ### `advanced_performance_testing.rb`
858
- Performance and observability:
859
- - Latency measurement (min, max, avg, p95, p99)
860
- - Throughput testing
861
- - Error rate tracking
862
- - Metrics export
863
-
864
- ### `advanced_edge_cases.rb`
865
- Boundary and edge case testing:
866
- - Empty/long prompts
867
- - Special characters and unicode
868
- - Minimal/strict schemas
869
- - Deeply nested structures
870
- - Enum constraints
871
-
872
- Run any example:
873
- ```bash
874
- ruby examples/advanced_multi_step_agent.rb
875
- ```
1301
+ **See the [ollama-agent-examples](https://github.com/shubhamtaywade82/ollama-agent-examples) repository for working implementations of this pattern.**
1302
+
1303
+ ## 📚 Examples
1304
+
1305
+ ### Minimal Examples (In This Repo)
1306
+
1307
+ The `examples/` directory contains minimal examples demonstrating **client usage only**:
1308
+
1309
+ - **`basic_generate.rb`** - Basic `/generate` usage with schema validation
1310
+ - **`basic_chat.rb`** - Basic `/chat` usage
1311
+ - **`tool_calling_parsing.rb`** - Tool-call parsing (no execution)
1312
+ - **`tool_dto_example.rb`** - Tool DTO serialization
1313
+
1314
+ These examples focus on **transport and protocol correctness**, not agent behavior.
1315
+
1316
+ ### Full Agent Examples (Separate Repository)
1317
+
1318
+ For complete agent examples (trading agents, coding agents, RAG agents, multi-step workflows, tool execution patterns, etc.), see:
1319
+
1320
+ **[ollama-agent-examples](https://github.com/shubhamtaywade82/ollama-agent-examples)**
1321
+
1322
+ This separation keeps `ollama-client` focused on the transport layer while providing comprehensive examples for agent developers.
1323
+
1324
+ **Why this separation?**
1325
+ - Examples rot faster than APIs
1326
+ - Agent examples pull in domain-specific dependencies
1327
+ - Tool examples imply opinions about tool design
1328
+ - The client stays clean and maintainable
1329
+ - Users don't confuse client vs agent responsibilities
876
1330
 
877
1331
  ## Development
878
1332