jrubyagents 0.2.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f365fc9c08f376ff08dcf933c1968829f15a79e518758ba79f8e82e2b8562452
4
+ data.tar.gz: 4e9e8eb4add2dbec0ceb7c2e175133ed8d1ec12fa2bcb6719c124fbf032b3301
5
+ SHA512:
6
+ metadata.gz: fbf21991c23fd1418939c1152f67995a1502db87fa08cc29f739c9cb58440a437a3a41d9ffacff21ae098ddfc3ad49d22ae418f72bb50b9a7de5b2a8e047948a
7
+ data.tar.gz: 6afa4e9696415fad82e3969216060b1ef396aaa846ce3fd9e4bafe7c13cef0e6834b8ba18ecaecdb3b54b074c64303816d7abc254d6909ac071c0584e51c1b74
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.8
data/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ Tools, MCP filtering, observability, and code agent improvements.
6
+
7
+ - **FileRead tool** -- Read file contents with path expansion and 50k char truncation
8
+ - **FileWrite tool** -- Write files with automatic parent directory creation
9
+ - **ListGems tool** -- Lists available Ruby gems; auto-included in CodeAgent
10
+ - **`tool_from_mcp`** -- Load a single tool from an MCP server by name
11
+ - **Run export** -- `to_h` / `to_json` on Memory, RunResult, and all step types for serialization
12
+ - **Richer callbacks** -- `Callback` base class with `on_run_start`, `on_step_start`, `on_step_end`, `on_tool_call`, `on_error`, `on_run_end`; backward-compatible with existing `step_callbacks`
13
+ - **`memory.replay`** -- Pretty-print a completed run with syntax-highlighted code and metrics
14
+ - **Code agent prompt** -- Tells the model about available Ruby stdlib and gems so it uses `net/http`, `json`, etc. without being asked
15
+
16
+ ## 0.1.0
17
+
18
+ Initial release.
19
+
20
+ - **CodeAgent** -- LLM writes and executes Ruby code in a sandboxed fork
21
+ - **ToolCallingAgent** -- LLM calls tools via structured tool_calls (OpenAI-style)
22
+ - **Model adapters** -- OpenAI, Anthropic, and Ollama out of the box
23
+ - **Tool DSL** -- Define tools as classes or inline blocks
24
+ - **MCP client** -- Load tools from any MCP server via stdio transport
25
+ - **Structured output** -- Validate final answers against JSON Schema or custom procs
26
+ - **Prompt customization** -- Override system prompts, planning prompts, or inject instructions
27
+ - **Final answer checks** -- Validation procs that can reject and retry answers
28
+ - **Step-by-step execution** -- `agent.step()` for debugging and custom UIs
29
+ - **Planning** -- Optional periodic re-planning during long runs
30
+ - **Managed agents** -- Nest agents as tools for multi-agent workflows
31
+ - **CLI** -- `rubyagents` command with interactive mode, tool loading, and MCP support
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Chris Hasiński
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,255 @@
1
+ # Rubyagents
2
+
3
+ A radically simple, code-first AI agent framework for Ruby. Inspired by [smolagents](https://github.com/huggingface/smolagents).
4
+
5
+ LLMs write and execute Ruby code -- not JSON blobs. This means tool calls are just method calls, variables persist between steps, and the full power of Ruby is available to the agent at every turn.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ gem install rubyagents
11
+ ```
12
+
13
+ Or add to your Gemfile:
14
+
15
+ ```ruby
16
+ gem "rubyagents"
17
+ ```
18
+
19
+ Requires Ruby 3.2+. JRuby 10.0.3.0+ is also supported — the sandbox automatically switches from fork-based to thread-based execution, and platform-specific gems (lipgloss) are skipped gracefully.
20
+
21
+ ## Quick start
22
+
23
+ ```ruby
24
+ require "rubyagents"
25
+
26
+ agent = Rubyagents::CodeAgent.new(model: "anthropic/claude-sonnet-4-20250514")
27
+ agent.run("What is the 118th Fibonacci number?")
28
+ ```
29
+
30
+ The agent will think, write Ruby code, execute it in a sandbox, and return the answer.
31
+
32
+ ## Model support
33
+
34
+ Pass a model string as `provider/model_name`:
35
+
36
+ ```ruby
37
+ # Anthropic
38
+ Rubyagents::CodeAgent.new(model: "anthropic/claude-sonnet-4-20250514")
39
+
40
+ # OpenAI
41
+ Rubyagents::CodeAgent.new(model: "openai/gpt-4o")
42
+
43
+ # Ollama (local)
44
+ Rubyagents::CodeAgent.new(model: "ollama/qwen2.5:3b")
45
+ ```
46
+
47
+ Set API keys via environment variables: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`.
48
+
49
+ ## Agent types
50
+
51
+ **CodeAgent** -- the LLM writes Ruby code that gets executed in a sandboxed environment. Tools are available as methods. Variables persist between steps. On MRI, code runs in a forked child process for full isolation; on JRuby, a thread-based executor is used automatically since fork is unavailable — no configuration needed.
52
+
53
+ ```ruby
54
+ agent = Rubyagents::CodeAgent.new(model: "anthropic/claude-sonnet-4-20250514")
55
+ ```
56
+
57
+ **ToolCallingAgent** -- the LLM uses structured tool calls (OpenAI function calling style). Better for models with strong tool_call support.
58
+
59
+ ```ruby
60
+ agent = Rubyagents::ToolCallingAgent.new(model: "openai/gpt-4o")
61
+ ```
62
+
63
+ ## Custom tools
64
+
65
+ Define tools as classes:
66
+
67
+ ```ruby
68
+ class StockPrice < Rubyagents::Tool
69
+ tool_name "stock_price"
70
+ description "Gets the current stock price for a ticker symbol"
71
+ input :ticker, type: :string, description: "Stock ticker symbol (e.g. AAPL)"
72
+ output_type :number
73
+
74
+ def call(ticker:)
75
+ # Your implementation here
76
+ 182.52
77
+ end
78
+ end
79
+
80
+ agent = Rubyagents::CodeAgent.new(
81
+ model: "anthropic/claude-sonnet-4-20250514",
82
+ tools: [StockPrice]
83
+ )
84
+ ```
85
+
86
+ Or define them inline:
87
+
88
+ ```ruby
89
+ weather = Rubyagents.tool(:weather, "Gets weather for a city", city: "City name") do |city:|
90
+ "72F and sunny in #{city}"
91
+ end
92
+
93
+ agent = Rubyagents::CodeAgent.new(model: "anthropic/claude-sonnet-4-20250514", tools: [weather])
94
+ ```
95
+
96
+ ## MCP tools
97
+
98
+ Load tools from any [MCP](https://modelcontextprotocol.io/) server:
99
+
100
+ ```ruby
101
+ tools = Rubyagents.tools_from_mcp(command: ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp"])
102
+
103
+ agent = Rubyagents::CodeAgent.new(
104
+ model: "anthropic/claude-sonnet-4-20250514",
105
+ tools: tools
106
+ )
107
+ ```
108
+
109
+ ## Structured output
110
+
111
+ Validate final answers against a JSON Schema or custom proc:
112
+
113
+ ```ruby
114
+ schema = {
115
+ "type" => "object",
116
+ "required" => ["name", "age"],
117
+ "properties" => {
118
+ "name" => { "type" => "string" },
119
+ "age" => { "type" => "integer" }
120
+ }
121
+ }
122
+
123
+ agent = Rubyagents::CodeAgent.new(
124
+ model: "anthropic/claude-sonnet-4-20250514",
125
+ output_type: schema
126
+ )
127
+ ```
128
+
129
+ If the output doesn't match, the agent retries automatically.
130
+
131
+ ## Final answer checks
132
+
133
+ Add validation procs that can reject answers and force retries:
134
+
135
+ ```ruby
136
+ agent = Rubyagents::CodeAgent.new(
137
+ model: "anthropic/claude-sonnet-4-20250514",
138
+ final_answer_checks: [
139
+ ->(answer, memory) { answer.length > 10 },
140
+ ->(answer, memory) { !answer.include?("I don't know") }
141
+ ]
142
+ )
143
+ ```
144
+
145
+ ## Prompt customization
146
+
147
+ Inject additional instructions without overriding the full system prompt:
148
+
149
+ ```ruby
150
+ agent = Rubyagents::CodeAgent.new(
151
+ model: "anthropic/claude-sonnet-4-20250514",
152
+ instructions: "Always respond in French. Use metric units."
153
+ )
154
+ ```
155
+
156
+ Or fully replace prompts:
157
+
158
+ ```ruby
159
+ templates = Rubyagents::PromptTemplates.new(
160
+ system_prompt: "You are a data analyst. Tools: {{tool_descriptions}}"
161
+ )
162
+
163
+ agent = Rubyagents::CodeAgent.new(
164
+ model: "anthropic/claude-sonnet-4-20250514",
165
+ prompt_templates: templates
166
+ )
167
+ ```
168
+
169
+ ## Step-by-step execution
170
+
171
+ Run one step at a time for debugging or custom UIs:
172
+
173
+ ```ruby
174
+ agent = Rubyagents::CodeAgent.new(model: "anthropic/claude-sonnet-4-20250514")
175
+
176
+ agent.step("What is 2+2?")
177
+ agent.step until agent.done?
178
+
179
+ puts agent.final_answer_value
180
+ ```
181
+
182
+ ## Multi-agent workflows
183
+
184
+ Nest agents as tools:
185
+
186
+ ```ruby
187
+ researcher = Rubyagents::ToolCallingAgent.new(
188
+ model: "openai/gpt-4o",
189
+ name: "researcher",
190
+ description: "Researches topics on the web",
191
+ tools: [Rubyagents::WebSearch]
192
+ )
193
+
194
+ manager = Rubyagents::CodeAgent.new(
195
+ model: "anthropic/claude-sonnet-4-20250514",
196
+ agents: [researcher]
197
+ )
198
+
199
+ manager.run("Find out when Ruby 3.4 was released and summarize the key features")
200
+ ```
201
+
202
+ ## Planning
203
+
204
+ Enable periodic re-planning during long runs:
205
+
206
+ ```ruby
207
+ agent = Rubyagents::CodeAgent.new(
208
+ model: "anthropic/claude-sonnet-4-20250514",
209
+ planning_interval: 3, # Re-plan every 3 steps
210
+ max_steps: 15
211
+ )
212
+ ```
213
+
214
+ ## CLI
215
+
216
+ ```bash
217
+ # Simple query
218
+ rubyagents "What is the 10th prime number?"
219
+
220
+ # With options
221
+ rubyagents -m anthropic/claude-sonnet-4-20250514 -t web_search "Who won the latest Super Bowl?"
222
+
223
+ # Tool-calling agent
224
+ rubyagents -a tool_calling -m openai/gpt-4o "What is 6 * 7?"
225
+
226
+ # With MCP tools
227
+ rubyagents --mcp "npx -y @modelcontextprotocol/server-filesystem /tmp" "List files in /tmp"
228
+
229
+ # Interactive mode
230
+ rubyagents -i
231
+ ```
232
+
233
+ ## Configuration
234
+
235
+ | Option | Default | Description |
236
+ |---|---|---|
237
+ | `model:` | -- | Model string (`provider/model_name`) |
238
+ | `tools:` | `[]` | Array of Tool classes or instances |
239
+ | `agents:` | `[]` | Array of Agent instances (become callable tools) |
240
+ | `max_steps:` | `10` | Maximum agent steps before stopping |
241
+ | `planning_interval:` | `nil` | Re-plan every N steps |
242
+ | `instructions:` | `nil` | Extra instructions appended to system prompt |
243
+ | `prompt_templates:` | `nil` | `PromptTemplates` to override system/planning prompts |
244
+ | `output_type:` | `nil` | Hash (JSON Schema) or Proc for output validation |
245
+ | `final_answer_checks:` | `[]` | Array of procs `(answer, memory) -> bool` |
246
+ | `step_callbacks:` | `[]` | Array of procs `(step, agent:) -> void` |
247
+
248
+ ## Credits
249
+
250
+ - [@khasinski](https://github.com/khasinski) — creator and maintainer of rubyagents
251
+ - [@parolkar](https://github.com/parolkar) — JRuby compatibility support
252
+
253
+ ## License
254
+
255
+ MIT
data/ROADMAP.md ADDED
@@ -0,0 +1,56 @@
1
+ # Roadmap
2
+
3
+ Gaps identified by comparing rubyagents with [smolagents](https://github.com/huggingface/smolagents), prioritized for Ruby developers building agents.
4
+
5
+ ## Phase 5 -- Core DX (high impact, low effort)
6
+
7
+ These make the framework usable for real work.
8
+
9
+ - [x] **MCP client for tools** -- Load tools from any MCP server (stdio + HTTP). This is the single biggest ecosystem unlock since MCP servers already exist for databases, APIs, file systems, browsers, etc. Ruby devs shouldn't have to rewrite tools that already exist.
10
+ - [x] **Structured output** -- Let agents return typed results (not just strings). Accept a schema or Data class, validate the final answer against it. Enables agents as reliable building blocks in larger apps.
11
+ - [x] **Prompt customization** -- Expose `PromptTemplates` object (system prompt, planning, managed agent) so users can override prompts without subclassing. Add `instructions:` parameter for injecting custom rules.
12
+ - [x] **`agent.step()` method** -- Single-step execution for debugging and building custom UIs. Returns the step, lets the caller inspect/modify memory before continuing.
13
+ - [x] **`final_answer_checks`** -- List of validation procs run before accepting a final answer. If any returns false, the agent keeps going. Cheap way to add guardrails.
14
+
15
+ ## Phase 6 -- Model & tool ecosystem (high impact, medium effort)
16
+
17
+ Broader model support and tool discovery. The RubyLLM migration (replacing 3 hand-rolled adapters with a single wrapper) completed most of the model items here.
18
+
19
+ - [x] **RubyLLM universal adapter** -- Replaced OpenAI, Anthropic, and Ollama adapters with a single RubyLLM wrapper. Supports 800+ models across OpenAI, Anthropic, Gemini, DeepSeek, OpenRouter, Ollama, and any OpenAI-compatible endpoint. Auto-configures from env vars.
20
+ - [x] **Rate limiting (basic)** -- RubyLLM provides built-in `max_retries` and `retry_interval` for 429s. Per-minute quotas are not yet exposed.
21
+ - [x] **More built-in tools** -- File read/write tools. Google search and Wikipedia search remain TODO.
22
+ - [x] **Tool.from_mcp** -- Load a single tool from an MCP server by name (vs loading all tools from a server).
23
+
24
+ ## Phase 7 -- Observability & debugging (medium impact, medium effort)
25
+
26
+ Understanding what agents actually do.
27
+
28
+ - [ ] **Structured logging** -- JSON-structured logs per step with run_id, step_number, thought, action, observation, timing, tokens. Emit to any Ruby logger.
29
+ - [x] **`memory.replay`** -- Pretty-print a completed run to the terminal (like smolagents' `agent.replay()`).
30
+ - [x] **Run export** -- Serialize a run (memory + steps + metadata) to JSON for later analysis or replay.
31
+ - [x] **Callbacks for observability** -- Richer callback interface: `on_step_start`, `on_step_end`, `on_tool_call`, `on_error`. Current `step_callbacks` only fires after completion.
32
+
33
+ ## Phase 8 -- Sandboxing & security (medium impact, high effort)
34
+
35
+ For production use where agent code can't be trusted.
36
+
37
+ - [ ] **Docker executor** -- Run agent code in a Docker container instead of a fork. Filesystem isolation, network control, resource limits.
38
+ - [ ] **Import/require allowlist** -- Restrict which Ruby gems/stdlib modules agent code can load in the sandbox (like smolagents' `additional_authorized_imports`).
39
+ - [ ] **Operation count limit** -- Cap iterations/operations in the sandbox to prevent infinite loops eating CPU (smolagents caps at 1M operations).
40
+
41
+ ## Phase 9 -- Advanced features (lower priority, nice to have)
42
+
43
+ - [ ] **Agent serialization** -- `agent.save(dir)` / `Agent.load(dir)` for persisting agent configuration (tools, prompts, model, settings).
44
+ - [ ] **Media types in tools** -- Support image/audio inputs and outputs for multimodal agents.
45
+ - [ ] **Async/parallel tool calls** -- ToolCallingAgent processes multiple tool calls concurrently (like smolagents' `max_tool_threads`).
46
+ - [ ] **Web UI** -- Lightweight web interface for interactive agent sessions (alternative to CLI). Could be a simple Rack app or use Hotwire.
47
+ - [ ] **Persistent memory** -- Long-term memory across runs (conversation history, learned facts). Could be file-based or backed by SQLite.
48
+
49
+ ## Not planned
50
+
51
+ These exist in smolagents but don't fit rubyagents' design goals:
52
+
53
+ - **Hub sharing** -- No equivalent to HuggingFace Hub in Ruby. Gems are the distribution mechanism.
54
+ - **LangChain/Gradio interop** -- Python-specific ecosystems.
55
+ - **WASM executor** -- Ruby WASM support is too immature.
56
+ - **MLX/vLLM adapters** -- Python-only inference runtimes. RubyLLM covers local models via Ollama.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/rubyagents"
5
+
6
+ class StockPrice < Rubyagents::Tool
7
+ tool_name "stock_price"
8
+ description "Gets the current stock price for a ticker symbol"
9
+ input :ticker, type: :string, description: "Stock ticker symbol (e.g. AAPL)"
10
+ output_type :number
11
+
12
+ def call(ticker:)
13
+ # Simulated stock prices for demo
14
+ prices = { "AAPL" => 182.52, "GOOGL" => 141.80, "TSLA" => 248.42, "RIVN" => 14.73 }
15
+ prices.fetch(ticker.upcase, "Unknown ticker: #{ticker}")
16
+ end
17
+ end
18
+
19
+ agent = Rubyagents::CodeAgent.new(
20
+ model: "anthropic/claude-sonnet-4-20250514",
21
+ tools: [StockPrice]
22
+ )
23
+
24
+ agent.run("What's the difference in stock price between AAPL and TSLA?")
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/rubyagents"
5
+
6
+ agent = Rubyagents::CodeAgent.new(model: "anthropic/claude-sonnet-4-20250514")
7
+ agent.run("What is the 118th Fibonacci number?")
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/rubyagents"
5
+ require_relative "../lib/rubyagents/tools/web_search"
6
+
7
+ agent = Rubyagents::CodeAgent.new(
8
+ model: "anthropic/claude-sonnet-4-20250514",
9
+ tools: [Rubyagents::WebSearch]
10
+ )
11
+
12
+ agent.run("What year was Ruby created and who created it?")
data/exe/rubyagents ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/rubyagents"
5
+
6
+ Rubyagents::CLI.run