ollama-client 0.2.1 → 0.2.2

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/FEATURES_ADDED.md +145 -0
  3. data/HANDLERS_ANALYSIS.md +190 -0
  4. data/README.md +213 -11
  5. data/examples/advanced_multi_step_agent.rb +11 -6
  6. data/examples/chat_console.rb +134 -0
  7. data/examples/dhan_console.rb +748 -0
  8. data/examples/dhanhq/agents/base_agent.rb +0 -2
  9. data/examples/dhanhq/agents/orchestrator_agent.rb +1 -2
  10. data/examples/dhanhq/agents/technical_analysis_agent.rb +61 -48
  11. data/examples/dhanhq/analysis/market_structure.rb +44 -28
  12. data/examples/dhanhq/analysis/pattern_recognizer.rb +64 -47
  13. data/examples/dhanhq/analysis/trend_analyzer.rb +6 -8
  14. data/examples/dhanhq/dhanhq_agent.rb +296 -99
  15. data/examples/dhanhq/indicators/technical_indicators.rb +3 -5
  16. data/examples/dhanhq/scanners/intraday_options_scanner.rb +360 -255
  17. data/examples/dhanhq/scanners/swing_scanner.rb +118 -84
  18. data/examples/dhanhq/services/data_service.rb +5 -7
  19. data/examples/dhanhq/services/trading_service.rb +0 -3
  20. data/examples/dhanhq/technical_analysis_agentic_runner.rb +217 -84
  21. data/examples/dhanhq/technical_analysis_runner.rb +216 -162
  22. data/examples/dhanhq/test_tool_calling.rb +538 -0
  23. data/examples/dhanhq/test_tool_calling_verbose.rb +251 -0
  24. data/examples/dhanhq/utils/trading_parameter_normalizer.rb +12 -17
  25. data/examples/dhanhq_agent.rb +136 -103
  26. data/examples/dhanhq_tools.rb +1013 -171
  27. data/examples/structured_tools.rb +89 -0
  28. data/examples/tool_calling_direct.rb +124 -0
  29. data/examples/tool_dto_example.rb +94 -0
  30. data/exe/dhan_console +4 -0
  31. data/exe/ollama-client +1 -1
  32. data/lib/ollama/agent/executor.rb +117 -16
  33. data/lib/ollama/client.rb +78 -8
  34. data/lib/ollama/config.rb +36 -0
  35. data/lib/ollama/dto.rb +187 -0
  36. data/lib/ollama/embeddings.rb +77 -0
  37. data/lib/ollama/options.rb +96 -0
  38. data/lib/ollama/response.rb +123 -0
  39. data/lib/ollama/tool/function/parameters/property.rb +72 -0
  40. data/lib/ollama/tool/function/parameters.rb +100 -0
  41. data/lib/ollama/tool/function.rb +78 -0
  42. data/lib/ollama/tool.rb +60 -0
  43. data/lib/ollama/version.rb +1 -1
  44. data/lib/ollama_client.rb +3 -0
  45. data/test_dhanhq_tool_calling.rb +282 -0
  46. data/test_tool_calling.rb +160 -0
  47. metadata +22 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b1ed7c3f2d09174f4127c92b0b22eef90fc3deb895dc86183d296a6a70cc3b4
4
- data.tar.gz: f2d0737f5cbb77557fa736fb45f7a5c7c94fd939d1005c53d68e82d229844283
3
+ metadata.gz: 86bef7e3dc6197748e9d75572705cec97bc94a24da8c9e8f7402d9b5db1e1dc8
4
+ data.tar.gz: 1ae799bfc340896179b3819350b81fe852c8f2b944f6987cf4f8c76b61791153
5
5
  SHA512:
6
- metadata.gz: 04dcc1b56d68d715727f5f51bd9511d1c4b633369435e4f95008b2bb826d988d00df6fa307dc900fb0c8365b6f4bc0e111e17b4c74deb9c25360bfa36f6148cd
7
- data.tar.gz: 8ea4bef981e5601dddd258a1f0dab481e76601f765bddc9783098bedc144cd1d0ceeb9e30b185541fa688ea8519fc796356a9ffe6abbc437a7dae6d93b757852
6
+ metadata.gz: 1b4a54c4625c0bcb947d74e914bedc0f974e6b0bbaad6f4f46673b8dce5eb6956c5a3bc75745845ccd7f79830149a24ad1e49c54bc03ef165c39ed71d14a92db
7
+ data.tar.gz: e2ce9114ab3396d65a3075f80c1d273e0d414d2d8be0d4bae4b5e1717fdc62451d2a57590d59e191be86613280fbbd925201ba9d1285a95a2d0cdfabdafc25c3
data/FEATURES_ADDED.md ADDED
@@ -0,0 +1,145 @@
1
+ # New Features Added from ollama-ruby
2
+
3
+ This document describes the features we've integrated from `ollama-ruby` that align with our **agent-first philosophy**.
4
+
5
+ ## ✅ Features Added
6
+
7
+ ### 1. Embeddings API (`client.embeddings`)
8
+
9
+ **Purpose**: Enable RAG (Retrieval-Augmented Generation) and semantic search in agents.
10
+
11
+ **Why it fits**: Agents often need to search knowledge bases, compare documents, and build context from embeddings.
12
+
13
+ **Usage**:
14
+ ```ruby
15
+ embedding = client.embeddings.embed(model: "all-minilm", input: "What is Ruby?")
16
+ ```
17
+
18
+ **Files Added**:
19
+ - `lib/ollama/embeddings.rb` - Embeddings API wrapper
20
+ - Updated `lib/ollama/client.rb` - Added `embeddings` accessor
21
+
22
+ ### 2. Config Loading from JSON (`Config.load_from_json`)
23
+
24
+ **Purpose**: Load configuration from JSON files for production deployments.
25
+
26
+ **Why it fits**: Production agents need configuration management without hardcoding values.
27
+
28
+ **Usage**:
29
+ ```ruby
30
+ config = Ollama::Config.load_from_json("config.json")
31
+ client = Ollama::Client.new(config: config)
32
+ ```
33
+
34
+ **Files Modified**:
35
+ - `lib/ollama/config.rb` - Added `load_from_json` class method
36
+
37
+ ### 3. Options Class (`Ollama::Options`)
38
+
39
+ **Purpose**: Type-safe model parameter configuration with validation.
40
+
41
+ **Why it fits**: Agents need to adjust model behavior dynamically with safety guarantees.
42
+
43
+ **Usage**:
44
+ ```ruby
45
+ options = Ollama::Options.new(temperature: 0.7, top_p: 0.95)
46
+ client.generate(prompt: "...", schema: {...}, options: options.to_h)
47
+ ```
48
+
49
+ **Files Added**:
50
+ - `lib/ollama/options.rb` - Options class with type checking
51
+
52
+ ### 4. Structured Tool Classes (`Ollama::Tool`, `Tool::Function`, etc.)
53
+
54
+ **Purpose**: Type-safe, explicit tool schema definitions for agent tools.
55
+
56
+ **Why it fits**: Production agents need explicit control over tool schemas beyond auto-inference. Enables enum constraints, detailed descriptions, and better documentation.
57
+
58
+ **Usage**:
59
+ ```ruby
60
+ # Define explicit schema
61
+ property = Ollama::Tool::Function::Parameters::Property.new(
62
+ type: "string",
63
+ description: "City name",
64
+ enum: %w[paris london tokyo] # Optional enum constraint
65
+ )
66
+
67
+ params = Ollama::Tool::Function::Parameters.new(
68
+ type: "object",
69
+ properties: { city: property },
70
+ required: %w[city]
71
+ )
72
+
73
+ function = Ollama::Tool::Function.new(
74
+ name: "get_weather",
75
+ description: "Get weather for a city",
76
+ parameters: params
77
+ )
78
+
79
+ tool = Ollama::Tool.new(type: "function", function: function)
80
+
81
+ # Use with Executor (explicit schema + callable)
82
+ tools = {
83
+ "get_weather" => {
84
+ tool: tool,
85
+ callable: ->(city:) { { city: city, temp: "22C" } }
86
+ }
87
+ }
88
+ ```
89
+
90
+ **Files Added**:
91
+ - `lib/ollama/dto.rb` - Simplified DTO module (no external dependencies)
92
+ - `lib/ollama/tool.rb` - Tool class with DTO support
93
+ - `lib/ollama/tool/function.rb` - Function definition with DTO support
94
+ - `lib/ollama/tool/function/parameters.rb` - Parameters specification with DTO support
95
+ - `lib/ollama/tool/function/parameters/property.rb` - Property definition with DTO support
96
+ - Updated `lib/ollama/agent/executor.rb` - Support for explicit Tool objects
97
+ - `examples/tool_dto_example.rb` - DTO serialization/deserialization examples
98
+
99
+ **DTO Features:**
100
+ - `to_json` - JSON serialization
101
+ - `from_hash` - Deserialization from hash/JSON
102
+ - `==` - Equality comparison based on hash representation
103
+ - `empty?` - Check if object has no meaningful attributes
104
+
105
+ All Tool classes include the DTO module, enabling serialization for storage, API responses, and testing.
106
+
107
+ ## 🎯 Design Decisions
108
+
109
+ ### What We Added
110
+ - ✅ Features that directly support agent workflows
111
+ - ✅ Type safety and validation (Options class)
112
+ - ✅ Production deployment needs (JSON config)
113
+ - ✅ RAG capabilities (embeddings)
114
+
115
+ ### What We Didn't Add
116
+ - ❌ Handler-based architecture (doesn't fit our schema-first approach)
117
+ - See `HANDLERS_ANALYSIS.md` for detailed comparison
118
+ - We use `StreamingObserver` instead for presentation-only streaming
119
+ - ❌ Interactive console (not needed for agents)
120
+ - ❌ CLI executables (outside our scope)
121
+ - ❌ Full API coverage (we focus on agent building blocks)
122
+
123
+ ## 📝 Examples
124
+
125
+ See `examples/structured_tools.rb` and `examples/tool_dto_example.rb` for complete examples demonstrating:
126
+ - Structured tool definitions
127
+ - RAG agent with embeddings
128
+ - Options usage for fine-tuning
129
+
130
+ ## 🔄 Migration Notes
131
+
132
+ All new features are **additive** and **backward compatible**:
133
+ - Existing code continues to work unchanged
134
+ - New features are opt-in
135
+ - No breaking changes to existing APIs
136
+
137
+ ## 🚀 Next Steps
138
+
139
+ These features enable:
140
+ 1. **RAG Agents**: Build knowledge bases with semantic search
141
+ 2. **Production Deployments**: JSON-based configuration
142
+ 3. **Fine-Tuning**: Type-safe model parameter adjustment
143
+ 4. **Structured Tools**: Explicit tool schemas with enum constraints and validation
144
+
145
+ All while maintaining our **agent-first philosophy** and **safety guarantees**.
@@ -0,0 +1,190 @@
1
+ # Handlers Analysis: ollama-ruby vs ollama-client
2
+
3
+ ## Handlers in ollama-ruby
4
+
5
+ The `ollama-ruby` gem uses a **handler-based architecture** where handlers respond to `to_proc` and return lambda expressions to process responses:
6
+
7
+ | Handler | Purpose | Use Case |
8
+ |---------|---------|----------|
9
+ | **Collector** | Collects all responses in an array | Streaming responses |
10
+ | **Single** | Returns single response directly | Non-streaming responses |
11
+ | **Progress** | Progress bar for create/pull/push | Model management operations |
12
+ | **DumpJSON** | Dumps responses as JSON | Debugging/inspection |
13
+ | **DumpYAML** | Dumps responses as YAML | Debugging/inspection |
14
+ | **Print** | Prints to display | Interactive use |
15
+ | **Markdown** | Prints as ANSI markdown | Interactive use |
16
+ | **Say** | Text-to-speech output | Accessibility |
17
+ | **NOP** | Does nothing | Silent operations |
18
+
19
+ ## Why We Didn't Add Handlers
20
+
21
+ ### 1. **Philosophical Mismatch**
22
+
23
+ **ollama-ruby approach:**
24
+ ```ruby
25
+ # Handler-based, flexible, general-purpose
26
+ ollama.chat(model: 'llama3.1', stream: true, messages: msgs, &Print)
27
+ ollama.generate(model: 'llama3.1', prompt: '...', &Markdown)
28
+ ```
29
+
30
+ **ollama-client approach:**
31
+ ```ruby
32
+ # Schema-first, contract-based, agent-focused
33
+ result = client.generate(prompt: '...', schema: schema)
34
+ # Returns validated, structured data directly
35
+ ```
36
+
37
+ ### 2. **Different Return Semantics**
38
+
39
+ - **ollama-ruby**: Handlers control what gets returned (could be array, single value, nothing)
40
+ - **ollama-client**: Always returns validated, structured data matching the schema
41
+
42
+ ### 3. **State Management**
43
+
44
+ - **ollama-ruby**: Handlers are stateless processors
45
+ - **ollama-client**: We have explicit state (Planner stateless, Executor stateful via messages)
46
+
47
+ ### 4. **Streaming Philosophy**
48
+
49
+ - **ollama-ruby**: Handlers process streaming chunks (could affect control flow)
50
+ - **ollama-client**: Streaming is **presentation-only** via `StreamingObserver` (never affects control flow)
51
+
52
+ ## What We Have Instead
53
+
54
+ ### StreamingObserver (Agent-Focused)
55
+
56
+ ```ruby
57
+ observer = Ollama::StreamingObserver.new do |event|
58
+ case event.type
59
+ when :token
60
+ print event.text
61
+ when :tool_call_detected
62
+ puts "\n[Tool: #{event.name}]"
63
+ when :state
64
+ puts "State: #{event.state}"
65
+ when :final
66
+ puts "\n--- DONE ---"
67
+ end
68
+ end
69
+
70
+ executor = Ollama::Agent::Executor.new(client, tools: tools, stream: observer)
71
+ ```
72
+
73
+ **Key differences:**
74
+ - ✅ Explicit event types (`:token`, `:tool_call_detected`, `:state`, `:final`)
75
+ - ✅ Never affects control flow (presentation-only)
76
+ - ✅ Agent-aware (knows about tool calls, state transitions)
77
+ - ✅ Structured events (not raw response chunks)
78
+
79
+ ### Direct Return Values
80
+
81
+ ```ruby
82
+ # Always returns validated, structured data
83
+ result = client.generate(prompt: '...', schema: schema)
84
+ # result is a Hash matching the schema exactly
85
+ ```
86
+
87
+ **vs ollama-ruby:**
88
+ ```ruby
89
+ # Handler determines return value
90
+ result = ollama.generate(model: '...', prompt: '...', &Collector)
91
+ # result could be array, single value, or nil depending on handler
92
+ ```
93
+
94
+ ## Potential Handler-Like Utilities for Agents
95
+
96
+ While we don't want the full handler architecture, there might be value in **debugging/development utilities**:
97
+
98
+ ### 1. **Response Debugger** (Useful for Agents)
99
+
100
+ ```ruby
101
+ # Could add as a utility, not a handler
102
+ module Ollama
103
+ module Debug
104
+ def self.dump_json(response, output: $stdout)
105
+ output.puts JSON.pretty_generate(response)
106
+ end
107
+
108
+ def self.dump_yaml(response, output: $stdout)
109
+ require 'yaml'
110
+ output.puts response.to_yaml
111
+ end
112
+ end
113
+ end
114
+
115
+ # Usage:
116
+ result = client.generate(prompt: '...', schema: schema)
117
+ Ollama::Debug.dump_json(result) if ENV['DEBUG']
118
+ ```
119
+
120
+ ### 2. **Progress Indicator** (For Long-Running Agent Operations)
121
+
122
+ ```ruby
123
+ # Could add for agent workflows that take time
124
+ module Ollama
125
+ module Agent
126
+ class ProgressIndicator
127
+ def initialize(total_steps:)
128
+ @total = total_steps
129
+ @current = 0
130
+ end
131
+
132
+ def step(message = nil)
133
+ @current += 1
134
+ print "\r[#{@current}/#{@total}] #{message || 'Processing...'}"
135
+ $stdout.flush
136
+ end
137
+
138
+ def finish
139
+ puts "\n✓ Complete"
140
+ end
141
+ end
142
+ end
143
+ end
144
+ ```
145
+
146
+ ### 3. **Response Formatter** (For Agent Output)
147
+
148
+ ```ruby
149
+ # Could add for pretty-printing agent responses
150
+ module Ollama
151
+ module Format
152
+ def self.markdown(text, output: $stdout)
153
+ # Use a markdown library to format
154
+ output.puts text
155
+ end
156
+
157
+ def self.structured(data, output: $stdout)
158
+ output.puts JSON.pretty_generate(data)
159
+ end
160
+ end
161
+ end
162
+ ```
163
+
164
+ ## Recommendation
165
+
166
+ **Don't add handlers** because:
167
+ 1. ❌ They conflict with our schema-first, contract-based approach
168
+ 2. ❌ They introduce ambiguity about return values
169
+ 3. ❌ They don't fit our explicit state management
170
+ 4. ❌ Our `StreamingObserver` already handles presentation needs
171
+
172
+ **Do consider** adding **utility modules** for:
173
+ 1. ✅ Debugging helpers (`Ollama::Debug.dump_json`)
174
+ 2. ✅ Progress indicators for long agent workflows
175
+ 3. ✅ Response formatters (if needed for agent output)
176
+
177
+ These would be **explicit utilities** rather than **handler callbacks**, maintaining our philosophy while providing useful development tools.
178
+
179
+ ## Summary
180
+
181
+ | Aspect | ollama-ruby Handlers | ollama-client Approach |
182
+ |--------|---------------------|------------------------|
183
+ | **Architecture** | Handler-based callbacks | Direct return values + StreamingObserver |
184
+ | **Return Values** | Handler-dependent | Always validated, structured data |
185
+ | **Streaming** | Handler processes chunks | Observer emits events (presentation-only) |
186
+ | **State** | Stateless processors | Explicit state (Planner/Executor) |
187
+ | **Use Case** | General-purpose, flexible | Agent-focused, contract-based |
188
+ | **Debugging** | DumpJSON/DumpYAML handlers | Could add utility modules |
189
+
190
+ **Conclusion**: Handlers don't fit our agent-first philosophy, but we could add explicit utility modules for debugging/development needs.
data/README.md CHANGED
@@ -36,15 +36,15 @@ This keeps it **clean and future-proof**.
36
36
 
37
37
  ## 🔒 Guarantees
38
38
 
39
- | Guarantee | Yes |
40
- | -------------------------------------- | --- |
41
- | Client requests are explicit | ✅ |
42
- | Planner is stateless (no hidden memory)| ✅ |
43
- | Executor is stateful (explicit messages)| ✅ |
44
- | Retry bounded | ✅ |
45
- | Schema validated (when schema provided)| ✅ |
46
- | Tools run in Ruby (not in the LLM) | ✅ |
47
- | Streaming is display-only (Executor) | ✅ |
39
+ | Guarantee | Yes |
40
+ | ---------------------------------------- | --- |
41
+ | Client requests are explicit | ✅ |
42
+ | Planner is stateless (no hidden memory) | ✅ |
43
+ | Executor is stateful (explicit messages) | ✅ |
44
+ | Retry bounded | ✅ |
45
+ | Schema validated (when schema provided) | ✅ |
46
+ | Tools run in Ruby (not in the LLM) | ✅ |
47
+ | Streaming is display-only (Executor) | ✅ |
48
48
 
49
49
  **Non-negotiable safety rule:** the **LLM never executes side effects**. It may request a tool call; **your Ruby code** executes the tool.
50
50
 
@@ -97,8 +97,8 @@ gem install ollama-client
97
97
 
98
98
  This gem intentionally focuses on **agent building blocks**:
99
99
 
100
- - **Supported**: `/api/generate`, `/api/chat`, `/api/tags`, `/api/ping`
101
- - **Not guaranteed**: full endpoint parity with every Ollama release (embeddings, advanced model mgmt, etc.)
100
+ - **Supported**: `/api/generate`, `/api/chat`, `/api/tags`, `/api/ping`, `/api/embeddings`
101
+ - **Not guaranteed**: full endpoint parity with every Ollama release (advanced model mgmt, etc.)
102
102
 
103
103
  ### Agent endpoint mapping (unambiguous)
104
104
 
@@ -130,6 +130,8 @@ puts plan
130
130
 
131
131
  ### Executor Agent (tool loop, /api/chat)
132
132
 
133
+ **Simple approach (auto-inferred schemas):**
134
+
133
135
  ```ruby
134
136
  require "ollama_client"
135
137
  require "json"
@@ -151,6 +153,68 @@ answer = executor.run(
151
153
  puts answer
152
154
  ```
153
155
 
156
+ **Structured approach (explicit schemas with Tool classes):**
157
+
158
+ ```ruby
159
+ require "ollama_client"
160
+
161
+ # Define explicit tool schema
162
+ location_prop = Ollama::Tool::Function::Parameters::Property.new(
163
+ type: "string",
164
+ description: "The city name"
165
+ )
166
+
167
+ params = Ollama::Tool::Function::Parameters.new(
168
+ type: "object",
169
+ properties: { city: location_prop },
170
+ required: %w[city]
171
+ )
172
+
173
+ function = Ollama::Tool::Function.new(
174
+ name: "fetch_weather",
175
+ description: "Get weather for a city",
176
+ parameters: params
177
+ )
178
+
179
+ tool = Ollama::Tool.new(type: "function", function: function)
180
+
181
+ # Associate tool schema with callable
182
+ tools = {
183
+ "fetch_weather" => {
184
+ tool: tool,
185
+ callable: ->(city:) { { city: city, forecast: "sunny" } }
186
+ }
187
+ }
188
+
189
+ executor = Ollama::Agent::Executor.new(client, tools: tools)
190
+ ```
191
+
192
+ Use structured tools when you need:
193
+ - Explicit control over parameter types and descriptions
194
+ - Enum constraints on parameters
195
+ - Better documentation for complex tools
196
+ - Serialization/deserialization (JSON storage, API responses)
197
+
198
+ **DTO (Data Transfer Object) functionality:**
199
+
200
+ All Tool classes support serialization and deserialization:
201
+
202
+ ```ruby
203
+ # Serialize to JSON
204
+ json = tool.to_json
205
+
206
+ # Deserialize from hash
207
+ tool = Ollama::Tool.from_hash(JSON.parse(json))
208
+
209
+ # Equality comparison
210
+ tool1 == tool2 # Compares hash representations
211
+
212
+ # Empty check
213
+ params.empty? # True if no properties/required fields
214
+ ```
215
+
216
+ See `examples/tool_dto_example.rb` for complete DTO usage examples.
217
+
154
218
  ### Streaming (Executor only; presentation-only)
155
219
 
156
220
  Streaming is treated as **presentation**, not control. The agent buffers the full assistant message and only
@@ -407,6 +471,67 @@ rescue Ollama::Error => e
407
471
  end
408
472
  ```
409
473
 
474
+ ### Example: Tool Calling (Direct API Usage)
475
+
476
+ For tool calling, use `chat_raw()` to access `tool_calls` from the response:
477
+
478
+ ```ruby
479
+ require "ollama_client"
480
+
481
+ client = Ollama::Client.new
482
+
483
+ # Define tool using Tool classes
484
+ tool = Ollama::Tool.new(
485
+ type: "function",
486
+ function: Ollama::Tool::Function.new(
487
+ name: "get_current_weather",
488
+ description: "Get the current weather for a location",
489
+ parameters: Ollama::Tool::Function::Parameters.new(
490
+ type: "object",
491
+ properties: {
492
+ location: Ollama::Tool::Function::Parameters::Property.new(
493
+ type: "string",
494
+ description: "The location to get the weather for, e.g. San Francisco, CA"
495
+ ),
496
+ temperature_unit: Ollama::Tool::Function::Parameters::Property.new(
497
+ type: "string",
498
+ description: "The unit to return the temperature in",
499
+ enum: %w[celsius fahrenheit]
500
+ )
501
+ },
502
+ required: %w[location temperature_unit]
503
+ )
504
+ )
505
+ )
506
+
507
+ # Create message
508
+ message = Ollama::Agent::Messages.user("What is the weather today in Paris?")
509
+
510
+ # Use chat_raw() to get full response with tool_calls
511
+ response = client.chat_raw(
512
+ model: "llama3.1:8b",
513
+ messages: [message],
514
+ tools: tool, # Pass Tool object directly (or array of Tool objects)
515
+ allow_chat: true
516
+ )
517
+
518
+ # Access tool_calls from response
519
+ tool_calls = response.dig("message", "tool_calls")
520
+ if tool_calls && !tool_calls.empty?
521
+ tool_calls.each do |call|
522
+ name = call.dig("function", "name")
523
+ args = call.dig("function", "arguments")
524
+ puts "Tool: #{name}, Args: #{args}"
525
+ end
526
+ end
527
+ ```
528
+
529
+ **Note:**
530
+ - `chat()` returns only the content (for simple use cases)
531
+ - `chat_raw()` returns the full response with `message.tool_calls` (for tool calling)
532
+ - Both methods accept `tools:` parameter (Tool object, array of Tool objects, or array of hashes)
533
+ - For agent tool loops, use `Ollama::Agent::Executor` instead (handles tool execution automatically)
534
+
410
535
  ### Example: Data Analysis with Validation
411
536
 
412
537
  ```ruby
@@ -505,6 +630,83 @@ models = client.list_models
505
630
  puts "Available models: #{models.join(', ')}"
506
631
  ```
507
632
 
633
+ ### Embeddings for RAG/Semantic Search
634
+
635
+ Use embeddings for building knowledge bases and semantic search in agents:
636
+
637
+ ```ruby
638
+ require "ollama_client"
639
+
640
+ client = Ollama::Client.new
641
+
642
+ # Single text embedding
643
+ embedding = client.embeddings.embed(
644
+ model: "all-minilm",
645
+ input: "What is Ruby programming?"
646
+ )
647
+ # Returns: [0.123, -0.456, ...] (array of floats)
648
+
649
+ # Multiple texts
650
+ embeddings = client.embeddings.embed(
651
+ model: "all-minilm",
652
+ input: ["What is Ruby?", "What is Python?", "What is JavaScript?"]
653
+ )
654
+ # Returns: [[...], [...], [...]] (array of embedding arrays)
655
+
656
+ # Use for semantic similarity in agents
657
+ def find_similar(query_embedding, document_embeddings, threshold: 0.7)
658
+ document_embeddings.select do |doc_emb|
659
+ cosine_similarity(query_embedding, doc_emb) > threshold
660
+ end
661
+ end
662
+ ```
663
+
664
+ ### Configuration from JSON
665
+
666
+ Load configuration from JSON files for production deployments:
667
+
668
+ ```ruby
669
+ require "ollama_client"
670
+
671
+ # config.json:
672
+ # {
673
+ # "base_url": "http://localhost:11434",
674
+ # "model": "llama3.1:8b",
675
+ # "timeout": 30,
676
+ # "retries": 3,
677
+ # "temperature": 0.2
678
+ # }
679
+
680
+ config = Ollama::Config.load_from_json("config.json")
681
+ client = Ollama::Client.new(config: config)
682
+ ```
683
+
684
+ ### Type-Safe Model Options
685
+
686
+ Use the `Options` class for type-checked model parameters:
687
+
688
+ ```ruby
689
+ require "ollama_client"
690
+
691
+ # Options with validation
692
+ options = Ollama::Options.new(
693
+ temperature: 0.7,
694
+ top_p: 0.95,
695
+ top_k: 40,
696
+ num_ctx: 4096,
697
+ seed: 42
698
+ )
699
+
700
+ # Will raise ArgumentError if values are out of range
701
+ # options.temperature = 3.0 # Error: temperature must be between 0.0 and 2.0
702
+
703
+ client.generate(
704
+ prompt: "Analyze this data",
705
+ schema: analysis_schema,
706
+ options: options.to_h
707
+ )
708
+ ```
709
+
508
710
  ### Error Handling
509
711
 
510
712
  ```ruby
@@ -123,7 +123,8 @@ class MultiStepAgent
123
123
  if recent_actions.length == 3 && recent_actions.uniq.length == 1
124
124
  puts "⚠️ Detected repetitive actions - forcing workflow progression"
125
125
  # Force next phase
126
- if recent_actions.first == "collect"
126
+ case recent_actions.first
127
+ when "collect"
127
128
  puts " → Moving to analysis phase"
128
129
  decision["action"]["type"] = "analyze"
129
130
  decision["action"]["parameters"] = { "target" => "collected_data" }
@@ -133,7 +134,7 @@ class MultiStepAgent
133
134
  action: "analyze",
134
135
  result: result
135
136
  }
136
- elsif recent_actions.first == "analyze"
137
+ when "analyze"
137
138
  puts " → Moving to validation phase"
138
139
  decision["action"]["type"] = "validate"
139
140
  decision["action"]["parameters"] = { "type" => "results" }
@@ -143,7 +144,7 @@ class MultiStepAgent
143
144
  action: "validate",
144
145
  result: result
145
146
  }
146
- elsif recent_actions.first == "validate"
147
+ when "validate"
147
148
  puts " → Completing workflow"
148
149
  decision["action"]["type"] = "complete"
149
150
  result = execute_action(decision)
@@ -269,9 +270,7 @@ class MultiStepAgent
269
270
  when "collect"
270
271
  data_key = params["data_type"] || params["key"] || "user_data"
271
272
  # Prevent collecting the same generic data repeatedly
272
- if @state[:data_collected].key?(data_key) && data_key.match?(/^(missing|unknown|data)$/i) && !@state[:data_collected].key?("user_data")
273
- data_key = "user_data"
274
- end
273
+ data_key = "user_data" if should_collect_user_data?(data_key)
275
274
  puts " 📥 Collecting: #{data_key}"
276
275
  @state[:data_collected][data_key] = "collected_at_#{Time.now.to_i}"
277
276
  { status: "collected", key: data_key }
@@ -304,6 +303,12 @@ class MultiStepAgent
304
303
  end
305
304
  end
306
305
 
306
+ def should_collect_user_data?(data_key)
307
+ @state[:data_collected].key?(data_key) &&
308
+ data_key.match?(/^(missing|unknown|data)$/i) &&
309
+ !@state[:data_collected].key?("user_data")
310
+ end
311
+
307
312
  def display_summary
308
313
  puts "\n" + "=" * 60
309
314
  puts "Workflow Summary"