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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +220 -12
- data/docs/CLOUD.md +29 -0
- data/docs/CONSOLE_IMPROVEMENTS.md +256 -0
- data/docs/FEATURES_ADDED.md +145 -0
- data/docs/HANDLERS_ANALYSIS.md +190 -0
- data/docs/README.md +37 -0
- data/docs/SCHEMA_FIXES.md +147 -0
- data/docs/TEST_UPDATES.md +107 -0
- data/examples/README.md +92 -0
- data/examples/advanced_complex_schemas.rb +6 -3
- data/examples/advanced_multi_step_agent.rb +13 -7
- data/examples/chat_console.rb +143 -0
- data/examples/complete_workflow.rb +14 -4
- data/examples/dhan_console.rb +843 -0
- data/examples/dhanhq/agents/base_agent.rb +0 -2
- data/examples/dhanhq/agents/orchestrator_agent.rb +1 -2
- data/examples/dhanhq/agents/technical_analysis_agent.rb +67 -49
- data/examples/dhanhq/analysis/market_structure.rb +44 -28
- data/examples/dhanhq/analysis/pattern_recognizer.rb +64 -47
- data/examples/dhanhq/analysis/trend_analyzer.rb +6 -8
- data/examples/dhanhq/dhanhq_agent.rb +296 -99
- data/examples/dhanhq/indicators/technical_indicators.rb +3 -5
- data/examples/dhanhq/scanners/intraday_options_scanner.rb +360 -255
- data/examples/dhanhq/scanners/swing_scanner.rb +118 -84
- data/examples/dhanhq/schemas/agent_schemas.rb +2 -2
- data/examples/dhanhq/services/data_service.rb +5 -7
- data/examples/dhanhq/services/trading_service.rb +0 -3
- data/examples/dhanhq/technical_analysis_agentic_runner.rb +217 -84
- data/examples/dhanhq/technical_analysis_runner.rb +216 -162
- data/examples/dhanhq/test_tool_calling.rb +538 -0
- data/examples/dhanhq/test_tool_calling_verbose.rb +251 -0
- data/examples/dhanhq/utils/trading_parameter_normalizer.rb +12 -17
- data/examples/dhanhq_agent.rb +159 -116
- data/examples/dhanhq_tools.rb +1158 -251
- data/examples/multi_step_agent_with_external_data.rb +368 -0
- data/examples/structured_tools.rb +89 -0
- data/examples/test_dhanhq_tool_calling.rb +375 -0
- data/examples/test_tool_calling.rb +160 -0
- data/examples/tool_calling_direct.rb +124 -0
- data/examples/tool_dto_example.rb +94 -0
- data/exe/dhan_console +4 -0
- data/exe/ollama-client +1 -1
- data/lib/ollama/agent/executor.rb +116 -15
- data/lib/ollama/client.rb +118 -55
- data/lib/ollama/config.rb +36 -0
- data/lib/ollama/dto.rb +187 -0
- data/lib/ollama/embeddings.rb +77 -0
- data/lib/ollama/options.rb +104 -0
- data/lib/ollama/response.rb +121 -0
- data/lib/ollama/tool/function/parameters/property.rb +72 -0
- data/lib/ollama/tool/function/parameters.rb +101 -0
- data/lib/ollama/tool/function.rb +78 -0
- data/lib/ollama/tool.rb +60 -0
- data/lib/ollama/version.rb +1 -1
- data/lib/ollama_client.rb +3 -0
- metadata +31 -3
- /data/{PRODUCTION_FIXES.md → docs/PRODUCTION_FIXES.md} +0 -0
- /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"
|