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,368 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Use local code instead of installed gem
|
|
5
|
+
$LOAD_PATH.unshift(File.expand_path("lib", __dir__))
|
|
6
|
+
require "ollama_client"
|
|
7
|
+
require "json"
|
|
8
|
+
require "fileutils"
|
|
9
|
+
|
|
10
|
+
puts "\n=== MULTI-STEP AGENT WITH EXTERNAL DATA TEST ===\n"
|
|
11
|
+
|
|
12
|
+
# Configuration via environment variables (defaults for local testing)
|
|
13
|
+
BASE_URL = ENV.fetch("OLLAMA_BASE_URL", "http://localhost:11434")
|
|
14
|
+
|
|
15
|
+
def client_for(model:, temperature:, timeout:)
|
|
16
|
+
config = Ollama::Config.new
|
|
17
|
+
config.base_url = BASE_URL
|
|
18
|
+
config.model = model
|
|
19
|
+
config.temperature = temperature
|
|
20
|
+
config.timeout = timeout
|
|
21
|
+
config.retries = 2
|
|
22
|
+
Ollama::Client.new(config: config)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# ------------------------------------------------------------
|
|
26
|
+
# PLANNER SETUP (STATELESS)
|
|
27
|
+
# ------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
planner_client = client_for(
|
|
30
|
+
model: ENV.fetch("OLLAMA_MODEL", "llama3.1:8b"),
|
|
31
|
+
temperature: 0,
|
|
32
|
+
timeout: 40
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
planner = Ollama::Agent::Planner.new(planner_client)
|
|
36
|
+
|
|
37
|
+
decision_schema = {
|
|
38
|
+
"type" => "object",
|
|
39
|
+
"required" => %w[action reason],
|
|
40
|
+
"properties" => {
|
|
41
|
+
"action" => {
|
|
42
|
+
"type" => "string",
|
|
43
|
+
"enum" => %w[read_file read_reference analyze_with_reference finish]
|
|
44
|
+
},
|
|
45
|
+
"reason" => { "type" => "string" }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# ------------------------------------------------------------
|
|
50
|
+
# EXECUTOR SETUP (STATEFUL)
|
|
51
|
+
# ------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
test_dir = File.expand_path("test_files", __dir__)
|
|
54
|
+
|
|
55
|
+
tools = {
|
|
56
|
+
"read_file" => lambda do |path:|
|
|
57
|
+
full_path = File.expand_path(path, test_dir)
|
|
58
|
+
return { error: "Path must be within test_files directory" } unless full_path.start_with?(test_dir)
|
|
59
|
+
|
|
60
|
+
return { error: "File not found: #{path}" } unless File.exist?(full_path)
|
|
61
|
+
|
|
62
|
+
{
|
|
63
|
+
path: path,
|
|
64
|
+
content: File.read(full_path),
|
|
65
|
+
size: File.size(full_path)
|
|
66
|
+
}
|
|
67
|
+
end,
|
|
68
|
+
|
|
69
|
+
"read_reference" => lambda do |reference_name:|
|
|
70
|
+
# Read external reference files
|
|
71
|
+
reference_files = {
|
|
72
|
+
"style_guide" => "ruby_style_guide.txt",
|
|
73
|
+
"checklist" => "code_review_checklist.txt"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
filename = reference_files[reference_name.to_s]
|
|
77
|
+
unless filename
|
|
78
|
+
return { error: "Unknown reference: #{reference_name}. Available: #{reference_files.keys.join(", ")}" }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
full_path = File.expand_path(filename, test_dir)
|
|
82
|
+
return { error: "Reference file not found: #{filename}" } unless File.exist?(full_path)
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
reference_name: reference_name,
|
|
86
|
+
content: File.read(full_path),
|
|
87
|
+
size: File.size(full_path)
|
|
88
|
+
}
|
|
89
|
+
end
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
executor_client = client_for(
|
|
93
|
+
model: ENV.fetch("OLLAMA_MODEL", "llama3.1:8b"),
|
|
94
|
+
temperature: 0.2,
|
|
95
|
+
timeout: 60
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
executor = Ollama::Agent::Executor.new(
|
|
99
|
+
executor_client,
|
|
100
|
+
tools: tools
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def step_limit_reached?(step, max_steps)
|
|
104
|
+
return false if step <= max_steps
|
|
105
|
+
|
|
106
|
+
puts "\n⚠️ Maximum steps reached (#{max_steps})"
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def build_status(context, step)
|
|
111
|
+
{
|
|
112
|
+
file_read: context[:file_read] ? "YES" : "NO",
|
|
113
|
+
references_read: context[:references_read].empty? ? "NONE" : context[:references_read].join(", "),
|
|
114
|
+
has_analysis: context[:analysis] ? "YES" : "NO",
|
|
115
|
+
step_number: step
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def missing_references(references_read)
|
|
120
|
+
%w[style_guide checklist] - references_read
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def early_plan_for(context)
|
|
124
|
+
return { "action" => "finish", "reason" => "Analysis complete" } if context[:analysis]
|
|
125
|
+
|
|
126
|
+
if context[:file_read] && context[:references_read].length >= 2
|
|
127
|
+
return { "action" => "analyze_with_reference",
|
|
128
|
+
"reason" => "File and all references read, ready to analyze" }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
return nil unless context[:file_read]
|
|
132
|
+
|
|
133
|
+
missing_refs = missing_references(context[:references_read])
|
|
134
|
+
return nil if missing_refs.empty?
|
|
135
|
+
|
|
136
|
+
{ "action" => "read_reference",
|
|
137
|
+
"reason" => "File read, need to read missing references: #{missing_refs.join(", ")}" }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def planner_prompt(status)
|
|
141
|
+
<<~PROMPT
|
|
142
|
+
You are a planning agent for code analysis with external reference data.
|
|
143
|
+
|
|
144
|
+
Available actions:
|
|
145
|
+
- read_file: Read the target code file (ONLY if file_read is NO)
|
|
146
|
+
- read_reference: Read external reference data (style_guide or checklist) - ONLY if not already read
|
|
147
|
+
- analyze_with_reference: Analyze code using the reference data (ONLY if file_read is YES and BOTH references are read)
|
|
148
|
+
- finish: Complete task (ONLY if analysis exists)
|
|
149
|
+
|
|
150
|
+
Current Status:
|
|
151
|
+
#{status.to_json}
|
|
152
|
+
|
|
153
|
+
CRITICAL RULES (follow strictly in order):
|
|
154
|
+
1. If file_read is NO → use read_file (DO NOT read file if already read)
|
|
155
|
+
2. If file_read is YES and references_read is missing style_guide or checklist → use read_reference for the missing one
|
|
156
|
+
3. If file_read is YES and BOTH references are read and analysis is NO → use analyze_with_reference
|
|
157
|
+
4. If analysis exists → use finish
|
|
158
|
+
|
|
159
|
+
DO NOT:
|
|
160
|
+
- Read file if file_read is YES
|
|
161
|
+
- Read a reference if it's already in references_read
|
|
162
|
+
- Analyze if analysis already exists
|
|
163
|
+
- Finish if analysis does not exist
|
|
164
|
+
|
|
165
|
+
Decide the next action. Return ONLY valid JSON.
|
|
166
|
+
PROMPT
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def plan_next_action(planner, decision_schema, status, context)
|
|
170
|
+
early_plan = early_plan_for(context)
|
|
171
|
+
return early_plan if early_plan
|
|
172
|
+
|
|
173
|
+
planner.run(prompt: planner_prompt(status), schema: decision_schema)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def handle_plan(plan, context, executor, test_dir)
|
|
177
|
+
case plan["action"]
|
|
178
|
+
when "read_file"
|
|
179
|
+
handle_read_file(context, test_dir)
|
|
180
|
+
when "read_reference"
|
|
181
|
+
handle_read_reference(context, test_dir)
|
|
182
|
+
when "analyze_with_reference"
|
|
183
|
+
handle_analyze_with_reference(context, executor, test_dir)
|
|
184
|
+
when "finish"
|
|
185
|
+
handle_finish
|
|
186
|
+
else
|
|
187
|
+
raise "Unknown action: #{plan["action"]}"
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def handle_read_file(context, test_dir)
|
|
192
|
+
if context[:file_read]
|
|
193
|
+
puts "\n⚠️ File already read, skipping..."
|
|
194
|
+
return :skip
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
puts "\n→ Executor: reading target file"
|
|
198
|
+
|
|
199
|
+
full_path = File.expand_path(context[:target_file], test_dir)
|
|
200
|
+
if File.exist?(full_path)
|
|
201
|
+
context[:file_content] = File.read(full_path)
|
|
202
|
+
context[:file_read] = true
|
|
203
|
+
puts "\n✅ File content read (#{context[:file_content].length} bytes)"
|
|
204
|
+
else
|
|
205
|
+
puts "\n❌ File not found: #{context[:target_file]}"
|
|
206
|
+
context[:file_read] = false
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
:continue
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def reference_file_name(reference_name)
|
|
213
|
+
reference_name == "style_guide" ? "ruby_style_guide.txt" : "code_review_checklist.txt"
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def handle_read_reference(context, test_dir)
|
|
217
|
+
puts "\n→ Executor: reading reference data"
|
|
218
|
+
|
|
219
|
+
references_to_read = missing_references(context[:references_read])
|
|
220
|
+
if references_to_read.empty?
|
|
221
|
+
puts "\n⚠️ All references already read"
|
|
222
|
+
return :skip
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
reference_name = references_to_read.first
|
|
226
|
+
puts "\nReading reference: #{reference_name}"
|
|
227
|
+
|
|
228
|
+
ref_file = reference_file_name(reference_name)
|
|
229
|
+
ref_path = File.expand_path(ref_file, test_dir)
|
|
230
|
+
|
|
231
|
+
if File.exist?(ref_path)
|
|
232
|
+
context[:reference_data][reference_name] = File.read(ref_path)
|
|
233
|
+
context[:references_read] << reference_name
|
|
234
|
+
puts "\n✅ Reference '#{reference_name}' loaded (#{context[:reference_data][reference_name].length} bytes)"
|
|
235
|
+
else
|
|
236
|
+
puts "\n❌ Reference file not found: #{ref_file}"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
:continue
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def analysis_system_prompt
|
|
243
|
+
<<~PROMPT
|
|
244
|
+
You are a Ruby code analysis agent. Analyze code using the provided reference guidelines and checklists.
|
|
245
|
+
Compare the code against the reference standards and provide specific recommendations.
|
|
246
|
+
PROMPT
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def analysis_user_prompt(code, reference_context)
|
|
250
|
+
<<~PROMPT
|
|
251
|
+
Analyze this Ruby code using the reference data:
|
|
252
|
+
|
|
253
|
+
CODE TO ANALYZE:
|
|
254
|
+
#{code}
|
|
255
|
+
|
|
256
|
+
REFERENCE DATA:
|
|
257
|
+
#{reference_context}
|
|
258
|
+
|
|
259
|
+
Provide a detailed analysis that:
|
|
260
|
+
1. Compares the code against the style guide
|
|
261
|
+
2. Checks items from the code review checklist
|
|
262
|
+
3. Provides specific, actionable recommendations
|
|
263
|
+
4. References specific guidelines from the reference data
|
|
264
|
+
|
|
265
|
+
Keep your analysis focused and reference the external data when making recommendations.
|
|
266
|
+
PROMPT
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def handle_analyze_with_reference(context, executor, _test_dir)
|
|
270
|
+
if context[:analysis]
|
|
271
|
+
puts "\n⚠️ Analysis already complete, skipping..."
|
|
272
|
+
return :skip
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
unless context[:file_read]
|
|
276
|
+
puts "\n❌ Cannot analyze: file not read yet"
|
|
277
|
+
return :skip
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
if context[:references_read].length < 2
|
|
281
|
+
puts "\n❌ Cannot analyze: need both references (have: #{context[:references_read].join(", ")})"
|
|
282
|
+
return :skip
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
puts "\n→ Executor: analyzing code using reference data"
|
|
286
|
+
|
|
287
|
+
reference_context = context[:reference_data].map do |name, content|
|
|
288
|
+
"#{name.upcase}:\n#{content}\n"
|
|
289
|
+
end.join("\n---\n\n")
|
|
290
|
+
|
|
291
|
+
result = executor.run(
|
|
292
|
+
system: analysis_system_prompt,
|
|
293
|
+
user: analysis_user_prompt(context[:file_content], reference_context)
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
puts "\nExecutor result:"
|
|
297
|
+
puts result
|
|
298
|
+
puts "\n#{"=" * 60}"
|
|
299
|
+
|
|
300
|
+
context[:analysis] = result
|
|
301
|
+
:continue
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def handle_finish
|
|
305
|
+
puts "\n→ Agent finished"
|
|
306
|
+
:finish
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# ------------------------------------------------------------
|
|
310
|
+
# AGENT LOOP
|
|
311
|
+
# ------------------------------------------------------------
|
|
312
|
+
|
|
313
|
+
context = {
|
|
314
|
+
target_file: "user_creator.rb",
|
|
315
|
+
file_read: false,
|
|
316
|
+
file_content: nil,
|
|
317
|
+
references_read: [],
|
|
318
|
+
reference_data: {},
|
|
319
|
+
analysis: nil
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
step = 0
|
|
323
|
+
max_steps = 15
|
|
324
|
+
|
|
325
|
+
loop do
|
|
326
|
+
step += 1
|
|
327
|
+
puts "\n→ Planner step #{step}"
|
|
328
|
+
|
|
329
|
+
break if step_limit_reached?(step, max_steps)
|
|
330
|
+
|
|
331
|
+
status = build_status(context, step)
|
|
332
|
+
plan = plan_next_action(planner, decision_schema, status, context)
|
|
333
|
+
pp plan
|
|
334
|
+
|
|
335
|
+
result = handle_plan(plan, context, executor, test_dir)
|
|
336
|
+
break if result == :finish
|
|
337
|
+
next if result == :skip
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# ------------------------------------------------------------
|
|
341
|
+
# FINAL ASSERTIONS
|
|
342
|
+
# ------------------------------------------------------------
|
|
343
|
+
|
|
344
|
+
puts "\n→ Assertions"
|
|
345
|
+
|
|
346
|
+
success = true
|
|
347
|
+
|
|
348
|
+
unless context[:file_read]
|
|
349
|
+
puts "❌ No file read"
|
|
350
|
+
success = false
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
unless context[:references_read].any?
|
|
354
|
+
puts "❌ No references read"
|
|
355
|
+
success = false
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
unless context[:analysis]
|
|
359
|
+
puts "❌ No analysis performed"
|
|
360
|
+
success = false
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
raise "❌ MULTI-STEP AGENT WITH EXTERNAL DATA FAILED" unless success
|
|
364
|
+
|
|
365
|
+
puts "\n✅ MULTI-STEP AGENT WITH EXTERNAL DATA PASSED"
|
|
366
|
+
puts "\nReferences used: #{context[:references_read].join(", ")}"
|
|
367
|
+
puts "\nAnalysis length: #{context[:analysis].length} characters"
|
|
368
|
+
puts "\n=== END ===\n"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example: Using Structured Tool Definitions
|
|
5
|
+
# Demonstrates explicit Tool classes for type-safe tool schemas
|
|
6
|
+
|
|
7
|
+
require "json"
|
|
8
|
+
require_relative "../lib/ollama_client"
|
|
9
|
+
|
|
10
|
+
puts "\n=== STRUCTURED TOOLS EXAMPLE ===\n"
|
|
11
|
+
|
|
12
|
+
client = Ollama::Client.new
|
|
13
|
+
|
|
14
|
+
# Define tools with explicit schemas using Tool classes
|
|
15
|
+
location_property = Ollama::Tool::Function::Parameters::Property.new(
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "The city and state, e.g. San Francisco, CA"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
unit_property = Ollama::Tool::Function::Parameters::Property.new(
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "The unit to return temperature in",
|
|
23
|
+
enum: %w[celsius fahrenheit]
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
weather_parameters = Ollama::Tool::Function::Parameters.new(
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
location: location_property,
|
|
30
|
+
unit: unit_property
|
|
31
|
+
},
|
|
32
|
+
required: %w[location unit]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
weather_function = Ollama::Tool::Function.new(
|
|
36
|
+
name: "get_current_weather",
|
|
37
|
+
description: "Get the current weather for a location",
|
|
38
|
+
parameters: weather_parameters
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
weather_tool = Ollama::Tool.new(
|
|
42
|
+
type: "function",
|
|
43
|
+
function: weather_function
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Define the actual callable
|
|
47
|
+
weather_callable = lambda do |location:, unit:|
|
|
48
|
+
{
|
|
49
|
+
location: location,
|
|
50
|
+
unit: unit,
|
|
51
|
+
temperature: unit == "celsius" ? "22" : "72",
|
|
52
|
+
condition: "sunny"
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Use structured tool definition with callable
|
|
57
|
+
tools = {
|
|
58
|
+
"get_current_weather" => {
|
|
59
|
+
tool: weather_tool,
|
|
60
|
+
callable: weather_callable
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Alternative: Simple callable (auto-inferred schema)
|
|
65
|
+
# This still works for backward compatibility - just pass the callable directly
|
|
66
|
+
simple_tools = {
|
|
67
|
+
"get_time" => lambda do |timezone: "UTC"|
|
|
68
|
+
{ timezone: timezone, time: Time.now.utc.iso8601 }
|
|
69
|
+
end
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Combine both approaches
|
|
73
|
+
# Simple callables can be passed directly (auto-inferred)
|
|
74
|
+
all_tools = tools.merge(simple_tools)
|
|
75
|
+
|
|
76
|
+
executor = Ollama::Agent::Executor.new(client, tools: all_tools)
|
|
77
|
+
|
|
78
|
+
begin
|
|
79
|
+
answer = executor.run(
|
|
80
|
+
system: "You are a helpful assistant. Use tools when needed.",
|
|
81
|
+
user: "What's the weather in Paris, France? Use celsius."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
puts "\nAnswer: #{answer}"
|
|
85
|
+
rescue Ollama::Error => e
|
|
86
|
+
puts "Error: #{e.message}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
puts "\n=== DONE ===\n"
|