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
data/examples/README.md
ADDED
|
@@ -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",
|
|
@@ -123,7 +124,8 @@ class MultiStepAgent
|
|
|
123
124
|
if recent_actions.length == 3 && recent_actions.uniq.length == 1
|
|
124
125
|
puts "⚠️ Detected repetitive actions - forcing workflow progression"
|
|
125
126
|
# Force next phase
|
|
126
|
-
|
|
127
|
+
case recent_actions.first
|
|
128
|
+
when "collect"
|
|
127
129
|
puts " → Moving to analysis phase"
|
|
128
130
|
decision["action"]["type"] = "analyze"
|
|
129
131
|
decision["action"]["parameters"] = { "target" => "collected_data" }
|
|
@@ -133,7 +135,7 @@ class MultiStepAgent
|
|
|
133
135
|
action: "analyze",
|
|
134
136
|
result: result
|
|
135
137
|
}
|
|
136
|
-
|
|
138
|
+
when "analyze"
|
|
137
139
|
puts " → Moving to validation phase"
|
|
138
140
|
decision["action"]["type"] = "validate"
|
|
139
141
|
decision["action"]["parameters"] = { "type" => "results" }
|
|
@@ -143,7 +145,7 @@ class MultiStepAgent
|
|
|
143
145
|
action: "validate",
|
|
144
146
|
result: result
|
|
145
147
|
}
|
|
146
|
-
|
|
148
|
+
when "validate"
|
|
147
149
|
puts " → Completing workflow"
|
|
148
150
|
decision["action"]["type"] = "complete"
|
|
149
151
|
result = execute_action(decision)
|
|
@@ -269,9 +271,7 @@ class MultiStepAgent
|
|
|
269
271
|
when "collect"
|
|
270
272
|
data_key = params["data_type"] || params["key"] || "user_data"
|
|
271
273
|
# Prevent collecting the same generic data repeatedly
|
|
272
|
-
|
|
273
|
-
data_key = "user_data"
|
|
274
|
-
end
|
|
274
|
+
data_key = "user_data" if should_collect_user_data?(data_key)
|
|
275
275
|
puts " 📥 Collecting: #{data_key}"
|
|
276
276
|
@state[:data_collected][data_key] = "collected_at_#{Time.now.to_i}"
|
|
277
277
|
{ status: "collected", key: data_key }
|
|
@@ -304,6 +304,12 @@ class MultiStepAgent
|
|
|
304
304
|
end
|
|
305
305
|
end
|
|
306
306
|
|
|
307
|
+
def should_collect_user_data?(data_key)
|
|
308
|
+
@state[:data_collected].key?(data_key) &&
|
|
309
|
+
data_key.match?(/^(missing|unknown|data)$/i) &&
|
|
310
|
+
!@state[:data_collected].key?("user_data")
|
|
311
|
+
end
|
|
312
|
+
|
|
307
313
|
def display_summary
|
|
308
314
|
puts "\n" + "=" * 60
|
|
309
315
|
puts "Workflow Summary"
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../lib/ollama_client"
|
|
5
|
+
require "tty-reader"
|
|
6
|
+
require "tty-screen"
|
|
7
|
+
require "tty-cursor"
|
|
8
|
+
|
|
9
|
+
def build_config
|
|
10
|
+
config = Ollama::Config.new
|
|
11
|
+
config.base_url = ENV["OLLAMA_BASE_URL"] if ENV["OLLAMA_BASE_URL"]
|
|
12
|
+
config.model = ENV["OLLAMA_MODEL"] if ENV["OLLAMA_MODEL"]
|
|
13
|
+
config.temperature = ENV["OLLAMA_TEMPERATURE"].to_f if ENV["OLLAMA_TEMPERATURE"]
|
|
14
|
+
config
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def exit_command?(text)
|
|
18
|
+
%w[/exit /quit exit quit].include?(text.downcase)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def add_system_message(messages)
|
|
22
|
+
system_prompt = ENV.fetch("OLLAMA_SYSTEM", nil)
|
|
23
|
+
return unless system_prompt && !system_prompt.strip.empty?
|
|
24
|
+
|
|
25
|
+
messages << { role: "system", content: system_prompt }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def print_banner(config)
|
|
29
|
+
puts "Ollama chat console"
|
|
30
|
+
puts "Model: #{config.model}"
|
|
31
|
+
puts "Base URL: #{config.base_url}"
|
|
32
|
+
puts "Type /exit to quit."
|
|
33
|
+
puts "Screen: #{TTY::Screen.width}x#{TTY::Screen.height}"
|
|
34
|
+
puts
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
HISTORY_PATH = ".ollama_chat_history"
|
|
38
|
+
MAX_HISTORY = 200
|
|
39
|
+
COLOR_RESET = "\e[0m"
|
|
40
|
+
COLOR_USER = "\e[32m"
|
|
41
|
+
COLOR_LLM = "\e[36m"
|
|
42
|
+
USER_PROMPT = "#{COLOR_USER}you>#{COLOR_RESET} ".freeze
|
|
43
|
+
LLM_PROMPT = "#{COLOR_LLM}llm>#{COLOR_RESET} ".freeze
|
|
44
|
+
|
|
45
|
+
def build_reader
|
|
46
|
+
TTY::Reader.new
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def read_input(reader)
|
|
50
|
+
reader.read_line(USER_PROMPT)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def load_history(reader, path)
|
|
54
|
+
history = load_history_list(path)
|
|
55
|
+
history.reverse_each { |line| reader.add_to_history(line) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def load_history_list(path)
|
|
59
|
+
return [] unless File.exist?(path)
|
|
60
|
+
|
|
61
|
+
unique_history(normalize_history(File.readlines(path, chomp: true)))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def normalize_history(lines)
|
|
65
|
+
lines.map(&:strip).reject(&:empty?)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def unique_history(lines)
|
|
69
|
+
seen = {}
|
|
70
|
+
lines.each_with_object([]) do |line, unique|
|
|
71
|
+
next if seen[line]
|
|
72
|
+
|
|
73
|
+
unique << line
|
|
74
|
+
seen[line] = true
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def update_history(path, text)
|
|
79
|
+
history = load_history_list(path)
|
|
80
|
+
history.delete(text)
|
|
81
|
+
history.unshift(text)
|
|
82
|
+
history = history.first(MAX_HISTORY)
|
|
83
|
+
|
|
84
|
+
File.write(path, history.join("\n") + (history.empty? ? "" : "\n"))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def chat_response(client, messages, config)
|
|
88
|
+
content = +""
|
|
89
|
+
prompt_printed = false
|
|
90
|
+
|
|
91
|
+
print "#{COLOR_LLM}...#{COLOR_RESET}"
|
|
92
|
+
$stdout.flush
|
|
93
|
+
|
|
94
|
+
client.chat_raw(
|
|
95
|
+
messages: messages,
|
|
96
|
+
allow_chat: true,
|
|
97
|
+
options: { temperature: config.temperature },
|
|
98
|
+
stream: true
|
|
99
|
+
) do |chunk|
|
|
100
|
+
token = chunk.dig("message", "content").to_s
|
|
101
|
+
next if token.empty?
|
|
102
|
+
|
|
103
|
+
unless prompt_printed
|
|
104
|
+
print "\r#{LLM_PROMPT}"
|
|
105
|
+
prompt_printed = true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
content << token
|
|
109
|
+
print token
|
|
110
|
+
$stdout.flush
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
puts
|
|
114
|
+
content
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def run_console(client, config)
|
|
118
|
+
messages = []
|
|
119
|
+
add_system_message(messages)
|
|
120
|
+
print_banner(config)
|
|
121
|
+
reader = build_reader
|
|
122
|
+
load_history(reader, HISTORY_PATH)
|
|
123
|
+
|
|
124
|
+
loop do
|
|
125
|
+
input = read_input(reader)
|
|
126
|
+
break unless input
|
|
127
|
+
|
|
128
|
+
text = input.strip
|
|
129
|
+
next if text.empty?
|
|
130
|
+
break if exit_command?(text)
|
|
131
|
+
|
|
132
|
+
update_history(HISTORY_PATH, text)
|
|
133
|
+
messages << { role: "user", content: text }
|
|
134
|
+
content = chat_response(client, messages, config)
|
|
135
|
+
messages << { role: "assistant", content: content }
|
|
136
|
+
end
|
|
137
|
+
rescue Interrupt
|
|
138
|
+
puts "\nExiting..."
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
config = build_config
|
|
142
|
+
client = Ollama::Client.new(config: config)
|
|
143
|
+
run_console(client, config)
|
|
@@ -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:
|
|
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:
|
|
171
|
+
prompt: prompt,
|
|
162
172
|
schema: @analysis_schema
|
|
163
173
|
)
|
|
164
174
|
|