ollama-client 0.2.2 → 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.
@@ -0,0 +1,92 @@
1
+ # Examples
2
+
3
+ This directory contains working examples demonstrating various features of the ollama-client gem.
4
+
5
+ ## Quick Start Examples
6
+
7
+ ### Basic Tool Calling
8
+ - **[test_tool_calling.rb](test_tool_calling.rb)** - Simple tool calling demo with weather tool
9
+ - **[tool_calling_pattern.rb](tool_calling_pattern.rb)** - Recommended patterns for tool calling
10
+ - **[tool_calling_direct.rb](tool_calling_direct.rb)** - Direct tool calling without Executor
11
+
12
+ ### DhanHQ Market Data
13
+ - **[test_dhanhq_tool_calling.rb](test_dhanhq_tool_calling.rb)** - DhanHQ tools with updated intraday & indicators
14
+ - **[dhanhq_tools.rb](dhanhq_tools.rb)** - DhanHQ API wrapper tools
15
+ - **[dhan_console.rb](dhan_console.rb)** - Interactive DhanHQ console with planning
16
+
17
+ ### Multi-Step Agents
18
+ - **[multi_step_agent_e2e.rb](multi_step_agent_e2e.rb)** - End-to-end multi-step agent example
19
+ - **[multi_step_agent_with_external_data.rb](multi_step_agent_with_external_data.rb)** - Agent with external data integration
20
+
21
+ ### Structured Data
22
+ - **[structured_outputs_chat.rb](structured_outputs_chat.rb)** - Structured outputs with schemas
23
+ - **[structured_tools.rb](structured_tools.rb)** - Structured tool definitions
24
+ - **[tool_dto_example.rb](tool_dto_example.rb)** - Using DTOs for tool definitions
25
+
26
+ ### Advanced Features
27
+ - **[advanced_multi_step_agent.rb](advanced_multi_step_agent.rb)** - Complex multi-step workflows
28
+ - **[advanced_error_handling.rb](advanced_error_handling.rb)** - Error handling patterns
29
+ - **[advanced_edge_cases.rb](advanced_edge_cases.rb)** - Edge case handling
30
+ - **[advanced_complex_schemas.rb](advanced_complex_schemas.rb)** - Complex schema definitions
31
+ - **[advanced_performance_testing.rb](advanced_performance_testing.rb)** - Performance testing
32
+
33
+ ### Interactive Consoles
34
+ - **[chat_console.rb](chat_console.rb)** - Simple chat console with streaming
35
+ - **[dhan_console.rb](dhan_console.rb)** - DhanHQ market data console with formatted tool results
36
+
37
+ ### Complete Workflows
38
+ - **[complete_workflow.rb](complete_workflow.rb)** - Complete agent workflow example
39
+
40
+ ## DhanHQ Examples
41
+
42
+ The `dhanhq/` subdirectory contains more specialized DhanHQ examples:
43
+ - Technical analysis agents
44
+ - Market scanners (intraday options, swing trading)
45
+ - Pattern recognition and trend analysis
46
+ - Multi-agent orchestration
47
+
48
+ See [dhanhq/README.md](dhanhq/README.md) for details.
49
+
50
+ ## Running Examples
51
+
52
+ Most examples are standalone and can be run directly:
53
+
54
+ ```bash
55
+ # Basic tool calling
56
+ ruby examples/test_tool_calling.rb
57
+
58
+ # DhanHQ with intraday data
59
+ ruby examples/test_dhanhq_tool_calling.rb
60
+
61
+ # Interactive console
62
+ ruby examples/chat_console.rb
63
+ ```
64
+
65
+ ### Requirements
66
+
67
+ Some examples require additional setup:
68
+
69
+ **DhanHQ Examples:**
70
+ - Set `DHANHQ_CLIENT_ID` and `DHANHQ_ACCESS_TOKEN` environment variables
71
+ - Or create `.env` file with credentials
72
+
73
+ **Ollama:**
74
+ - Ollama server running (default: `http://localhost:11434`)
75
+ - Set `OLLAMA_BASE_URL` if using a different URL
76
+ - Set `OLLAMA_MODEL` if not using default model
77
+
78
+ ## Learning Path
79
+
80
+ 1. **Start here:** `test_tool_calling.rb` - Learn basic tool calling
81
+ 2. **Structured data:** `structured_outputs_chat.rb` - Schema-based outputs
82
+ 3. **Multi-step:** `multi_step_agent_e2e.rb` - Complex agent workflows
83
+ 4. **Market data:** `test_dhanhq_tool_calling.rb` - Real-world API integration
84
+ 5. **Interactive:** `dhan_console.rb` - Full-featured console with planning
85
+
86
+ ## Contributing
87
+
88
+ When adding new examples:
89
+ - Include clear comments explaining what the example demonstrates
90
+ - Add `#!/usr/bin/env ruby` shebang at the top
91
+ - Use `frozen_string_literal: true`
92
+ - Update this README with a description
@@ -35,7 +35,8 @@ class FinancialAnalyzer
35
35
  "profit_margin" => {
36
36
  "type" => "number",
37
37
  "minimum" => 0,
38
- "maximum" => 100
38
+ "maximum" => 100,
39
+ "description" => "Profit margin as percentage (0 to 100)"
39
40
  },
40
41
  "growth_rate" => {
41
42
  "type" => "number"
@@ -121,7 +122,8 @@ class CodeReviewer
121
122
  "overall_score" => {
122
123
  "type" => "integer",
123
124
  "minimum" => 0,
124
- "maximum" => 100
125
+ "maximum" => 100,
126
+ "description" => "Overall quality score (0 to 100)"
125
127
  },
126
128
  "issues" => {
127
129
  "type" => "array",
@@ -262,7 +264,8 @@ class ResearchAnalyzer
262
264
  "reproducibility_score" => {
263
265
  "type" => "number",
264
266
  "minimum" => 0,
265
- "maximum" => 1
267
+ "maximum" => 1,
268
+ "description" => "Reproducibility score (0.0 to 1.0, where 1.0 means fully reproducible)"
266
269
  }
267
270
  }
268
271
  }
@@ -46,7 +46,8 @@ class MultiStepAgent
46
46
  "confidence" => {
47
47
  "type" => "number",
48
48
  "minimum" => 0,
49
- "maximum" => 1
49
+ "maximum" => 1,
50
+ "description" => "Confidence level (0.0 to 1.0, where 1.0 is 100% confident)"
50
51
  },
51
52
  "next_steps" => {
52
53
  "type" => "array",
@@ -39,8 +39,8 @@ MAX_HISTORY = 200
39
39
  COLOR_RESET = "\e[0m"
40
40
  COLOR_USER = "\e[32m"
41
41
  COLOR_LLM = "\e[36m"
42
- USER_PROMPT = "#{COLOR_USER}you>#{COLOR_RESET} "
43
- LLM_PROMPT = "#{COLOR_LLM}llm>#{COLOR_RESET} "
42
+ USER_PROMPT = "#{COLOR_USER}you>#{COLOR_RESET} ".freeze
43
+ LLM_PROMPT = "#{COLOR_LLM}llm>#{COLOR_RESET} ".freeze
44
44
 
45
45
  def build_reader
46
46
  TTY::Reader.new
@@ -86,7 +86,10 @@ end
86
86
 
87
87
  def chat_response(client, messages, config)
88
88
  content = +""
89
- print LLM_PROMPT
89
+ prompt_printed = false
90
+
91
+ print "#{COLOR_LLM}...#{COLOR_RESET}"
92
+ $stdout.flush
90
93
 
91
94
  client.chat_raw(
92
95
  messages: messages,
@@ -97,8 +100,14 @@ def chat_response(client, messages, config)
97
100
  token = chunk.dig("message", "content").to_s
98
101
  next if token.empty?
99
102
 
103
+ unless prompt_printed
104
+ print "\r#{LLM_PROMPT}"
105
+ prompt_printed = true
106
+ end
107
+
100
108
  content << token
101
109
  print token
110
+ $stdout.flush
102
111
  end
103
112
 
104
113
  puts
@@ -30,7 +30,7 @@ class TaskPlanner
30
30
  "type" => "number",
31
31
  "minimum" => 0,
32
32
  "maximum" => 1,
33
- "description" => "Confidence in this decision"
33
+ "description" => "Confidence in this decision (0.0 to 1.0, where 1.0 is 100% confident)"
34
34
  },
35
35
  "next_step" => {
36
36
  "type" => "string",
@@ -49,8 +49,13 @@ class TaskPlanner
49
49
  puts "Context: #{context}\n\n"
50
50
 
51
51
  begin
52
+ prompt = "Given this context: #{context}\n\n" \
53
+ "Decide the next action to take.\n\n" \
54
+ "IMPORTANT: Use decimal values for confidence " \
55
+ "(e.g., 0.95 for 95% confident, 0.80 for 80% confident, 1.0 for 100% confident)."
56
+
52
57
  result = @client.generate(
53
- prompt: "Given this context: #{context}\n\nDecide the next action to take.",
58
+ prompt: prompt,
54
59
  schema: @task_schema
55
60
  )
56
61
 
@@ -132,7 +137,8 @@ class DataAnalyzer
132
137
  "confidence" => {
133
138
  "type" => "number",
134
139
  "minimum" => 0,
135
- "maximum" => 1
140
+ "maximum" => 1,
141
+ "description" => "Confidence level (0.0 to 1.0, where 1.0 is 100% confident)"
136
142
  },
137
143
  "key_points" => {
138
144
  "type" => "array",
@@ -157,8 +163,12 @@ class DataAnalyzer
157
163
  puts "Data: #{data}\n\n"
158
164
 
159
165
  begin
166
+ prompt = "Analyze this data and provide insights: #{data}\n\n" \
167
+ "IMPORTANT: Express confidence as a decimal between 0.0 and 1.0 " \
168
+ "(e.g., 0.85 for 85% confidence, not 85)."
169
+
160
170
  result = @client.generate(
161
- prompt: "Analyze this data and provide insights: #{data}",
171
+ prompt: prompt,
162
172
  schema: @analysis_schema
163
173
  )
164
174
 
@@ -42,8 +42,8 @@ MAX_HISTORY = 200
42
42
  COLOR_RESET = "\e[0m"
43
43
  COLOR_USER = "\e[32m"
44
44
  COLOR_LLM = "\e[36m"
45
- USER_PROMPT = "#{COLOR_USER}you>#{COLOR_RESET} "
46
- LLM_PROMPT = "#{COLOR_LLM}llm>#{COLOR_RESET} "
45
+ USER_PROMPT = "#{COLOR_USER}you>#{COLOR_RESET} ".freeze
46
+ LLM_PROMPT = "#{COLOR_LLM}llm>#{COLOR_RESET} ".freeze
47
47
 
48
48
  def build_reader
49
49
  TTY::Reader.new
@@ -497,7 +497,7 @@ def build_tools
497
497
  "get_historical_data" => lambda do |exchange_segment:, from_date:, to_date:, symbol: nil, security_id: nil,
498
498
  interval: nil, expiry_code: nil, calculate_indicators: false|
499
499
  # Convert security_id to integer if provided (LLM may pass it as string)
500
- normalized_security_id = security_id ? security_id.to_i : nil
500
+ normalized_security_id = security_id&.to_i
501
501
  DhanHQDataTools.get_historical_data(**compact_kwargs(exchange_segment: exchange_segment,
502
502
  symbol: symbol,
503
503
  security_id: normalized_security_id,
@@ -509,14 +509,14 @@ def build_tools
509
509
  end,
510
510
  "get_expiry_list" => lambda do |exchange_segment:, symbol: nil, security_id: nil|
511
511
  # Convert security_id to integer if provided (LLM may pass it as string)
512
- normalized_security_id = security_id ? security_id.to_i : nil
512
+ normalized_security_id = security_id&.to_i
513
513
  DhanHQDataTools.get_expiry_list(**compact_kwargs(exchange_segment: exchange_segment,
514
514
  symbol: symbol,
515
515
  security_id: normalized_security_id))
516
516
  end,
517
517
  "get_option_chain" => lambda do |exchange_segment:, symbol: nil, security_id: nil, expiry: nil, strikes_count: 5|
518
518
  # Convert security_id to integer if provided (LLM may pass it as string)
519
- normalized_security_id = security_id ? security_id.to_i : nil
519
+ normalized_security_id = security_id&.to_i
520
520
  # Default to 5 strikes (2 ITM, ATM, 2 OTM) - good balance for analysis
521
521
  normalized_strikes_count = strikes_count.to_i
522
522
  normalized_strikes_count = 5 if normalized_strikes_count < 1 # Minimum 1 (ATM)
@@ -555,16 +555,18 @@ def tool_messages(messages)
555
555
  end
556
556
 
557
557
  def print_tool_results(messages)
558
- puts "Tool Results:"
559
558
  tool_messages(messages).each do |message|
560
559
  print_tool_message(message)
561
560
  end
561
+ puts # blank line after tool results
562
562
  end
563
563
 
564
564
  def print_tool_message(message)
565
565
  tool_name = message[:name] || "unknown_tool"
566
- puts "- #{tool_name}"
567
- puts format_tool_content(message[:content])
566
+ content = parse_tool_content(message[:content])
567
+
568
+ puts "\n#{COLOR_LLM}🔧 Tool Called:#{COLOR_RESET} #{tool_name}"
569
+ print_formatted_result(tool_name, content)
568
570
  end
569
571
 
570
572
  def format_tool_content(content)
@@ -582,6 +584,99 @@ rescue JSON::ParserError
582
584
  content
583
585
  end
584
586
 
587
+ def print_formatted_result(tool_name, content)
588
+ return puts content if content.is_a?(String)
589
+
590
+ result = content["result"] || content
591
+
592
+ case tool_name
593
+ when "get_live_ltp"
594
+ print_ltp_result(result)
595
+ when "get_market_quote"
596
+ print_quote_result(result)
597
+ when "get_historical_data"
598
+ print_historical_result(result, content)
599
+ when "get_option_chain"
600
+ print_option_chain_result(result)
601
+ when "get_expiry_list"
602
+ print_expiry_list_result(result)
603
+ when "find_instrument"
604
+ print_instrument_result(result)
605
+ else
606
+ puts " #{COLOR_LLM}→#{COLOR_RESET} #{JSON.pretty_generate(content)}"
607
+ end
608
+ end
609
+
610
+ def print_ltp_result(result)
611
+ symbol = result["symbol"] || "Unknown"
612
+ ltp = result["ltp"] || result.dig("ltp_data", "last_price")
613
+ exchange = result["exchange_segment"]
614
+
615
+ puts " #{COLOR_LLM}→#{COLOR_RESET} #{symbol} (#{exchange})"
616
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Last Price: ₹#{ltp}"
617
+ end
618
+
619
+ def print_quote_result(result)
620
+ symbol = result["symbol"] || "Unknown"
621
+ quote = result["quote"] || {}
622
+ ohlc = quote["ohlc"] || {}
623
+
624
+ puts " #{COLOR_LLM}→#{COLOR_RESET} #{symbol}"
625
+ puts " #{COLOR_LLM}→#{COLOR_RESET} LTP: ₹#{quote['last_price']}"
626
+ puts " #{COLOR_LLM}→#{COLOR_RESET} OHLC: O:#{ohlc['open']} H:#{ohlc['high']} L:#{ohlc['low']} C:#{ohlc['close']}"
627
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Volume: #{quote['volume']}" if quote["volume"]&.positive?
628
+ end
629
+
630
+ def print_historical_result(result, content)
631
+ if result.is_a?(Hash) && result.key?("indicators")
632
+ print_indicator_result(result)
633
+ elsif result.is_a?(Hash)
634
+ data_points = result["data"]&.size || 0
635
+ interval = content.dig("params", "interval")
636
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Historical data: #{data_points} records"
637
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Interval: #{interval}" if interval
638
+ elsif result.is_a?(Array)
639
+ puts " #{COLOR_LLM}→#{COLOR_RESET} #{result.size} data points"
640
+ end
641
+ end
642
+
643
+ def print_indicator_result(result)
644
+ indicators = result["indicators"]
645
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Technical Indicators:"
646
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Current Price: ₹#{indicators['current_price']}"
647
+ puts " #{COLOR_LLM}→#{COLOR_RESET} RSI(14): #{indicators['rsi']&.round(2)}"
648
+ puts " #{COLOR_LLM}→#{COLOR_RESET} MACD: #{indicators.dig('macd', 'macd')&.round(2)}"
649
+ puts " #{COLOR_LLM}→#{COLOR_RESET} SMA(20): ₹#{indicators['sma_20']&.round(2)}"
650
+ puts " #{COLOR_LLM}→#{COLOR_RESET} SMA(50): ₹#{indicators['sma_50']&.round(2)}"
651
+ end
652
+
653
+ def print_option_chain_result(result)
654
+ oc = result.dig("data", "oc") || {}
655
+ last_price = result.dig("data", "last_price")
656
+ strikes = oc.keys.size
657
+
658
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Spot: ₹#{last_price}"
659
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Strikes: #{strikes}"
660
+ puts " #{COLOR_LLM}→#{COLOR_RESET} (Filtered: ATM/OTM/ITM with both CE & PE)"
661
+ end
662
+
663
+ def print_expiry_list_result(result)
664
+ expiries = result["expiries"] || []
665
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Available expiries: #{expiries.size}"
666
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Next expiry: #{expiries.first}" if expiries.any?
667
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Expiries: #{expiries[0..4].join(', ')}#{'...' if expiries.size > 5}"
668
+ end
669
+
670
+ def print_instrument_result(result)
671
+ symbol = result["symbol"]
672
+ security_id = result["security_id"]
673
+ exchange = result["exchange_segment"]
674
+
675
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Found: #{symbol}"
676
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Security ID: #{security_id}"
677
+ puts " #{COLOR_LLM}→#{COLOR_RESET} Exchange: #{exchange}"
678
+ end
679
+
585
680
  def show_llm_summary?
586
681
  ENV["SHOW_LLM_SUMMARY"] == "true"
587
682
  end
@@ -236,7 +236,12 @@ module DhanHQ
236
236
  "stop_loss" => { "type" => "number" },
237
237
  "target_price" => { "type" => "number" },
238
238
  "risk_reward_ratio" => { "type" => "number" },
239
- "confidence" => { "type" => "number", "minimum" => 0, "maximum" => 1 },
239
+ "confidence" => {
240
+ "type" => "number",
241
+ "minimum" => 0,
242
+ "maximum" => 1,
243
+ "description" => "Confidence (0.0 to 1.0)"
244
+ },
240
245
  "reasoning" => { "type" => "string" },
241
246
  "timeframe" => { "type" => "string" }
242
247
  }
@@ -21,7 +21,7 @@ module DhanHQ
21
21
  "type" => "number",
22
22
  "minimum" => 0,
23
23
  "maximum" => 1,
24
- "description" => "Confidence in this decision"
24
+ "description" => "Confidence in this decision (0.0 to 1.0, where 1.0 is 100% confident)"
25
25
  },
26
26
  "parameters" => {
27
27
  "type" => "object",
@@ -47,7 +47,7 @@ module DhanHQ
47
47
  "type" => "number",
48
48
  "minimum" => 0,
49
49
  "maximum" => 1,
50
- "description" => "Confidence in this decision"
50
+ "description" => "Confidence in this decision (0.0 to 1.0, where 1.0 is 100% confident)"
51
51
  },
52
52
  "parameters" => {
53
53
  "type" => "object",
@@ -76,7 +76,7 @@ class DataAgent
76
76
  "type" => "number",
77
77
  "minimum" => 0,
78
78
  "maximum" => 1,
79
- "description" => "Confidence in this decision"
79
+ "description" => "Confidence in this decision (0.0 to 1.0, where 1.0 is 100% confident)"
80
80
  },
81
81
  "parameters" => {
82
82
  "type" => "object",
@@ -304,7 +304,7 @@ class TradingAgent
304
304
  "type" => "number",
305
305
  "minimum" => 0,
306
306
  "maximum" => 1,
307
- "description" => "Confidence in this decision"
307
+ "description" => "Confidence in this decision (0.0 to 1.0, where 1.0 is 100% confident)"
308
308
  },
309
309
  "parameters" => {
310
310
  "type" => "object",
@@ -662,16 +662,26 @@ if __FILE__ == $PROGRAM_NAME
662
662
  puts " ⚠️ RELIANCE data error: #{e.message}"
663
663
  end
664
664
 
665
- # NOTE: Positions and holdings are not part of the 6 Data APIs
665
+ # NOTE: Positions and holdings are not part of the 6 Data APIs, but available via DhanHQ gem
666
666
  begin
667
- positions_result = { action: "check_positions", result: { positions: [], count: 0 },
668
- note: "Positions API not available in Data Tools" }
669
- if positions_result[:result]
670
- market_data[:positions] = positions_result[:result][:positions] || []
671
- puts " ✅ Positions: #{positions_result[:result][:count] || 0} active"
672
- else
673
- puts " ✅ Positions: 0 active (Positions API not in Data Tools)"
674
- market_data[:positions] = []
667
+ positions_list = DhanHQ::Models::Position.all
668
+ positions_data = positions_list.map do |pos|
669
+ {
670
+ trading_symbol: pos.trading_symbol,
671
+ quantity: pos.net_qty,
672
+ average_price: pos.buy_avg,
673
+ exchange_segment: pos.exchange_segment,
674
+ security_id: pos.security_id,
675
+ pnl: pos.realized_profit
676
+ }
677
+ end
678
+ market_data[:positions] = positions_data
679
+ puts " ✅ Positions: #{positions_data.length} active"
680
+
681
+ if positions_data.any?
682
+ positions_data.each do |pos|
683
+ puts " - #{pos[:trading_symbol]}: Qty #{pos[:quantity]} @ ₹#{pos[:average_price]}"
684
+ end
675
685
  end
676
686
  rescue StandardError => e
677
687
  puts " ⚠️ Positions error: #{e.message}"
@@ -852,8 +862,8 @@ if __FILE__ == $PROGRAM_NAME
852
862
  begin
853
863
  # NOTE: Options symbols may need different format
854
864
  # Try with NIFTY which typically has options
855
- # First, get the list of available expiries
856
- expiry_list_result = DhanHQDataTools.get_option_chain(
865
+ # First, get the list of available expiries using get_expiry_list
866
+ expiry_list_result = DhanHQDataTools.get_expiry_list(
857
867
  symbol: "NIFTY", # NIFTY typically has options, RELIANCE might not
858
868
  exchange_segment: "IDX_I"
859
869
  )