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
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
|
-
|
|
30
|
-
* ❌
|
|
31
|
-
* ❌
|
|
32
|
-
* ❌
|
|
33
|
-
* ❌ A
|
|
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
|
-
- Don
|
|
93
|
-
- Don
|
|
94
|
-
- Don
|
|
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
|
-
|
|
466
|
+
tool2 = Ollama::Tool.from_hash(JSON.parse(json))
|
|
208
467
|
|
|
209
468
|
# Equality comparison
|
|
210
|
-
|
|
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
|
-
|
|
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: "
|
|
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:
|
|
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
|
-
|
|
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
|
-
#
|
|
644
|
-
|
|
645
|
-
|
|
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
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
682
|
-
|
|
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
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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(
|
|
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
|
|
816
|
-
|
|
817
|
-
##
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
-
|
|
824
|
-
-
|
|
825
|
-
-
|
|
826
|
-
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
-
|
|
840
|
-
-
|
|
841
|
-
-
|
|
842
|
-
-
|
|
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
|
|