ollama_agent 0.1.0
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 +7 -0
- data/.cursor/.gitignore +1 -0
- data/.cursor/skills/ollama-agent-patterns/SKILL.md +132 -0
- data/.cursor/skills/ollama-agent-patterns/reference.md +428 -0
- data/.env.example +27 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +147 -0
- data/Rakefile +12 -0
- data/exe/ollama_agent +13 -0
- data/lib/ollama_agent/agent.rb +146 -0
- data/lib/ollama_agent/agent_prompt.rb +44 -0
- data/lib/ollama_agent/cli.rb +73 -0
- data/lib/ollama_agent/console.rb +136 -0
- data/lib/ollama_agent/diff_path_validator.rb +141 -0
- data/lib/ollama_agent/ollama_connection.rb +14 -0
- data/lib/ollama_agent/patch_support.rb +78 -0
- data/lib/ollama_agent/repo_list.rb +50 -0
- data/lib/ollama_agent/ruby_index/builder.rb +115 -0
- data/lib/ollama_agent/ruby_index/extractor_visitor.rb +81 -0
- data/lib/ollama_agent/ruby_index/formatter.rb +65 -0
- data/lib/ollama_agent/ruby_index/index.rb +51 -0
- data/lib/ollama_agent/ruby_index/naming.rb +27 -0
- data/lib/ollama_agent/ruby_index.rb +17 -0
- data/lib/ollama_agent/ruby_index_tool_support.rb +52 -0
- data/lib/ollama_agent/ruby_search_modes.rb +9 -0
- data/lib/ollama_agent/sandboxed_tools.rb +216 -0
- data/lib/ollama_agent/think_param.rb +27 -0
- data/lib/ollama_agent/timeout_param.rb +20 -0
- data/lib/ollama_agent/tool_arguments.rb +26 -0
- data/lib/ollama_agent/tool_content_parser.rb +44 -0
- data/lib/ollama_agent/tools_schema.rb +78 -0
- data/lib/ollama_agent/version.rb +5 -0
- data/lib/ollama_agent.rb +11 -0
- data/sig/ollama_agent.rbs +4 -0
- metadata +182 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 73ed8cb32bcabdd008dee785606c96aefc9bf626a3c3a1187b7bab8ef68c9627
|
|
4
|
+
data.tar.gz: 150f32efcba0bd8602cd571654bcfc8f42b99af56873a57d792202bb957b4701
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 18ce5ec435f2b8330e2684dd116bd33b9e399d93857a5469365902a19a3f99b61bf7d227df746b5bbe82d7b77c4f952b3e2ad8ac61b204498e4d07f469978de0
|
|
7
|
+
data.tar.gz: fd59f660457f2de80e949a098283b38eeccb94cb9daa757158d76d3f457a1f0926ef30f5f5e98ba864ef7504470842775c971ed0a373f48ba8e26b6b0ecdc129
|
data/.cursor/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
plans/
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ollama-agent-patterns
|
|
3
|
+
description: >-
|
|
4
|
+
Blueprint for building a CLI Ollama-based coding agent (Ruby gem) with design
|
|
5
|
+
patterns and judicious metaprogramming—Facade, Template Method, Factory/Registry,
|
|
6
|
+
Builder, Adapter, Proxy, Command, Observer, Strategy, State, and tool DSLs. Use
|
|
7
|
+
when working on ollama_agent, ollama-client integration, tools, LLM adapters,
|
|
8
|
+
prompts, streaming, patch application, or when the user asks for agent
|
|
9
|
+
architecture, extensibility, registries, or Ruby metaprogramming for agents.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Skill: Ollama Agent — Design Patterns & Metaprogramming
|
|
13
|
+
|
|
14
|
+
## 1. Overview
|
|
15
|
+
|
|
16
|
+
Blueprint for a **CLI coding agent** (e.g. `ollama_agent`) that uses **ollama-client** to chat with tools: read/search files, apply small unified diffs from natural language. Patterns keep the design **extensible and maintainable** without front-loading complexity.
|
|
17
|
+
|
|
18
|
+
## 2. Core components & patterns
|
|
19
|
+
|
|
20
|
+
| Component | Pattern(s) | Purpose |
|
|
21
|
+
|-----------|------------|---------|
|
|
22
|
+
| **Agent** | Facade, Template Method | Simple API (`run`); skeleton loop with overridable steps. |
|
|
23
|
+
| **Tools** | Factory Method, Command, Registry | Load/execute tools; optional logging/replay/undo. |
|
|
24
|
+
| **LLM client** | Adapter, (optional) Proxy | Swap backends; logging/metrics without forking core. |
|
|
25
|
+
| **Prompts** | Builder | Fluent construction of messages + tools + options. |
|
|
26
|
+
| **Streaming** | Observer | Multiple subscribers for tokens (UI, logs). |
|
|
27
|
+
| **Patch application** | Strategy | Swap `patch(1)` vs Ruby-native apply, etc. |
|
|
28
|
+
| **Conversation** | State | Phases (idle, tools, confirmation) without giant `if/else`. |
|
|
29
|
+
|
|
30
|
+
**Client lifetime:** **Singleton** is optional. Prefer **injectable** `Ollama::Client` (or adapter) for tests and per-run config (`timeout`, `base_url`, `api_key`).
|
|
31
|
+
|
|
32
|
+
## 3. Principles
|
|
33
|
+
|
|
34
|
+
1. **Start simple** — add Factory, State, Strategy when duplication or branching hurts; do not adopt every pattern up front.
|
|
35
|
+
2. **Core loop stays explicit** — readable `run` / tool loop; collaborators (`Adapter`, `PromptBuilder`, `ToolCommand`) hide detail, not `send` spaghetti.
|
|
36
|
+
3. **Metaprogramming at boundaries** — registration / DSLs at tool edges; avoid dynamic dispatch in error paths and hot loops unless measured.
|
|
37
|
+
4. **Workspace rules** — validated tool schemas; loggable/replayable actions; **no hardcoded model names** (runtime config).
|
|
38
|
+
|
|
39
|
+
## 4. Pattern map (where code lives)
|
|
40
|
+
|
|
41
|
+
| Area | Patterns | Role |
|
|
42
|
+
|------|-----------|------|
|
|
43
|
+
| **Agent** | Facade, Template Method | Single entry; fixed loop with overridable hooks. |
|
|
44
|
+
| **Tools** | Factory, Command, Registry | Instantiate tools; optional command objects for audit. |
|
|
45
|
+
| **LLM** | Adapter, Proxy | Backends; cross-cutting logging/rate limits. |
|
|
46
|
+
| **Prompts** | Builder | Messages + tools + options. |
|
|
47
|
+
| **Streaming** | Observer | Fan-out from client `hooks`. |
|
|
48
|
+
| **Patches** | Strategy | Pluggable apply path. |
|
|
49
|
+
| **Conversation** | State | Explicit phases when control flow grows. |
|
|
50
|
+
|
|
51
|
+
### Target layout (illustrative)
|
|
52
|
+
|
|
53
|
+
A fuller **pattern-oriented** tree (with `bin/console`, `commands/`, `strategies/`, `states/`, etc.) and a **“this repo today”** note live in **reference.md** under *Recommended gem structure*. Migrate only when complexity justifies new directories.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
ollama_agent/
|
|
57
|
+
├── exe/ollama_agent
|
|
58
|
+
├── lib/ollama_agent.rb
|
|
59
|
+
└── lib/ollama_agent/
|
|
60
|
+
├── agent.rb
|
|
61
|
+
├── cli.rb
|
|
62
|
+
├── prompt_builder.rb # Builder (optional)
|
|
63
|
+
├── tool_registry.rb
|
|
64
|
+
├── tools/base.rb
|
|
65
|
+
├── tools/
|
|
66
|
+
├── llm/base_adapter.rb
|
|
67
|
+
├── llm/ollama_adapter.rb
|
|
68
|
+
├── llm/logging_proxy.rb
|
|
69
|
+
├── commands/tool_command.rb
|
|
70
|
+
├── observers/
|
|
71
|
+
├── strategies/
|
|
72
|
+
└── states/
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 5. Creational patterns (summary)
|
|
76
|
+
|
|
77
|
+
- **Factory + registry** — Replace a growing `case` with `ToolRegistry.get(name)`; auto-register via `inherited` **or** explicit `tool_name` + hash (clearer than magic naming).
|
|
78
|
+
- **Builder** — `PromptBuilder` when message construction branches; skip until you have real optional composition.
|
|
79
|
+
- **Singleton** — Avoid as default for HTTP clients; use when you truly need one process-wide resource and tests can still stub.
|
|
80
|
+
|
|
81
|
+
## 6. Structural patterns (summary)
|
|
82
|
+
|
|
83
|
+
- **Adapter** — Common `chat(messages:, tools:, **options)` surface for Ollama vs future providers.
|
|
84
|
+
- **Proxy** — Wrap adapter for logging/metrics; keep thin.
|
|
85
|
+
- **Facade** — `Agent#run` — already the right shape.
|
|
86
|
+
|
|
87
|
+
## 7. Behavioral patterns (summary)
|
|
88
|
+
|
|
89
|
+
- **Command** — Wrap tool invocations when you need queues, structured logs, or replay; **undo** only with a real story (VCS/snapshots).
|
|
90
|
+
- **Observer** — Fan-out streaming tokens; compose with ollama-client `hooks`.
|
|
91
|
+
- **Strategy** — `PatchStrategy` if you need non-`patch` apply paths.
|
|
92
|
+
- **State** — When confirmation + tool rounds + idle become tangled.
|
|
93
|
+
- **Template Method** — Base agent class only if you have **multiple** agent variants sharing one loop.
|
|
94
|
+
|
|
95
|
+
## 8. Metaprogramming (judicious)
|
|
96
|
+
|
|
97
|
+
| Technique | Use when | Caution |
|
|
98
|
+
|-----------|----------|--------|
|
|
99
|
+
| **`inherited` + registry** | Many tools, stable naming | Keep in sync with **tool JSON schema**; consider explicit `tool_name`. |
|
|
100
|
+
| **Tool DSL** (`tool :name do …`) | Repetition dominates | Stack traces and IDE nav suffer; keep DSL thin. |
|
|
101
|
+
| **`send` for hooks** | Fixed event names | Prefer explicit methods for public API. |
|
|
102
|
+
| **`method_missing`** | Rare delegation | Not for core tool dispatch — use a Hash/registry. |
|
|
103
|
+
| **Plugin `extend`** | Third-party tools | Document load order and sandbox rules. |
|
|
104
|
+
|
|
105
|
+
## 9. When to avoid heavy metaprogramming
|
|
106
|
+
|
|
107
|
+
- Main agent loop and **error handling** — explicit flow wins.
|
|
108
|
+
- **Performance-sensitive** inner loops — measure before dynamic dispatch.
|
|
109
|
+
- **Public APIs** — stable, documented entry points over hidden DSL magic.
|
|
110
|
+
|
|
111
|
+
## 10. Summary
|
|
112
|
+
|
|
113
|
+
| Pattern | Benefit |
|
|
114
|
+
|---------|---------|
|
|
115
|
+
| Factory Method | Decouples tool creation from call sites. |
|
|
116
|
+
| Builder | Composes prompts/options without positional arg soup. |
|
|
117
|
+
| Singleton | Single shared resource (use sparingly for HTTP clients). |
|
|
118
|
+
| Adapter | Multiple LLM backends behind one shape. |
|
|
119
|
+
| Proxy | Cross-cutting concerns on the client. |
|
|
120
|
+
| Facade | Hides orchestration from CLI users. |
|
|
121
|
+
| Command | Tool calls as objects (log/replay/queue). |
|
|
122
|
+
| Observer | Decoupled streaming consumers. |
|
|
123
|
+
| Strategy | Swappable patch application. |
|
|
124
|
+
| State | Explicit conversation phases. |
|
|
125
|
+
| Template Method | Shared loop, varied steps. |
|
|
126
|
+
| Metaprogramming | Less boilerplate at **boundaries** only. |
|
|
127
|
+
|
|
128
|
+
**Start with straightforward code; refactor into patterns when pain appears.**
|
|
129
|
+
|
|
130
|
+
## 11. Full code examples
|
|
131
|
+
|
|
132
|
+
Runnable snippets (registry, `PromptBuilder`, adapters, proxy, command, observer, strategy, state, template-method skeleton, DSL sketch) live in **[reference.md](reference.md)**. Prefer copying from there and adapting to the real `ollama-client` API and this repo’s `SandboxedTools` / `tools_schema` constraints.
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# Reference: patterns & Ruby snippets
|
|
2
|
+
|
|
3
|
+
Concise examples for `ollama_agent`-style gems. Adapt names to the real `ollama-client` API.
|
|
4
|
+
|
|
5
|
+
## Factory Method + registry (`inherited`)
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# lib/ollama_agent/tools/base.rb
|
|
9
|
+
module OllamaAgent
|
|
10
|
+
module Tools
|
|
11
|
+
class Base
|
|
12
|
+
def initialize(args)
|
|
13
|
+
@args = args
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.inherited(subclass)
|
|
21
|
+
return if subclass.name.nil?
|
|
22
|
+
|
|
23
|
+
name = subclass.name.split("::").last.underscore
|
|
24
|
+
ToolRegistry.register(name, subclass)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# lib/ollama_agent/tools/registry.rb — or tool_registry.rb at module root
|
|
31
|
+
module OllamaAgent
|
|
32
|
+
module Tools
|
|
33
|
+
class ToolRegistry
|
|
34
|
+
@tools = {}
|
|
35
|
+
class << self
|
|
36
|
+
def register(name, klass)
|
|
37
|
+
@tools[name.to_s] = klass
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get(name)
|
|
41
|
+
@tools[name.to_s]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Builder (`PromptBuilder`)
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
class PromptBuilder
|
|
53
|
+
attr_reader :messages, :tools, :options
|
|
54
|
+
|
|
55
|
+
def initialize
|
|
56
|
+
@messages = []
|
|
57
|
+
@tools = []
|
|
58
|
+
@options = {}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def system(content)
|
|
62
|
+
@messages << { role: "system", content: content }
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def user(content)
|
|
67
|
+
@messages << { role: "user", content: content }
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def add_tool(tool_def)
|
|
72
|
+
@tools << tool_def
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def temperature(value)
|
|
77
|
+
@options[:temperature] = value
|
|
78
|
+
self
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def build
|
|
82
|
+
{ messages: @messages, tools: @tools, options: @options }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Singleton (optional client holder)
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
require "singleton"
|
|
91
|
+
|
|
92
|
+
class OllamaClientWrapper
|
|
93
|
+
include Singleton
|
|
94
|
+
|
|
95
|
+
attr_reader :client
|
|
96
|
+
|
|
97
|
+
def initialize
|
|
98
|
+
@client = Ollama::Client.new(model: ENV.fetch("OLLAMA_MODEL"))
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Prefer **dependency injection** of a client/adapter in tests instead of `Singleton` when practical.
|
|
104
|
+
|
|
105
|
+
## Adapter + Proxy
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
# lib/ollama_agent/llm/base_adapter.rb
|
|
109
|
+
module OllamaAgent
|
|
110
|
+
module LLM
|
|
111
|
+
class BaseAdapter
|
|
112
|
+
def chat(messages:, tools:, **options)
|
|
113
|
+
raise NotImplementedError
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# lib/ollama_agent/llm/ollama_adapter.rb
|
|
120
|
+
module OllamaAgent
|
|
121
|
+
module LLM
|
|
122
|
+
class OllamaAdapter < BaseAdapter
|
|
123
|
+
def initialize(client = Ollama::Client.new)
|
|
124
|
+
@client = client
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def chat(messages:, tools:, **options)
|
|
128
|
+
@client.chat(messages: messages, tools: tools, **options)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# lib/ollama_agent/llm/logging_proxy.rb
|
|
135
|
+
module OllamaAgent
|
|
136
|
+
module LLM
|
|
137
|
+
class LoggingProxy
|
|
138
|
+
def initialize(adapter, logger: $stderr)
|
|
139
|
+
@adapter = adapter
|
|
140
|
+
@logger = logger
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def chat(...)
|
|
144
|
+
@logger.puts "LLM chat request"
|
|
145
|
+
@adapter.chat(...)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Command (tool execution)
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
class ToolCommand
|
|
156
|
+
attr_reader :name, :args, :result
|
|
157
|
+
|
|
158
|
+
def initialize(name, args)
|
|
159
|
+
@name = name
|
|
160
|
+
@args = args
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def execute
|
|
164
|
+
klass = Tools::ToolRegistry.get(@name)
|
|
165
|
+
raise KeyError, "Unknown tool: #{@name}" unless klass
|
|
166
|
+
|
|
167
|
+
@result = klass.new(@args).call
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def undo
|
|
171
|
+
# Optional: revert file edits, etc.
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Observer (streaming)
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
class Agent
|
|
180
|
+
def initialize(adapter:)
|
|
181
|
+
@adapter = adapter
|
|
182
|
+
@observers = []
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def add_observer(observer)
|
|
186
|
+
@observers << observer
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def notify_observers(token, logprobs)
|
|
190
|
+
@observers.each { |obs| obs.update(token, logprobs) }
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Wire `on_token` (or equivalent) from `ollama-client` to `notify_observers`.
|
|
196
|
+
|
|
197
|
+
## Strategy (patch application)
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
class PatchStrategy
|
|
201
|
+
def apply(path, diff)
|
|
202
|
+
raise NotImplementedError
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
class SystemPatchStrategy < PatchStrategy
|
|
207
|
+
def apply(path, diff)
|
|
208
|
+
IO.popen(["patch", "-p1", "-f", path.to_s], "w") { |stdin| stdin.write(diff) }
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## State (conversation phases)
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
class AgentState
|
|
217
|
+
def handle(agent, response)
|
|
218
|
+
raise NotImplementedError
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
class IdleState < AgentState
|
|
223
|
+
def handle(agent, response)
|
|
224
|
+
# transition when tool calls appear
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
class ToolExecutionState < AgentState
|
|
229
|
+
def handle(agent, response)
|
|
230
|
+
# run tools, append results, return to idle
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Template Method (agent loop)
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
class BaseAgent
|
|
239
|
+
def run(query)
|
|
240
|
+
messages = build_initial_messages(query)
|
|
241
|
+
loop do
|
|
242
|
+
response = send_to_llm(messages)
|
|
243
|
+
if tool_calls?(response)
|
|
244
|
+
messages << assistant_message(response)
|
|
245
|
+
process_tool_calls(tool_calls(response), messages)
|
|
246
|
+
else
|
|
247
|
+
handle_final_response(response, messages)
|
|
248
|
+
break
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def build_initial_messages(query)
|
|
254
|
+
raise NotImplementedError
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def send_to_llm(messages)
|
|
258
|
+
raise NotImplementedError
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def process_tool_calls(_tool_calls, _messages)
|
|
262
|
+
raise NotImplementedError
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def handle_final_response(_response, _messages)
|
|
266
|
+
raise NotImplementedError
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Metaprogramming: tool DSL (optional)
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
class BaseTool
|
|
275
|
+
def self.tool(name, description, &block)
|
|
276
|
+
define_method(:call, &block)
|
|
277
|
+
Tools::ToolRegistry.register(name.to_s, self)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Prefer explicit subclasses + `inherited` registration if the DSL obscures tests.
|
|
283
|
+
|
|
284
|
+
## Dynamic hooks
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
def trigger_hook(event)
|
|
288
|
+
m = :"on_#{event}"
|
|
289
|
+
send(m) if respond_to?(m, true)
|
|
290
|
+
end
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Putting it together (illustrative)
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
module OllamaAgent
|
|
297
|
+
class Agent
|
|
298
|
+
def initialize(adapter:, tool_registry: Tools::ToolRegistry)
|
|
299
|
+
@adapter = adapter
|
|
300
|
+
@tool_registry = tool_registry
|
|
301
|
+
@state = IdleState.new
|
|
302
|
+
@observers = []
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def run(query)
|
|
306
|
+
built = PromptBuilder.new
|
|
307
|
+
.system(SYSTEM_PROMPT)
|
|
308
|
+
.user(query)
|
|
309
|
+
.temperature(0.2)
|
|
310
|
+
.build
|
|
311
|
+
|
|
312
|
+
messages = built[:messages]
|
|
313
|
+
# Loop: @adapter.chat with messages/tools/options; @state.handle; execute tools via ToolCommand
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def add_observer(observer)
|
|
317
|
+
@observers << observer
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def execute_tool(tool_call)
|
|
321
|
+
klass = @tool_registry.get(tool_call.name)
|
|
322
|
+
raise KeyError, "Unknown tool: #{tool_call.name}" unless klass
|
|
323
|
+
|
|
324
|
+
command = ToolCommand.new(tool_call.name, tool_call.arguments)
|
|
325
|
+
command.execute
|
|
326
|
+
command.result
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Main entry `require` pattern
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
# lib/ollama_agent.rb
|
|
336
|
+
require_relative "ollama_agent/version"
|
|
337
|
+
require_relative "ollama_agent/tool_registry"
|
|
338
|
+
require_relative "ollama_agent/tools/base"
|
|
339
|
+
Dir[File.join(__dir__, "ollama_agent", "tools", "*.rb")].sort.each { |f| require f }
|
|
340
|
+
# require LLM, commands, agent, cli
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Skip globs if load order matters; require explicit files instead.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Recommended gem structure (pattern-oriented layout)
|
|
348
|
+
|
|
349
|
+
Use this when growing toward explicit registries, adapters, and strategies. Omit directories you do not need yet (e.g. `states/`, `strategies/`).
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
ollama_agent/
|
|
353
|
+
├── bin/
|
|
354
|
+
│ └── console
|
|
355
|
+
├── exe/
|
|
356
|
+
│ └── ollama_agent
|
|
357
|
+
├── lib/
|
|
358
|
+
│ ├── ollama_agent.rb
|
|
359
|
+
│ ├── ollama_agent/
|
|
360
|
+
│ │ ├── version.rb
|
|
361
|
+
│ │ ├── cli.rb
|
|
362
|
+
│ │ ├── agent.rb # Facade + template-method loop
|
|
363
|
+
│ │ ├── prompt_builder.rb
|
|
364
|
+
│ │ ├── tool_registry.rb # name → class (not a process Singleton)
|
|
365
|
+
│ │ ├── tools/
|
|
366
|
+
│ │ │ ├── base.rb # inherited hook / optional DSL
|
|
367
|
+
│ │ │ ├── read_file.rb
|
|
368
|
+
│ │ │ ├── search_code.rb
|
|
369
|
+
│ │ │ └── edit_file.rb
|
|
370
|
+
│ │ ├── llm/
|
|
371
|
+
│ │ │ ├── base_adapter.rb
|
|
372
|
+
│ │ │ ├── ollama_adapter.rb
|
|
373
|
+
│ │ │ └── logging_proxy.rb
|
|
374
|
+
│ │ ├── commands/
|
|
375
|
+
│ │ │ └── tool_command.rb
|
|
376
|
+
│ │ ├── observers/
|
|
377
|
+
│ │ │ ├── base_observer.rb
|
|
378
|
+
│ │ │ └── token_observer.rb
|
|
379
|
+
│ │ ├── strategies/
|
|
380
|
+
│ │ │ ├── patch_strategy.rb
|
|
381
|
+
│ │ │ └── system_patch_strategy.rb
|
|
382
|
+
│ │ └── states/
|
|
383
|
+
│ │ ├── agent_state.rb
|
|
384
|
+
│ │ ├── idle_state.rb
|
|
385
|
+
│ │ └── tool_execution_state.rb
|
|
386
|
+
├── spec/
|
|
387
|
+
└── ollama_agent.gemspec
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Where each pattern lives
|
|
391
|
+
|
|
392
|
+
| Pattern / concept | Location |
|
|
393
|
+
|-------------------|----------|
|
|
394
|
+
| Facade | `agent.rb` — `run` |
|
|
395
|
+
| Template method | `agent.rb` — loop skeleton; subclasses override hooks if needed |
|
|
396
|
+
| Builder | `prompt_builder.rb` |
|
|
397
|
+
| Registry | `tool_registry.rb` — map of tool names to classes |
|
|
398
|
+
| Factory / auto-registration | `tools/base.rb` — `inherited` (or explicit registration) |
|
|
399
|
+
| Adapter | `llm/base_adapter.rb`, `llm/ollama_adapter.rb` |
|
|
400
|
+
| Proxy | `llm/logging_proxy.rb` |
|
|
401
|
+
| Command | `commands/tool_command.rb` |
|
|
402
|
+
| Observer | `observers/` |
|
|
403
|
+
| Strategy | `strategies/` — patch application |
|
|
404
|
+
| State | `states/` — conversation phases |
|
|
405
|
+
| Metaprogramming / DSL | `tools/base.rb` — optional `tool` class method |
|
|
406
|
+
|
|
407
|
+
### `lib/ollama_agent.rb` require order (example)
|
|
408
|
+
|
|
409
|
+
```ruby
|
|
410
|
+
require_relative "ollama_agent/version"
|
|
411
|
+
require_relative "ollama_agent/cli"
|
|
412
|
+
require_relative "ollama_agent/agent"
|
|
413
|
+
require_relative "ollama_agent/prompt_builder"
|
|
414
|
+
require_relative "ollama_agent/tool_registry"
|
|
415
|
+
require_relative "ollama_agent/tools/base"
|
|
416
|
+
Dir[File.join(__dir__, "ollama_agent", "tools", "*.rb")].sort.each { |f| require f }
|
|
417
|
+
# then llm/, commands/, strategies/, etc.
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Prefer explicit `require_relative` per file when load order matters (subclasses after `Base`).
|
|
421
|
+
|
|
422
|
+
### Benefits
|
|
423
|
+
|
|
424
|
+
- Separation of concerns; isolated unit tests; clear extension points for tools, strategies, observers.
|
|
425
|
+
|
|
426
|
+
### This repository today (flatter)
|
|
427
|
+
|
|
428
|
+
The live `ollama_agent` gem currently uses a **smaller** layout: e.g. `sandboxed_tools.rb`, `tools_schema.rb`, `patch_support.rb`, `diff_path_validator.rb`, `agent_prompt.rb`, `ollama_connection.rb` — same responsibilities, fewer directories. Migrating to the tree above is optional and should follow real need (second LLM backend, multiple patch strategies, etc.).
|
data/.env.example
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Example environment variables for OllamaAgent gem
|
|
2
|
+
# Disable colored output (set to any value to respect https://no-color.org/)
|
|
3
|
+
NO_COLOR=
|
|
4
|
+
# Enable colored output (default: enabled). Set to "0" to disable.
|
|
5
|
+
OLLAMA_AGENT_COLOR=1
|
|
6
|
+
# Enable markdown rendering in terminal (default: enabled). Set to "0" to disable.
|
|
7
|
+
OLLAMA_AGENT_MARKDOWN=1
|
|
8
|
+
# Render thinking output with markdown (default: disabled). Set to "1" to enable.
|
|
9
|
+
OLLAMA_AGENT_THINKING_MARKDOWN=0
|
|
10
|
+
# Base URL for Ollama server
|
|
11
|
+
OLLAMA_BASE_URL=https://ollama.com
|
|
12
|
+
# API key for Ollama (if required)
|
|
13
|
+
OLLAMA_API_KEY=your_api_key_here
|
|
14
|
+
# Think parameter: true/false/high/medium/low (default: true)
|
|
15
|
+
OLLAMA_AGENT_THINK=1
|
|
16
|
+
# Root directory for the agent (default: current working directory)
|
|
17
|
+
OLLAMA_AGENT_ROOT=.
|
|
18
|
+
# Debug mode (set to "1" for verbose output)
|
|
19
|
+
OLLAMA_AGENT_DEBUG=0
|
|
20
|
+
# Maximum tool rounds (default: 64)
|
|
21
|
+
OLLAMA_AGENT_MAX_TURNS=64
|
|
22
|
+
# Model to use (default: from Ollama config)
|
|
23
|
+
OLLAMA_AGENT_MODEL=gpt-oss:120b-cloud
|
|
24
|
+
# HTTP timeout in seconds for Ollama requests
|
|
25
|
+
OLLAMA_AGENT_TIMEOUT=120
|
|
26
|
+
# Parse tool JSON from assistant output (set to "1" to enable)
|
|
27
|
+
OLLAMA_AGENT_PARSE_TOOL_JSON=0
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"ollama_agent" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["shubhamtaywade82@gmail.com"](mailto:"shubhamtaywade82@gmail.com").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shubham Taywade
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|