Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
.rspec_status
Gemfile.lock
/onnx/onnx/
.cache
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ require "gliner"

Gliner.configure do |config|
config.threshold = 0.2
# If unset, auto! downloads the default model to .cache/
# By default, the gem downloads the default model to .cache/
# Or set a local path explicitly:
# config.model = "/path/to/gliner2-multi-v1"
config.variant = :fp16
config.auto!
end

text = "Apple CEO Tim Cook announced iPhone 15 in Cupertino yesterday."
Expand Down
45 changes: 6 additions & 39 deletions bin/console
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env ruby

# frozen_string_literal: true

begin
Expand All @@ -7,53 +8,20 @@ rescue LoadError
end

require "gliner"
require "fileutils"
require "httpx"
require "irb"

DEFAULT_REPO_ID = "cuerbot/gliner2-multi-v1"
DEFAULT_MODEL_FILE = "model_fp16.onnx"
DEFAULT_MODEL_SUBDIR = "onnx"

def ensure_model_dir!(repo_id:, model_file:, model_subdir:)
dir = File.expand_path("../tmp/models/#{repo_id.tr('/', '__')}", __dir__)
FileUtils.mkdir_p(dir)

base = "https://huggingface.co/#{repo_id}/resolve/main"
base = "#{base}/#{model_subdir}" unless model_subdir.nil? || model_subdir.empty?
files = ["tokenizer.json", "config.json", model_file]

files.each do |file|
dest = File.join(dir, file)
next if File.exist?(dest) && File.size?(dest)
download("#{base}/#{file}", dest)
end

dir
end

def download(url, dest)
response = HTTPX.get(url)
raise "Download failed: #{url} (status: #{response.status})" unless response.status.between?(200, 299)

File.binwrite(dest, response.body.to_s)
end

model_dir = ARGV[0] || ENV["GLINER_MODEL_DIR"]
repo_id = ENV["GLINER_REPO_ID"] || DEFAULT_REPO_ID
model_file = ENV["GLINER_MODEL_FILE"] || DEFAULT_MODEL_FILE
model_subdir = ENV["GLINER_MODEL_SUBDIR"] || DEFAULT_MODEL_SUBDIR
model_file = ENV["GLINER_MODEL_FILE"]

if model_dir && !model_dir.empty?
$gliner_model = Gliner.load(model_dir, file: model_file)
$gliner_model = model_file ? Gliner.load(model_dir, file: model_file) : Gliner.load(model_dir)
else
begin
require "fileutils"
model_dir = ensure_model_dir!(repo_id: repo_id, model_file: model_file, model_subdir: model_subdir)
$gliner_model = Gliner.load(model_dir, file: model_file)
Gliner.configure { |config| config.auto = true }
$gliner_model = Gliner.model
rescue => e
warn "No model loaded (auto-download failed: #{e.class}: #{e.message})"
warn "Set GLINER_MODEL_DIR to a local model dir, or set GLINER_REPO_ID/GLINER_MODEL_FILE for auto-download."
warn "Set GLINER_MODEL_DIR or configure Gliner.config.model to a local model dir."
end
end

Expand All @@ -79,6 +47,5 @@ puts "- helper: gliner_classify(text, tasks)"
puts "- helper: gliner_extract_json(text, structures)"
puts "- model variable: $gliner_model"
puts "- model dir: #{model_dir.inspect}"
puts "- auto-download env: GLINER_REPO_ID=#{repo_id.inspect} GLINER_MODEL_FILE=#{model_file.inspect}" unless $gliner_model

IRB.start(__FILE__)
1 change: 1 addition & 0 deletions gliner.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Gem::Specification.new do |spec|
spec.add_dependency 'tokenizers', '~> 0.6'

spec.add_development_dependency 'rake', '~> 13.0'
spec.add_development_dependency 'irb', '~> 1.16.0'
spec.add_development_dependency 'rspec', '~> 3.13'
spec.add_development_dependency 'rubocop', '~> 1.50'

Expand Down
36 changes: 29 additions & 7 deletions lib/gliner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
require 'gliner/configuration'
require 'gliner/model'
require 'gliner/runners/prepared_task'
require 'gliner/runners/inspectable'
require 'gliner/runners/entity_runner'
require 'gliner/runners/structured_runner'
require 'gliner/runners/classification_runner'
Expand Down Expand Up @@ -68,7 +69,10 @@ def load(dir, file: nil)
end

def model
@model ||= model_from_config || model_from_env
@model ||= begin
apply_model_source!
model_from_config || model_from_env
end
end

def [](config)
Expand Down Expand Up @@ -98,7 +102,7 @@ def model_from_config
end

def model_from_env
dir = ENV.fetch('GLINER_MODEL_DIR', nil)
dir = env_model_dir
return if dir.nil?

file = ENV['GLINER_MODEL_FILE'] || model_file_for_variant(config.variant)
Expand Down Expand Up @@ -130,7 +134,8 @@ def apply_model_source!
return unless config.auto?

source = config.model
return unless source.nil? || source.empty?
return unless source.nil?
return if env_model_dir

config.model = download_default_model
end
Expand All @@ -143,15 +148,32 @@ def download_default_model
FileUtils.mkdir_p(dir)

files = ['tokenizer.json', 'config.json', model_file]
client = HTTPX.plugin(:follow_redirects)
client = HTTPX.plugin(:follow_redirects).with(max_redirects: 5)

files.each do |file|
response = client.get("#{DEFAULT_MODEL_BASE}/#{file}")
raise Error, "Download failed: #{file}" if response.error?
dest = File.join(dir, file)
next if File.exist?(dest) && File.size?(dest)
download_file!(client, "#{DEFAULT_MODEL_BASE}/#{file}", dest)
end

dir
end

File.binwrite(File.join(dir, file), response.body.to_s)
def download_file!(client, url, dest)
response = client.get(url)
status = response.status

unless status && status.between?(200, 299)
raise Error, "Download failed: #{url} (status: #{status || 'unknown'})"
end

File.binwrite(dest, response.body.to_s)
end

def env_model_dir
dir = ENV.fetch('GLINER_MODEL_DIR', nil)
return nil if dir.nil? || dir.empty?

dir
end

Expand Down
2 changes: 1 addition & 1 deletion lib/gliner/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def initialize
@threshold = DEFAULT_THRESHOLD
@model = nil
@variant = :fp16
@auto = false
@auto = true
end

def variant=(value)
Expand Down
7 changes: 7 additions & 0 deletions lib/gliner/runners/classification_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module Gliner
module Runners
class ClassificationRunner
include Inspectable

def self.[](tasks)
new(Gliner.model!, tasks)
end
Expand All @@ -21,6 +23,11 @@ def [](text, **options)
end

alias call []

private

def inspect_label = 'Classification'
def inspect_items = @tasks.keys
end
end
end
9 changes: 9 additions & 0 deletions lib/gliner/runners/entity_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
module Gliner
module Runners
class EntityRunner
include Inspectable

def initialize(model, config)
parsed = model.entity_task.parse_config(config)

@labels = parsed[:labels]
@task = PreparedTask.new(model.entity_task, parsed)
end

Expand All @@ -14,6 +18,11 @@ def [](text, **options)
end

alias call []

private

def inspect_label = 'Entity'
def inspect_items = @labels
end
end
end
13 changes: 13 additions & 0 deletions lib/gliner/runners/inspectable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module Gliner
module Runners
module Inspectable
def inspect
items = Array(inspect_items).map(&:to_s)

"#<Gliner(#{inspect_label}) input=#{items.inspect}>"
end
end
end
end
5 changes: 5 additions & 0 deletions lib/gliner/runners/structured_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module Gliner
module Runners
class StructuredRunner
include Inspectable

def initialize(model, config)
@tasks = build_tasks(model, config)
end
Expand All @@ -17,6 +19,9 @@ def [](text, **options)

private

def inspect_label = 'Structure'
def inspect_items = @tasks.keys

def build_tasks(model, config)
raise Error, 'structures must be a Hash' unless config.is_a?(Hash)

Expand Down
30 changes: 30 additions & 0 deletions spec/gliner/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,35 @@
expect(Gliner).not_to have_received(:download_default_model)
end
end

it 'auto! does not download when model is explicitly set' do
allow(Gliner).to receive(:download_default_model)

Gliner.configure do |config|
config.model = ''
config.auto!
end

expect(Gliner.config.model).to eq('')
expect(Gliner).not_to have_received(:download_default_model)
end

it 'auto! respects GLINER_MODEL_DIR when set' do
allow(Gliner).to receive(:download_default_model)

previous = ENV['GLINER_MODEL_DIR']
ENV['GLINER_MODEL_DIR'] = '/tmp/env-model'

begin
Gliner.configure do |config|
config.auto!
end

expect(Gliner.config.model).to be_nil
expect(Gliner).not_to have_received(:download_default_model)
ensure
ENV['GLINER_MODEL_DIR'] = previous
end
end
end
end