ollama-client 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/README.md +138 -76
  4. data/docs/EXAMPLE_REORGANIZATION.md +412 -0
  5. data/docs/GETTING_STARTED.md +361 -0
  6. data/docs/INTEGRATION_TESTING.md +170 -0
  7. data/docs/NEXT_STEPS_SUMMARY.md +114 -0
  8. data/docs/PERSONAS.md +383 -0
  9. data/docs/QUICK_START.md +195 -0
  10. data/docs/TESTING.md +392 -170
  11. data/docs/TEST_CHECKLIST.md +450 -0
  12. data/examples/README.md +51 -66
  13. data/examples/basic_chat.rb +33 -0
  14. data/examples/basic_generate.rb +29 -0
  15. data/examples/tool_calling_parsing.rb +59 -0
  16. data/exe/ollama-client +128 -1
  17. data/lib/ollama/agent/planner.rb +7 -2
  18. data/lib/ollama/chat_session.rb +101 -0
  19. data/lib/ollama/client.rb +41 -35
  20. data/lib/ollama/config.rb +4 -1
  21. data/lib/ollama/document_loader.rb +1 -1
  22. data/lib/ollama/embeddings.rb +41 -26
  23. data/lib/ollama/errors.rb +1 -0
  24. data/lib/ollama/personas.rb +287 -0
  25. data/lib/ollama/version.rb +1 -1
  26. data/lib/ollama_client.rb +7 -0
  27. metadata +14 -48
  28. data/examples/advanced_complex_schemas.rb +0 -366
  29. data/examples/advanced_edge_cases.rb +0 -241
  30. data/examples/advanced_error_handling.rb +0 -200
  31. data/examples/advanced_multi_step_agent.rb +0 -341
  32. data/examples/advanced_performance_testing.rb +0 -186
  33. data/examples/chat_console.rb +0 -143
  34. data/examples/complete_workflow.rb +0 -245
  35. data/examples/dhan_console.rb +0 -843
  36. data/examples/dhanhq/README.md +0 -236
  37. data/examples/dhanhq/agents/base_agent.rb +0 -74
  38. data/examples/dhanhq/agents/data_agent.rb +0 -66
  39. data/examples/dhanhq/agents/orchestrator_agent.rb +0 -120
  40. data/examples/dhanhq/agents/technical_analysis_agent.rb +0 -252
  41. data/examples/dhanhq/agents/trading_agent.rb +0 -81
  42. data/examples/dhanhq/analysis/market_structure.rb +0 -138
  43. data/examples/dhanhq/analysis/pattern_recognizer.rb +0 -192
  44. data/examples/dhanhq/analysis/trend_analyzer.rb +0 -88
  45. data/examples/dhanhq/builders/market_context_builder.rb +0 -67
  46. data/examples/dhanhq/dhanhq_agent.rb +0 -829
  47. data/examples/dhanhq/indicators/technical_indicators.rb +0 -158
  48. data/examples/dhanhq/scanners/intraday_options_scanner.rb +0 -492
  49. data/examples/dhanhq/scanners/swing_scanner.rb +0 -247
  50. data/examples/dhanhq/schemas/agent_schemas.rb +0 -61
  51. data/examples/dhanhq/services/base_service.rb +0 -46
  52. data/examples/dhanhq/services/data_service.rb +0 -118
  53. data/examples/dhanhq/services/trading_service.rb +0 -59
  54. data/examples/dhanhq/technical_analysis_agentic_runner.rb +0 -411
  55. data/examples/dhanhq/technical_analysis_runner.rb +0 -420
  56. data/examples/dhanhq/test_tool_calling.rb +0 -538
  57. data/examples/dhanhq/test_tool_calling_verbose.rb +0 -251
  58. data/examples/dhanhq/utils/instrument_helper.rb +0 -32
  59. data/examples/dhanhq/utils/parameter_cleaner.rb +0 -28
  60. data/examples/dhanhq/utils/parameter_normalizer.rb +0 -45
  61. data/examples/dhanhq/utils/rate_limiter.rb +0 -23
  62. data/examples/dhanhq/utils/trading_parameter_normalizer.rb +0 -72
  63. data/examples/dhanhq_agent.rb +0 -964
  64. data/examples/dhanhq_tools.rb +0 -1663
  65. data/examples/multi_step_agent_with_external_data.rb +0 -368
  66. data/examples/structured_outputs_chat.rb +0 -72
  67. data/examples/structured_tools.rb +0 -89
  68. data/examples/test_dhanhq_tool_calling.rb +0 -375
  69. data/examples/test_tool_calling.rb +0 -160
  70. data/examples/tool_calling_direct.rb +0 -124
  71. data/examples/tool_calling_pattern.rb +0 -269
  72. data/exe/dhan_console +0 -4
@@ -1,368 +0,0 @@
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"
@@ -1,72 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example: Ollama structured outputs using chat API
5
- # This matches the JavaScript example from the Ollama documentation
6
-
7
- require "json"
8
- require_relative "../lib/ollama_client"
9
-
10
- def run(model)
11
- # Define the JSON schema for friend info
12
- friend_info_schema = {
13
- "type" => "object",
14
- "required" => %w[name age is_available],
15
- "properties" => {
16
- "name" => {
17
- "type" => "string",
18
- "description" => "The name of the friend"
19
- },
20
- "age" => {
21
- "type" => "integer",
22
- "description" => "The age of the friend"
23
- },
24
- "is_available" => {
25
- "type" => "boolean",
26
- "description" => "Whether the friend is available"
27
- }
28
- }
29
- }
30
-
31
- # Define the schema for friend list
32
- friend_list_schema = {
33
- "type" => "object",
34
- "required" => ["friends"],
35
- "properties" => {
36
- "friends" => {
37
- "type" => "array",
38
- "description" => "An array of friends",
39
- "items" => friend_info_schema
40
- }
41
- }
42
- }
43
- client = Ollama::Client.new
44
-
45
- messages = [{
46
- role: "user",
47
- content: "I have two friends. The first is Ollama 22 years old busy saving the world, " \
48
- "and the second is Alonso 23 years old and wants to hang out. " \
49
- "Return a list of friends in JSON format"
50
- }]
51
-
52
- response = client.chat(
53
- model: model,
54
- messages: messages,
55
- format: friend_list_schema,
56
- allow_chat: true,
57
- options: {
58
- temperature: 0 # Make responses more deterministic
59
- }
60
- )
61
-
62
- # Parse and validate the response (already validated by client, but showing usage)
63
- begin
64
- friends_response = response # Already parsed and validated
65
- puts JSON.pretty_generate(friends_response)
66
- rescue Ollama::SchemaViolationError => e
67
- puts "Generated invalid response: #{e.message}"
68
- end
69
- end
70
-
71
- # Run with the same model as the JavaScript example
72
- run("llama3.1:8b")
@@ -1,89 +0,0 @@
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"