ollama-client 0.2.2 → 0.2.4

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +7 -1
  4. data/docs/CLOUD.md +29 -0
  5. data/docs/CONSOLE_IMPROVEMENTS.md +256 -0
  6. data/docs/GEM_RELEASE_GUIDE.md +794 -0
  7. data/docs/GET_RUBYGEMS_SECRET.md +151 -0
  8. data/docs/QUICK_OTP_SETUP.md +80 -0
  9. data/docs/QUICK_RELEASE.md +106 -0
  10. data/docs/README.md +43 -0
  11. data/docs/RUBYGEMS_OTP_SETUP.md +199 -0
  12. data/docs/SCHEMA_FIXES.md +147 -0
  13. data/docs/TEST_UPDATES.md +107 -0
  14. data/examples/README.md +92 -0
  15. data/examples/advanced_complex_schemas.rb +6 -3
  16. data/examples/advanced_multi_step_agent.rb +2 -1
  17. data/examples/chat_console.rb +12 -3
  18. data/examples/complete_workflow.rb +14 -4
  19. data/examples/dhan_console.rb +103 -8
  20. data/examples/dhanhq/agents/technical_analysis_agent.rb +6 -1
  21. data/examples/dhanhq/schemas/agent_schemas.rb +2 -2
  22. data/examples/dhanhq_agent.rb +23 -13
  23. data/examples/dhanhq_tools.rb +311 -246
  24. data/examples/multi_step_agent_with_external_data.rb +368 -0
  25. data/{test_dhanhq_tool_calling.rb → examples/test_dhanhq_tool_calling.rb} +99 -6
  26. data/lib/ollama/agent/executor.rb +30 -30
  27. data/lib/ollama/client.rb +73 -80
  28. data/lib/ollama/dto.rb +7 -7
  29. data/lib/ollama/options.rb +17 -9
  30. data/lib/ollama/response.rb +4 -6
  31. data/lib/ollama/tool/function/parameters.rb +1 -0
  32. data/lib/ollama/version.rb +1 -1
  33. metadata +24 -9
  34. /data/{FEATURES_ADDED.md → docs/FEATURES_ADDED.md} +0 -0
  35. /data/{HANDLERS_ANALYSIS.md → docs/HANDLERS_ANALYSIS.md} +0 -0
  36. /data/{PRODUCTION_FIXES.md → docs/PRODUCTION_FIXES.md} +0 -0
  37. /data/{TESTING.md → docs/TESTING.md} +0 -0
  38. /data/{test_tool_calling.rb → examples/test_tool_calling.rb} +0 -0
@@ -0,0 +1,147 @@
1
+ # Schema Validation Fixes
2
+
3
+ ## Problem
4
+
5
+ LLMs were returning confidence values as percentages (e.g., 95, 100) instead of decimals (e.g., 0.95, 1.0), causing schema validation errors:
6
+
7
+ ```
8
+ The property '#/confidence' did not have a maximum value of 1, inclusively
9
+ ```
10
+
11
+ ## Root Cause
12
+
13
+ JSON schemas defined numeric constraints (0-1) but lacked explicit descriptions explaining the expected format. LLMs naturally interpret "confidence" as percentages without guidance.
14
+
15
+ ## Solution
16
+
17
+ Added explicit descriptions to all numeric fields with constrained ranges, clarifying the expected format.
18
+
19
+ ### Fixed Fields
20
+
21
+ #### Confidence Fields (0.0 to 1.0)
22
+ Updated in 8 locations across the codebase:
23
+
24
+ - `examples/complete_workflow.rb` (2 instances)
25
+ - TaskPlanner schema
26
+ - DataAnalyzer schema
27
+ - `examples/dhanhq_agent.rb` (2 instances)
28
+ - `examples/dhanhq/schemas/agent_schemas.rb` (2 instances)
29
+ - `examples/dhanhq/agents/technical_analysis_agent.rb` (1 instance)
30
+ - `examples/advanced_multi_step_agent.rb` (1 instance)
31
+
32
+ **Before:**
33
+ ```ruby
34
+ "confidence" => {
35
+ "type" => "number",
36
+ "minimum" => 0,
37
+ "maximum" => 1
38
+ }
39
+ ```
40
+
41
+ **After:**
42
+ ```ruby
43
+ "confidence" => {
44
+ "type" => "number",
45
+ "minimum" => 0,
46
+ "maximum" => 1,
47
+ "description" => "Confidence in this decision (0.0 to 1.0, where 1.0 is 100% confident)"
48
+ }
49
+ ```
50
+
51
+ #### Other Constrained Fields
52
+
53
+ - `reproducibility_score` in `advanced_complex_schemas.rb`
54
+ - Added: "Reproducibility score (0.0 to 1.0, where 1.0 means fully reproducible)"
55
+
56
+ - `profit_margin` in `advanced_complex_schemas.rb`
57
+ - Added: "Profit margin as percentage (0 to 100)"
58
+
59
+ - `overall_score` in `advanced_complex_schemas.rb`
60
+ - Added: "Overall quality score (0 to 100)"
61
+
62
+ ## Best Practices
63
+
64
+ ### When Defining Schemas
65
+
66
+ 1. **Always include descriptions** for numeric fields with constraints
67
+ 2. **Be explicit about format**: decimal (0.0-1.0) vs percentage (0-100)
68
+ 3. **Provide examples** in descriptions when helpful
69
+ 4. **Use clear units**: "as percentage", "as decimal", "where 1.0 is 100%"
70
+
71
+ ### When Writing Prompts
72
+
73
+ **Schema descriptions alone may not be enough!** Always reinforce numeric formats in the prompt itself:
74
+
75
+ ```ruby
76
+ # ❌ BAD: Relies only on schema description
77
+ result = @client.generate(
78
+ prompt: "Analyze this data: #{data}",
79
+ schema: @schema
80
+ )
81
+
82
+ # ✅ GOOD: Explicit examples in prompt
83
+ result = @client.generate(
84
+ prompt: "Analyze this data: #{data}\n\nIMPORTANT: Express confidence as a decimal between 0.0 and 1.0 (e.g., 0.85 for 85% confidence, not 85).",
85
+ schema: @schema
86
+ )
87
+ ```
88
+
89
+ **Why this matters:**
90
+ - LLMs may not always read schema descriptions carefully
91
+ - Concrete examples in prompts are more effective
92
+ - Prevents "100" vs "1.0" confusion
93
+
94
+ ### Example Patterns
95
+
96
+ **Decimal confidence (0-1):**
97
+ ```ruby
98
+ "confidence" => {
99
+ "type" => "number",
100
+ "minimum" => 0,
101
+ "maximum" => 1,
102
+ "description" => "Confidence level (0.0 to 1.0, where 1.0 is 100% confident)"
103
+ }
104
+ ```
105
+
106
+ **Percentage score (0-100):**
107
+ ```ruby
108
+ "score" => {
109
+ "type" => "number",
110
+ "minimum" => 0,
111
+ "maximum" => 100,
112
+ "description" => "Quality score as percentage (0 to 100)"
113
+ }
114
+ ```
115
+
116
+ **Probability (0-1):**
117
+ ```ruby
118
+ "probability" => {
119
+ "type" => "number",
120
+ "minimum" => 0,
121
+ "maximum" => 1,
122
+ "description" => "Probability value (0.0 to 1.0)"
123
+ }
124
+ ```
125
+
126
+ ## Testing
127
+
128
+ All modified files passed syntax validation:
129
+ ```bash
130
+ ruby -c examples/complete_workflow.rb # Syntax OK
131
+ ruby -c examples/dhanhq_agent.rb # Syntax OK
132
+ ruby -c examples/advanced_multi_step_agent.rb # Syntax OK
133
+ ruby -c examples/advanced_complex_schemas.rb # Syntax OK
134
+ ```
135
+
136
+ ## Impact
137
+
138
+ - **Examples now run without schema validation errors**
139
+ - **LLMs receive clear guidance** on numeric formats
140
+ - **Consistent patterns** across all example schemas
141
+ - **Better developer experience** with self-documenting schemas
142
+
143
+ ## Related Files
144
+
145
+ See also:
146
+ - [Testing Guide](TESTING.md) - How to test structured outputs
147
+ - [Features Added](FEATURES_ADDED.md) - Schema validation features
@@ -0,0 +1,107 @@
1
+ # Test File Updates
2
+
3
+ ## Summary
4
+
5
+ Updated test examples to reflect recent enhancements to DhanHQ tool calling, particularly around historical data and technical indicators.
6
+
7
+ ## Files Updated
8
+
9
+ ### 1. `examples/test_dhanhq_tool_calling.rb`
10
+
11
+ #### Changes Made:
12
+
13
+ 1. **Updated `historical_data_tool` definition** (lines 66-101):
14
+ - Changed `interval` enum from `["daily", "weekly", "monthly"]` to `["1", "5", "15", "25", "60"]`
15
+ - Added proper description: "Minute interval for intraday data. Omit for daily data."
16
+ - Added `calculate_indicators` boolean parameter
17
+ - Updated description to mention technical indicators (RSI, MACD, SMA, EMA, Bollinger Bands, ATR)
18
+ - Added `required` fields: `from_date` and `to_date`
19
+
20
+ 2. **Added Test 5: Historical Data with Technical Indicators**
21
+ - Tests the new `calculate_indicators` parameter
22
+ - Verifies LLM includes all required parameters including interval
23
+ - Shows how to request intraday data with indicators
24
+
25
+ 3. **Added Test 6: Intraday Data with 5-minute intervals**
26
+ - Tests intraday data specifically
27
+ - Uses current date dynamically
28
+ - Validates interval parameter is one of the valid values (1, 5, 15, 25, 60)
29
+ - Shows date range handling
30
+
31
+ 4. **Enhanced Summary Section**
32
+ - Added notes about historical data enhancements
33
+ - Documents interval usage for intraday vs daily data
34
+ - Explains `calculate_indicators` feature and its benefits
35
+
36
+ ## Key Features Tested
37
+
38
+ ### Intraday Data
39
+ - ✅ Interval parameter with values: "1", "5", "15", "25", "60"
40
+ - ✅ Date handling (from_date, to_date)
41
+ - ✅ 5-minute intervals (most commonly used for intraday analysis)
42
+
43
+ ### Technical Indicators
44
+ - ✅ `calculate_indicators` parameter
45
+ - ✅ Returns RSI, MACD, SMA, EMA, Bollinger Bands, ATR
46
+ - ✅ Reduces response size vs raw OHLCV data
47
+
48
+ ### Tool Calling
49
+ - ✅ chat_raw() method for accessing tool_calls
50
+ - ✅ Structured Tool classes
51
+ - ✅ Parameter validation
52
+
53
+ ## Example Usage
54
+
55
+ ```ruby
56
+ # Get intraday data with indicators
57
+ historical_data_tool = Ollama::Tool.new(
58
+ type: "function",
59
+ function: Ollama::Tool::Function.new(
60
+ name: "get_historical_data",
61
+ description: "Get historical price data (OHLCV) or technical indicators...",
62
+ parameters: Ollama::Tool::Function::Parameters.new(
63
+ type: "object",
64
+ properties: {
65
+ interval: Ollama::Tool::Function::Parameters::Property.new(
66
+ type: "string",
67
+ description: "Minute interval for intraday data",
68
+ enum: %w[1 5 15 25 60] # Updated from ["daily", "weekly", "monthly"]
69
+ ),
70
+ calculate_indicators: Ollama::Tool::Function::Parameters::Property.new(
71
+ type: "boolean",
72
+ description: "If true, returns technical indicators instead of raw data"
73
+ ),
74
+ # ... other properties
75
+ }
76
+ )
77
+ )
78
+ )
79
+
80
+ # Request with intraday and indicators
81
+ response = client.chat_raw(
82
+ messages: [Ollama::Agent::Messages.user(
83
+ "Get NIFTY intraday data with 5-minute intervals and technical indicators"
84
+ )],
85
+ tools: historical_data_tool
86
+ )
87
+ ```
88
+
89
+ ## Testing
90
+
91
+ Run the updated tests:
92
+
93
+ ```bash
94
+ # Tool calling examples
95
+ ruby examples/test_dhanhq_tool_calling.rb
96
+ ruby examples/test_tool_calling.rb
97
+
98
+ # DhanHQ specific tests
99
+ ruby examples/dhanhq/test_tool_calling.rb
100
+ ```
101
+
102
+ ## Notes
103
+
104
+ - The `interval` parameter is now correctly defined for intraday minute intervals
105
+ - The old values ("daily", "weekly", "monthly") were incorrect for the DhanHQ API
106
+ - The new `calculate_indicators` parameter provides technical analysis without large response sizes
107
+ - Tests now include date handling examples using `Date.today`
@@ -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",