Skip to content

Commit ac29ef2

Browse files
committed
chore(config): refactor configuration management and add local config support
This commit introduces several configuration-related changes: - Migrated from custom configuration to Anyway::Config for more robust configuration management - Updated config access from direct attributes to nested configuration - Added support for local configuration file (config/htm.local.yml) - Updated examples and version to reflect configuration changes Refs: #config_refactor
1 parent 96fbbe1 commit ac29ef2

27 files changed

Lines changed: 2028 additions & 930 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,6 @@ build-iPhoneSimulator/
6565

6666
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
6767
# .rubocop-https?--*
68+
69+
# Local configuration (anyway_config local overrides)
70+
config/htm.local.yml

.irbrc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ end
2121

2222
# Configure HTM with sensible defaults for interactive use
2323
HTM.configure do |config|
24-
config.job_backend = :inline
25-
config.embedding_provider = :ollama
26-
config.embedding_model = 'nomic-embed-text:latest'
27-
config.embedding_dimensions = 768
28-
config.tag_provider = :ollama
29-
config.tag_model = 'gemma3:latest'
24+
config.job.backend = :inline
25+
config.embedding.provider = :ollama
26+
config.embedding.model = 'nomic-embed-text:latest'
27+
config.embedding.dimensions = 768
28+
config.tag.provider = :ollama
29+
config.tag.model = 'gemma3:latest'
3030
config.reset_to_defaults
3131
end
3232
puts "✓ HTM configured (inline jobs, Ollama provider)"

Gemfile.lock

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
PATH
22
remote: .
33
specs:
4-
htm (0.0.17)
4+
htm (0.0.18)
55
activerecord
6+
anyway_config (>= 2.6)
67
baran
78
chronic
89
fast-mcp
@@ -38,6 +39,8 @@ GEM
3839
addressable (2.8.8)
3940
public_suffix (>= 2.0.2, < 8.0)
4041
ansi (1.5.0)
42+
anyway_config (2.7.2)
43+
ruby-next-core (~> 1.0)
4144
baran (0.2.1)
4245
base64 (0.3.0)
4346
bigdecimal (3.3.1)
@@ -144,6 +147,7 @@ GEM
144147
rb_sys (0.9.123)
145148
rake-compiler-dock (= 1.10.0)
146149
rexml (3.4.4)
150+
ruby-next-core (1.1.2)
147151
ruby-progressbar (1.13.0)
148152
ruby_llm (1.9.1)
149153
base64
@@ -168,7 +172,7 @@ GEM
168172
simplecov_json_formatter (~> 0.1)
169173
simplecov-html (0.13.2)
170174
simplecov_json_formatter (0.1.4)
171-
tiktoken_ruby (0.0.13)
175+
tiktoken_ruby (0.0.14.1)
172176
rb_sys (~> 0.9)
173177
timeout (0.6.0)
174178
tzinfo (2.0.6)

examples/cli_app/htm_cli.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def initialize
3939
# Configure HTM for CLI usage
4040
HTM.configure do |config|
4141
# Use inline mode for synchronous execution
42-
config.job_backend = :inline
42+
config.job.backend = :inline
4343

4444
# CLI-friendly logging
4545
config.logger.level = Logger::INFO

htm.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Gem::Specification.new do |spec|
4646
spec.add_dependency "chronic"
4747
spec.add_dependency "fast-mcp"
4848
spec.add_dependency "baran"
49+
spec.add_dependency "anyway_config", ">= 2.6"
4950
# Optional runtime dependencies for different job backends
5051
# - ActiveJob (bundled with Rails)
5152
# - Sidekiq (add to Gemfile if using :sidekiq backend)

lib/htm.rb

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
require_relative "htm/version"
44
require_relative "htm/errors"
5-
require_relative "htm/configuration"
5+
require_relative "htm/config"
66
require_relative "htm/circuit_breaker"
77
require_relative "htm/active_record_config"
88
require_relative "htm/database"
@@ -170,7 +170,7 @@ def remember(content, tags: [], metadata: {})
170170
enqueue_tags_job(node_id, manual_tags: tags)
171171

172172
# Enqueue proposition extraction if enabled and not already a proposition
173-
if HTM.configuration.extract_propositions && !metadata[:is_proposition]
173+
if HTM.config.extract_propositions && !metadata[:is_proposition]
174174
enqueue_propositions_job(node_id)
175175
end
176176
else
@@ -679,4 +679,125 @@ def validate_metadata!(metadata)
679679
end
680680
end
681681

682+
# ===========================================================================
683+
# Class Methods
684+
# ===========================================================================
685+
686+
class << self
687+
# Get current configuration (singleton)
688+
#
689+
# @return [HTM::Config]
690+
#
691+
def config
692+
@config ||= Config.new
693+
end
694+
695+
# Alias for backward compatibility
696+
alias configuration config
697+
698+
# Configure HTM
699+
#
700+
# @yield [config] Configuration object
701+
# @yieldparam config [HTM::Config]
702+
#
703+
# @example Custom configuration
704+
# HTM.configure do |config|
705+
# config.embedding_generator = ->(text) { MyEmbedder.embed(text) }
706+
# config.tag_extractor = ->(text, ontology) { MyTagger.extract(text, ontology) }
707+
# end
708+
#
709+
# @example Default configuration
710+
# HTM.configure # Uses RubyLLM defaults
711+
#
712+
def configure
713+
yield(config) if block_given?
714+
config.validate!
715+
config
716+
end
717+
718+
# Reset configuration to defaults
719+
def reset_configuration!
720+
@config = nil
721+
end
722+
723+
# Get current environment
724+
#
725+
# @return [String] Current environment name
726+
#
727+
def env
728+
Config.env
729+
end
730+
731+
# Check if running in test environment
732+
#
733+
# @return [Boolean]
734+
#
735+
def test?
736+
env == 'test'
737+
end
738+
739+
# Check if running in development environment
740+
#
741+
# @return [Boolean]
742+
#
743+
def development?
744+
env == 'development'
745+
end
746+
747+
# Check if running in production environment
748+
#
749+
# @return [Boolean]
750+
#
751+
def production?
752+
env == 'production'
753+
end
754+
755+
# Generate embedding using EmbeddingService
756+
#
757+
# @param text [String] Text to embed
758+
# @return [Array<Float>] Embedding vector (original, not padded)
759+
#
760+
def embed(text)
761+
result = HTM::EmbeddingService.generate(text)
762+
result[:embedding]
763+
end
764+
765+
# Extract tags using TagService
766+
#
767+
# @param text [String] Text to analyze
768+
# @param existing_ontology [Array<String>] Sample of existing tags for context
769+
# @return [Array<String>] Extracted and validated tag names
770+
#
771+
def extract_tags(text, existing_ontology: [])
772+
HTM::TagService.extract(text, existing_ontology: existing_ontology)
773+
end
774+
775+
# Extract propositions using PropositionService
776+
#
777+
# @param text [String] Text to analyze
778+
# @return [Array<String>] Extracted atomic propositions
779+
#
780+
def extract_propositions(text)
781+
HTM::PropositionService.extract(text)
782+
end
783+
784+
# Count tokens using configured counter
785+
#
786+
# @param text [String] Text to count tokens for
787+
# @return [Integer] Token count
788+
#
789+
def count_tokens(text)
790+
config.token_counter.call(text)
791+
rescue StandardError => e
792+
raise HTM::ValidationError, "Token counting failed: #{e.message}"
793+
end
794+
795+
# Get configured logger
796+
#
797+
# @return [Logger] Configured logger instance
798+
#
799+
def logger
800+
config.logger
801+
end
802+
end
682803
end

lib/htm/active_record_config.rb

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
require 'active_record'
44
require 'pg'
55
require 'neighbor'
6-
require 'erb'
7-
require 'yaml'
86

97
class HTM
108
# ActiveRecord database configuration and model loading
9+
#
10+
# Uses HTM::Config for database settings. Configuration can come from:
11+
# - config/htm.yml (environment-specific)
12+
# - Environment variables (HTM_DB_URL, HTM_DB_HOST, etc.)
13+
# - Programmatic configuration via HTM.configure
14+
#
1115
class ActiveRecordConfig
1216
class << self
13-
# Establish database connection from config/database.yml
17+
# Establish database connection from HTM::Config
1418
def establish_connection!
1519
config = load_database_config
1620

@@ -25,27 +29,26 @@ def establish_connection!
2529
true
2630
end
2731

28-
# Load and parse database configuration from YAML with ERB
32+
# Load database configuration from HTM::Config
33+
#
34+
# @return [Hash] ActiveRecord-compatible configuration hash
35+
#
2936
def load_database_config
30-
config_path = File.expand_path('../../config/database.yml', __dir__)
31-
32-
unless File.exist?(config_path)
33-
raise "Database configuration file not found at #{config_path}"
37+
htm_config = HTM.config
38+
39+
# If we have a database URL, parse it
40+
if htm_config.database_url
41+
htm_config.database_config
42+
else
43+
# Fall back to legacy config/database.yml if it exists and no config in HTM::Config
44+
legacy_config_path = File.expand_path('../../config/database.yml', __dir__)
45+
46+
if File.exist?(legacy_config_path) && !htm_config.database_configured?
47+
load_legacy_database_config(legacy_config_path)
48+
else
49+
htm_config.database_config
50+
end
3451
end
35-
36-
# Read and parse ERB
37-
erb_content = ERB.new(File.read(config_path)).result
38-
db_config = YAML.safe_load(erb_content, aliases: true)
39-
40-
# Get configuration for current environment
41-
config = db_config[HTM.env]
42-
43-
unless config
44-
raise "No database configuration found for environment: #{HTM.env}"
45-
end
46-
47-
# Convert string keys to symbols for ActiveRecord
48-
config.transform_keys(&:to_sym)
4952
end
5053

5154
# Check if connection is established and active
@@ -103,6 +106,27 @@ def connection_stats
103106

104107
private
105108

109+
# Load legacy database.yml configuration (for backward compatibility)
110+
#
111+
# @param config_path [String] Path to database.yml
112+
# @return [Hash] ActiveRecord-compatible configuration hash
113+
#
114+
def load_legacy_database_config(config_path)
115+
require 'erb'
116+
require 'yaml'
117+
118+
erb_content = ERB.new(File.read(config_path)).result
119+
db_config = YAML.safe_load(erb_content, aliases: true)
120+
121+
config = db_config[HTM.env]
122+
123+
unless config
124+
raise "No database configuration found for environment: #{HTM.env}"
125+
end
126+
127+
config.transform_keys(&:to_sym)
128+
end
129+
106130
# Require all model files
107131
def require_models
108132
require_relative 'models/robot'

0 commit comments

Comments
 (0)