ruby_llm-red_candle 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/.rspec +3 -0
- data/.rubocop.yml +39 -0
- data/CHANGELOG.md +26 -0
- data/LICENSE.txt +21 -0
- data/README.md +378 -0
- data/Rakefile +10 -0
- data/examples/smoke_test.rb +320 -0
- data/lib/ruby_llm/red_candle/capabilities.rb +112 -0
- data/lib/ruby_llm/red_candle/chat.rb +445 -0
- data/lib/ruby_llm/red_candle/configuration.rb +38 -0
- data/lib/ruby_llm/red_candle/models.rb +120 -0
- data/lib/ruby_llm/red_candle/provider.rb +92 -0
- data/lib/ruby_llm/red_candle/schema_validator.rb +102 -0
- data/lib/ruby_llm/red_candle/streaming.rb +38 -0
- data/lib/ruby_llm/red_candle/version.rb +7 -0
- data/lib/ruby_llm-red_candle.rb +32 -0
- metadata +172 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module RedCandle
|
|
5
|
+
# Validates JSON schemas for structured generation
|
|
6
|
+
module SchemaValidator
|
|
7
|
+
class << self
|
|
8
|
+
# Validate a schema for structured generation
|
|
9
|
+
# @param schema [Hash] the JSON schema to validate
|
|
10
|
+
# @raise [RubyLLM::Error] if the schema is invalid
|
|
11
|
+
# @return [true] if validation passes
|
|
12
|
+
def validate!(schema)
|
|
13
|
+
errors = validate(schema)
|
|
14
|
+
return true if errors.empty?
|
|
15
|
+
|
|
16
|
+
raise RubyLLM::Error.new(
|
|
17
|
+
nil,
|
|
18
|
+
build_error_message(errors, schema)
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Validate a schema and return any errors
|
|
23
|
+
# @param schema [Hash] the JSON schema to validate
|
|
24
|
+
# @return [Array<String>] list of validation errors (empty if valid)
|
|
25
|
+
def validate(schema)
|
|
26
|
+
errors = []
|
|
27
|
+
|
|
28
|
+
# Check schema is a Hash
|
|
29
|
+
unless schema.is_a?(Hash)
|
|
30
|
+
errors << "Schema must be a Hash, got #{schema.class.name}"
|
|
31
|
+
return errors # Can't continue validation
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Check type is "object"
|
|
35
|
+
schema_type = schema[:type] || schema["type"]
|
|
36
|
+
if schema_type.nil?
|
|
37
|
+
errors << "Schema must have a 'type' field"
|
|
38
|
+
elsif schema_type.to_s != "object"
|
|
39
|
+
errors << "Schema type must be 'object' for structured generation, got '#{schema_type}'"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Check properties exist and are valid
|
|
43
|
+
properties = schema[:properties] || schema["properties"]
|
|
44
|
+
if properties.nil?
|
|
45
|
+
errors << "Schema must have a 'properties' field defining the expected output structure"
|
|
46
|
+
elsif !properties.is_a?(Hash)
|
|
47
|
+
errors << "Schema 'properties' must be a Hash, got #{properties.class.name}"
|
|
48
|
+
elsif properties.empty?
|
|
49
|
+
errors << "Schema 'properties' must define at least one property"
|
|
50
|
+
else
|
|
51
|
+
# Validate each property has a type
|
|
52
|
+
properties.each do |key, value|
|
|
53
|
+
unless value.is_a?(Hash)
|
|
54
|
+
errors << "Property '#{key}' must be a Hash with at least a 'type' field"
|
|
55
|
+
next
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
prop_type = value[:type] || value["type"]
|
|
59
|
+
if prop_type.nil?
|
|
60
|
+
errors << "Property '#{key}' must have a 'type' field"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
errors
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Check if a schema is valid without raising
|
|
69
|
+
# @param schema [Hash] the JSON schema to validate
|
|
70
|
+
# @return [Boolean] true if valid
|
|
71
|
+
def valid?(schema)
|
|
72
|
+
validate(schema).empty?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def build_error_message(errors, schema)
|
|
78
|
+
message = ["Invalid schema for structured generation:"]
|
|
79
|
+
errors.each { |e| message << " - #{e}" }
|
|
80
|
+
message << ""
|
|
81
|
+
message << "Expected a JSON Schema like:"
|
|
82
|
+
message << " {"
|
|
83
|
+
message << " type: 'object',"
|
|
84
|
+
message << " properties: {"
|
|
85
|
+
message << " name: { type: 'string' },"
|
|
86
|
+
message << " age: { type: 'integer' }"
|
|
87
|
+
message << " },"
|
|
88
|
+
message << " required: ['name', 'age']"
|
|
89
|
+
message << " }"
|
|
90
|
+
message << ""
|
|
91
|
+
message << "Received: #{truncate_schema(schema)}"
|
|
92
|
+
message.join("\n")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def truncate_schema(schema)
|
|
96
|
+
str = schema.inspect
|
|
97
|
+
str.length > 200 ? "#{str[0..200]}..." : str
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module RedCandle
|
|
5
|
+
# Streaming methods of the RedCandle integration
|
|
6
|
+
module Streaming
|
|
7
|
+
def stream(payload, &block)
|
|
8
|
+
if payload[:stream]
|
|
9
|
+
perform_streaming_completion!(payload, &block)
|
|
10
|
+
else
|
|
11
|
+
# Non-streaming fallback
|
|
12
|
+
result = perform_completion!(payload)
|
|
13
|
+
# Yield the complete result as a single chunk
|
|
14
|
+
chunk = {
|
|
15
|
+
content: result[:content],
|
|
16
|
+
role: result[:role],
|
|
17
|
+
finish_reason: result[:finish_reason]
|
|
18
|
+
}
|
|
19
|
+
block.call(chunk)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def stream_processor
|
|
26
|
+
# Red Candle handles streaming internally through blocks
|
|
27
|
+
# This method is here for compatibility with the base streaming interface
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def process_stream_response(response)
|
|
32
|
+
# Red Candle doesn't use HTTP responses
|
|
33
|
+
# Streaming is handled directly in perform_streaming_completion!
|
|
34
|
+
response
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ruby_llm"
|
|
4
|
+
|
|
5
|
+
require_relative "ruby_llm/red_candle/version"
|
|
6
|
+
require_relative "ruby_llm/red_candle/configuration"
|
|
7
|
+
require_relative "ruby_llm/red_candle/schema_validator"
|
|
8
|
+
require_relative "ruby_llm/red_candle/capabilities"
|
|
9
|
+
require_relative "ruby_llm/red_candle/models"
|
|
10
|
+
require_relative "ruby_llm/red_candle/streaming"
|
|
11
|
+
require_relative "ruby_llm/red_candle/chat"
|
|
12
|
+
require_relative "ruby_llm/red_candle/provider"
|
|
13
|
+
|
|
14
|
+
module RubyLLM
|
|
15
|
+
# Red Candle plugin module - provides local LLM execution using quantized GGUF models
|
|
16
|
+
module RedCandle
|
|
17
|
+
class << self
|
|
18
|
+
# Register the provider with RubyLLM
|
|
19
|
+
def register!
|
|
20
|
+
RubyLLM::Provider.register :red_candle, Provider
|
|
21
|
+
|
|
22
|
+
# Register Red Candle models with the global registry
|
|
23
|
+
Provider.models.each do |model|
|
|
24
|
+
RubyLLM.models.instance_variable_get(:@models) << model
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Auto-register when the gem is loaded
|
|
32
|
+
RubyLLM::RedCandle.register!
|
metadata
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ruby_llm-red_candle
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Chris Petersen
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-12-11 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: ruby_llm
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.2'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '3.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '1.2'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: red-candle
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.3'
|
|
40
|
+
type: :runtime
|
|
41
|
+
prerelease: false
|
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.3'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: rake
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '13.0'
|
|
61
|
+
- !ruby/object:Gem::Dependency
|
|
62
|
+
name: rspec
|
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.12'
|
|
68
|
+
type: :development
|
|
69
|
+
prerelease: false
|
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.12'
|
|
75
|
+
- !ruby/object:Gem::Dependency
|
|
76
|
+
name: rubocop
|
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '1.0'
|
|
82
|
+
type: :development
|
|
83
|
+
prerelease: false
|
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '1.0'
|
|
89
|
+
- !ruby/object:Gem::Dependency
|
|
90
|
+
name: rubocop-rspec
|
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '3.0'
|
|
96
|
+
type: :development
|
|
97
|
+
prerelease: false
|
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '3.0'
|
|
103
|
+
- !ruby/object:Gem::Dependency
|
|
104
|
+
name: simplecov
|
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0.22'
|
|
110
|
+
type: :development
|
|
111
|
+
prerelease: false
|
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0.22'
|
|
117
|
+
description: |
|
|
118
|
+
A RubyLLM plugin that enables local LLM execution using the Red Candle gem.
|
|
119
|
+
Run quantized GGUF models directly in Ruby without external API calls.
|
|
120
|
+
Supports streaming, structured output, and multiple model architectures
|
|
121
|
+
including Gemma, Llama, Qwen, Mistral, and Phi.
|
|
122
|
+
email:
|
|
123
|
+
- chris@scientist.com
|
|
124
|
+
executables: []
|
|
125
|
+
extensions: []
|
|
126
|
+
extra_rdoc_files: []
|
|
127
|
+
files:
|
|
128
|
+
- ".rspec"
|
|
129
|
+
- ".rubocop.yml"
|
|
130
|
+
- CHANGELOG.md
|
|
131
|
+
- LICENSE.txt
|
|
132
|
+
- README.md
|
|
133
|
+
- Rakefile
|
|
134
|
+
- examples/smoke_test.rb
|
|
135
|
+
- lib/ruby_llm-red_candle.rb
|
|
136
|
+
- lib/ruby_llm/red_candle/capabilities.rb
|
|
137
|
+
- lib/ruby_llm/red_candle/chat.rb
|
|
138
|
+
- lib/ruby_llm/red_candle/configuration.rb
|
|
139
|
+
- lib/ruby_llm/red_candle/models.rb
|
|
140
|
+
- lib/ruby_llm/red_candle/provider.rb
|
|
141
|
+
- lib/ruby_llm/red_candle/schema_validator.rb
|
|
142
|
+
- lib/ruby_llm/red_candle/streaming.rb
|
|
143
|
+
- lib/ruby_llm/red_candle/version.rb
|
|
144
|
+
homepage: https://github.com/scientist-labs/ruby_llm-red_candle
|
|
145
|
+
licenses:
|
|
146
|
+
- MIT
|
|
147
|
+
metadata:
|
|
148
|
+
homepage_uri: https://github.com/scientist-labs/ruby_llm-red_candle
|
|
149
|
+
source_code_uri: https://github.com/scientist-labs/ruby_llm-red_candle
|
|
150
|
+
changelog_uri: https://github.com/scientist-labs/ruby_llm-red_candle/blob/main/CHANGELOG.md
|
|
151
|
+
rubygems_mfa_required: 'true'
|
|
152
|
+
post_install_message:
|
|
153
|
+
rdoc_options: []
|
|
154
|
+
require_paths:
|
|
155
|
+
- lib
|
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
157
|
+
requirements:
|
|
158
|
+
- - ">="
|
|
159
|
+
- !ruby/object:Gem::Version
|
|
160
|
+
version: 3.1.0
|
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
|
+
requirements:
|
|
163
|
+
- - ">="
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: '0'
|
|
166
|
+
requirements: []
|
|
167
|
+
rubygems_version: 3.5.3
|
|
168
|
+
signing_key:
|
|
169
|
+
specification_version: 4
|
|
170
|
+
summary: Red Candle provider for RubyLLM - local LLM execution using quantized GGUF
|
|
171
|
+
models
|
|
172
|
+
test_files: []
|