Skip to content

Commit e36fdbe

Browse files
committed
Add RSpec configuration, tests, and update client functionality
1 parent c822d11 commit e36fdbe

22 files changed

+954
-43
lines changed

.github/workflows/gem-push.yml

+9-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ on:
77
branches: [ "main" ]
88

99
jobs:
10+
test:
11+
uses: ./.github/workflows/test.yml
12+
1013
build:
14+
needs: test # This ensures tests must pass before building/publishing
1115
name: Build + Publish
1216
runs-on: ubuntu-latest
1317
permissions:
@@ -16,10 +20,12 @@ jobs:
1620

1721
steps:
1822
- uses: actions/checkout@v4
19-
- name: Set up Ruby 3.3
23+
24+
- name: Set up Ruby
2025
uses: ruby/setup-ruby@v1
2126
with:
22-
ruby-version: 3.3.6
27+
ruby-version: '3.3'
28+
bundler-cache: true
2329

2430
- name: Publish to GPR
2531
run: |
@@ -42,4 +48,4 @@ jobs:
4248
gem build *.gemspec
4349
gem push *.gem
4450
env:
45-
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
51+
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"

.github/workflows/test.yml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Test
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
ruby-version: ['3.1', '3.2', '3.3']
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Set up Ruby
20+
uses: ruby/setup-ruby@v1
21+
with:
22+
ruby-version: ${{ matrix.ruby-version }}
23+
bundler-cache: true
24+
25+
- name: Install dependencies
26+
run: bundle install
27+
28+
- name: Check code format
29+
run: bundle exec rubocop
30+
31+
- name: Run tests
32+
run: bundle exec rspec

.overcommit.yml

+8-18
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,3 @@
1-
# Use this file to configure the Overcommit hooks you wish to use. This will
2-
# extend the default configuration defined in:
3-
# https://github.com/sds/overcommit/blob/master/config/default.yml
4-
#
5-
# At the topmost level of this YAML file is a key representing type of hook
6-
# being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
7-
# customize each hook, such as whether to only run it on certain files (via
8-
# `include`), whether to only display output if it fails (via `quiet`), etc.
9-
#
10-
# For a complete list of hooks, see:
11-
# https://github.com/sds/overcommit/tree/master/lib/overcommit/hook
12-
#
13-
# For a complete list of options that you can use to customize hooks, see:
14-
# https://github.com/sds/overcommit#configuration
15-
#
16-
# Uncomment the following lines to make the configuration take effect.
17-
181
PreCommit:
192
RuboCop:
203
enabled: true
@@ -24,6 +7,13 @@ PreCommit:
247
Flay:
258
enabled: true
269

10+
RSpec:
11+
enabled: true
12+
command: ['bundle', 'exec', 'rspec']
13+
on_warn: fail
14+
include:
15+
- 'spec/**/*_spec.rb'
16+
2717
TrailingWhitespace:
2818
enabled: true
2919
auto_correct: true
@@ -35,4 +25,4 @@ PostCheckout:
3525
quiet: true # Change all post-checkout hooks to only display output on failure
3626

3727
IndexTags:
38-
enabled: true # Generate a tags file with `ctags` each time HEAD changes
28+
enabled: true # Generate a tags file with `ctags` each time HEAD changes

.rspec

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--require spec_helper
2+
--format documentation
3+
--color

README.md

+42-12
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ RubyLLM provides a unified interface for interacting with various LLM providers
1010
## Features
1111

1212
- 🤝 Unified interface for multiple LLM providers (OpenAI, Anthropic, etc.)
13-
- 🛠️ Tool/Function calling support
13+
- 🛠️ Simple and flexible tool/function calling
1414
- 📊 Automatic token counting and tracking
1515
- 🔄 Streaming support
1616
- 🚂 Seamless Rails integration
@@ -60,22 +60,15 @@ response = client.chat([
6060
puts response.content
6161
```
6262

63-
### Streaming
63+
### Tools (Function Calling)
6464

65-
```ruby
66-
client.chat([
67-
{ role: :user, content: "Count to 10 slowly" }
68-
], stream: true) do |chunk|
69-
print chunk.content
70-
end
71-
```
72-
73-
### Tool Usage
65+
RubyLLM supports tools/functions with a simple, flexible interface. You can create tools using blocks or wrap existing methods:
7466

7567
```ruby
68+
# Using a block
7669
calculator = RubyLLM::Tool.new(
7770
name: "calculator",
78-
description: "Perform mathematical calculations",
71+
description: "Performs mathematical calculations",
7972
parameters: {
8073
expression: {
8174
type: "string",
@@ -87,9 +80,46 @@ calculator = RubyLLM::Tool.new(
8780
eval(args[:expression]).to_s
8881
end
8982

83+
# Using an existing method
84+
class MathUtils
85+
def arithmetic(x, y, operation)
86+
case operation
87+
when 'add' then x + y
88+
when 'subtract' then x - y
89+
when 'multiply' then x * y
90+
when 'divide' then x.to_f / y
91+
else
92+
raise ArgumentError, "Unknown operation: #{operation}"
93+
end
94+
end
95+
end
96+
97+
math_tool = RubyLLM::Tool.from_method(
98+
MathUtils.instance_method(:arithmetic),
99+
description: "Performs basic arithmetic operations",
100+
parameter_descriptions: {
101+
x: "First number in the operation",
102+
y: "Second number in the operation",
103+
operation: "Operation to perform (add, subtract, multiply, divide)"
104+
}
105+
)
106+
107+
# Use tools in conversations
90108
response = client.chat([
91109
{ role: :user, content: "What is 123 * 456?" }
92110
], tools: [calculator])
111+
112+
puts response.content
113+
```
114+
115+
### Streaming
116+
117+
```ruby
118+
client.chat([
119+
{ role: :user, content: "Count to 10 slowly" }
120+
], stream: true) do |chunk|
121+
print chunk.content
122+
end
93123
```
94124

95125
## Rails Integration

bin/console

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env ruby
22
# frozen_string_literal: true
3+
34
require 'bundler/setup'
45
require 'ruby_llm'
56
require 'dotenv/load'

lib/ruby_llm.rb

+20
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
require 'zeitwerk'
44
require 'faraday'
55
require 'json'
6+
require 'securerandom'
7+
require 'logger'
68

79
# Main module for RubyLLM functionality
810
module RubyLLM
@@ -23,16 +25,34 @@ def client
2325
def loader
2426
@loader ||= begin
2527
loader = Zeitwerk::Loader.for_gem
28+
loader.push_dir(File.expand_path('..', __dir__))
2629
loader.inflector.inflect(
2730
'llm' => 'LLM',
2831
'openai' => 'OpenAI',
2932
'api' => 'API'
3033
)
34+
35+
# Log the paths being loaded in development
36+
loader.logger = Logger.new($stdout) if ENV['RUBY_LLM_DEBUG']
37+
3138
loader.setup
3239
loader
3340
end
3441
end
3542
end
3643
end
3744

45+
# Setup Zeitwerk loader first
3846
RubyLLM.loader
47+
48+
# Then require core files explicitly
49+
require_relative 'ruby_llm/configuration'
50+
require_relative 'ruby_llm/message'
51+
require_relative 'ruby_llm/tool'
52+
require_relative 'ruby_llm/providers/base'
53+
require_relative 'ruby_llm/client'
54+
require_relative 'ruby_llm/conversation'
55+
56+
# Load providers
57+
require_relative 'ruby_llm/providers/openai'
58+
require_relative 'ruby_llm/providers/anthropic'

lib/ruby_llm/client.rb

+8-9
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,25 @@ module RubyLLM
55
class Client
66
def initialize
77
@providers = {}
8-
@conversations = {}
98
end
109

11-
def chat(messages, model: nil, temperature: 0.7, stream: false, &block)
10+
def chat(messages, model: nil, temperature: 0.7, stream: false, tools: nil, &block)
11+
# Convert any hash messages to Message objects
12+
formatted_messages = messages.map do |msg|
13+
msg.is_a?(Message) ? msg : Message.new(**msg)
14+
end
15+
1216
provider = provider_for(model)
1317
provider.chat(
14-
messages,
18+
formatted_messages,
1519
model: model,
1620
temperature: temperature,
1721
stream: stream,
22+
tools: tools,
1823
&block
1924
)
2025
end
2126

22-
def create_conversation(tools: [])
23-
conversation = Conversation.new(tools: tools)
24-
@conversations[conversation.id] = conversation
25-
conversation
26-
end
27-
2827
private
2928

3029
def provider_for(model)

0 commit comments

Comments
 (0)