ollama-client 0.2.1 → 0.2.3

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +220 -12
  4. data/docs/CLOUD.md +29 -0
  5. data/docs/CONSOLE_IMPROVEMENTS.md +256 -0
  6. data/docs/FEATURES_ADDED.md +145 -0
  7. data/docs/HANDLERS_ANALYSIS.md +190 -0
  8. data/docs/README.md +37 -0
  9. data/docs/SCHEMA_FIXES.md +147 -0
  10. data/docs/TEST_UPDATES.md +107 -0
  11. data/examples/README.md +92 -0
  12. data/examples/advanced_complex_schemas.rb +6 -3
  13. data/examples/advanced_multi_step_agent.rb +13 -7
  14. data/examples/chat_console.rb +143 -0
  15. data/examples/complete_workflow.rb +14 -4
  16. data/examples/dhan_console.rb +843 -0
  17. data/examples/dhanhq/agents/base_agent.rb +0 -2
  18. data/examples/dhanhq/agents/orchestrator_agent.rb +1 -2
  19. data/examples/dhanhq/agents/technical_analysis_agent.rb +67 -49
  20. data/examples/dhanhq/analysis/market_structure.rb +44 -28
  21. data/examples/dhanhq/analysis/pattern_recognizer.rb +64 -47
  22. data/examples/dhanhq/analysis/trend_analyzer.rb +6 -8
  23. data/examples/dhanhq/dhanhq_agent.rb +296 -99
  24. data/examples/dhanhq/indicators/technical_indicators.rb +3 -5
  25. data/examples/dhanhq/scanners/intraday_options_scanner.rb +360 -255
  26. data/examples/dhanhq/scanners/swing_scanner.rb +118 -84
  27. data/examples/dhanhq/schemas/agent_schemas.rb +2 -2
  28. data/examples/dhanhq/services/data_service.rb +5 -7
  29. data/examples/dhanhq/services/trading_service.rb +0 -3
  30. data/examples/dhanhq/technical_analysis_agentic_runner.rb +217 -84
  31. data/examples/dhanhq/technical_analysis_runner.rb +216 -162
  32. data/examples/dhanhq/test_tool_calling.rb +538 -0
  33. data/examples/dhanhq/test_tool_calling_verbose.rb +251 -0
  34. data/examples/dhanhq/utils/trading_parameter_normalizer.rb +12 -17
  35. data/examples/dhanhq_agent.rb +159 -116
  36. data/examples/dhanhq_tools.rb +1158 -251
  37. data/examples/multi_step_agent_with_external_data.rb +368 -0
  38. data/examples/structured_tools.rb +89 -0
  39. data/examples/test_dhanhq_tool_calling.rb +375 -0
  40. data/examples/test_tool_calling.rb +160 -0
  41. data/examples/tool_calling_direct.rb +124 -0
  42. data/examples/tool_dto_example.rb +94 -0
  43. data/exe/dhan_console +4 -0
  44. data/exe/ollama-client +1 -1
  45. data/lib/ollama/agent/executor.rb +116 -15
  46. data/lib/ollama/client.rb +118 -55
  47. data/lib/ollama/config.rb +36 -0
  48. data/lib/ollama/dto.rb +187 -0
  49. data/lib/ollama/embeddings.rb +77 -0
  50. data/lib/ollama/options.rb +104 -0
  51. data/lib/ollama/response.rb +121 -0
  52. data/lib/ollama/tool/function/parameters/property.rb +72 -0
  53. data/lib/ollama/tool/function/parameters.rb +101 -0
  54. data/lib/ollama/tool/function.rb +78 -0
  55. data/lib/ollama/tool.rb +60 -0
  56. data/lib/ollama/version.rb +1 -1
  57. data/lib/ollama_client.rb +3 -0
  58. metadata +31 -3
  59. /data/{PRODUCTION_FIXES.md → docs/PRODUCTION_FIXES.md} +0 -0
  60. /data/{TESTING.md → docs/TESTING.md} +0 -0
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Test DhanHQ Tool Calling with chat_raw()
5
+ # Demonstrates using structured Tool classes with DhanHQ tools
6
+
7
+ require_relative "lib/ollama_client"
8
+ require_relative "examples/dhanhq_tools"
9
+ require "date"
10
+
11
+ puts "\n=== DHANHQ TOOL CALLING TEST ===\n"
12
+ puts "Using chat_raw() to access tool_calls\n"
13
+
14
+ client = Ollama::Client.new
15
+
16
+ # Define DhanHQ tools using structured Tool classes
17
+ # This provides better type safety and LLM understanding
18
+
19
+ # Market Quote Tool
20
+ market_quote_tool = Ollama::Tool.new(
21
+ type: "function",
22
+ function: Ollama::Tool::Function.new(
23
+ name: "get_market_quote",
24
+ description: "Get market quote for a symbol. Returns OHLC, depth, volume, and other market data.",
25
+ parameters: Ollama::Tool::Function::Parameters.new(
26
+ type: "object",
27
+ properties: {
28
+ symbol: Ollama::Tool::Function::Parameters::Property.new(
29
+ type: "string",
30
+ description: "Stock or index symbol (e.g., RELIANCE, NIFTY)"
31
+ ),
32
+ exchange_segment: Ollama::Tool::Function::Parameters::Property.new(
33
+ type: "string",
34
+ description: "Exchange segment",
35
+ enum: %w[NSE_EQ NSE_FNO BSE_EQ BSE_FNO IDX_I]
36
+ )
37
+ },
38
+ required: %w[symbol exchange_segment]
39
+ )
40
+ )
41
+ )
42
+
43
+ # Live LTP Tool
44
+ live_ltp_tool = Ollama::Tool.new(
45
+ type: "function",
46
+ function: Ollama::Tool::Function.new(
47
+ name: "get_live_ltp",
48
+ description: "Get live last traded price (LTP) for a symbol. Fast API for current price.",
49
+ parameters: Ollama::Tool::Function::Parameters.new(
50
+ type: "object",
51
+ properties: {
52
+ symbol: Ollama::Tool::Function::Parameters::Property.new(
53
+ type: "string",
54
+ description: "Stock or index symbol"
55
+ ),
56
+ exchange_segment: Ollama::Tool::Function::Parameters::Property.new(
57
+ type: "string",
58
+ description: "Exchange segment",
59
+ enum: %w[NSE_EQ NSE_FNO BSE_EQ BSE_FNO IDX_I]
60
+ )
61
+ },
62
+ required: %w[symbol exchange_segment]
63
+ )
64
+ )
65
+ )
66
+
67
+ # Historical Data Tool
68
+ historical_data_tool = Ollama::Tool.new(
69
+ type: "function",
70
+ function: Ollama::Tool::Function.new(
71
+ name: "get_historical_data",
72
+ description: "Get historical price data (OHLCV) or technical indicators. " \
73
+ "Use interval for intraday data (1, 5, 15, 25, 60 minutes). " \
74
+ "Omit interval for daily data. " \
75
+ "Set calculate_indicators=true to get technical indicators " \
76
+ "(RSI, MACD, SMA, EMA, Bollinger Bands, ATR) instead of raw data.",
77
+ parameters: Ollama::Tool::Function::Parameters.new(
78
+ type: "object",
79
+ properties: {
80
+ symbol: Ollama::Tool::Function::Parameters::Property.new(
81
+ type: "string",
82
+ description: "Stock or index symbol"
83
+ ),
84
+ exchange_segment: Ollama::Tool::Function::Parameters::Property.new(
85
+ type: "string",
86
+ description: "Exchange segment",
87
+ enum: %w[NSE_EQ NSE_FNO BSE_EQ BSE_FNO IDX_I]
88
+ ),
89
+ interval: Ollama::Tool::Function::Parameters::Property.new(
90
+ type: "string",
91
+ description: "Minute interval for intraday data. Omit for daily data. " \
92
+ "Values: 1 (1-min), 5 (5-min), 15 (15-min), 25 (25-min), 60 (1-hour)",
93
+ enum: %w[1 5 15 25 60]
94
+ ),
95
+ from_date: Ollama::Tool::Function::Parameters::Property.new(
96
+ type: "string",
97
+ description: "Start date (YYYY-MM-DD format)"
98
+ ),
99
+ to_date: Ollama::Tool::Function::Parameters::Property.new(
100
+ type: "string",
101
+ description: "End date (YYYY-MM-DD format, non-inclusive)"
102
+ ),
103
+ calculate_indicators: Ollama::Tool::Function::Parameters::Property.new(
104
+ type: "boolean",
105
+ description: "If true, returns technical indicators instead of raw data. " \
106
+ "Reduces response size and provides ready-to-use indicator values."
107
+ )
108
+ },
109
+ required: %w[symbol exchange_segment from_date to_date]
110
+ )
111
+ )
112
+ )
113
+
114
+ # Combine tools
115
+ dhanhq_tools = [market_quote_tool, live_ltp_tool, historical_data_tool]
116
+
117
+ puts "--- Test 1: Single tool (Market Quote) ---"
118
+ puts "Request: Get market quote for RELIANCE\n"
119
+
120
+ begin
121
+ response = client.chat_raw(
122
+ model: "llama3.1:8b",
123
+ messages: [Ollama::Agent::Messages.user("Get the market quote for RELIANCE stock")],
124
+ tools: market_quote_tool,
125
+ allow_chat: true
126
+ )
127
+
128
+ puts "✅ Response received"
129
+ puts "Response class: #{response.class.name}\n"
130
+
131
+ # Method access (recommended - like ollama-ruby)
132
+ tool_calls = response.message&.tool_calls
133
+ if tool_calls && !tool_calls.empty?
134
+ puts "✅ Tool calls detected (via method access):"
135
+ tool_calls.each do |call|
136
+ puts " Tool: #{call.name}"
137
+ puts " Arguments: #{call.arguments.inspect}"
138
+ puts " ID: #{call.id}\n"
139
+ end
140
+ else
141
+ puts "⚠️ No tool calls detected"
142
+ puts "Content: #{response.message&.content}\n"
143
+ end
144
+ rescue Ollama::Error => e
145
+ puts "❌ Error: #{e.class.name}"
146
+ puts " Message: #{e.message}"
147
+ puts "\n Note: Expected if Ollama server is not running"
148
+ puts " The important part is that tool_calls structure is correct"
149
+ end
150
+
151
+ puts "\n--- Test 2: Multiple tools (LLM chooses) ---"
152
+ puts "Request: Get current price for NIFTY\n"
153
+
154
+ begin
155
+ response = client.chat_raw(
156
+ model: "llama3.1:8b",
157
+ messages: [Ollama::Agent::Messages.user("What's the current price of NIFTY?")],
158
+ tools: dhanhq_tools, # Array of Tool objects
159
+ allow_chat: true
160
+ )
161
+
162
+ puts "✅ Response received\n"
163
+
164
+ tool_calls = response.message&.tool_calls
165
+ if tool_calls && !tool_calls.empty?
166
+ puts "✅ Tool calls detected:"
167
+ tool_calls.each do |call|
168
+ puts " - #{call.name}"
169
+ puts " Args: #{call.arguments.inspect}\n"
170
+ end
171
+ else
172
+ puts "⚠️ No tool calls detected"
173
+ puts "Content: #{response.message&.content}\n"
174
+ end
175
+ rescue Ollama::Error => e
176
+ puts "❌ Error: #{e.class.name}"
177
+ puts " Message: #{e.message}"
178
+ end
179
+
180
+ puts "\n--- Test 3: Compare chat() vs chat_raw() ---"
181
+ puts "Request: Get RELIANCE market data\n"
182
+
183
+ # chat_raw() - returns full response with tool_calls
184
+ puts "\nUsing chat_raw() (recommended for tool calling):"
185
+ begin
186
+ response = client.chat_raw(
187
+ model: "llama3.1:8b",
188
+ messages: [Ollama::Agent::Messages.user("Get market data for RELIANCE")],
189
+ tools: market_quote_tool,
190
+ allow_chat: true
191
+ )
192
+
193
+ tool_calls = response.message&.tool_calls
194
+ if tool_calls && !tool_calls.empty?
195
+ puts " ✅ Can access tool_calls: #{tool_calls.length} call(s)"
196
+ else
197
+ puts " ⚠️ No tool_calls (content only)"
198
+ end
199
+ rescue Ollama::Error => e
200
+ puts " ❌ Error: #{e.message}"
201
+ end
202
+
203
+ # chat() - returns content only
204
+ puts "\nUsing chat() (not recommended for tool calling):"
205
+ begin
206
+ content = client.chat(
207
+ model: "llama3.1:8b",
208
+ messages: [Ollama::Agent::Messages.user("Get market data for RELIANCE")],
209
+ tools: market_quote_tool,
210
+ allow_chat: true
211
+ )
212
+
213
+ if content.empty?
214
+ puts " ⚠️ Content is empty (tool_calls present but not accessible)"
215
+ puts " 💡 Use chat_raw() to access tool_calls"
216
+ else
217
+ puts " ✅ Content: #{content[0..50]}..."
218
+ end
219
+ rescue Ollama::Error => e
220
+ puts " ❌ Error: #{e.message}"
221
+ end
222
+
223
+ puts "\n--- Test 4: Full flow - Execute tool after getting tool_calls ---"
224
+ puts "Request: Get market quote for RELIANCE and execute it\n"
225
+
226
+ begin
227
+ # Step 1: Get tool_calls from LLM
228
+ response = client.chat_raw(
229
+ model: "llama3.1:8b",
230
+ messages: [Ollama::Agent::Messages.user("Get the market quote for RELIANCE stock on NSE")],
231
+ tools: market_quote_tool,
232
+ allow_chat: true
233
+ )
234
+
235
+ tool_calls = response.message&.tool_calls
236
+ if tool_calls && !tool_calls.empty?
237
+ puts "✅ Step 1: Tool call received from LLM"
238
+ call = tool_calls.first
239
+ puts " Tool: #{call.name}"
240
+ puts " Arguments: #{call.arguments.inspect}\n"
241
+
242
+ # Step 2: Execute the tool (finds instrument first, then calls API)
243
+ args = call.arguments
244
+ symbol = args["symbol"] || args[:symbol]
245
+ exchange_segment = args["exchange_segment"] || args[:exchange_segment]
246
+
247
+ puts "✅ Step 2: Executing tool..."
248
+ puts " Finding instrument: #{symbol} on #{exchange_segment}"
249
+
250
+ begin
251
+ # The tool will find instrument internally
252
+ result = DhanHQDataTools.get_market_quote(
253
+ symbol: symbol,
254
+ exchange_segment: exchange_segment
255
+ )
256
+
257
+ if result[:error]
258
+ puts " ❌ Error: #{result[:error]}"
259
+ else
260
+ puts " ✅ Instrument found and quote retrieved"
261
+ quote = result[:result][:quote]
262
+ if quote
263
+ ohlc = quote[:ohlc]
264
+ puts " 📊 Last Price: #{quote[:last_price]}"
265
+ puts " 📊 Volume: #{quote[:volume]}"
266
+ puts " 📊 OHLC: O=#{ohlc[:open]}, H=#{ohlc[:high]}, " \
267
+ "L=#{ohlc[:low]}, C=#{ohlc[:close]}"
268
+ end
269
+ end
270
+ rescue StandardError => e
271
+ puts " ❌ Tool execution error: #{e.message}"
272
+ puts " Note: This is expected if DhanHQ is not configured or market is closed"
273
+ end
274
+
275
+ # Step 3: Feed result back to LLM (optional - for multi-turn)
276
+ puts "\n✅ Step 3: Tool result can be fed back to LLM for next turn"
277
+ puts " (This would be done automatically by Executor)"
278
+
279
+ else
280
+ puts "⚠️ No tool calls detected"
281
+ end
282
+ rescue Ollama::Error => e
283
+ puts "❌ Error: #{e.class.name}"
284
+ puts " Message: #{e.message}"
285
+ end
286
+
287
+ puts "\n--- Test 5: Historical Data with Technical Indicators ---"
288
+ puts "Request: Get NIFTY intraday data with technical indicators\n"
289
+
290
+ begin
291
+ response = client.chat_raw(
292
+ model: "llama3.1:8b",
293
+ messages: [Ollama::Agent::Messages.user(
294
+ "Get intraday historical data for NIFTY with technical indicators for the last 30 days"
295
+ )],
296
+ tools: historical_data_tool,
297
+ allow_chat: true
298
+ )
299
+
300
+ tool_calls = response.message&.tool_calls
301
+ if tool_calls && !tool_calls.empty?
302
+ puts "✅ Tool call received"
303
+ call = tool_calls.first
304
+ puts " Tool: #{call.name}"
305
+ puts " Arguments: #{call.arguments.inspect}\n"
306
+
307
+ args = call.arguments
308
+ puts " Expected parameters:"
309
+ puts " - symbol: #{args['symbol'] || args[:symbol]}"
310
+ puts " - exchange_segment: #{args['exchange_segment'] || args[:exchange_segment]}"
311
+ puts " - interval: #{args['interval'] || args[:interval]} (for intraday)"
312
+ puts " - from_date: #{args['from_date'] || args[:from_date]}"
313
+ puts " - to_date: #{args['to_date'] || args[:to_date]}"
314
+ puts " - calculate_indicators: #{args['calculate_indicators'] || args[:calculate_indicators]}"
315
+ else
316
+ puts "⚠️ No tool calls detected"
317
+ puts "Content: #{response.message&.content}\n"
318
+ end
319
+ rescue Ollama::Error => e
320
+ puts "❌ Error: #{e.class.name}"
321
+ puts " Message: #{e.message}"
322
+ end
323
+
324
+ puts "\n--- Test 6: Intraday Data with 5-minute intervals ---"
325
+ puts "Request: Get NIFTY 5-minute intraday data for today\n"
326
+
327
+ begin
328
+ today = Date.today.strftime("%Y-%m-%d")
329
+ response = client.chat_raw(
330
+ model: "llama3.1:8b",
331
+ messages: [Ollama::Agent::Messages.user(
332
+ "Get NIFTY intraday data with 5-minute intervals for today (#{today})"
333
+ )],
334
+ tools: historical_data_tool,
335
+ allow_chat: true
336
+ )
337
+
338
+ tool_calls = response.message&.tool_calls
339
+ if tool_calls && !tool_calls.empty?
340
+ puts "✅ Tool call received"
341
+ call = tool_calls.first
342
+ args = call.arguments
343
+
344
+ puts " Verifying intraday parameters:"
345
+ interval = args["interval"] || args[:interval]
346
+ if %w[1 5 15 25 60].include?(interval.to_s)
347
+ puts " ✅ Interval: #{interval} (valid intraday value)"
348
+ else
349
+ puts " ❌ Interval: #{interval} (should be one of: 1, 5, 15, 25, 60)"
350
+ end
351
+
352
+ from_date = args["from_date"] || args[:from_date]
353
+ to_date = args["to_date"] || args[:to_date]
354
+ puts " Date range: #{from_date} to #{to_date}"
355
+ else
356
+ puts "⚠️ No tool calls detected"
357
+ end
358
+ rescue Ollama::Error => e
359
+ puts "❌ Error: #{e.message}"
360
+ end
361
+
362
+ puts "\n--- Summary ---"
363
+ puts "✅ Use chat_raw() for tool calling - gives access to tool_calls"
364
+ puts "⚠️ Use chat() only for simple content responses (no tool_calls needed)"
365
+ puts "📝 Tool execution flow:"
366
+ puts " 1. LLM requests tool via chat_raw() → get tool_calls"
367
+ puts " 2. Find instrument using exchange_segment + symbol"
368
+ puts " 3. Execute tool with instrument"
369
+ puts " 4. Feed result back to LLM (if using Executor)"
370
+ puts "\n📊 Historical Data enhancements:"
371
+ puts " - Use interval (1, 5, 15, 25, 60) for intraday data"
372
+ puts " - Omit interval for daily data"
373
+ puts " - Use calculate_indicators: true for technical indicators (RSI, MACD, etc.)"
374
+ puts " - Reduces response size and provides ready-to-use indicator values"
375
+ puts "\n=== DONE ===\n"
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Quick test script to verify Tool calling with chat_raw() and chat()
5
+ # This demonstrates the new structured Tool classes and Response wrapper
6
+
7
+ require_relative "lib/ollama_client"
8
+
9
+ puts "\n=== TOOL CALLING TEST ===\n"
10
+
11
+ client = Ollama::Client.new
12
+
13
+ # Define a simple tool using structured Tool classes
14
+ weather_tool = Ollama::Tool.new(
15
+ type: "function",
16
+ function: Ollama::Tool::Function.new(
17
+ name: "get_weather",
18
+ description: "Get the current weather for a location",
19
+ parameters: Ollama::Tool::Function::Parameters.new(
20
+ type: "object",
21
+ properties: {
22
+ location: Ollama::Tool::Function::Parameters::Property.new(
23
+ type: "string",
24
+ description: "The city name, e.g. Paris, London"
25
+ ),
26
+ unit: Ollama::Tool::Function::Parameters::Property.new(
27
+ type: "string",
28
+ description: "Temperature unit",
29
+ enum: %w[celsius fahrenheit]
30
+ )
31
+ },
32
+ required: %w[location]
33
+ )
34
+ )
35
+ )
36
+
37
+ # Test 1: Using chat_raw() to access tool_calls
38
+ puts "\n--- Test 1: chat_raw() with Tool object ---"
39
+ puts "Sending request with tool definition..."
40
+
41
+ begin
42
+ response = client.chat_raw(
43
+ model: "llama3.1:8b",
44
+ messages: [Ollama::Agent::Messages.user("What's the weather in Paris? Use celsius.")],
45
+ tools: weather_tool,
46
+ allow_chat: true
47
+ )
48
+
49
+ puts "\n✅ Response received"
50
+ puts "Response class: #{response.class.name}"
51
+
52
+ # Method access (like ollama-ruby)
53
+ tool_calls = response.message&.tool_calls
54
+ if tool_calls && !tool_calls.empty?
55
+ puts "\n✅ Tool calls detected (via method access):"
56
+ tool_calls.each do |call|
57
+ puts " - Tool: #{call.name}"
58
+ puts " Arguments: #{call.arguments}"
59
+ puts " ID: #{call.id}"
60
+ end
61
+ else
62
+ puts "\n⚠️ No tool calls detected"
63
+ puts "Content: #{response.message&.content}"
64
+ end
65
+
66
+ # Hash access (backward compatible)
67
+ tool_calls_hash = response.to_h.dig("message", "tool_calls")
68
+ if tool_calls_hash && !tool_calls_hash.empty?
69
+ puts "\n✅ Tool calls also accessible via hash:"
70
+ puts " Count: #{tool_calls_hash.length}"
71
+ end
72
+ rescue Ollama::Error => e
73
+ puts "\n❌ Error: #{e.class.name}"
74
+ puts " Message: #{e.message}"
75
+ puts "\n Note: This is expected if Ollama server is not running"
76
+ puts " The important part is that the code structure is correct"
77
+ end
78
+
79
+ # Test 2: Using chat() with tools (returns content only)
80
+ puts "\n--- Test 2: chat() with Tool object ---"
81
+ puts "Sending request with tool definition..."
82
+
83
+ begin
84
+ content = client.chat(
85
+ model: "llama3.1:8b",
86
+ messages: [Ollama::Agent::Messages.user("What's the weather in London?")],
87
+ tools: weather_tool,
88
+ allow_chat: true
89
+ )
90
+
91
+ puts "\n✅ Response received"
92
+ if content.empty?
93
+ puts "⚠️ Content is empty (model returned only tool_calls)"
94
+ puts " Use chat_raw() to access tool_calls"
95
+ else
96
+ puts "Content: #{content}"
97
+ end
98
+ rescue Ollama::Error => e
99
+ puts "\n❌ Error: #{e.class.name}"
100
+ puts " Message: #{e.message}"
101
+ end
102
+
103
+ # Test 3: Multiple tools (array)
104
+ puts "\n--- Test 3: Multiple tools (array) ---"
105
+
106
+ time_tool = Ollama::Tool.new(
107
+ type: "function",
108
+ function: Ollama::Tool::Function.new(
109
+ name: "get_time",
110
+ description: "Get the current time",
111
+ parameters: Ollama::Tool::Function::Parameters.new(
112
+ type: "object",
113
+ properties: {
114
+ timezone: Ollama::Tool::Function::Parameters::Property.new(
115
+ type: "string",
116
+ description: "Timezone (default: UTC)",
117
+ enum: %w[UTC EST PST]
118
+ )
119
+ },
120
+ required: []
121
+ )
122
+ )
123
+ )
124
+
125
+ begin
126
+ response = client.chat_raw(
127
+ model: "llama3.1:8b",
128
+ messages: [Ollama::Agent::Messages.user("What time is it? Use the get_time tool.")],
129
+ tools: [weather_tool, time_tool], # Array of Tool objects
130
+ allow_chat: true
131
+ )
132
+
133
+ puts "\n✅ Response received"
134
+ tool_calls = response.message&.tool_calls
135
+ if tool_calls && !tool_calls.empty?
136
+ puts "\n✅ Tool calls detected:"
137
+ tool_calls.each do |call|
138
+ puts " - #{call.name}: #{call.arguments}"
139
+ end
140
+ else
141
+ puts "\n⚠️ No tool calls detected"
142
+ end
143
+ rescue Ollama::Error => e
144
+ puts "\n❌ Error: #{e.class.name}"
145
+ puts " Message: #{e.message}"
146
+ end
147
+
148
+ # Test 4: Verify Tool object structure
149
+ puts "\n--- Test 4: Tool object structure ---"
150
+ puts "Weather tool:"
151
+ puts " Type: #{weather_tool.type}"
152
+ puts " Function name: #{weather_tool.function.name}"
153
+ puts " Function description: #{weather_tool.function.description}"
154
+ puts " Parameters type: #{weather_tool.function.parameters.type}"
155
+ puts " Required params: #{weather_tool.function.parameters.required}"
156
+
157
+ puts "\nTool.to_h (for API):"
158
+ puts weather_tool.to_h.inspect
159
+
160
+ puts "\n=== DONE ===\n"
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: Direct Tool Calling (matching ollama-ruby pattern)
5
+ # Demonstrates using Tool objects directly with chat() and chat_raw()
6
+
7
+ require "json"
8
+ require_relative "../lib/ollama_client"
9
+
10
+ puts "\n=== DIRECT TOOL CALLING EXAMPLE ===\n"
11
+
12
+ client = Ollama::Client.new
13
+
14
+ # Define tool using Tool classes (matching ollama-ruby pattern)
15
+ tool = Ollama::Tool.new(
16
+ type: "function",
17
+ function: Ollama::Tool::Function.new(
18
+ name: "get_current_weather",
19
+ description: "Get the current weather for a location",
20
+ parameters: Ollama::Tool::Function::Parameters.new(
21
+ type: "object",
22
+ properties: {
23
+ location: Ollama::Tool::Function::Parameters::Property.new(
24
+ type: "string",
25
+ description: "The location to get the weather for, e.g. San Francisco, CA"
26
+ ),
27
+ temperature_unit: Ollama::Tool::Function::Parameters::Property.new(
28
+ type: "string",
29
+ description: "The unit to return the temperature in, either 'celsius' or 'fahrenheit'",
30
+ enum: %w[celsius fahrenheit]
31
+ )
32
+ },
33
+ required: %w[location temperature_unit]
34
+ )
35
+ )
36
+ )
37
+
38
+ # Create message
39
+ def message(location)
40
+ Ollama::Agent::Messages.user("What is the weather today in #{location}?")
41
+ end
42
+
43
+ puts "\n--- Using chat_raw() to access tool_calls ---"
44
+
45
+ # Use chat_raw() to get full response with tool_calls
46
+ response = client.chat_raw(
47
+ model: "llama3.1:8b",
48
+ messages: [message("The City of Love")],
49
+ tools: tool, # Pass Tool object directly
50
+ allow_chat: true
51
+ )
52
+
53
+ # Access tool_calls from response (using method access like ollama-ruby)
54
+ tool_calls = response.message&.tool_calls
55
+ if tool_calls && !tool_calls.empty?
56
+ puts "\nTool calls detected:"
57
+ tool_calls.each do |call|
58
+ name = call.name
59
+ args = call.arguments
60
+ puts " - #{name}: #{args}"
61
+ end
62
+ else
63
+ puts "\nNo tool calls in response"
64
+ puts "Response: #{response.message&.content}"
65
+ end
66
+
67
+ # You can also use hash access if preferred:
68
+ # tool_calls = response.to_h.dig("message", "tool_calls")
69
+
70
+ puts "\n--- Using chat() with tools (returns content only) ---"
71
+
72
+ # chat() returns only the content, not tool_calls
73
+ # When tools are used and model returns only tool_calls (no content),
74
+ # chat() returns empty string. Use chat_raw() to access tool_calls.
75
+ begin
76
+ content = client.chat(
77
+ model: "llama3.1:8b",
78
+ messages: [message("The Windy City")],
79
+ tools: tool,
80
+ allow_chat: true
81
+ )
82
+
83
+ if content.empty?
84
+ puts "Content: (empty - model returned only tool_calls, use chat_raw() to access them)"
85
+ else
86
+ puts "Content: #{content}"
87
+ end
88
+ rescue Ollama::Error => e
89
+ puts "Note: #{e.message}"
90
+ puts "For tool calling, use chat_raw() instead of chat()"
91
+ end
92
+
93
+ puts "\n--- Multiple tools (array) ---"
94
+
95
+ # You can also pass an array of tools
96
+ tool2 = Ollama::Tool.new(
97
+ type: "function",
98
+ function: Ollama::Tool::Function.new(
99
+ name: "get_time",
100
+ description: "Get the current time",
101
+ parameters: Ollama::Tool::Function::Parameters.new(
102
+ type: "object",
103
+ properties: {},
104
+ required: []
105
+ )
106
+ )
107
+ )
108
+
109
+ response2 = client.chat_raw(
110
+ model: "llama3.1:8b",
111
+ messages: [Ollama::Agent::Messages.user("What time is it? Use the get_time tool.")],
112
+ tools: [tool, tool2], # Array of Tool objects
113
+ allow_chat: true
114
+ )
115
+
116
+ tool_calls2 = response2.message&.tool_calls
117
+ if tool_calls2 && !tool_calls2.empty?
118
+ puts "\nTool calls from multiple tools:"
119
+ tool_calls2.each do |call|
120
+ puts " - #{call.name}"
121
+ end
122
+ end
123
+
124
+ puts "\n=== DONE ===\n"