ai-agents 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3d23007ce65c6dae4c20025ad5cacb683652452a973472fa3c9fba1166606e57
4
+ data.tar.gz: 39b4522bd4ee7e329285a3806a4e06e5e312526e9139d4369bb7a12f4ede2d06
5
+ SHA512:
6
+ metadata.gz: bc45c4328b0ec188abb77249ee3da9de9515251f2c872b8468c1704d49ea3765cb1dae285cad84c6fd1b644d6adde35073ddf09a6071c82b8ae7671ebbc67d52
7
+ data.tar.gz: 354a10db6fab8d2885c1bdd6e20e72086a9cd87e1a16b99c1b744f0c35b962b19b07aafcf7c0ca0301f30bccc0a8e3ba00de13807178e7d02ec16466ba002a79
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --color
3
+ --format documentation
data/.rubocop.yml ADDED
@@ -0,0 +1,26 @@
1
+ require:
2
+ - rubocop-rspec
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 3.1
6
+
7
+ Style/StringLiterals:
8
+ EnforcedStyle: double_quotes
9
+
10
+ Style/StringLiteralsInInterpolation:
11
+ EnforcedStyle: double_quotes
12
+
13
+ RSpec/MultipleExpectations:
14
+ Max: 10
15
+
16
+ RSpec/ExampleLength:
17
+ Max: 20
18
+
19
+ RSpec/MultipleMemoizedHelpers:
20
+ Max: 15
21
+
22
+ RSpec/SpecFilePathFormat:
23
+ Enabled: false
24
+
25
+ Metrics/MethodLength:
26
+ Max: 20
data/CLAUDE.md ADDED
@@ -0,0 +1,188 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Repository Overview
6
+
7
+ This is a Ruby AI Agents SDK that provides multi-agent orchestration capabilities, similar to OpenAI's Agents SDK but built for Ruby. The SDK enables the creation of sophisticated AI workflows with specialized agents, tool execution, and conversation handoffs.
8
+
9
+ **IMPORTANT**: This is a generic agent library. When implementing library code, ensure that:
10
+ - No domain-specific logic from examples (airline booking, FAQ, seat management, etc.) leaks into the core library
11
+ - The library remains agnostic to specific use cases
12
+ - All domain-specific implementations belong in the examples directory only
13
+
14
+ ## Development Commands
15
+
16
+ ### Building and Testing
17
+ ```bash
18
+ # Install dependencies
19
+ bundle install
20
+
21
+ # Run tests with RSpec
22
+ rake spec
23
+ # OR
24
+ bundle exec rspec
25
+
26
+ # Run specific spec file
27
+ bundle exec rspec spec/agents/agent_spec.rb
28
+
29
+ # Run tests with coverage report
30
+ bundle exec rspec # SimpleCov will generate coverage/index.html
31
+
32
+ # Lint code (includes RSpec cops)
33
+ rake rubocop
34
+ # OR
35
+ bundle exec rubocop
36
+
37
+ # Auto-fix linting issues
38
+ bundle exec rubocop -a
39
+
40
+ # Run all checks (spec + lint)
41
+ rake
42
+ ```
43
+
44
+ ### Interactive Development
45
+ ```bash
46
+ # Start interactive console
47
+ bin/console
48
+
49
+ # Run the airline booking demo
50
+ ruby examples/booking/interactive.rb
51
+
52
+ # Run automatic booking demo
53
+ ruby examples/booking/automatic.rb
54
+ ```
55
+
56
+ ## Architecture and Code Structure
57
+
58
+ ### Core Components (Generic Library)
59
+
60
+ **lib/agents.rb** - Main module and configuration entry point. Configures both the Agents SDK and underlying RubyLLM library. Contains the `RECOMMENDED_HANDOFF_PROMPT_PREFIX` for multi-agent workflows.
61
+
62
+ **lib/agents/agent.rb** - The core `Agent` class with Ruby-like DSL for defining AI agents. Key features:
63
+ - Class-level configuration: `name`, `instructions`, `provider`, `model`, `uses`, `handoffs`
64
+ - Instance execution via `call` method with conversation history management
65
+ - Tool and handoff tool registration at runtime
66
+ - Context-aware execution with proper conversation history restoration using `chat.add_message`
67
+ - **No domain-specific logic** - remains completely generic
68
+
69
+ **lib/agents/tool.rb** - Base class for tools that agents can use. Inherits from `RubyLLM::Tool` and adds:
70
+ - Ruby-style parameter definitions with automatic JSON schema conversion
71
+ - Context injection through `perform` method (called by `execute`)
72
+ - Enhanced parameter definition supporting Ruby types (String, Integer, etc.)
73
+ - **Domain-agnostic** - specific tool implementations belong in user code
74
+
75
+ **lib/agents/handoff.rb** - Contains the handoff system classes:
76
+ - `HandoffResult` - Represents a handoff decision
77
+ - `AgentResponse` - Wraps agent responses with optional handoff results
78
+ - `HandoffTool` - Generic tool for transferring between agents using context-based signaling
79
+ - **Generic handoff mechanism** - no assumptions about specific agent types
80
+
81
+ **lib/agents/context.rb** - Base context class for sharing state between agents and tools across handoffs. Must be subclassed for domain-specific context. The base class provides only generic state management capabilities.
82
+
83
+ **lib/agents/runner.rb** - Execution engine for orchestrating multi-agent workflows (future implementation).
84
+
85
+ ### Key Design Patterns
86
+
87
+ #### Agent Definition Pattern (Generic)
88
+ ```ruby
89
+ class MyAgent < Agents::Agent
90
+ name "Agent Name"
91
+ instructions "Behavior description" # Can be dynamic via Proc
92
+ provider :openai # Optional, defaults to configured provider
93
+ model "gpt-4o" # Optional, defaults to configured model
94
+
95
+ uses SomeTool # Register tools by class
96
+ handoffs OtherAgent, AnotherAgent # Define possible handoff targets
97
+ end
98
+ ```
99
+
100
+ #### Tool Definition Pattern (Generic)
101
+ ```ruby
102
+ class MyTool < Agents::Tool
103
+ description "What this tool does"
104
+ param :input_param, String, "Parameter description"
105
+ param :optional_param, Integer, "Optional param", required: false
106
+
107
+ def perform(input_param:, optional_param: nil, context:)
108
+ # context is always available for state management
109
+ # Must implement perform, not execute
110
+ # Tool logic should be domain-specific in user implementations
111
+ "Tool result"
112
+ end
113
+ end
114
+ ```
115
+
116
+ #### Context-Based Handoff System
117
+ The handoff system uses context signaling rather than text parsing:
118
+ 1. `HandoffTool` instances are created automatically from `handoffs` declarations
119
+ 2. When called, `HandoffTool.perform` sets `context[:pending_handoff]`
120
+ 3. `Agent.detect_handoff_from_context` checks for pending handoffs after LLM responses
121
+ 4. Interactive systems handle handoffs by switching to the target agent class
122
+
123
+ #### Conversation History Management
124
+ Critical for multi-turn conversations:
125
+ - Agents maintain `@conversation_history` as array of `{user:, assistant:, timestamp:}` hashes
126
+ - `restore_conversation_history(chat)` uses `chat.add_message(role:, content:)` to restore RubyLLM chat state
127
+ - This prevents agents from "forgetting" previous conversation turns
128
+
129
+ ### Configuration Rules
130
+
131
+ #### Class Naming
132
+ - Use flat naming like `class Agents::Tool` instead of nested declarations
133
+ - Follow Ruby naming conventions for agent and tool classes
134
+
135
+ #### Documentation
136
+ - Always write doc strings when writing functions
137
+ - Use YARD format for documentation
138
+ - Always write RDoc for new methods
139
+ - When creating a new file, start the file with a description comment on what the file has and where does it fit in the project
140
+
141
+ #### Model Defaults
142
+ - Default model for OpenAI provider is `gpt-4.1-mini` (configured in lib/agents.rb)
143
+ - Can be overridden in agent classes or runtime configuration
144
+
145
+ ### Examples Structure
146
+
147
+ **examples/booking/** - Complete airline booking demo showcasing multi-agent workflows. This is just one example of how the SDK can be used. The example demonstrates:
148
+ - Multi-agent workflow patterns
149
+ - Context sharing between agents
150
+ - Tool usage patterns
151
+ - Interactive CLI and automatic execution modes
152
+ - Proper handoff handling
153
+
154
+ Note: The airline booking scenario is purely demonstrative. The SDK is not limited to or designed specifically for airline systems.
155
+
156
+ ### Dependencies and Integration
157
+
158
+ **RubyLLM Integration** - Built on top of RubyLLM library for LLM communication:
159
+ - Agents SDK configures RubyLLM automatically via `Agents.configure`
160
+ - Tools inherit from `RubyLLM::Tool` but use enhanced `perform` method
161
+ - Conversation history restored using `chat.add_message`
162
+ - Debug mode available via `ENV["RUBYLLM_DEBUG"] = "true"`
163
+
164
+ **Provider Support** - Currently supports OpenAI through RubyLLM, extensible to other providers
165
+
166
+ ### Important Implementation Details
167
+
168
+ 1. **Conversation History**: Must call `restore_conversation_history(chat)` before each agent execution to maintain conversation state across turns.
169
+
170
+ 2. **Tool Context Flow**: `RubyLLM.execute()` → `Agents::Tool.execute()` → `Tool.perform(context:, **args)` - the context injection happens in the base `Tool.execute` method.
171
+
172
+ 3. **Handoff Detection**: Uses context-based detection (`@context[:pending_handoff]`) rather than parsing LLM responses for tool calls.
173
+
174
+ 4. **Model Configuration**: Default model is `gpt-4.1-mini` but examples use `gpt-4o` for better performance.
175
+
176
+ 5. **Thread Safety**: Agents are designed to be stateless with context passed through execution rather than stored in instance variables.
177
+
178
+ 6. **Library vs Example Code**: The core library (lib/agents/*) must remain completely generic and free of domain-specific logic. All domain-specific implementations (airline booking, FAQ systems, etc.) belong exclusively in the examples directory.
179
+
180
+ ### Testing Guidelines
181
+
182
+ When writing tests, follow the rules:
183
+ 1. Avoid stubbing using allow_any_instance_of`
184
+ 2. Each example block `it ... end` should have less than 20 lines
185
+ 3. Example group should not have more than 10 memoized helpers, not more than 10 except statements
186
+ 4. Never use `receive_message_chain`
187
+ 5. When writing tests, always use verifying doubles and never normal doubles
188
+ ```
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Chatwoot Inc.
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,283 @@
1
+ <div align="center">
2
+ <br>
3
+ <br>
4
+ <p>
5
+ <img src="./.github/ruby-agent.png" width="200px"/>
6
+ <h1>Ruby Agents</h1>
7
+ </p>
8
+ <br>
9
+ <br>
10
+ </div>
11
+
12
+ A Ruby SDK for building multi-agent AI workflows with seamless handoffs, inspired by OpenAI's Agents SDK but built specifically for Ruby developers.
13
+
14
+ ## ✨ Features
15
+
16
+ - **🤖 Multi-Agent Orchestration**: Create specialized AI agents that work together
17
+ - **🔄 Seamless Handoffs**: Transparent agent-to-agent transfers (users never know!)
18
+ - **🛠️ Tool Integration**: Agents can use custom tools and functions
19
+ - **💾 Shared Context**: State management across agent interactions
20
+ - **🚀 Simple API**: One method call handles everything including handoffs
21
+ - **🔌 Provider Agnostic**: Works with OpenAI, Anthropic, and other LLM providers
22
+
23
+ ## 🚀 Quick Start
24
+
25
+ ### Installation
26
+
27
+ Add to your Gemfile:
28
+
29
+ ```ruby
30
+ gem 'ai-agents'
31
+ ```
32
+
33
+ ### Basic Usage
34
+
35
+ ```ruby
36
+ require 'agents'
37
+
38
+ # Configure with your API key
39
+ Agents.configure do |config|
40
+ config.openai_api_key = ENV['OPENAI_API_KEY']
41
+ end
42
+
43
+ # Create a simple agent
44
+ agent = Agents::Agent.new(
45
+ name: "Weather Assistant",
46
+ instructions: "Help users get weather information",
47
+ tools: [WeatherTool.new]
48
+ )
49
+
50
+ # Use the agent with the Runner
51
+ result = Agents::Runner.run(agent, "What's the weather like today?")
52
+ puts result.output
53
+ ```
54
+
55
+ ### Multi-Agent Workflows with Handoffs
56
+
57
+ The real power comes from multi-agent workflows with automatic handoffs:
58
+
59
+ ```ruby
60
+ # Create specialized agents
61
+ triage = Agents::Agent.new(
62
+ name: "Triage Agent",
63
+ instructions: "Route customers to the right specialist"
64
+ )
65
+
66
+ faq = Agents::Agent.new(
67
+ name: "FAQ Agent",
68
+ instructions: "Answer frequently asked questions",
69
+ tools: [FaqLookupTool.new]
70
+ )
71
+
72
+ support = Agents::Agent.new(
73
+ name: "Support Agent",
74
+ instructions: "Handle technical issues",
75
+ tools: [TicketTool.new]
76
+ )
77
+
78
+ # Wire up handoff relationships - clean and simple!
79
+ triage.register_handoffs(faq, support)
80
+ faq.register_handoffs(triage) # Can route back to triage
81
+ support.register_handoffs(triage) # Hub-and-spoke pattern
82
+
83
+ # Run a conversation with automatic handoffs
84
+ result = Agents::Runner.run(triage, "How many seats are on the plane?")
85
+ # User gets direct answer from FAQ agent without knowing about the handoff!
86
+ ```
87
+
88
+ ## 🏗️ Architecture
89
+
90
+ ### Core Components
91
+
92
+ - **Agent**: Individual AI agents with specific roles and capabilities
93
+ - **Runner**: Orchestrates multi-agent conversations with automatic handoffs
94
+ - **Context**: Shared state management across agents
95
+ - **Tools**: Custom functions that agents can use
96
+ - **Handoffs**: Seamless transfers between specialized agents
97
+
98
+ ### Agent Definition
99
+
100
+ Agents can be created in two ways:
101
+
102
+ #### Instance-based (Recommended for dynamic agents)
103
+
104
+ ```ruby
105
+ # Create agents as instances
106
+ agent = Agents::Agent.new(
107
+ name: "Customer Service",
108
+ instructions: "You are a helpful customer service agent.",
109
+ model: "gpt-4o",
110
+ tools: [EmailTool.new, TicketTool.new]
111
+ )
112
+
113
+ # Register handoffs after creation
114
+ agent.register_handoffs(technical_support, billing)
115
+ ```
116
+
117
+ #### Class-based (Coming soon)
118
+
119
+ ```ruby
120
+ class CustomerServiceAgent < Agents::Agent
121
+ name "Customer Service"
122
+ instructions <<~PROMPT
123
+ You are a helpful customer service agent.
124
+ Always be polite and professional.
125
+ PROMPT
126
+
127
+ model "gpt-4o"
128
+ uses EmailTool, TicketTool
129
+ end
130
+ ```
131
+
132
+ ### Custom Tools
133
+
134
+ ```ruby
135
+ class EmailTool < Agents::Tool
136
+ description "Send emails to customers"
137
+ param :to, String, "Email address"
138
+ param :subject, String, "Email subject"
139
+ param :body, String, "Email body"
140
+
141
+ def perform(to:, subject:, body:, context:)
142
+ # Send email logic here
143
+ "Email sent to #{to}"
144
+ end
145
+ end
146
+ ```
147
+
148
+ ### Handoff Patterns
149
+
150
+ #### Hub-and-Spoke Pattern (Recommended)
151
+
152
+ ```ruby
153
+ # Central triage agent routes to specialists
154
+ triage = Agents::Agent.new(name: "Triage")
155
+ billing = Agents::Agent.new(name: "Billing")
156
+ support = Agents::Agent.new(name: "Support")
157
+
158
+ # Triage can route to any specialist
159
+ triage.register_handoffs(billing, support)
160
+
161
+ # Specialists only route back to triage
162
+ billing.register_handoffs(triage)
163
+ support.register_handoffs(triage)
164
+ ```
165
+
166
+ #### Circular Handoffs
167
+
168
+ ```ruby
169
+ # Agents can hand off to each other
170
+ sales = Agents::Agent.new(name: "Sales")
171
+ customer_info = Agents::Agent.new(name: "Customer Info")
172
+
173
+ # Both agents can transfer to each other
174
+ sales.register_handoffs(customer_info)
175
+ customer_info.register_handoffs(sales)
176
+ ```
177
+
178
+ ### Context Management
179
+
180
+ ```ruby
181
+ # Context is automatically managed by the Runner
182
+ context = {}
183
+ result = Agents::Runner.run(agent, "Hello", context: context)
184
+
185
+ # Access conversation history and agent state
186
+ puts context[:conversation_history]
187
+ puts context[:current_agent].name
188
+ ```
189
+
190
+ ## 📋 Examples
191
+
192
+ ### ISP Customer Support
193
+
194
+ See the complete ISP support example in `examples/isp-support/`:
195
+
196
+ ```ruby
197
+ # Run the interactive demo
198
+ ruby examples/isp-support/interactive.rb
199
+ ```
200
+
201
+ This showcases:
202
+ - **Triage Agent**: Routes customers to appropriate specialists
203
+ - **Customer Info Agent**: Handles account info and billing inquiries
204
+ - **Sales Agent**: Manages new connections and upgrades
205
+ - **Support Agent**: Provides technical troubleshooting
206
+ - **Hub-and-Spoke Handoffs**: Clean architecture pattern
207
+
208
+ ### Airline Customer Service
209
+
210
+ See the airline booking example in `examples/booking/`:
211
+
212
+ ```ruby
213
+ # Run the interactive demo
214
+ ruby examples/booking/interactive.rb
215
+ ```
216
+
217
+ This showcases:
218
+ - **Triage Agent**: Routes questions to specialists
219
+ - **FAQ Agent**: Answers questions about policies, seats, baggage
220
+ - **Seat Booking Agent**: Handles seat changes and updates
221
+ - **Seamless Handoffs**: Users never repeat their questions
222
+
223
+ ### Sample Conversation
224
+
225
+ ```
226
+ You: How many seats are on the plane?
227
+
228
+ Agent: The plane has a total of 120 seats, which includes 22 business
229
+ class seats and 98 economy seats. Exit rows are located at rows 4 and
230
+ 16, and rows 5-8 are designated as Economy Plus, offering extra legroom.
231
+ ```
232
+
233
+ Behind the scenes:
234
+ 1. Triage Agent receives question
235
+ 2. Automatically transfers to FAQ Agent
236
+ 3. FAQ Agent processes original question and responds
237
+ 4. User sees seamless experience!
238
+
239
+ ## 🔧 Configuration
240
+
241
+ ```ruby
242
+ Agents.configure do |config|
243
+ # Provider API keys
244
+ config.openai_api_key = ENV['OPENAI_API_KEY']
245
+ config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
246
+ config.gemini_api_key = ENV['GEMINI_API_KEY']
247
+
248
+ # Defaults
249
+ config.default_provider = :openai
250
+ config.default_model = 'gpt-4o'
251
+
252
+ # Performance
253
+ config.request_timeout = 120
254
+ config.max_turns = 10
255
+
256
+ # Debugging
257
+ config.debug = true
258
+ end
259
+ ```
260
+
261
+ ## 🤝 Contributing
262
+
263
+ 1. Fork the repository
264
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
265
+ 3. Run tests (`rake test`)
266
+ 4. Run linter (`rake rubocop`)
267
+ 5. Commit your changes (`git commit -am 'Add amazing feature'`)
268
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
269
+ 7. Open a Pull Request
270
+
271
+ ## 📝 License
272
+
273
+ This project is licensed under the MIT License - see the LICENSE file for details.
274
+
275
+ ## 🙏 Acknowledgments
276
+
277
+ - Inspired by [OpenAI's Agents SDK](https://github.com/openai/agents)
278
+ - Built on top of [RubyLLM](https://rubyllm.com) for LLM integration
279
+ - Thanks to the Ruby community for continuous inspiration
280
+
281
+ ---
282
+
283
+ **Built with ❤️ by the Chatwoot Team**
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,64 @@
1
+ # Examples
2
+
3
+ This directory contains examples demonstrating how to use the Ruby Agents SDK.
4
+
5
+ ## Prerequisites
6
+
7
+ 1. **Set up your API key:**
8
+ ```bash
9
+ export OPENAI_API_KEY=your_api_key_here
10
+ ```
11
+
12
+ 2. **Install dependencies:**
13
+ ```bash
14
+ bundle install
15
+ ```
16
+
17
+ ## Running Examples
18
+
19
+ ### Basic Usage
20
+ ```bash
21
+ ruby examples/basic_usage.rb
22
+ ```
23
+
24
+ This example demonstrates:
25
+ - Configuring the Agents gem
26
+ - Creating a simple tool with parameters
27
+ - Creating an agent that uses the tool
28
+ - Testing both tool and agent functionality
29
+ - Viewing conversation history
30
+
31
+ ### Expected Output
32
+
33
+ The example will show:
34
+ - ✅ Configuration success message
35
+ - 🔧 Direct tool testing with different greeting styles
36
+ - 🤖 Agent conversations using the tool
37
+ - 📝 Conversation history with timestamps
38
+
39
+ ## What You'll Learn
40
+
41
+ - How to configure the gem with `Agents.configure`
42
+ - How to create tools that extend `Agents::Tool`
43
+ - How to create agents that extend `Agents::Agent`
44
+ - How to use tools within agents
45
+ - How tools and agents integrate with RubyLLM
46
+
47
+ ## Troubleshooting
48
+
49
+ ### "No API keys configured" Error
50
+ Make sure you've set the `OPENAI_API_KEY` environment variable:
51
+ ```bash
52
+ export OPENAI_API_KEY=sk-your-key-here
53
+ ```
54
+
55
+ ### Connection Errors
56
+ - Check your internet connection
57
+ - Verify your API key is valid
58
+ - Ensure you have sufficient API credits
59
+
60
+ ### Ruby Version
61
+ Make sure you're using Ruby 3.1 or later:
62
+ ```bash
63
+ ruby --version
64
+ ```