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
data/docs/PERSONAS.md ADDED
@@ -0,0 +1,383 @@
1
+ # Personas: Explicit Personalization for Ollama
2
+
3
+ ## Core Principle
4
+
5
+ **You cannot "install" ChatGPT-style personalization into Ollama globally.** You **inject it explicitly** at the **system / prompt layer**, and you do it **deliberately**, depending on whether you are:
6
+
7
+ - doing **schema-based agent work**, or
8
+ - doing **chat / streaming UI work**.
9
+
10
+ This is by design — and it's actually a *good thing*.
11
+
12
+ ## Mental Model
13
+
14
+ ### ChatGPT Personalization
15
+ - Stored server-side
16
+ - Implicit
17
+ - Always applied
18
+ - You don't control when it's used
19
+
20
+ ### Ollama (local / Docker)
21
+ - **No implicit memory**
22
+ - **No global personality**
23
+ - Everything must be **explicitly provided**
24
+ - You decide *when* it applies
25
+
26
+ So your customization becomes a **tool**, not a background bias. That's architecturally superior.
27
+
28
+ ## Where Personalization Lives
29
+
30
+ There are **exactly three valid places** to apply your personalization:
31
+
32
+ | Context | How | When |
33
+ | ----------------------- | ---------------- | -------------------- |
34
+ | **Planner / generate** | Prompt prefix | Deterministic agents |
35
+ | **Chat / UI assistant** | `system` message | Human-facing chat |
36
+ | **Executor tool loop** | System guard | Controlled reasoning |
37
+
38
+ You do **NOT** bake it into:
39
+ - the model
40
+ - Docker image
41
+ - Ollama server config
42
+
43
+ ## Using Personas
44
+
45
+ ### 1. Planner (Schema-Based Agent Work)
46
+
47
+ Use **compressed agent-safe personas** for deterministic structured outputs:
48
+
49
+ ```ruby
50
+ require "ollama_client"
51
+
52
+ client = Ollama::Client.new
53
+
54
+ planner = Ollama::Agent::Planner.new(
55
+ client,
56
+ system_prompt: Ollama::Personas.get(:architect, variant: :agent)
57
+ )
58
+
59
+ plan = planner.run(
60
+ prompt: "Design a caching layer for a high-traffic API.",
61
+ schema: DECISION_SCHEMA
62
+ )
63
+ ```
64
+
65
+ ✅ This preserves determinism
66
+ ✅ No chatty behavior
67
+ ✅ No markdown drift
68
+
69
+ ### 2. Executor (Tool-Calling Agents)
70
+
71
+ Use **compressed agent-safe personas** for tool-calling agents:
72
+
73
+ ```ruby
74
+ executor = Ollama::Agent::Executor.new(client, tools: tools)
75
+
76
+ answer = executor.run(
77
+ system: Ollama::Personas.get(:trading, variant: :agent),
78
+ user: "Analyze AAPL. Get current price and technical indicators."
79
+ )
80
+ ```
81
+
82
+ ### 3. ChatSession (Human-Facing Chat)
83
+
84
+ Use **minimal chat-safe personas** for human-facing chat interfaces:
85
+
86
+ ```ruby
87
+ config = Ollama::Config.new
88
+ config.allow_chat = true
89
+ config.streaming_enabled = true
90
+
91
+ client = Ollama::Client.new(config: config)
92
+
93
+ observer = Ollama::StreamingObserver.new do |event|
94
+ print event.text if event.type == :token
95
+ end
96
+
97
+ chat = Ollama::ChatSession.new(
98
+ client,
99
+ system: Ollama::Personas.get(:architect, variant: :chat),
100
+ stream: observer
101
+ )
102
+
103
+ chat.say("How should I structure a multi-agent system?")
104
+ ```
105
+
106
+ Chat-safe personas:
107
+ - Allow explanations and examples (chat needs)
108
+ - Allow streaming (presentation needs)
109
+ - Still prevent hallucination (safety)
110
+ - Explicitly disclaim authority (boundaries)
111
+ - Never imply side effects (safety)
112
+
113
+ Now:
114
+ - Streaming works
115
+ - Tone matches your architect persona
116
+ - UI feels consistent
117
+ - Agents are unaffected
118
+
119
+ ## Available Personas
120
+
121
+ ### Architect
122
+ - **Agent variant**: Minimal, focused on correctness and invariants
123
+ - **Chat variant**: Minimal chat-safe, allows explanations while preventing hallucination
124
+ - **Use case**: System design, architecture decisions, planning
125
+
126
+ ### Trading
127
+ - **Agent variant**: Minimal, data-driven analysis
128
+ - **Chat variant**: Minimal chat-safe, allows explanations while preventing hallucination
129
+ - **Use case**: Market analysis, trading decisions, risk management
130
+
131
+ ### Reviewer
132
+ - **Agent variant**: Minimal, focused on maintainability
133
+ - **Chat variant**: Minimal chat-safe, allows explanations while preventing hallucination
134
+ - **Use case**: Code review, refactoring, quality assurance
135
+
136
+ ## Agent-Safe vs Chat-Safe Personas
137
+
138
+ ### Agent-Safe Personas (`:agent` variant)
139
+
140
+ Designed for `/api/generate` with JSON schemas:
141
+
142
+ - **Minimal and directive** - reduces token noise and drift
143
+ - **Non-chatty** - avoids markdown and verbosity
144
+ - **Schema-first** - protects deterministic parsing
145
+ - **No persona fluff** - no tone bleed into output
146
+ - **Preserves determinism** - for planners, routers, decision engines
147
+
148
+ **Use with:**
149
+ - `Planner` for structured outputs
150
+ - `generate()` with schemas
151
+ - Tool routing and decision making
152
+
153
+ **Will NOT fight:**
154
+ - Schema enforcement
155
+ - Retries
156
+ - Validation
157
+ - Tool routing
158
+ - Policy gates
159
+
160
+ ### Chat-Safe Personas (`:chat` variant)
161
+
162
+ Designed for `/api/chat` with ChatSession:
163
+
164
+ - **Allows explanations** - chat needs context
165
+ - **Allows streaming** - presentation needs
166
+ - **Still prevents hallucination** - safety first
167
+ - **Explicitly disclaims authority** - clear boundaries
168
+ - **Never implies side effects** - safety boundaries
169
+
170
+ **Use with:**
171
+ - `ChatSession` for human-facing interfaces
172
+ - Streaming conversations
173
+ - Explanatory interactions
174
+
175
+ **Must NEVER be used for:**
176
+ - Schema-based agent work
177
+ - `/api/generate` calls
178
+ - Deterministic structured outputs
179
+
180
+ ## Critical Separation
181
+
182
+ **Agent personas** (`:agent`):
183
+ - `/api/generate` + schemas = deterministic reasoning
184
+ - Use for planners, routers, decision engines
185
+ - Preserves determinism and schema enforcement
186
+
187
+ **Chat personas** (`:chat`):
188
+ - `/api/chat` + humans = explanatory conversation
189
+ - Use for ChatSession, streaming, UI interactions
190
+ - Allows explanations while maintaining safety
191
+
192
+ **NEVER mix them:**
193
+ - Using chat personas in agents breaks determinism
194
+ - Using agent personas in chat suppresses explanations
195
+ - They serve different purposes with different contracts
196
+
197
+ ## Persona Registry
198
+
199
+ ```ruby
200
+ # List all available personas
201
+ Ollama::Personas.available
202
+ # => [:architect, :trading, :reviewer]
203
+
204
+ # Check if persona exists
205
+ Ollama::Personas.exists?(:architect)
206
+ # => true
207
+
208
+ # Get persona (defaults to :agent variant)
209
+ Ollama::Personas.get(:architect)
210
+ Ollama::Personas.get(:architect, variant: :agent)
211
+ Ollama::Personas.get(:architect, variant: :chat)
212
+ ```
213
+
214
+ ## Dynamic Persona Selection
215
+
216
+ ```ruby
217
+ def select_persona_for_task(task_type)
218
+ case task_type
219
+ when :planning, :architecture
220
+ Ollama::Personas.get(:architect, variant: :agent)
221
+ when :trading, :analysis
222
+ Ollama::Personas.get(:trading, variant: :agent)
223
+ when :review, :refactor
224
+ Ollama::Personas.get(:reviewer, variant: :agent)
225
+ else
226
+ nil
227
+ end
228
+ end
229
+
230
+ planner = Ollama::Agent::Planner.new(
231
+ client,
232
+ system_prompt: select_persona_for_task(:planning)
233
+ )
234
+ ```
235
+
236
+ ## Per-Call Persona Override
237
+
238
+ ```ruby
239
+ planner = Ollama::Agent::Planner.new(
240
+ client,
241
+ system_prompt: Ollama::Personas.get(:architect, variant: :agent)
242
+ )
243
+
244
+ # Override for specific call
245
+ plan = planner.run(
246
+ prompt: "Review this code for maintainability issues.",
247
+ schema: REVIEW_SCHEMA,
248
+ system_prompt: Ollama::Personas.get(:reviewer, variant: :agent)
249
+ )
250
+ ```
251
+
252
+ ## Why NOT Bake Into Docker / Model
253
+
254
+ You might be tempted to:
255
+ - create a custom Modelfile
256
+ - bake instructions into the model
257
+ - hardcode personality in the server
258
+
259
+ **Don't.** Here's why:
260
+
261
+ ❌ All agents inherit it (bad)
262
+ ❌ Hard to change per task
263
+ ❌ Breaks determinism
264
+ ❌ Makes debugging impossible
265
+ ❌ Pollutes structured outputs
266
+
267
+ Your personalization is **contextual**, not universal.
268
+
269
+ ## Multiple Personas (This is Powerful)
270
+
271
+ Once explicit, you can do this:
272
+
273
+ ```ruby
274
+ PERSONAS = {
275
+ architect: Ollama::Personas.get(:architect, variant: :agent),
276
+ trading: Ollama::Personas.get(:trading, variant: :agent),
277
+ reviewer: Ollama::Personas.get(:reviewer, variant: :agent)
278
+ }
279
+
280
+ # Choose per call
281
+ prompt = PERSONAS[:architect] + task_prompt
282
+ ```
283
+
284
+ This is **far more powerful** than ChatGPT's single global personality.
285
+
286
+ ## Philosophy Alignment
287
+
288
+ Your instruction says:
289
+
290
+ > "Treat LLMs as components, not oracles"
291
+
292
+ That **forces** explicit prompting. Implicit personalization would actually violate your own design principles.
293
+
294
+ ## Validation Checklist
295
+
296
+ ### For Agent Personas
297
+
298
+ If the model:
299
+ - ✅ Emits pure JSON matching schema exactly → correct usage
300
+ - ✅ No markdown or explanations → correct usage
301
+ - ✅ Deterministic outputs → correct usage
302
+ - ❌ Emits markdown → prompt is being misused
303
+ - ❌ Adds extra fields → schema too loose or prompt issue
304
+ - ❌ Explains decisions → prompt leaked into chat mode
305
+ - ❌ Hallucinates APIs → tool boundaries not enforced
306
+
307
+ ### For Chat Personas
308
+
309
+ If the model:
310
+ - ✅ Explains reasoning when helpful → correct usage
311
+ - ✅ Uses markdown for readability → correct usage
312
+ - ✅ Disclaims authority explicitly → correct usage
313
+ - ✅ No side effects implied → correct usage
314
+ - ❌ Executes actions → boundaries not clear
315
+ - ❌ Invents data/APIs → hallucination prevention failed
316
+ - ❌ Makes guarantees → safety boundaries not enforced
317
+
318
+ ## What NOT to Do
319
+
320
+ ### ❌ Don't Use Chat Personas for Agent Work
321
+
322
+ ```ruby
323
+ # WRONG - breaks determinism
324
+ planner = Ollama::Agent::Planner.new(
325
+ client,
326
+ system_prompt: Ollama::Personas.get(:architect, variant: :chat) # ❌
327
+ )
328
+
329
+ plan = planner.run(prompt: "...", schema: SCHEMA)
330
+ # This will fight schema enforcement and break determinism
331
+ ```
332
+
333
+ ### ❌ Don't Use Agent Personas for Chat
334
+
335
+ ```ruby
336
+ # WRONG - suppresses explanations
337
+ chat = Ollama::ChatSession.new(
338
+ client,
339
+ system: Ollama::Personas.get(:architect, variant: :agent) # ❌
340
+ )
341
+ # This makes chat feel robotic and suppresses helpful explanations
342
+ ```
343
+
344
+ ### ❌ Don't Mix Personas
345
+
346
+ ```ruby
347
+ # WRONG - creates confusion
348
+ prompt = Ollama::Personas.get(:architect, variant: :agent) +
349
+ Ollama::Personas.get(:architect, variant: :chat)
350
+ # This creates conflicting instructions
351
+ ```
352
+
353
+ ### ✅ Do Keep Them Separate
354
+
355
+ ```ruby
356
+ # CORRECT - explicit separation
357
+ agent_persona = Ollama::Personas.get(:architect, variant: :agent)
358
+ chat_persona = Ollama::Personas.get(:architect, variant: :chat)
359
+
360
+ # Use agent persona for planning
361
+ planner = Ollama::Agent::Planner.new(client, system_prompt: agent_persona)
362
+
363
+ # Use chat persona for UI
364
+ chat = Ollama::ChatSession.new(client, system: chat_persona)
365
+ ```
366
+
367
+ ## Summary
368
+
369
+ **Q:** How do I use this customization with Ollama / Docker / ollama-client?
370
+ **A:**
371
+
372
+ - You **do NOT install it into Ollama**
373
+ - You **inject it as a system prompt**
374
+ - You **use minimal agent-safe version for agents** (`/api/generate` + schemas)
375
+ - You **use minimal chat-safe version for chat UIs** (`/api/chat` + ChatSession)
376
+ - You **never make it implicit**
377
+ - You **never mix agent and chat personas**
378
+
379
+ That's not a limitation. That's **correct system design**.
380
+
381
+ ## Examples
382
+
383
+ See `examples/personas_example.rb` for complete working examples.
@@ -0,0 +1,195 @@
1
+ # Quick Start: Copy-Paste Examples
2
+
3
+ All examples below are **complete and copy-pasteable** - no missing constants or undefined variables.
4
+
5
+ ## Basic Client Setup
6
+
7
+ ```ruby
8
+ require "ollama_client"
9
+
10
+ # Simplest client (uses defaults)
11
+ client = Ollama::Client.new
12
+
13
+ # Or with custom config
14
+ config = Ollama::Config.new
15
+ config.model = ENV["OLLAMA_MODEL"] || "llama3.1:8b"
16
+ config.base_url = ENV["OLLAMA_BASE_URL"] || "http://localhost:11434"
17
+ client = Ollama::Client.new(config: config)
18
+ ```
19
+
20
+ ## Generate with Schema (Structured Output)
21
+
22
+ ```ruby
23
+ require "ollama_client"
24
+
25
+ client = Ollama::Client.new
26
+
27
+ DECISION_SCHEMA = {
28
+ "type" => "object",
29
+ "required" => ["action", "reasoning"],
30
+ "properties" => {
31
+ "action" => {
32
+ "type" => "string",
33
+ "enum" => ["search", "calculate", "finish"]
34
+ },
35
+ "reasoning" => {
36
+ "type" => "string"
37
+ }
38
+ }
39
+ }
40
+
41
+ result = client.generate(
42
+ prompt: "Analyze the situation and decide next action.",
43
+ schema: DECISION_SCHEMA
44
+ )
45
+
46
+ puts result["action"] # => "search"
47
+ puts result["reasoning"] # => "User needs data..."
48
+ ```
49
+
50
+ ## Generate Plain Text
51
+
52
+ ```ruby
53
+ require "ollama_client"
54
+
55
+ client = Ollama::Client.new
56
+
57
+ response = client.generate(
58
+ prompt: "Explain Ruby blocks in one sentence.",
59
+ allow_plain_text: true
60
+ )
61
+
62
+ puts response # => Plain text/markdown String
63
+ ```
64
+
65
+ ## Planner with Persona
66
+
67
+ ```ruby
68
+ require "ollama_client"
69
+
70
+ client = Ollama::Client.new
71
+
72
+ planner = Ollama::Agent::Planner.new(
73
+ client,
74
+ system_prompt: Ollama::Personas.get(:architect, variant: :agent)
75
+ )
76
+
77
+ DECISION_SCHEMA = {
78
+ "type" => "object",
79
+ "required" => ["action", "reasoning"],
80
+ "properties" => {
81
+ "action" => {
82
+ "type" => "string",
83
+ "enum" => ["refactor", "test", "document", "defer"]
84
+ },
85
+ "reasoning" => {
86
+ "type" => "string"
87
+ }
88
+ }
89
+ }
90
+
91
+ plan = planner.run(
92
+ prompt: "Design a caching layer for a high-traffic API.",
93
+ schema: DECISION_SCHEMA
94
+ )
95
+
96
+ puts plan["action"] # => "refactor" (or one of the enum values)
97
+ puts plan["reasoning"] # => Explanation string
98
+ ```
99
+
100
+ ## Executor with Tools
101
+
102
+ ```ruby
103
+ require "ollama_client"
104
+
105
+ client = Ollama::Client.new
106
+
107
+ # Define tools (copy-paste ready)
108
+ tools = {
109
+ "get_price" => ->(symbol:) { { symbol: symbol, price: 24500.50, volume: 1_000_000 } },
110
+ "get_indicators" => ->(symbol:) { { symbol: symbol, rsi: 65.5, macd: 1.2 } }
111
+ }
112
+
113
+ executor = Ollama::Agent::Executor.new(client, tools: tools)
114
+
115
+ answer = executor.run(
116
+ system: Ollama::Personas.get(:trading, variant: :agent),
117
+ user: "Analyze NIFTY. Get current price and technical indicators."
118
+ )
119
+
120
+ puts answer
121
+ ```
122
+
123
+ ## ChatSession (Human-Facing Chat)
124
+
125
+ ```ruby
126
+ require "ollama_client"
127
+
128
+ config = Ollama::Config.new
129
+ config.allow_chat = true
130
+ config.streaming_enabled = true
131
+
132
+ client = Ollama::Client.new(config: config)
133
+
134
+ observer = Ollama::StreamingObserver.new do |event|
135
+ print event.text if event.type == :token
136
+ puts "\n" if event.type == :final
137
+ end
138
+
139
+ chat = Ollama::ChatSession.new(
140
+ client,
141
+ system: Ollama::Personas.get(:architect, variant: :chat),
142
+ stream: observer
143
+ )
144
+
145
+ chat.say("How should I structure a multi-agent system?")
146
+ ```
147
+
148
+ ## Complete Working Example
149
+
150
+ ```ruby
151
+ #!/usr/bin/env ruby
152
+ # frozen_string_literal: true
153
+
154
+ require "ollama_client"
155
+
156
+ # Step 1: Create client
157
+ client = Ollama::Client.new
158
+
159
+ # Step 2: Define schema
160
+ DECISION_SCHEMA = {
161
+ "type" => "object",
162
+ "required" => ["action", "reasoning"],
163
+ "properties" => {
164
+ "action" => {
165
+ "type" => "string",
166
+ "enum" => ["refactor", "test", "document", "defer"]
167
+ },
168
+ "reasoning" => {
169
+ "type" => "string"
170
+ }
171
+ }
172
+ }
173
+
174
+ # Step 3: Create planner with persona
175
+ planner = Ollama::Agent::Planner.new(
176
+ client,
177
+ system_prompt: Ollama::Personas.get(:architect, variant: :agent)
178
+ )
179
+
180
+ # Step 4: Use planner
181
+ begin
182
+ plan = planner.run(
183
+ prompt: "Design a caching layer for a high-traffic API.",
184
+ schema: DECISION_SCHEMA
185
+ )
186
+
187
+ puts "✅ Success!"
188
+ puts "Action: #{plan['action']}"
189
+ puts "Reasoning: #{plan['reasoning']}"
190
+ rescue Ollama::Error => e
191
+ puts "❌ Error: #{e.message}"
192
+ end
193
+ ```
194
+
195
+ All examples above are **complete and ready to copy-paste**!