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,411 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # True Agentic Technical Analysis Runner
5
- # Uses Ollama::Agent::Executor for dynamic tool calling
6
- # LLM decides which tools to call and when, based on results
7
-
8
- require "json"
9
- require "date"
10
- require "dhan_hq"
11
- require_relative "../../lib/ollama_client"
12
- require_relative "../dhanhq_tools"
13
-
14
- # Load all modules
15
- require_relative "utils/instrument_helper"
16
- require_relative "utils/rate_limiter"
17
- require_relative "utils/parameter_normalizer"
18
- require_relative "utils/parameter_cleaner"
19
- require_relative "utils/trading_parameter_normalizer"
20
- require_relative "builders/market_context_builder"
21
- require_relative "schemas/agent_schemas"
22
- require_relative "services/base_service"
23
- require_relative "services/data_service"
24
- require_relative "services/trading_service"
25
- require_relative "indicators/technical_indicators"
26
- require_relative "analysis/market_structure"
27
- require_relative "analysis/pattern_recognizer"
28
- require_relative "analysis/trend_analyzer"
29
- require_relative "agents/base_agent"
30
- require_relative "agents/data_agent"
31
- require_relative "agents/trading_agent"
32
- require_relative "agents/technical_analysis_agent"
33
- require_relative "agents/orchestrator_agent"
34
- require_relative "scanners/swing_scanner"
35
- require_relative "scanners/intraday_options_scanner"
36
-
37
- module DhanHQ
38
- # Main agent orchestrator
39
- class Agent
40
- def initialize(ollama_client: nil, trading_ollama_client: nil)
41
- @ollama_client = ollama_client || Ollama::Client.new
42
- @trading_ollama_client = trading_ollama_client || create_trading_client
43
- @data_agent = Agents::DataAgent.new(ollama_client: @ollama_client)
44
- @trading_agent = Agents::TradingAgent.new(ollama_client: @trading_ollama_client)
45
- @analysis_agent = Agents::TechnicalAnalysisAgent.new(ollama_client: @ollama_client)
46
- @orchestrator_agent = Agents::OrchestratorAgent.new(ollama_client: @ollama_client)
47
- @swing_scanner = Scanners::SwingScanner.new
48
- @options_scanner = Scanners::IntradayOptionsScanner.new
49
- end
50
-
51
- attr_reader :data_agent, :trading_agent, :analysis_agent, :orchestrator_agent, :swing_scanner, :options_scanner
52
-
53
- private
54
-
55
- def create_trading_client
56
- config = Ollama::Config.new
57
- config.timeout = 60
58
- Ollama::Client.new(config: config)
59
- end
60
- end
61
- end
62
-
63
- # Configure DhanHQ
64
- begin
65
- DhanHQ.configure_with_env
66
- puts "✅ DhanHQ configured"
67
- rescue StandardError => e
68
- puts "⚠️ DhanHQ configuration error: #{e.message}"
69
- puts " Make sure CLIENT_ID and ACCESS_TOKEN are set in ENV"
70
- exit 1
71
- end
72
-
73
- puts "=" * 60
74
- puts "TECHNICAL ANALYSIS - TRUE AGENTIC TOOL CALLING"
75
- puts "=" * 60
76
- puts
77
-
78
- # Initialize agent
79
- agent = DhanHQ::Agent.new
80
-
81
- # Define tools using structured Tool classes for better type safety and LLM understanding
82
- # This provides explicit schemas with descriptions, types, and enums
83
-
84
- # Swing Scan Tool - Structured definition
85
- swing_scan_tool = Ollama::Tool.new(
86
- type: "function",
87
- function: Ollama::Tool::Function.new(
88
- name: "swing_scan",
89
- description: "Scans for swing trading opportunities in EQUITY STOCKS only. " \
90
- "Use for stocks like RELIANCE, TCS, INFY, HDFC. " \
91
- "Do NOT use for indices (NIFTY, SENSEX, BANKNIFTY).",
92
- parameters: Ollama::Tool::Function::Parameters.new(
93
- type: "object",
94
- properties: {
95
- symbol: Ollama::Tool::Function::Parameters::Property.new(
96
- type: "string",
97
- description: "Stock symbol to scan (e.g., RELIANCE, TCS, INFY). Must be a stock, not an index."
98
- ),
99
- exchange_segment: Ollama::Tool::Function::Parameters::Property.new(
100
- type: "string",
101
- description: "Exchange segment (default: NSE_EQ)",
102
- enum: %w[NSE_EQ BSE_EQ]
103
- ),
104
- min_score: Ollama::Tool::Function::Parameters::Property.new(
105
- type: "integer",
106
- description: "Minimum score threshold (default: 40)"
107
- ),
108
- verbose: Ollama::Tool::Function::Parameters::Property.new(
109
- type: "boolean",
110
- description: "Verbose output (default: false)"
111
- )
112
- },
113
- required: %w[symbol]
114
- )
115
- )
116
- )
117
-
118
- # Options Scan Tool - Structured definition
119
- options_scan_tool = Ollama::Tool.new(
120
- type: "function",
121
- function: Ollama::Tool::Function.new(
122
- name: "options_scan",
123
- description: "Scans for intraday options buying opportunities in INDICES only. " \
124
- "Use for NIFTY, SENSEX, BANKNIFTY. Do NOT use for stocks.",
125
- parameters: Ollama::Tool::Function::Parameters.new(
126
- type: "object",
127
- properties: {
128
- symbol: Ollama::Tool::Function::Parameters::Property.new(
129
- type: "string",
130
- description: "Index symbol (NIFTY, SENSEX, or BANKNIFTY). Must be an index, not a stock.",
131
- enum: %w[NIFTY SENSEX BANKNIFTY]
132
- ),
133
- exchange_segment: Ollama::Tool::Function::Parameters::Property.new(
134
- type: "string",
135
- description: "Exchange segment (default: IDX_I)",
136
- enum: %w[IDX_I]
137
- ),
138
- min_score: Ollama::Tool::Function::Parameters::Property.new(
139
- type: "integer",
140
- description: "Minimum score threshold (default: 40)"
141
- ),
142
- verbose: Ollama::Tool::Function::Parameters::Property.new(
143
- type: "boolean",
144
- description: "Verbose output (default: false)"
145
- )
146
- },
147
- required: %w[symbol]
148
- )
149
- )
150
- )
151
-
152
- # Technical Analysis Tool - Structured definition
153
- technical_analysis_tool = Ollama::Tool.new(
154
- type: "function",
155
- function: Ollama::Tool::Function.new(
156
- name: "technical_analysis",
157
- description: "Performs full technical analysis including trend, indicators, and patterns. " \
158
- "Can be used for both stocks and indices.",
159
- parameters: Ollama::Tool::Function::Parameters.new(
160
- type: "object",
161
- properties: {
162
- symbol: Ollama::Tool::Function::Parameters::Property.new(
163
- type: "string",
164
- description: "Symbol to analyze (stock or index)"
165
- ),
166
- exchange_segment: Ollama::Tool::Function::Parameters::Property.new(
167
- type: "string",
168
- description: "Exchange segment (default: NSE_EQ)",
169
- enum: %w[NSE_EQ BSE_EQ NSE_FNO BSE_FNO IDX_I]
170
- )
171
- },
172
- required: %w[symbol]
173
- )
174
- )
175
- )
176
-
177
- # Define tools with structured Tool classes and callables
178
- tools = {
179
- "swing_scan" => {
180
- tool: swing_scan_tool,
181
- callable: lambda do |symbol:, exchange_segment: "NSE_EQ", min_score: 40, verbose: false|
182
- # CRITICAL: Only allow equity stocks, not indices
183
- if %w[NIFTY SENSEX BANKNIFTY].include?(symbol.to_s.upcase)
184
- return { error: "#{symbol} is an index, not a stock. Use options_scan for indices." }
185
- end
186
-
187
- begin
188
- candidates = agent.swing_scanner.scan_symbols(
189
- [symbol.to_s],
190
- exchange_segment: exchange_segment.to_s,
191
- min_score: min_score.to_i,
192
- verbose: verbose
193
- )
194
-
195
- {
196
- symbol: symbol,
197
- exchange_segment: exchange_segment,
198
- candidates_found: candidates.length,
199
- candidates: candidates.map do |c|
200
- {
201
- symbol: c[:symbol],
202
- score: c[:score],
203
- trend: c[:analysis][:trend][:trend],
204
- recommendation: c[:recommendation]
205
- }
206
- end
207
- }
208
- rescue StandardError => e
209
- { error: e.message, backtrace: e.backtrace.first(3) }
210
- end
211
- end
212
- },
213
-
214
- "options_scan" => {
215
- tool: options_scan_tool,
216
- callable: lambda do |symbol:, exchange_segment: "IDX_I", min_score: 40, verbose: false|
217
- # CRITICAL: Only allow indices, not stocks
218
- unless %w[NIFTY SENSEX BANKNIFTY].include?(symbol.to_s.upcase)
219
- error_message = "#{symbol} is not an index. Use swing_scan for stocks. " \
220
- "Options are only available for indices (NIFTY, SENSEX, BANKNIFTY)."
221
- return { error: error_message }
222
- end
223
-
224
- begin
225
- options_setups = agent.options_scanner.scan_for_options_setups(
226
- symbol.to_s,
227
- exchange_segment: exchange_segment.to_s,
228
- min_score: min_score.to_i,
229
- verbose: verbose
230
- )
231
-
232
- if options_setups[:error]
233
- { error: options_setups[:error] }
234
- else
235
- underlying_price = options_setups.dig(:underlying_analysis, :current_price)
236
- {
237
- symbol: symbol,
238
- exchange_segment: exchange_segment,
239
- underlying_price: underlying_price,
240
- setups_found: options_setups[:setups]&.length || 0,
241
- setups: options_setups[:setups]&.map do |s|
242
- {
243
- type: s[:type],
244
- strike: s[:strike],
245
- score: s[:score],
246
- iv: s[:iv],
247
- ltp: s[:ltp],
248
- recommendation: s[:recommendation]
249
- }
250
- end || []
251
- }
252
- end
253
- rescue StandardError => e
254
- { error: e.message, backtrace: e.backtrace.first(3) }
255
- end
256
- end
257
- },
258
-
259
- "technical_analysis" => {
260
- tool: technical_analysis_tool,
261
- callable: lambda do |symbol:, exchange_segment: "NSE_EQ"|
262
- analysis_result = agent.analysis_agent.analyze_symbol(
263
- symbol: symbol.to_s,
264
- exchange_segment: exchange_segment.to_s
265
- )
266
-
267
- if analysis_result[:error]
268
- { error: analysis_result[:error] }
269
- else
270
- analysis = analysis_result[:analysis]
271
- {
272
- symbol: symbol,
273
- exchange_segment: exchange_segment,
274
- trend: analysis[:trend]&.dig(:trend),
275
- trend_strength: analysis[:trend]&.dig(:strength),
276
- rsi: analysis[:indicators]&.dig(:rsi)&.round(2),
277
- macd: analysis[:indicators]&.dig(:macd)&.round(2),
278
- current_price: analysis[:current_price],
279
- patterns_count: analysis[:patterns]&.dig(:candlestick)&.length || 0,
280
- structure_break: analysis[:structure_break]&.dig(:broken) || false
281
- }
282
- end
283
- rescue StandardError => e
284
- { error: e.message, backtrace: e.backtrace.first(3) }
285
- end
286
- }
287
- }
288
-
289
- # Get user query and market context
290
- user_query = ARGV[0] || "Analyze SENSEX and find swing trading opportunities"
291
- market_context = ARGV[1] || "Current market: SENSEX, NIFTY and RELIANCE are active. Looking for trading opportunities."
292
-
293
- puts "🤔 Agentic Analysis - LLM will decide which tools to call dynamically"
294
- puts "─" * 60
295
- puts "User Query: #{user_query}"
296
- puts "Market Context: #{market_context}"
297
- puts
298
-
299
- # Build system prompt
300
- system_prompt = <<~PROMPT
301
- You are a trading analysis agent. Your job is to help users analyze markets and find trading opportunities.
302
-
303
- Available Tools:
304
- 1. swing_scan(symbol, exchange_segment="NSE_EQ", min_score=40, verbose=false)
305
- - Scans for swing trading opportunities in EQUITY STOCKS only
306
- - Use for: RELIANCE, TCS, INFY, HDFC, etc. (stocks, not indices)
307
- - Returns: List of swing candidates with scores
308
- - CRITICAL: Do NOT use for indices (NIFTY, SENSEX, BANKNIFTY)
309
-
310
- 2. options_scan(symbol, exchange_segment="IDX_I", min_score=40, verbose=false)
311
- - Scans for intraday options buying opportunities in INDICES only
312
- - Use for: NIFTY, SENSEX, BANKNIFTY (indices, not stocks)
313
- - Returns: List of options setups (calls/puts) with scores
314
- - CRITICAL: Do NOT use for stocks (RELIANCE, TCS, etc.)
315
-
316
- 3. technical_analysis(symbol, exchange_segment="NSE_EQ")
317
- - Performs full technical analysis (trend, indicators, patterns)
318
- - Can be used for both stocks and indices
319
- - Returns: Complete technical analysis results
320
-
321
- Rules:
322
- - For swing trading: Use swing_scan ONLY on equity stocks (NSE_EQ, BSE_EQ)
323
- - For options: Use options_scan ONLY on indices (IDX_I) like NIFTY, SENSEX, BANKNIFTY
324
- - You can call multiple tools in sequence
325
- - Use tool results to decide what to do next
326
- - If a tool returns an error, try a different approach or inform the user
327
-
328
- Market Context: #{market_context}
329
- PROMPT
330
-
331
- user_prompt = user_query
332
-
333
- # Create executor with tools
334
- executor = Ollama::Agent::Executor.new(
335
- Ollama::Client.new,
336
- tools: tools,
337
- max_steps: 20
338
- )
339
-
340
- def tool_messages(messages)
341
- messages.select { |message| message[:role] == "tool" }
342
- end
343
-
344
- def print_tool_results(messages)
345
- puts "Tool Results:"
346
- tool_messages(messages).each do |message|
347
- print_tool_message(message)
348
- end
349
- end
350
-
351
- def print_tool_message(message)
352
- tool_name = message[:name] || "unknown_tool"
353
- puts "- #{tool_name}"
354
- puts format_tool_content(message[:content])
355
- end
356
-
357
- def format_tool_content(content)
358
- parsed = parse_tool_content(content)
359
- return parsed if parsed.is_a?(String)
360
-
361
- JSON.pretty_generate(parsed)
362
- end
363
-
364
- def parse_tool_content(content)
365
- return content unless content.is_a?(String)
366
-
367
- JSON.parse(content)
368
- rescue JSON::ParserError
369
- content
370
- end
371
-
372
- def print_llm_summary(result)
373
- return unless ENV["SHOW_LLM_SUMMARY"] == "true"
374
-
375
- puts
376
- puts "LLM Summary (unverified):"
377
- puts result
378
- end
379
-
380
- def print_hallucination_warning
381
- puts "No tool results were produced."
382
- puts "LLM output suppressed to avoid hallucinated data."
383
- end
384
-
385
- # Run the agentic loop
386
- begin
387
- puts "🔄 Starting agentic tool-calling loop..."
388
- puts
389
-
390
- result = executor.run(
391
- system: system_prompt,
392
- user: user_prompt
393
- )
394
-
395
- puts
396
- puts "=" * 60
397
- puts "Agentic Analysis Complete"
398
- puts "=" * 60
399
- if tool_messages(executor.messages).empty?
400
- print_hallucination_warning
401
- else
402
- print_tool_results(executor.messages)
403
- print_llm_summary(result)
404
- end
405
- rescue Ollama::Error => e
406
- puts "❌ Error: #{e.message}"
407
- puts e.backtrace.first(5).join("\n") if e.backtrace
408
- rescue StandardError => e
409
- puts "❌ Unexpected error: #{e.message}"
410
- puts e.backtrace.first(5).join("\n") if e.backtrace
411
- end