htm 0.0.18 → 0.0.30

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.
Files changed (216) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +119 -1
  3. data/README.md +12 -0
  4. data/Rakefile +104 -18
  5. data/db/migrate/00001_enable_extensions.rb +9 -5
  6. data/db/migrate/00002_create_robots.rb +18 -6
  7. data/db/migrate/00003_create_file_sources.rb +30 -17
  8. data/db/migrate/00004_create_nodes.rb +60 -48
  9. data/db/migrate/00005_create_tags.rb +24 -12
  10. data/db/migrate/00006_create_node_tags.rb +28 -13
  11. data/db/migrate/00007_create_robot_nodes.rb +40 -26
  12. data/db/schema.sql +17 -1
  13. data/db/seeds.rb +34 -34
  14. data/docs/api/embedding-service.md +140 -110
  15. data/docs/api/yard/HTM/ActiveRecordConfig.md +6 -0
  16. data/docs/api/yard/HTM/Config.md +173 -0
  17. data/docs/api/yard/HTM/ConfigSection.md +28 -0
  18. data/docs/api/yard/HTM/Database.md +1 -1
  19. data/docs/api/yard/HTM/Railtie.md +2 -2
  20. data/docs/api/yard/HTM.md +0 -57
  21. data/docs/api/yard/index.csv +76 -61
  22. data/docs/api/yard-reference.md +2 -1
  23. data/docs/architecture/adrs/003-ollama-embeddings.md +45 -36
  24. data/docs/architecture/adrs/004-hive-mind.md +1 -1
  25. data/docs/architecture/adrs/008-robot-identification.md +1 -1
  26. data/docs/architecture/index.md +11 -9
  27. data/docs/architecture/overview.md +11 -7
  28. data/docs/assets/images/balanced-strategy-decay.svg +41 -0
  29. data/docs/assets/images/class-hierarchy.svg +1 -1
  30. data/docs/assets/images/eviction-priority.svg +43 -0
  31. data/docs/assets/images/exception-hierarchy.svg +2 -2
  32. data/docs/assets/images/hive-mind-shared-memory.svg +52 -0
  33. data/docs/assets/images/htm-architecture-overview.svg +3 -3
  34. data/docs/assets/images/htm-core-components.svg +4 -4
  35. data/docs/assets/images/htm-layered-architecture.svg +1 -1
  36. data/docs/assets/images/htm-memory-addition-flow.svg +2 -2
  37. data/docs/assets/images/htm-memory-recall-flow.svg +2 -2
  38. data/docs/assets/images/memory-topology.svg +53 -0
  39. data/docs/assets/images/two-tier-memory-architecture.svg +55 -0
  40. data/docs/database/naming-convention.md +244 -0
  41. data/docs/database_rake_tasks.md +31 -0
  42. data/docs/development/rake-tasks.md +80 -35
  43. data/docs/development/setup.md +76 -44
  44. data/docs/examples/basic-usage.md +133 -0
  45. data/docs/examples/config-files.md +170 -0
  46. data/docs/examples/file-loading.md +208 -0
  47. data/docs/examples/index.md +116 -0
  48. data/docs/examples/llm-configuration.md +168 -0
  49. data/docs/examples/mcp-client.md +172 -0
  50. data/docs/examples/rails-integration.md +173 -0
  51. data/docs/examples/robot-groups.md +210 -0
  52. data/docs/examples/sinatra-integration.md +218 -0
  53. data/docs/examples/standalone-app.md +216 -0
  54. data/docs/examples/telemetry.md +224 -0
  55. data/docs/examples/timeframes.md +143 -0
  56. data/docs/getting-started/installation.md +97 -40
  57. data/docs/getting-started/quick-start.md +28 -11
  58. data/docs/guides/configuration.md +515 -0
  59. data/docs/guides/file-loading.md +322 -0
  60. data/docs/guides/getting-started.md +40 -9
  61. data/docs/guides/index.md +3 -3
  62. data/docs/guides/mcp-server.md +100 -13
  63. data/docs/guides/propositions.md +264 -0
  64. data/docs/guides/recalling-memories.md +4 -4
  65. data/docs/guides/search-strategies.md +3 -3
  66. data/docs/guides/tags.md +318 -0
  67. data/docs/guides/telemetry.md +229 -0
  68. data/docs/index.md +8 -16
  69. data/docs/{architecture → robots}/hive-mind.md +8 -111
  70. data/docs/robots/index.md +73 -0
  71. data/docs/{guides → robots}/multi-robot.md +3 -3
  72. data/docs/{guides → robots}/robot-groups.md +8 -7
  73. data/docs/{architecture → robots}/two-tier-memory.md +13 -149
  74. data/docs/robots/why-robots.md +85 -0
  75. data/examples/.envrc +6 -0
  76. data/examples/.gitignore +2 -0
  77. data/examples/00_create_examples_db.rb +94 -0
  78. data/examples/{basic_usage.rb → 01_basic_usage.rb} +12 -16
  79. data/examples/{custom_llm_configuration.rb → 03_custom_llm_configuration.rb} +13 -3
  80. data/examples/{file_loader_usage.rb → 04_file_loader_usage.rb} +11 -14
  81. data/examples/{timeframe_demo.rb → 05_timeframe_demo.rb} +10 -3
  82. data/examples/{example_app → 06_example_app}/app.rb +15 -15
  83. data/examples/{cli_app → 07_cli_app}/htm_cli.rb +15 -22
  84. data/examples/08_sinatra_app/Gemfile.lock +241 -0
  85. data/examples/{sinatra_app → 08_sinatra_app}/app.rb +19 -18
  86. data/examples/{mcp_client.rb → 09_mcp_client.rb} +5 -8
  87. data/examples/{telemetry → 10_telemetry}/SETUP_README.md +1 -1
  88. data/examples/{telemetry → 10_telemetry}/demo.rb +14 -10
  89. data/examples/11_robot_groups/README.md +335 -0
  90. data/examples/{robot_groups → 11_robot_groups/lib}/robot_worker.rb +17 -3
  91. data/examples/{robot_groups → 11_robot_groups}/multi_process.rb +9 -9
  92. data/examples/{robot_groups → 11_robot_groups}/same_process.rb +9 -12
  93. data/examples/{rails_app → 12_rails_app}/Gemfile +3 -0
  94. data/examples/{rails_app → 12_rails_app}/Gemfile.lock +87 -58
  95. data/examples/{rails_app → 12_rails_app}/app/controllers/dashboard_controller.rb +10 -6
  96. data/examples/{rails_app → 12_rails_app}/app/controllers/files_controller.rb +5 -5
  97. data/examples/{rails_app → 12_rails_app}/app/controllers/memories_controller.rb +11 -7
  98. data/examples/{rails_app → 12_rails_app}/app/controllers/robots_controller.rb +8 -8
  99. data/examples/12_rails_app/app/controllers/tags_controller.rb +36 -0
  100. data/examples/{rails_app → 12_rails_app}/app/views/dashboard/index.html.erb +2 -2
  101. data/examples/{rails_app → 12_rails_app}/app/views/files/new.html.erb +5 -2
  102. data/examples/{rails_app → 12_rails_app}/app/views/memories/_memory_card.html.erb +3 -3
  103. data/examples/{rails_app → 12_rails_app}/app/views/memories/deleted.html.erb +3 -3
  104. data/examples/{rails_app → 12_rails_app}/app/views/memories/edit.html.erb +3 -3
  105. data/examples/{rails_app → 12_rails_app}/app/views/memories/show.html.erb +4 -4
  106. data/examples/{rails_app → 12_rails_app}/app/views/robots/index.html.erb +2 -2
  107. data/examples/{rails_app → 12_rails_app}/app/views/robots/show.html.erb +4 -4
  108. data/examples/{rails_app → 12_rails_app}/app/views/search/index.html.erb +1 -1
  109. data/examples/{rails_app → 12_rails_app}/app/views/tags/index.html.erb +2 -2
  110. data/examples/{rails_app → 12_rails_app}/app/views/tags/show.html.erb +1 -1
  111. data/examples/12_rails_app/config/initializers/htm.rb +7 -0
  112. data/examples/12_rails_app/config/initializers/rack.rb +5 -0
  113. data/examples/README.md +230 -211
  114. data/examples/examples_helper.rb +138 -0
  115. data/lib/htm/config/builder.rb +167 -0
  116. data/lib/htm/config/database.rb +317 -0
  117. data/lib/htm/config/defaults.yml +41 -13
  118. data/lib/htm/config/section.rb +74 -0
  119. data/lib/htm/config/validator.rb +83 -0
  120. data/lib/htm/config.rb +65 -361
  121. data/lib/htm/database.rb +85 -127
  122. data/lib/htm/errors.rb +14 -0
  123. data/lib/htm/integrations/sinatra.rb +13 -44
  124. data/lib/htm/job_adapter.rb +75 -1
  125. data/lib/htm/jobs/generate_embedding_job.rb +3 -4
  126. data/lib/htm/jobs/generate_propositions_job.rb +4 -5
  127. data/lib/htm/jobs/generate_tags_job.rb +16 -15
  128. data/lib/htm/loaders/defaults_loader.rb +23 -0
  129. data/lib/htm/loaders/markdown_loader.rb +17 -15
  130. data/lib/htm/loaders/xdg_config_loader.rb +9 -9
  131. data/lib/htm/long_term_memory/fulltext_search.rb +14 -14
  132. data/lib/htm/long_term_memory/hybrid_search.rb +396 -229
  133. data/lib/htm/long_term_memory/node_operations.rb +24 -23
  134. data/lib/htm/long_term_memory/relevance_scorer.rb +23 -20
  135. data/lib/htm/long_term_memory/robot_operations.rb +4 -4
  136. data/lib/htm/long_term_memory/tag_operations.rb +91 -77
  137. data/lib/htm/long_term_memory/vector_search.rb +4 -5
  138. data/lib/htm/long_term_memory.rb +13 -13
  139. data/lib/htm/mcp/cli.rb +115 -8
  140. data/lib/htm/mcp/resources.rb +4 -3
  141. data/lib/htm/mcp/server.rb +5 -4
  142. data/lib/htm/mcp/tools.rb +37 -28
  143. data/lib/htm/migration.rb +72 -0
  144. data/lib/htm/models/file_source.rb +52 -31
  145. data/lib/htm/models/node.rb +224 -108
  146. data/lib/htm/models/node_tag.rb +49 -28
  147. data/lib/htm/models/robot.rb +38 -27
  148. data/lib/htm/models/robot_node.rb +63 -35
  149. data/lib/htm/models/tag.rb +126 -123
  150. data/lib/htm/observability.rb +45 -41
  151. data/lib/htm/proposition_service.rb +76 -7
  152. data/lib/htm/railtie.rb +2 -2
  153. data/lib/htm/robot_group.rb +30 -18
  154. data/lib/htm/sequel_config.rb +215 -0
  155. data/lib/htm/sql_builder.rb +14 -16
  156. data/lib/htm/tag_service.rb +78 -0
  157. data/lib/htm/tasks.rb +3 -0
  158. data/lib/htm/version.rb +1 -1
  159. data/lib/htm/workflows/remember_workflow.rb +213 -0
  160. data/lib/htm.rb +27 -22
  161. data/lib/tasks/db.rake +0 -2
  162. data/lib/tasks/doc.rake +2 -2
  163. data/lib/tasks/files.rake +11 -18
  164. data/lib/tasks/htm.rake +190 -62
  165. data/lib/tasks/jobs.rake +179 -54
  166. data/lib/tasks/tags.rake +8 -13
  167. data/mkdocs.yml +33 -8
  168. data/scripts/backfill_parent_tags.rb +376 -0
  169. data/scripts/normalize_plural_tags.rb +335 -0
  170. metadata +168 -86
  171. data/docs/api/yard/HTM/Configuration.md +0 -240
  172. data/docs/telemetry.md +0 -391
  173. data/examples/rails_app/app/controllers/tags_controller.rb +0 -30
  174. data/examples/sinatra_app/Gemfile.lock +0 -166
  175. data/lib/htm/active_record_config.rb +0 -104
  176. /data/examples/{config_file_example → 02_config_file_example}/README.md +0 -0
  177. /data/examples/{config_file_example → 02_config_file_example}/config/htm.local.yml +0 -0
  178. /data/examples/{config_file_example → 02_config_file_example}/custom_config.yml +0 -0
  179. /data/examples/{config_file_example → 02_config_file_example}/show_config.rb +0 -0
  180. /data/examples/{example_app → 06_example_app}/Rakefile +0 -0
  181. /data/examples/{cli_app → 07_cli_app}/README.md +0 -0
  182. /data/examples/{sinatra_app → 08_sinatra_app}/Gemfile +0 -0
  183. /data/examples/{telemetry → 10_telemetry}/README.md +0 -0
  184. /data/examples/{telemetry → 10_telemetry}/grafana/dashboards/htm-metrics.json +0 -0
  185. /data/examples/{rails_app → 12_rails_app}/.gitignore +0 -0
  186. /data/examples/{rails_app → 12_rails_app}/Procfile.dev +0 -0
  187. /data/examples/{rails_app → 12_rails_app}/README.md +0 -0
  188. /data/examples/{rails_app → 12_rails_app}/Rakefile +0 -0
  189. /data/examples/{rails_app → 12_rails_app}/app/assets/stylesheets/application.css +0 -0
  190. /data/examples/{rails_app → 12_rails_app}/app/assets/stylesheets/inter-font.css +0 -0
  191. /data/examples/{rails_app → 12_rails_app}/app/controllers/application_controller.rb +0 -0
  192. /data/examples/{rails_app → 12_rails_app}/app/controllers/search_controller.rb +0 -0
  193. /data/examples/{rails_app → 12_rails_app}/app/javascript/application.js +0 -0
  194. /data/examples/{rails_app → 12_rails_app}/app/javascript/controllers/application.js +0 -0
  195. /data/examples/{rails_app → 12_rails_app}/app/javascript/controllers/index.js +0 -0
  196. /data/examples/{rails_app → 12_rails_app}/app/views/files/index.html.erb +0 -0
  197. /data/examples/{rails_app → 12_rails_app}/app/views/files/show.html.erb +0 -0
  198. /data/examples/{rails_app → 12_rails_app}/app/views/layouts/application.html.erb +0 -0
  199. /data/examples/{rails_app → 12_rails_app}/app/views/memories/index.html.erb +0 -0
  200. /data/examples/{rails_app → 12_rails_app}/app/views/memories/new.html.erb +0 -0
  201. /data/examples/{rails_app → 12_rails_app}/app/views/robots/new.html.erb +0 -0
  202. /data/examples/{rails_app → 12_rails_app}/app/views/shared/_navbar.html.erb +0 -0
  203. /data/examples/{rails_app → 12_rails_app}/app/views/shared/_stat_card.html.erb +0 -0
  204. /data/examples/{rails_app → 12_rails_app}/bin/dev +0 -0
  205. /data/examples/{rails_app → 12_rails_app}/bin/rails +0 -0
  206. /data/examples/{rails_app → 12_rails_app}/bin/rake +0 -0
  207. /data/examples/{rails_app → 12_rails_app}/config/application.rb +0 -0
  208. /data/examples/{rails_app → 12_rails_app}/config/boot.rb +0 -0
  209. /data/examples/{rails_app → 12_rails_app}/config/database.yml +0 -0
  210. /data/examples/{rails_app → 12_rails_app}/config/environment.rb +0 -0
  211. /data/examples/{rails_app → 12_rails_app}/config/importmap.rb +0 -0
  212. /data/examples/{rails_app → 12_rails_app}/config/routes.rb +0 -0
  213. /data/examples/{rails_app → 12_rails_app}/config/tailwind.config.js +0 -0
  214. /data/examples/{rails_app → 12_rails_app}/config.ru +0 -0
  215. /data/examples/{rails_app → 12_rails_app}/log/.keep +0 -0
  216. /data/examples/{rails_app → 12_rails_app}/tmp/local_secret.txt +0 -0
@@ -91,8 +91,9 @@ defaults:
91
91
  RULES:
92
92
  1. Each concept belongs to ONE path only
93
93
  2. Use lowercase, hyphens for multi-word terms
94
- 3. Return 2-5 tags that best classify this text
95
- 4. Match existing taxonomy paths when applicable
94
+ 3. ALWAYS use SINGULAR forms, never plurals (user NOT users, framework NOT frameworks, model NOT models)
95
+ 4. Return 2-5 tags that best classify this text
96
+ 5. Match existing taxonomy paths when applicable
96
97
 
97
98
  TEXT: %{text}
98
99
 
@@ -112,27 +113,54 @@ defaults:
112
113
  provider: ollama
113
114
  model: gemma3:latest
114
115
  timeout: 180
115
- enabled: false
116
+ enabled: true
117
+ min_length: 10
118
+ max_length: 1000
119
+ min_words: 5
116
120
 
117
121
  system_prompt: |
118
- You are an atomic fact extraction system. Your goal is maximum decomposition.
119
- Break every statement into its smallest possible factual units.
122
+ You are an atomic fact extraction system. Extract factual propositions from text.
120
123
  Output ONLY propositions, one per line, prefixed with a dash (-).
124
+ NEVER ask for clarification or more text - extract what you can from the given text.
125
+ If the text contains no extractable facts, output nothing.
121
126
 
122
127
  user_prompt_template: |
123
- Extract all ATOMIC factual propositions from the following text.
128
+ Extract ATOMIC factual propositions from the following text.
124
129
 
125
- An atomic proposition expresses exactly ONE relationship or fact.
130
+ An atomic proposition:
131
+ - Expresses exactly ONE fact or relationship
132
+ - Is understandable WITHOUT any additional context
133
+ - Uses FULL NAMES, never pronouns (he, she, it, they, this, that)
134
+ - Contains at least 5 words for sufficient context
126
135
 
127
- Rules:
136
+ PRONOUN REPLACEMENT EXAMPLES:
137
+ BAD: "It is important to know the enemy."
138
+ GOOD: "Knowing the enemy is important for spiritual warfare."
139
+
140
+ BAD: "They support thousands of users."
141
+ GOOD: "The Every.to products support thousands of daily users."
142
+
143
+ BAD: "This results in a calculation."
144
+ GOOD: "The age calculation depends on whether the birthday has occurred."
145
+
146
+ CONTEXT ENRICHMENT EXAMPLES:
147
+ BAD: "Wiring costs $1,000."
148
+ GOOD: "Solar panel wiring costs $1,000 for the Oklahoma barndominium project."
149
+
150
+ BAD: "The driveway is gravel."
151
+ GOOD: "Dewayne's barndominium driveway is gravel."
152
+
153
+ RULES:
128
154
  1. Split compound statements into separate atomic facts
129
155
  2. Each proposition = exactly one fact
130
- 3. Use full names, never pronouns
131
- 4. Make each proposition understandable in isolation
156
+ 3. Replace ALL pronouns with the actual names/nouns they refer to
157
+ 4. Include enough context to understand the fact in isolation
158
+ 5. If unsure what a pronoun refers to, omit that proposition
132
159
 
133
160
  TEXT: %{text}
134
161
 
135
162
  Return ONLY atomic propositions, one per line. Use a dash (-) prefix for each.
163
+ If you cannot extract any facts, return nothing. Do NOT ask for more text.
136
164
 
137
165
  # ---------------------------------------------------------------------------
138
166
  # Chunking Configuration (for file loading)
@@ -167,14 +195,14 @@ defaults:
167
195
  # Access: HTM.config.job.backend
168
196
  # ---------------------------------------------------------------------------
169
197
  job:
170
- backend: ~
198
+ backend: fiber
171
199
 
172
200
  # ---------------------------------------------------------------------------
173
201
  # General Settings
174
202
  # Access: HTM.config.week_start, HTM.config.connection_timeout, etc.
175
203
  # ---------------------------------------------------------------------------
176
204
  week_start: sunday
177
- connection_timeout: 30
205
+ connection_timeout: 60
178
206
  telemetry_enabled: false
179
207
  log_level: info
180
208
 
@@ -197,7 +225,7 @@ defaults:
197
225
  azure:
198
226
  api_key: ~
199
227
  endpoint: ~
200
- api_version: '2024-02-01'
228
+ api_version: "2024-02-01"
201
229
 
202
230
  ollama:
203
231
  url: http://localhost:11434
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HTM
4
+ # ConfigSection provides method access to nested configuration hashes
5
+ #
6
+ # @example
7
+ # section = ConfigSection.new(host: 'localhost', port: 5432)
8
+ # section.host # => 'localhost'
9
+ # section.port # => 5432
10
+ #
11
+ class ConfigSection
12
+ def initialize(hash = {})
13
+ @data = {}
14
+ (hash || {}).each do |key, value|
15
+ @data[key.to_sym] = value.is_a?(Hash) ? ConfigSection.new(value) : value
16
+ end
17
+ end
18
+
19
+ def method_missing(method, *args, &block)
20
+ key = method.to_s
21
+ if key.end_with?('=')
22
+ @data[key.chomp('=').to_sym] = args.first
23
+ elsif @data.key?(method)
24
+ @data[method]
25
+ else
26
+ nil
27
+ end
28
+ end
29
+
30
+ def respond_to_missing?(method, include_private = false)
31
+ key = method.to_s.chomp('=').to_sym
32
+ @data.key?(key) || super
33
+ end
34
+
35
+ def to_h
36
+ @data.transform_values do |v|
37
+ v.is_a?(ConfigSection) ? v.to_h : v
38
+ end
39
+ end
40
+
41
+ def [](key)
42
+ @data[key.to_sym]
43
+ end
44
+
45
+ def []=(key, value)
46
+ @data[key.to_sym] = value
47
+ end
48
+
49
+ def merge(other)
50
+ other_hash = other.is_a?(ConfigSection) ? other.to_h : other
51
+ ConfigSection.new(deep_merge(to_h, other_hash || {}))
52
+ end
53
+
54
+ def keys
55
+ @data.keys
56
+ end
57
+
58
+ def each(&block)
59
+ @data.each(&block)
60
+ end
61
+
62
+ private
63
+
64
+ def deep_merge(base, overlay)
65
+ base.merge(overlay) do |_key, old_val, new_val|
66
+ if old_val.is_a?(Hash) && new_val.is_a?(Hash)
67
+ deep_merge(old_val, new_val)
68
+ else
69
+ new_val
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HTM
4
+ class Config
5
+ module Validator
6
+ SUPPORTED_PROVIDERS = %i[
7
+ openai anthropic gemini azure ollama
8
+ huggingface openrouter bedrock deepseek
9
+ ].freeze
10
+
11
+ SUPPORTED_JOB_BACKENDS = %i[active_job sidekiq inline thread fiber].freeze
12
+ SUPPORTED_WEEK_STARTS = %i[sunday monday].freeze
13
+
14
+ def validate_config
15
+ validate_providers
16
+ validate_job_backend
17
+ validate_week_start
18
+ validate_relevance_weights
19
+ end
20
+
21
+ def validate_providers
22
+ validate_provider(:embedding_provider, embedding_provider)
23
+ validate_provider(:tag_provider, tag_provider)
24
+ validate_provider(:proposition_provider, proposition_provider)
25
+ end
26
+
27
+ def validate_provider(name, value)
28
+ return if value.nil?
29
+
30
+ unless SUPPORTED_PROVIDERS.include?(value)
31
+ raise_validation_error("#{name} must be one of: #{SUPPORTED_PROVIDERS.join(', ')} (got #{value.inspect})")
32
+ end
33
+ end
34
+
35
+ def validate_job_backend
36
+ return unless job_backend
37
+
38
+ unless SUPPORTED_JOB_BACKENDS.include?(job_backend)
39
+ raise_validation_error("job.backend must be one of: #{SUPPORTED_JOB_BACKENDS.join(', ')} (got #{job_backend.inspect})")
40
+ end
41
+ end
42
+
43
+ def validate_week_start
44
+ unless SUPPORTED_WEEK_STARTS.include?(week_start)
45
+ raise_validation_error("week_start must be one of: #{SUPPORTED_WEEK_STARTS.join(', ')} (got #{week_start.inspect})")
46
+ end
47
+ end
48
+
49
+ def validate_relevance_weights
50
+ total = relevance_semantic_weight + relevance_tag_weight +
51
+ relevance_recency_weight + relevance_access_weight
52
+
53
+ unless (0.99..1.01).cover?(total)
54
+ raise_validation_error("relevance weights must sum to 1.0 (got #{total})")
55
+ end
56
+ end
57
+
58
+ def validate_callables
59
+ unless @embedding_generator.respond_to?(:call)
60
+ raise HTM::ValidationError, "embedding_generator must be callable"
61
+ end
62
+
63
+ unless @tag_extractor.respond_to?(:call)
64
+ raise HTM::ValidationError, "tag_extractor must be callable"
65
+ end
66
+
67
+ unless @proposition_extractor.respond_to?(:call)
68
+ raise HTM::ValidationError, "proposition_extractor must be callable"
69
+ end
70
+
71
+ unless @token_counter.respond_to?(:call)
72
+ raise HTM::ValidationError, "token_counter must be callable"
73
+ end
74
+ end
75
+
76
+ def validate_logger
77
+ unless @logger.respond_to?(:info) && @logger.respond_to?(:warn) && @logger.respond_to?(:error)
78
+ raise HTM::ValidationError, "logger must respond to :info, :warn, and :error"
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end