htm 0.0.1
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/.architecture/decisions/adrs/001-use-postgresql-timescaledb-storage.md +227 -0
- data/.architecture/decisions/adrs/002-two-tier-memory-architecture.md +322 -0
- data/.architecture/decisions/adrs/003-ollama-default-embedding-provider.md +339 -0
- data/.architecture/decisions/adrs/004-multi-robot-shared-memory-hive-mind.md +374 -0
- data/.architecture/decisions/adrs/005-rag-based-retrieval-with-hybrid-search.md +443 -0
- data/.architecture/decisions/adrs/006-context-assembly-strategies.md +444 -0
- data/.architecture/decisions/adrs/007-working-memory-eviction-strategy.md +461 -0
- data/.architecture/decisions/adrs/008-robot-identification-system.md +550 -0
- data/.architecture/decisions/adrs/009-never-forget-explicit-deletion-only.md +570 -0
- data/.architecture/decisions/adrs/010-redis-working-memory-rejected.md +323 -0
- data/.architecture/decisions/adrs/011-database-side-embedding-generation-with-pgai.md +585 -0
- data/.architecture/decisions/adrs/012-llm-driven-ontology-topic-extraction.md +583 -0
- data/.architecture/decisions/adrs/013-activerecord-orm-and-many-to-many-tagging.md +299 -0
- data/.architecture/decisions/adrs/014-client-side-embedding-generation-workflow.md +569 -0
- data/.architecture/decisions/adrs/015-hierarchical-tag-ontology-and-llm-extraction.md +701 -0
- data/.architecture/decisions/adrs/016-async-embedding-and-tag-generation.md +694 -0
- data/.architecture/members.yml +144 -0
- data/.architecture/reviews/2025-10-29-llm-configuration-and-async-processing-review.md +1137 -0
- data/.architecture/reviews/initial-system-analysis.md +330 -0
- data/.envrc +32 -0
- data/.irbrc +145 -0
- data/CHANGELOG.md +150 -0
- data/COMMITS.md +196 -0
- data/LICENSE +21 -0
- data/README.md +1347 -0
- data/Rakefile +51 -0
- data/SETUP.md +268 -0
- data/config/database.yml +67 -0
- data/db/migrate/20250101000001_enable_extensions.rb +14 -0
- data/db/migrate/20250101000002_create_robots.rb +14 -0
- data/db/migrate/20250101000003_create_nodes.rb +42 -0
- data/db/migrate/20250101000005_create_tags.rb +38 -0
- data/db/migrate/20250101000007_add_node_vector_indexes.rb +30 -0
- data/db/schema.sql +473 -0
- data/db/seed_data/README.md +100 -0
- data/db/seed_data/presidents.md +136 -0
- data/db/seed_data/states.md +151 -0
- data/db/seeds.rb +208 -0
- data/dbdoc/README.md +173 -0
- data/dbdoc/public.node_stats.md +48 -0
- data/dbdoc/public.node_stats.svg +41 -0
- data/dbdoc/public.node_tags.md +40 -0
- data/dbdoc/public.node_tags.svg +112 -0
- data/dbdoc/public.nodes.md +54 -0
- data/dbdoc/public.nodes.svg +118 -0
- data/dbdoc/public.nodes_tags.md +39 -0
- data/dbdoc/public.nodes_tags.svg +112 -0
- data/dbdoc/public.ontology_structure.md +48 -0
- data/dbdoc/public.ontology_structure.svg +38 -0
- data/dbdoc/public.operations_log.md +42 -0
- data/dbdoc/public.operations_log.svg +130 -0
- data/dbdoc/public.relationships.md +39 -0
- data/dbdoc/public.relationships.svg +41 -0
- data/dbdoc/public.robot_activity.md +46 -0
- data/dbdoc/public.robot_activity.svg +35 -0
- data/dbdoc/public.robots.md +35 -0
- data/dbdoc/public.robots.svg +90 -0
- data/dbdoc/public.schema_migrations.md +29 -0
- data/dbdoc/public.schema_migrations.svg +26 -0
- data/dbdoc/public.tags.md +35 -0
- data/dbdoc/public.tags.svg +60 -0
- data/dbdoc/public.topic_relationships.md +45 -0
- data/dbdoc/public.topic_relationships.svg +32 -0
- data/dbdoc/schema.json +1437 -0
- data/dbdoc/schema.svg +154 -0
- data/docs/api/database.md +806 -0
- data/docs/api/embedding-service.md +532 -0
- data/docs/api/htm.md +797 -0
- data/docs/api/index.md +259 -0
- data/docs/api/long-term-memory.md +1096 -0
- data/docs/api/working-memory.md +665 -0
- data/docs/architecture/adrs/001-postgresql-timescaledb.md +314 -0
- data/docs/architecture/adrs/002-two-tier-memory.md +411 -0
- data/docs/architecture/adrs/003-ollama-embeddings.md +421 -0
- data/docs/architecture/adrs/004-hive-mind.md +437 -0
- data/docs/architecture/adrs/005-rag-retrieval.md +531 -0
- data/docs/architecture/adrs/006-context-assembly.md +496 -0
- data/docs/architecture/adrs/007-eviction-strategy.md +645 -0
- data/docs/architecture/adrs/008-robot-identification.md +625 -0
- data/docs/architecture/adrs/009-never-forget.md +648 -0
- data/docs/architecture/adrs/010-redis-working-memory-rejected.md +323 -0
- data/docs/architecture/adrs/011-pgai-integration.md +494 -0
- data/docs/architecture/adrs/index.md +215 -0
- data/docs/architecture/hive-mind.md +736 -0
- data/docs/architecture/index.md +351 -0
- data/docs/architecture/overview.md +538 -0
- data/docs/architecture/two-tier-memory.md +873 -0
- data/docs/assets/css/custom.css +83 -0
- data/docs/assets/images/htm-core-components.svg +63 -0
- data/docs/assets/images/htm-database-schema.svg +93 -0
- data/docs/assets/images/htm-hive-mind-architecture.svg +125 -0
- data/docs/assets/images/htm-importance-scoring-framework.svg +83 -0
- data/docs/assets/images/htm-layered-architecture.svg +71 -0
- data/docs/assets/images/htm-long-term-memory-architecture.svg +115 -0
- data/docs/assets/images/htm-working-memory-architecture.svg +120 -0
- data/docs/assets/images/htm.jpg +0 -0
- data/docs/assets/images/htm_demo.gif +0 -0
- data/docs/assets/js/mathjax.js +18 -0
- data/docs/assets/videos/htm_video.mp4 +0 -0
- data/docs/database_rake_tasks.md +322 -0
- data/docs/development/contributing.md +787 -0
- data/docs/development/index.md +336 -0
- data/docs/development/schema.md +596 -0
- data/docs/development/setup.md +719 -0
- data/docs/development/testing.md +819 -0
- data/docs/guides/adding-memories.md +824 -0
- data/docs/guides/context-assembly.md +1009 -0
- data/docs/guides/getting-started.md +577 -0
- data/docs/guides/index.md +118 -0
- data/docs/guides/long-term-memory.md +941 -0
- data/docs/guides/multi-robot.md +866 -0
- data/docs/guides/recalling-memories.md +927 -0
- data/docs/guides/search-strategies.md +953 -0
- data/docs/guides/working-memory.md +717 -0
- data/docs/index.md +214 -0
- data/docs/installation.md +477 -0
- data/docs/multi_framework_support.md +519 -0
- data/docs/quick-start.md +655 -0
- data/docs/setup_local_database.md +302 -0
- data/docs/using_rake_tasks_in_your_app.md +383 -0
- data/examples/basic_usage.rb +93 -0
- data/examples/cli_app/README.md +317 -0
- data/examples/cli_app/htm_cli.rb +270 -0
- data/examples/custom_llm_configuration.rb +183 -0
- data/examples/example_app/Rakefile +71 -0
- data/examples/example_app/app.rb +206 -0
- data/examples/sinatra_app/Gemfile +21 -0
- data/examples/sinatra_app/app.rb +335 -0
- data/lib/htm/active_record_config.rb +113 -0
- data/lib/htm/configuration.rb +342 -0
- data/lib/htm/database.rb +594 -0
- data/lib/htm/embedding_service.rb +115 -0
- data/lib/htm/errors.rb +34 -0
- data/lib/htm/job_adapter.rb +154 -0
- data/lib/htm/jobs/generate_embedding_job.rb +65 -0
- data/lib/htm/jobs/generate_tags_job.rb +82 -0
- data/lib/htm/long_term_memory.rb +965 -0
- data/lib/htm/models/node.rb +109 -0
- data/lib/htm/models/node_tag.rb +33 -0
- data/lib/htm/models/robot.rb +52 -0
- data/lib/htm/models/tag.rb +76 -0
- data/lib/htm/railtie.rb +76 -0
- data/lib/htm/sinatra.rb +157 -0
- data/lib/htm/tag_service.rb +135 -0
- data/lib/htm/tasks.rb +38 -0
- data/lib/htm/version.rb +5 -0
- data/lib/htm/working_memory.rb +182 -0
- data/lib/htm.rb +400 -0
- data/lib/tasks/db.rake +19 -0
- data/lib/tasks/htm.rake +147 -0
- data/lib/tasks/jobs.rake +312 -0
- data/mkdocs.yml +190 -0
- data/scripts/install_local_database.sh +309 -0
- metadata +341 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
# ADR-012: LLM-Driven Ontology Topic Extraction
|
|
2
|
+
|
|
3
|
+
**Status**: ~~Accepted~~ **PARTIALLY SUPERSEDED** (2025-10-27)
|
|
4
|
+
|
|
5
|
+
**Date**: 2025-10-26
|
|
6
|
+
|
|
7
|
+
**Decision Makers**: Dewayne VanHoozer, Claude (Anthropic)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ⚠️ IMPLEMENTATION STATUS (2025-10-27)
|
|
12
|
+
|
|
13
|
+
**Database-side topic extraction via pgai has been removed** following the reversal of ADR-011.
|
|
14
|
+
|
|
15
|
+
**What Remains**:
|
|
16
|
+
- The ontology concept (hierarchical topics in `root:level1:level2` format)
|
|
17
|
+
- Database schema support (tags table, ontology views)
|
|
18
|
+
- Manual topic tagging capability
|
|
19
|
+
|
|
20
|
+
**What Was Removed**:
|
|
21
|
+
- Automatic LLM-driven topic extraction via pgai triggers
|
|
22
|
+
- Database-side LLM calls using `ai.ollama_generate()`
|
|
23
|
+
- Automatic topic generation on INSERT/UPDATE
|
|
24
|
+
|
|
25
|
+
**Future Consideration**: Client-side LLM topic extraction may be implemented in the future, but the database-side approach proved impractical due to pgai installation issues (see ADR-011 reversal).
|
|
26
|
+
|
|
27
|
+
**Related Change (2025-10-28)**: The TimescaleDB extension was also removed from HTM as it was not providing sufficient value for proof-of-concept applications. See [ADR-001](001-use-postgresql-timescaledb-storage.md) for details.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Original Quick Summary (Historical)
|
|
32
|
+
|
|
33
|
+
HTM uses **LLM-driven hierarchical topic extraction** via pgai database triggers to automatically generate ontological tags from node content, creating an emergent knowledge structure.
|
|
34
|
+
|
|
35
|
+
**Why**: Automatic topic extraction builds a browsable ontology that complements vector embeddings, enabling both structured navigation and semantic discovery. Database-side extraction via pgai follows the same proven pattern as embedding generation (ADR-011).
|
|
36
|
+
|
|
37
|
+
**Impact**: Nodes automatically tagged with hierarchical topics (e.g., `database:postgresql:performance`), enabling ontology-based navigation alongside vector similarity search.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Context
|
|
42
|
+
|
|
43
|
+
### Problem: Limited Content Organization
|
|
44
|
+
|
|
45
|
+
HTM currently provides two organization mechanisms:
|
|
46
|
+
|
|
47
|
+
1. **Vector embeddings** (ADR-011): Semantic similarity search
|
|
48
|
+
- Excellent for: "Find things like this"
|
|
49
|
+
- Limitation: No visible structure, discovery-only
|
|
50
|
+
|
|
51
|
+
2. **Flat category field**: Single-level classification
|
|
52
|
+
- Simple but limiting: Cannot represent hierarchies
|
|
53
|
+
- Inconsistent: Manual categorization prone to errors
|
|
54
|
+
- Redundant: Duplicates root-level topic information
|
|
55
|
+
|
|
56
|
+
### The Ontology Gap
|
|
57
|
+
|
|
58
|
+
**Symbolic vs. Sub-symbolic Retrieval**:
|
|
59
|
+
|
|
60
|
+
| Aspect | Vector Embeddings (Sub-symbolic) | Ontological Tags (Symbolic) |
|
|
61
|
+
|--------|----------------------------------|----------------------------|
|
|
62
|
+
| **Nature** | Implicit semantic representation | Explicit hierarchical structure |
|
|
63
|
+
| **Structure** | No visible structure | Browsable hierarchy |
|
|
64
|
+
| **Retrieval** | Fuzzy, associative | Precise, categorical |
|
|
65
|
+
| **Use Case** | "Find similar content" | "Show me all about X" |
|
|
66
|
+
| **Interpretability** | Opaque numbers | Clear semantic meaning |
|
|
67
|
+
|
|
68
|
+
**They are complementary, not redundant**:
|
|
69
|
+
- Tags = Vision (see the structure)
|
|
70
|
+
- Embeddings = Intuition (feel the similarity)
|
|
71
|
+
|
|
72
|
+
### Hierarchical Topics Concept
|
|
73
|
+
|
|
74
|
+
Format: `root:level1:level2:level3...`
|
|
75
|
+
|
|
76
|
+
Examples:
|
|
77
|
+
```
|
|
78
|
+
database:postgresql:timescaledb:hypertables
|
|
79
|
+
database:postgresql:pgvector:indexes
|
|
80
|
+
ai:embeddings:ollama:nomic-embed-text
|
|
81
|
+
ai:llm:providers:anthropic
|
|
82
|
+
programming:ruby:gems:htm
|
|
83
|
+
performance:optimization:database
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Benefits:
|
|
87
|
+
- **Multiple classification paths**: Same content can have multiple topic hierarchies
|
|
88
|
+
- **Browsable structure**: Navigate from broad to specific
|
|
89
|
+
- **Semantic relationships**: Hierarchy encodes is-a relationships
|
|
90
|
+
- **Machine-readable**: Colon-separated format enables programmatic processing
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Decision
|
|
95
|
+
|
|
96
|
+
We will implement **automatic LLM-driven topic extraction** using pgai database triggers, following the same architectural pattern as embedding generation (ADR-011).
|
|
97
|
+
|
|
98
|
+
### Implementation Strategy
|
|
99
|
+
|
|
100
|
+
**1. Database Trigger for Topic Extraction**
|
|
101
|
+
|
|
102
|
+
```sql
|
|
103
|
+
CREATE OR REPLACE FUNCTION extract_ontology_topics()
|
|
104
|
+
RETURNS TRIGGER AS $$
|
|
105
|
+
DECLARE
|
|
106
|
+
topic_provider TEXT;
|
|
107
|
+
topic_model TEXT;
|
|
108
|
+
base_url TEXT;
|
|
109
|
+
llm_prompt TEXT;
|
|
110
|
+
llm_response TEXT;
|
|
111
|
+
extracted_topics TEXT[];
|
|
112
|
+
topic TEXT;
|
|
113
|
+
BEGIN
|
|
114
|
+
-- Get configuration from session variables
|
|
115
|
+
topic_provider := COALESCE(current_setting('htm.topic_provider', true), 'ollama');
|
|
116
|
+
topic_model := COALESCE(current_setting('htm.topic_model', true), 'llama3');
|
|
117
|
+
base_url := COALESCE(current_setting('htm.topic_base_url', true), 'http://localhost:11434');
|
|
118
|
+
|
|
119
|
+
-- Build prompt for LLM
|
|
120
|
+
llm_prompt := 'Extract hierarchical topic tags from this text.
|
|
121
|
+
Format as colon-separated paths (e.g., database:postgresql:performance).
|
|
122
|
+
Use lowercase with hyphens for multi-word terms.
|
|
123
|
+
Return ONLY the topic tags, one per line, no explanations.
|
|
124
|
+
Maximum depth: 5 levels.
|
|
125
|
+
|
|
126
|
+
Text: ' || NEW.value;
|
|
127
|
+
|
|
128
|
+
-- Call LLM via pgai to extract topics
|
|
129
|
+
IF topic_provider = 'ollama' THEN
|
|
130
|
+
llm_response := ai.ollama_generate(
|
|
131
|
+
topic_model,
|
|
132
|
+
llm_prompt,
|
|
133
|
+
system_prompt => 'You are a precise topic extraction system. Output only topic tags in the format root:subtopic:detail.',
|
|
134
|
+
host => base_url
|
|
135
|
+
)->>'response';
|
|
136
|
+
END IF;
|
|
137
|
+
|
|
138
|
+
-- Parse response and insert validated topics into tags table
|
|
139
|
+
extracted_topics := string_to_array(trim(llm_response), E'\n');
|
|
140
|
+
|
|
141
|
+
FOREACH topic IN ARRAY extracted_topics LOOP
|
|
142
|
+
topic := trim(topic);
|
|
143
|
+
IF topic ~ '^[a-z0-9\-]+(:[a-z0-9\-]+)*$' THEN
|
|
144
|
+
INSERT INTO tags (node_id, tag, created_at)
|
|
145
|
+
VALUES (NEW.id, topic, CURRENT_TIMESTAMP)
|
|
146
|
+
ON CONFLICT (node_id, tag) DO NOTHING;
|
|
147
|
+
END IF;
|
|
148
|
+
END LOOP;
|
|
149
|
+
|
|
150
|
+
RETURN NEW;
|
|
151
|
+
EXCEPTION
|
|
152
|
+
WHEN OTHERS THEN
|
|
153
|
+
RAISE WARNING 'Topic extraction failed for node %: %', NEW.id, SQLERRM;
|
|
154
|
+
RETURN NEW;
|
|
155
|
+
END;
|
|
156
|
+
$$ LANGUAGE plpgsql;
|
|
157
|
+
|
|
158
|
+
CREATE TRIGGER nodes_extract_topics
|
|
159
|
+
AFTER INSERT OR UPDATE OF value ON nodes
|
|
160
|
+
FOR EACH ROW
|
|
161
|
+
EXECUTE FUNCTION extract_ontology_topics();
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**2. Configuration via Session Variables**
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
# In LongTermMemory#initialize
|
|
168
|
+
def configure_topic_settings(conn)
|
|
169
|
+
provider = ENV.fetch('HTM_TOPIC_PROVIDER', 'ollama')
|
|
170
|
+
model = ENV.fetch('HTM_TOPIC_MODEL', 'llama3')
|
|
171
|
+
base_url = ENV.fetch('HTM_TOPIC_BASE_URL', 'http://localhost:11434')
|
|
172
|
+
|
|
173
|
+
conn.exec_params("SELECT set_config('htm.topic_provider', $1, false)", [provider])
|
|
174
|
+
conn.exec_params("SELECT set_config('htm.topic_model', $1, false)", [model])
|
|
175
|
+
conn.exec_params("SELECT set_config('htm.topic_base_url', $1, false)", [base_url])
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**3. Environment Variables**
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# .envrc
|
|
183
|
+
export HTM_TOPIC_PROVIDER=ollama
|
|
184
|
+
export HTM_TOPIC_MODEL=llama3
|
|
185
|
+
export HTM_TOPIC_BASE_URL=http://localhost:11434
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**4. Ontology Exploration Views**
|
|
189
|
+
|
|
190
|
+
```sql
|
|
191
|
+
-- View the ontology structure
|
|
192
|
+
CREATE OR REPLACE VIEW ontology_structure AS
|
|
193
|
+
SELECT
|
|
194
|
+
split_part(tag, ':', 1) AS root_topic,
|
|
195
|
+
split_part(tag, ':', 2) AS level1_topic,
|
|
196
|
+
split_part(tag, ':', 3) AS level2_topic,
|
|
197
|
+
tag AS full_path,
|
|
198
|
+
COUNT(DISTINCT node_id) AS node_count
|
|
199
|
+
FROM tags
|
|
200
|
+
WHERE tag ~ '^[a-z0-9\-]+(:[a-z0-9\-]+)*$'
|
|
201
|
+
GROUP BY tag
|
|
202
|
+
ORDER BY root_topic, level1_topic, level2_topic;
|
|
203
|
+
|
|
204
|
+
-- View topic relationships (co-occurrence)
|
|
205
|
+
CREATE OR REPLACE VIEW topic_relationships AS
|
|
206
|
+
SELECT
|
|
207
|
+
t1.tag AS topic1,
|
|
208
|
+
t2.tag AS topic2,
|
|
209
|
+
COUNT(DISTINCT t1.node_id) AS shared_nodes
|
|
210
|
+
FROM tags t1
|
|
211
|
+
JOIN tags t2 ON t1.node_id = t2.node_id AND t1.tag < t2.tag
|
|
212
|
+
GROUP BY t1.tag, t2.tag
|
|
213
|
+
HAVING COUNT(DISTINCT t1.node_id) >= 2
|
|
214
|
+
ORDER BY shared_nodes DESC;
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Rationale
|
|
220
|
+
|
|
221
|
+
### Why LLM-Driven Extraction?
|
|
222
|
+
|
|
223
|
+
**Consistency**:
|
|
224
|
+
- LLM applies uniform reasoning about topic hierarchies
|
|
225
|
+
- No human inconsistency in categorization
|
|
226
|
+
- Same text always produces same topics (with temperature=0)
|
|
227
|
+
|
|
228
|
+
**Depth**:
|
|
229
|
+
- Identifies topics at multiple abstraction levels simultaneously
|
|
230
|
+
- Captures nuanced semantic relationships
|
|
231
|
+
- Represents complex subjects with multiple classification paths
|
|
232
|
+
|
|
233
|
+
**Discovery**:
|
|
234
|
+
- Finds classifications humans might miss
|
|
235
|
+
- Identifies cross-domain connections
|
|
236
|
+
- Reveals implicit semantic structure
|
|
237
|
+
|
|
238
|
+
**Evolution**:
|
|
239
|
+
- Ontology emerges organically from collective knowledge
|
|
240
|
+
- No manual taxonomy maintenance
|
|
241
|
+
- Adapts to new domains automatically
|
|
242
|
+
|
|
243
|
+
### Why Database-Side via pgai?
|
|
244
|
+
|
|
245
|
+
Following the proven pattern from ADR-011 (embedding generation):
|
|
246
|
+
|
|
247
|
+
**Performance**:
|
|
248
|
+
- No Ruby HTTP overhead
|
|
249
|
+
- Connection reuse to LLM provider
|
|
250
|
+
- Parallel execution via connection pool
|
|
251
|
+
|
|
252
|
+
**Simplicity**:
|
|
253
|
+
- Automatic on INSERT/UPDATE
|
|
254
|
+
- No application-side topic management
|
|
255
|
+
- Fewer bugs (can't forget to extract topics)
|
|
256
|
+
|
|
257
|
+
**Consistency**:
|
|
258
|
+
- Same LLM model for all operations
|
|
259
|
+
- Centralized configuration
|
|
260
|
+
- Automatic for all nodes
|
|
261
|
+
|
|
262
|
+
### Complementary to Vector Embeddings
|
|
263
|
+
|
|
264
|
+
**Use Both Together**:
|
|
265
|
+
|
|
266
|
+
1. **Filtered Similarity**: "Find similar nodes, but only within `database:postgresql`"
|
|
267
|
+
- Topic filtering provides scope
|
|
268
|
+
- Embedding search finds semantic matches within scope
|
|
269
|
+
|
|
270
|
+
2. **Cross-Topic Discovery**: "This node is about database performance, find similar concepts in other domains"
|
|
271
|
+
- Embedding search finds semantically similar concepts
|
|
272
|
+
- Topic tags reveal they're in unexpected domains (e.g., `ai:model-optimization`)
|
|
273
|
+
|
|
274
|
+
3. **Ontology Validation**:
|
|
275
|
+
- If nodes with very different topics have similar embeddings
|
|
276
|
+
- Suggests ontology might need refinement
|
|
277
|
+
- Indicates concepts are more related than tags suggest
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Consequences
|
|
282
|
+
|
|
283
|
+
### Positive
|
|
284
|
+
|
|
285
|
+
- **Emergent ontology**: Knowledge structure discovered through AI analysis
|
|
286
|
+
- **Automatic tagging**: No manual categorization required
|
|
287
|
+
- **Hierarchical navigation**: Browse from broad to specific
|
|
288
|
+
- **Multi-perspective**: Same content tagged from different angles
|
|
289
|
+
- **Complementary to embeddings**: Symbolic + sub-symbolic retrieval
|
|
290
|
+
- **Pattern recognition**: Ontology reveals knowledge base emphasis
|
|
291
|
+
- **Cross-pollination**: Unexpected connections across topics
|
|
292
|
+
- **Consistency**: LLM applies uniform topic extraction logic
|
|
293
|
+
|
|
294
|
+
### Negative
|
|
295
|
+
|
|
296
|
+
- **LLM dependency**: Requires running LLM (Ollama/OpenAI)
|
|
297
|
+
- **Cost**: API calls for topic extraction (if using OpenAI)
|
|
298
|
+
- **Latency**: LLM call adds time to INSERT operations
|
|
299
|
+
- **Quality variation**: LLM mistakes in topic extraction
|
|
300
|
+
- **Ontology churn**: Inconsistent topics if LLM behavior changes
|
|
301
|
+
- **Debugging complexity**: Topic extraction errors in database triggers
|
|
302
|
+
|
|
303
|
+
### Neutral
|
|
304
|
+
|
|
305
|
+
- **Configuration**: Environment variables for topic settings
|
|
306
|
+
- **Storage**: Topics stored in existing tags table
|
|
307
|
+
- **Category field**: Becomes redundant with root topics (future: remove)
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Migration Path
|
|
312
|
+
|
|
313
|
+
### For New Installations
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# 1. Set environment variables
|
|
317
|
+
export HTM_TOPIC_PROVIDER=ollama
|
|
318
|
+
export HTM_TOPIC_MODEL=llama3
|
|
319
|
+
export HTM_TOPIC_BASE_URL=http://localhost:11434
|
|
320
|
+
|
|
321
|
+
# 2. Run migrations
|
|
322
|
+
HTM::Database.migrate
|
|
323
|
+
|
|
324
|
+
# 3. Use HTM normally - topics extracted automatically!
|
|
325
|
+
htm.add_node('memory_001', 'PostgreSQL with TimescaleDB handles time-series data efficiently')
|
|
326
|
+
# Topics automatically extracted: database:postgresql, database:timescaledb, performance:time-series
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### For Existing Installations
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
# 1. Run migration to add topic extraction trigger
|
|
333
|
+
HTM::Database.migrate
|
|
334
|
+
|
|
335
|
+
# 2. (Optional) Re-extract topics for existing nodes
|
|
336
|
+
psql $HTM_DBURL -c "UPDATE nodes SET value = value;"
|
|
337
|
+
# Triggers topic extraction for all existing nodes
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Risks and Mitigations
|
|
343
|
+
|
|
344
|
+
### Risk: LLM Produces Inconsistent Topics
|
|
345
|
+
|
|
346
|
+
**Likelihood**: Medium (LLM output varies)
|
|
347
|
+
|
|
348
|
+
**Impact**: Medium (ontology quality degrades)
|
|
349
|
+
|
|
350
|
+
**Mitigation**:
|
|
351
|
+
- Use temperature=0 for deterministic output
|
|
352
|
+
- Validate topic format with regex before inserting
|
|
353
|
+
- Log invalid topics as warnings
|
|
354
|
+
- Provide ontology refinement tools (future)
|
|
355
|
+
|
|
356
|
+
### Risk: Topic Extraction Fails
|
|
357
|
+
|
|
358
|
+
**Likelihood**: Medium (LLM downtime, errors)
|
|
359
|
+
|
|
360
|
+
**Impact**: Low (node insert succeeds without topics)
|
|
361
|
+
|
|
362
|
+
**Mitigation**:
|
|
363
|
+
- Exception handler in trigger (warn but don't fail)
|
|
364
|
+
- Retry logic for transient failures (future)
|
|
365
|
+
- Batch re-extraction for failed nodes (future)
|
|
366
|
+
|
|
367
|
+
### Risk: Performance Degradation on Bulk Inserts
|
|
368
|
+
|
|
369
|
+
**Likelihood**: High (LLM call per node)
|
|
370
|
+
|
|
371
|
+
**Impact**: High (batch operations much slower)
|
|
372
|
+
|
|
373
|
+
**Mitigation**:
|
|
374
|
+
- Disable trigger for bulk imports (ALTER TABLE ... DISABLE TRIGGER)
|
|
375
|
+
- Batch topic extraction after import
|
|
376
|
+
- Document bulk import best practices
|
|
377
|
+
|
|
378
|
+
### Risk: Ontology Quality Issues
|
|
379
|
+
|
|
380
|
+
**Likelihood**: Medium (LLM interpretation varies)
|
|
381
|
+
|
|
382
|
+
**Impact**: Medium (navigation less useful)
|
|
383
|
+
|
|
384
|
+
**Mitigation**:
|
|
385
|
+
- Provide ontology visualization tools
|
|
386
|
+
- Enable manual topic correction/refinement
|
|
387
|
+
- Ontology review and consolidation process (future)
|
|
388
|
+
- Allow providing existing ontology context to LLM (future)
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Future Enhancements
|
|
393
|
+
|
|
394
|
+
### 1. Ontology-Aware Extraction
|
|
395
|
+
|
|
396
|
+
```sql
|
|
397
|
+
-- Provide existing ontology to LLM for consistency
|
|
398
|
+
llm_prompt := 'Existing topics in ontology: ' || existing_topics_summary || '
|
|
399
|
+
|
|
400
|
+
Extract hierarchical topics for this text...';
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### 2. Topic Confidence Scores
|
|
404
|
+
|
|
405
|
+
```sql
|
|
406
|
+
-- Store confidence alongside topics
|
|
407
|
+
ALTER TABLE tags ADD COLUMN confidence REAL;
|
|
408
|
+
INSERT INTO tags (node_id, tag, confidence) VALUES (...);
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### 3. Ontology Refinement Tools
|
|
412
|
+
|
|
413
|
+
```ruby
|
|
414
|
+
class OntologyRefiner
|
|
415
|
+
def suggest_merges # Identify similar topic branches to merge
|
|
416
|
+
def detect_orphans # Find single-use topics to consolidate
|
|
417
|
+
def validate_hierarchy # Check for hierarchy inconsistencies
|
|
418
|
+
end
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### 4. Category Field Removal
|
|
422
|
+
|
|
423
|
+
```sql
|
|
424
|
+
-- Remove redundant category field
|
|
425
|
+
ALTER TABLE nodes DROP COLUMN category;
|
|
426
|
+
|
|
427
|
+
-- Derive category from root topic
|
|
428
|
+
CREATE VIEW nodes_with_category AS
|
|
429
|
+
SELECT *, split_part(tags.tag, ':', 1) AS category
|
|
430
|
+
FROM nodes
|
|
431
|
+
LEFT JOIN tags ON nodes.id = tags.node_id;
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### 5. Batch Topic Extraction Mode
|
|
435
|
+
|
|
436
|
+
```sql
|
|
437
|
+
-- Async topic extraction for performance
|
|
438
|
+
CREATE TABLE topic_extraction_queue (
|
|
439
|
+
node_id BIGINT,
|
|
440
|
+
status TEXT,
|
|
441
|
+
created_at TIMESTAMP
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
-- Background worker processes queue
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## Alternatives Comparison
|
|
450
|
+
|
|
451
|
+
| Approach | Quality | Performance | Maintainability | Decision |
|
|
452
|
+
|----------|---------|-------------|-----------------|----------|
|
|
453
|
+
| **LLM Database Triggers** | **High** | **Medium** | **Good** | **ACCEPTED** |
|
|
454
|
+
| Manual categorization | Low | Fast | Poor | Rejected |
|
|
455
|
+
| Keyword extraction | Medium | Fast | Good | Rejected |
|
|
456
|
+
| Topic modeling (LDA) | Medium | Slow | Poor | Rejected |
|
|
457
|
+
| Ruby-side LLM extraction | High | Slow | Medium | Rejected |
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## References
|
|
462
|
+
|
|
463
|
+
- [ontology_notes.md](../../../ontology_notes.md) - Detailed ontology design discussion
|
|
464
|
+
- [ADR-011: Database-Side Embedding Generation](011-database-side-embedding-generation-with-pgai.md) - Same architectural pattern
|
|
465
|
+
- [pgai Documentation](https://github.com/timescale/pgai) - LLM integration via pgai
|
|
466
|
+
- [Knowledge Graph Concepts](https://en.wikipedia.org/wiki/Ontology_(information_science))
|
|
467
|
+
- [Folksonomy](https://en.wikipedia.org/wiki/Folksonomy) - Emergent classification systems
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## Review Notes
|
|
472
|
+
|
|
473
|
+
**AI Engineer**: LLM-driven ontology is the right approach. Emergent structure beats manual taxonomy.
|
|
474
|
+
|
|
475
|
+
**Database Architect**: Following ADR-011 pattern is smart. Consider async extraction for bulk operations.
|
|
476
|
+
|
|
477
|
+
**Knowledge Engineer**: Hierarchical topics + vector embeddings = powerful combination. Validates ontology with similarity.
|
|
478
|
+
|
|
479
|
+
**Systems Architect**: Database-side extraction maintains consistency. Separation of concerns.
|
|
480
|
+
|
|
481
|
+
**Ruby Developer**: Environment variable configuration is clean. Session variables pattern works well.
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
## Related ADRs
|
|
486
|
+
|
|
487
|
+
Updates:
|
|
488
|
+
- [ADR-011: Database-Side Embedding Generation](011-database-side-embedding-generation-with-pgai.md) - Same architectural pattern
|
|
489
|
+
|
|
490
|
+
Future:
|
|
491
|
+
- ADR-013: Category Field Removal (pending - after ontology validation)
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Reversal Details (2025-10-27)
|
|
496
|
+
|
|
497
|
+
### Why the Partial Reversal?
|
|
498
|
+
|
|
499
|
+
Following the reversal of ADR-011, the pgai-based automatic topic extraction was also removed because:
|
|
500
|
+
|
|
501
|
+
1. **Dependency on pgai**: Topic extraction relied on `ai.ollama_generate()` function from pgai
|
|
502
|
+
2. **Installation Issues**: Same pgai installation problems that affected embedding generation
|
|
503
|
+
3. **Consistency**: Better to have a unified approach (no database-side LLM calls)
|
|
504
|
+
4. **Architectural Simplicity**: Avoid split between database-side and client-side processing
|
|
505
|
+
|
|
506
|
+
### What Remains
|
|
507
|
+
|
|
508
|
+
The **ontology concept is still valid and valuable**:
|
|
509
|
+
- Tags table supports hierarchical topic paths (`root:level1:level2`)
|
|
510
|
+
- Ontology structure views provide navigation
|
|
511
|
+
- Manual topic tagging works
|
|
512
|
+
- Applications can still implement topic extraction client-side
|
|
513
|
+
|
|
514
|
+
### Future Direction: Client-Side Topic Extraction
|
|
515
|
+
|
|
516
|
+
A future implementation may add client-side LLM-driven topic extraction:
|
|
517
|
+
|
|
518
|
+
```ruby
|
|
519
|
+
class TopicExtractor
|
|
520
|
+
def initialize(llm_provider: :ollama, model: 'llama3')
|
|
521
|
+
@provider = llm_provider
|
|
522
|
+
@model = model
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def extract_topics(content)
|
|
526
|
+
# Generate prompt
|
|
527
|
+
prompt = build_extraction_prompt(content)
|
|
528
|
+
|
|
529
|
+
# Call LLM (Ruby-side)
|
|
530
|
+
response = call_llm(prompt)
|
|
531
|
+
|
|
532
|
+
# Parse and validate topics
|
|
533
|
+
parse_topics(response)
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
private
|
|
537
|
+
|
|
538
|
+
def build_extraction_prompt(content)
|
|
539
|
+
<<~PROMPT
|
|
540
|
+
Extract hierarchical topic tags from this text.
|
|
541
|
+
Format: root:level1:level2 (lowercase, hyphens for spaces)
|
|
542
|
+
Maximum depth: 5 levels
|
|
543
|
+
|
|
544
|
+
Text: #{content}
|
|
545
|
+
PROMPT
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# Usage in HTM
|
|
550
|
+
topics = topic_extractor.extract_topics(content)
|
|
551
|
+
htm.add_message(content, tags: topics, ...)
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
**Benefits of Client-Side Approach**:
|
|
555
|
+
- ✅ No database extension dependencies
|
|
556
|
+
- ✅ Easier debugging (errors in Ruby)
|
|
557
|
+
- ✅ More flexible (can modify extraction logic easily)
|
|
558
|
+
- ✅ Works on all platforms
|
|
559
|
+
- ✅ Can provide context (existing ontology) to LLM more easily
|
|
560
|
+
|
|
561
|
+
**Trade-offs**:
|
|
562
|
+
- ❌ Slightly slower (HTTP call from Ruby)
|
|
563
|
+
- ❌ Not automatic on UPDATE (must be called explicitly)
|
|
564
|
+
- ❌ Application must manage topic extraction lifecycle
|
|
565
|
+
|
|
566
|
+
### Current Recommendation
|
|
567
|
+
|
|
568
|
+
**For now**: Use manual topic tagging via the `tags` parameter:
|
|
569
|
+
```ruby
|
|
570
|
+
htm.add_message(
|
|
571
|
+
"PostgreSQL with TimescaleDB handles time-series efficiently",
|
|
572
|
+
tags: ["database:postgresql", "database:timescaledb", "performance:time-series"]
|
|
573
|
+
)
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
**Future**: Implement optional client-side topic extraction for automatic tagging
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## Changelog
|
|
581
|
+
|
|
582
|
+
- **2025-10-27**: **DATABASE-SIDE EXTRACTION REMOVED** - Removed pgai-based automatic extraction following ADR-011 reversal. Ontology concept and schema remain.
|
|
583
|
+
- **2025-10-26**: Initial version - LLM-driven ontology topic extraction with pgai triggers
|