From 299d0104ddca8319de45f33ec836921b3c66c964 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Thu, 22 May 2025 11:43:38 -0700 Subject: [PATCH] Refactor concept extraction to use singleton concepts with separate relationship tracking. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplifies concept records to contain only text (making them reusable singletons) while moving relationship complexity to a new me.comind.relationship.concept lexicon. Updates conceptualizer to output structured concept-relationship pairs instead of embedded connection metadata. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- lexicons/me/comind/concept.json | 9 +-- lexicons/me/comind/relationship/concept.json | 46 ++++++++++++++++ prompts/cominds/conceptualizer.co | 32 ++++++++++- src/comind/comind.py | 58 ++++++++++++++++---- src/record_manager.py | 5 +- 5 files changed, 130 insertions(+), 20 deletions(-) create mode 100644 lexicons/me/comind/relationship/concept.json diff --git a/lexicons/me/comind/concept.json b/lexicons/me/comind/concept.json index 63dfa77..cce89b0 100644 --- a/lexicons/me/comind/concept.json +++ b/lexicons/me/comind/concept.json @@ -2,23 +2,18 @@ "lexicon": 1, "id": "me.comind.concept", "revision": 3, - "description": "A simplified concept node in the comind network. Contains a concept and its source. (PATTERN OF 'concept': [a-z0-9 ]+)", + "description": "A simplified concept node in the comind network. Contains only concept text. (PATTERN OF 'concept': [a-z0-9 ]+)", "defs": { "main": { "type": "record", "key": "tid", "record": { "type": "object", - "required": ["concept", "source"], + "required": ["concept"], "properties": { "concept": { "type": "string", "description": "The concept text. Must use only the characters [a-z0-9 ]. Should be a single word or phrase, like 'data', 'privacy', 'ai', 'security', 'social networks', etc." - }, - "source": { - "type": "ref", - "ref": "com.atproto.repo.strongRef", - "description": "Reference to the source record this concept was extracted from." } } } diff --git a/lexicons/me/comind/relationship/concept.json b/lexicons/me/comind/relationship/concept.json new file mode 100644 index 0000000..09422d8 --- /dev/null +++ b/lexicons/me/comind/relationship/concept.json @@ -0,0 +1,46 @@ +{ + "lexicon": 1, + "id": "me.comind.relationship.concept", + "revision": 1, + "description": "A relationship between a source record and a concept.", + "defs": { + "main": { + "type": "record", + "key": "cid", + "record": { + "type": "object", + "required": ["createdAt", "source", "target", "relationship"], + "properties": { + "createdAt": { + "type": "string", + "format": "datetime" + }, + "source": { + "type": "ref", + "ref": "com.atproto.repo.strongRef", + "description": "The source record that relates to the concept." + }, + "target": { + "type": "ref", + "ref": "com.atproto.repo.strongRef", + "description": "Reference to the concept record." + }, + "relationship": { + "type": "string", + "enum": [ + "RELATES_TO", + "DESCRIBES", + "MENTIONS", + "EXEMPLIFIES", + "CONTRADICTS", + "QUESTIONS", + "SUPPORTS", + "CRITIQUES" + ], + "description": "The type of relationship between the source and concept." + } + } + } + } + } +} \ No newline at end of file diff --git a/prompts/cominds/conceptualizer.co b/prompts/cominds/conceptualizer.co index d57c85b..d094b47 100644 --- a/prompts/cominds/conceptualizer.co +++ b/prompts/cominds/conceptualizer.co @@ -15,10 +15,40 @@ Your core perspective is CRITICAL - it determines which concepts you extract and ## Your task -Extract 5-15 simple concepts from content that connect to your core perspective. +Extract 5-15 simple concepts from content that connect to your core perspective, along with how each concept relates to the content. Concepts are short words or phrases (1-3 words) that must be lowercase and may contain spaces. They serve as semantic anchors connecting content across the network. +For each concept, specify the relationship type: +- RELATES_TO: Default catch-all relationship +- DESCRIBES: Source describes/explains the concept +- MENTIONS: Weak reference, just name-dropped +- EXEMPLIFIES: "This content EXEMPLIFIES the concept" +- CONTRADICTS: "This content CONTRADICTS the concept" +- QUESTIONS: "This content QUESTIONS the concept" +- SUPPORTS: "This content SUPPORTS the concept" +- CRITIQUES: "This content CRITIQUES the concept" + +Example output: +```json +{ + "concepts": [ + { + "text": "distributed systems", + "relationship": "DESCRIBES" + }, + { + "text": "network effects", + "relationship": "MENTIONS" + }, + { + "text": "centralization", + "relationship": "CRITIQUES" + } + ] +} +``` + ## Guidelines - **Your core perspective "{core_perspective}" shapes everything** - only extract concepts that relate to this perspective diff --git a/src/comind/comind.py b/src/comind/comind.py index 3a78c86..8ff74ad 100644 --- a/src/comind/comind.py +++ b/src/comind/comind.py @@ -300,15 +300,36 @@ class Conceptualizer(Comind): ) def schema(self): - # Simple schema for concept extraction + # Schema for concept extraction with relationships return { "type": "object", "properties": { "concepts": { "type": "array", "items": { - "type": "string", - "description": "A simple concept (1-3 words, lowercase, spaces allowed)", + "type": "object", + "properties": { + "text": { + "type": "string", + "pattern": "[a-z0-9 ]+", + "description": "A simple concept (1-3 words, lowercase, spaces allowed)", + }, + "relationship": { + "type": "string", + "enum": [ + "RELATES_TO", + "DESCRIBES", + "MENTIONS", + "EXEMPLIFIES", + "CONTRADICTS", + "QUESTIONS", + "SUPPORTS", + "CRITIQUES" + ], + "description": "The relationship between the source content and this concept", + } + }, + "required": ["text", "relationship"], }, "minItems": 5, "maxItems": 15, @@ -334,23 +355,25 @@ class Conceptualizer(Comind): """ Uploads the result to the Comind network. - The result is expected to be a list of concept strings. + The result is expected to be a list of concept objects with text and relationship fields. """ - # Load the concepts (now just strings) + # Load the concepts (now objects with text and relationship) concepts = result["concepts"] # Upload the concepts to the Comind network - for concept_text in concepts: - self.logger.info(f"[bold cyan]Concept:[/bold cyan] {concept_text}") + for concept_obj in concepts: + concept_text = concept_obj["text"] + relationship = concept_obj["relationship"] + + self.logger.info(f"[bold cyan]Concept:[/bold cyan] {concept_text} ({relationship})") - # Create the simplified concept record + # Create the simplified concept record (just concept text) concept_record = { "$type": "me.comind.concept", "concept": concept_text, - "source": target, # Reference to the source record } - # Upload the concept to the Comind network + # Check if concept already exists maybe_record = record_manager.try_get_record( "me.comind.concept", concept_text.lower().replace(" ", "-"), @@ -366,7 +389,22 @@ class Conceptualizer(Comind): ) self.logger.debug(f"Created new concept: {concept_text}") + # Create relationship record linking source to concept + relationship_record = { + "$type": "me.comind.relationship.concept", + "createdAt": datetime.now().isoformat(), + "source": target, + "target": concept_creation_result.uri, + "relationship": relationship, + } + + relationship_result = record_manager.create_record( + "me.comind.relationship.concept", + relationship_record, + ) + self.logger.debug(f"Concept result: {concept_creation_result}") + self.logger.debug(f"Relationship result: {relationship_result}") class Feeler(Comind): diff --git a/src/record_manager.py b/src/record_manager.py index 6faca27..3f01dc3 100644 --- a/src/record_manager.py +++ b/src/record_manager.py @@ -186,8 +186,9 @@ class RecordManager: create_params['rkey'] = create_params['record']['title'].lower().replace(" ", "-") if collection == "me.comind.concept": - # Set rkey to lowercase title with hyphens instead of spaces - create_params['rkey'] = create_params['record']['generated']['text'].lower().replace(" ", "-") + # Set rkey to lowercase concept with hyphens instead of spaces + # Updated to use the new simplified concept structure + create_params['rkey'] = create_params['record']['concept'].lower().replace(" ", "-") if rkey is not None: create_params['rkey'] = rkey -- 2.51.2