Commit 5ac95e4
Changed files (7)
lib
elelem
llama
spec
elelem
llama
lib/elelem/llama/model_resolver.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Elelem
+ module Llama
+ module ModelResolver
+ def self.resolve(model:, env: ENV, models_dir: "~/.agents/models")
+ model || env.fetch("ELELEM_GGUF_MODEL") { default_model(models_dir) }
+ end
+
+ def self.default_model(models_dir)
+ dir = File.expand_path(models_dir)
+ link = File.join(dir, "default.gguf")
+ return link if File.symlink?(link) || File.exist?(link)
+
+ models = Dir[File.join(dir, "*.gguf")]
+ case models.length
+ when 1 then models.first
+ when 0 then raise "no .gguf model found in #{dir}. Set ELELEM_GGUF_MODEL=/path/to/model.gguf"
+ else raise "multiple .gguf models found in #{dir}. Set ELELEM_GGUF_MODEL=/path/to/model.gguf or symlink " \
+ "#{link} to the one to use by default"
+ end
+ end
+ end
+ end
+end
lib/elelem/llama.rb
@@ -5,25 +5,12 @@ require "json"
require "elelem"
require_relative "llama/version"
+require_relative "llama/model_resolver"
require_relative "llama/provider"
Elelem.configure do |config|
config.provider(:llama) do |model: nil, **|
- default_model = lambda {
- dir = File.expand_path("~/.agents/models")
- link = File.join(dir, "default.gguf")
- return link if File.symlink?(link) || File.exist?(link)
-
- models = Dir[File.join(dir, "*.gguf")]
- case models.length
- when 1 then models.first
- when 0 then raise "no .gguf model found in #{dir}. Set ELELEM_GGUF_MODEL=/path/to/model.gguf"
- else raise "multiple .gguf models found in #{dir}. Set ELELEM_GGUF_MODEL=/path/to/model.gguf or symlink " \
- "#{link} to the one to use by default"
- end
- }
-
- model_path = model || ENV.fetch("ELELEM_GGUF_MODEL") { default_model.call }
+ model_path = Elelem::Llama::ModelResolver.resolve(model:)
default_gpu_layers = lambda {
backend = Elelem::Llama::Provider.backend
spec/elelem/llama/model_resolver_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+RSpec.describe Elelem::Llama::ModelResolver do
+ around do |example|
+ Dir.mktmpdir { |dir| @models_dir = dir and example.run }
+ end
+
+ def resolve(model: nil, env: {})
+ described_class.resolve(model:, env:, models_dir: @models_dir)
+ end
+
+ it "prefers an explicit model" do
+ expect(resolve(model: "/explicit.gguf", env: { "ELELEM_GGUF_MODEL" => "/env.gguf" })).to eq("/explicit.gguf")
+ end
+
+ it "falls back to ELELEM_GGUF_MODEL when no explicit model is given" do
+ expect(resolve(env: { "ELELEM_GGUF_MODEL" => "/env.gguf" })).to eq("/env.gguf")
+ end
+
+ context "with no explicit model or ENV" do
+ it "picks the only .gguf file in the models dir" do
+ FileUtils.touch(File.join(@models_dir, "only.gguf"))
+
+ expect(resolve).to eq(File.join(@models_dir, "only.gguf"))
+ end
+
+ it "prefers a default.gguf symlink when multiple models exist" do
+ FileUtils.touch(File.join(@models_dir, "a.gguf"))
+ FileUtils.touch(File.join(@models_dir, "b.gguf"))
+ File.symlink("a.gguf", File.join(@models_dir, "default.gguf"))
+
+ expect(resolve).to eq(File.join(@models_dir, "default.gguf"))
+ end
+
+ it "raises when no .gguf model is found" do
+ expect { resolve }.to raise_error(/no \.gguf model found/)
+ end
+
+ it "raises when multiple .gguf models exist with no default" do
+ FileUtils.touch(File.join(@models_dir, "a.gguf"))
+ FileUtils.touch(File.join(@models_dir, "b.gguf"))
+
+ expect { resolve }.to raise_error(/multiple \.gguf models found/)
+ end
+ end
+end
spec/spec_helper.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require "fileutils"
+require "tmpdir"
+require_relative "../lib/elelem/llama/model_resolver"
+
+RSpec.configure do |config|
+ config.disable_monkey_patching!
+
+ config.expect_with :rspec do |c|
+ c.syntax = :expect
+ end
+end
.rspec
@@ -0,0 +1,1 @@
+--require spec_helper
elelem-llama.gemspec
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
"LICENSE.txt",
"lib/elelem/llama.rb",
"lib/elelem/llama/version.rb",
+ "lib/elelem/llama/model_resolver.rb",
"lib/elelem/llama/provider.rb",
"exe/elelem-llama",
"ext/elelem/llama/elelem.cpp",
Gemfile.lock
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
- elelem-llama (0.3.0)
+ elelem-llama (0.3.1)
elelem (~> 0.13)
fiddle (~> 1.1)
json (~> 3.0)
@@ -123,7 +123,7 @@ CHECKSUMS
bundler (4.0.20) sha256=7978a8ac648767f5e635bc522445b79e80a52b907a39a36c2d8085ed6bc762ae
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
elelem (0.13.0)
- elelem-llama (0.3.0)
+ elelem-llama (0.3.1)
erb (6.0.7) sha256=c5ca6dc25b0ef974a44dc8f59fe847577122483b1968a38dec305c60bf91ee92
fiddle (1.1.8) sha256=7fa8ee3627271497f3add5503acdbc3f40b32f610fc1cf49634f083ef3f32eee
forwardable (1.4.0) sha256=f1cd40cc9812937980e1c76f1aa053660990a7c9b6a98fc37d945468afcce838