conceptvector / analysis /graph_generator.py
Tawhid Bin Omar
Initial deployment of RealityCheck AI backend
8176754
"""
Concept Graph Generator
Generates canonical concept dependency graphs for given concepts
"""
from typing import Dict, List
import os
import requests
import json
class ConceptGraphGenerator:
def __init__(self):
self.hf_api_key = os.getenv('HUGGINGFACE_API_KEY')
self.llm_endpoint = "https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.2"
self._ready = True
def is_ready(self) -> bool:
return self._ready
async def generate_graph(self, concept: str) -> Dict:
"""
Generate canonical concept dependency graph
Returns:
{
'nodes': [{'id': str, 'label': str, 'level': int}],
'edges': [{'source': str, 'target': str, 'relationship': str}]
}
"""
# Use LLM to generate concept structure
graph_structure = await self._llm_generate_structure(concept)
# Validate and format
return self._format_graph(graph_structure, concept)
async def _llm_generate_structure(self, concept: str) -> Dict:
"""Use LLM to generate concept prerequisite structure"""
prompt = f"""<s>[INST] You are a concept structure expert. For the concept "{concept}", identify the core prerequisite concepts that must be understood first, and their relationships.
Output a JSON structure with:
1. "prerequisites": list of prerequisite concepts needed to understand {concept}
2. "core_components": main parts/aspects of {concept} itself
3. "relationships": how concepts connect (prerequisite, causal, etc.)
Be precise and pedagogical. Focus on understanding order.
Output only valid JSON, no other text. [/INST]"""
try:
headers = {"Authorization": f"Bearer {self.hf_api_key}"}
payload = {
"inputs": prompt,
"parameters": {
"max_new_tokens": 800,
"temperature": 0.4,
"return_full_text": False
}
}
response = requests.post(self.llm_endpoint, headers=headers, json=payload, timeout=30)
if response.status_code == 200:
result = response.json()
text = result[0]['generated_text'] if isinstance(result, list) else result.get('generated_text', '')
# Try to parse JSON from response
try:
# Extract JSON if wrapped in other text
start = text.find('{')
end = text.rfind('}') + 1
if start != -1 and end > start:
json_str = text[start:end]
return json.loads(json_str)
except:
pass
return self._fallback_graph(concept)
else:
return self._fallback_graph(concept)
except Exception as e:
print(f"Graph generation error: {e}")
return self._fallback_graph(concept)
def _fallback_graph(self, concept: str) -> Dict:
"""Fallback: create a basic graph structure"""
# Predefined templates for common concepts
templates = {
'entropy': {
'prerequisites': ['energy', 'system states', 'probability'],
'core_components': ['disorder measure', 'thermodynamic entropy', 'information entropy'],
'relationships': [
('energy', 'entropy', 'prerequisite'),
('system states', 'entropy', 'prerequisite'),
('probability', 'entropy', 'prerequisite')
]
},
'neural networks': {
'prerequisites': ['linear algebra', 'calculus', 'probability'],
'core_components': ['neurons', 'layers', 'weights', 'activation functions', 'backpropagation'],
'relationships': [
('linear algebra', 'neural networks', 'prerequisite'),
('neurons', 'layers', 'component'),
('weights', 'neurons', 'component'),
('backpropagation', 'weights', 'causal')
]
},
'photosynthesis': {
'prerequisites': ['energy', 'chemical reactions', 'cells'],
'core_components': ['light reactions', 'dark reactions', 'chlorophyll', 'glucose production'],
'relationships': [
('energy', 'light reactions', 'prerequisite'),
('light reactions', 'dark reactions', 'causal'),
('dark reactions', 'glucose production', 'causal')
]
}
}
# Check if concept matches template
concept_lower = concept.lower()
for key, template in templates.items():
if key in concept_lower:
return template
# Generic fallback
return {
'prerequisites': ['foundational knowledge'],
'core_components': [concept, f'{concept} principles', f'{concept} applications'],
'relationships': [
('foundational knowledge', concept, 'prerequisite')
]
}
def _format_graph(self, structure: Dict, concept: str) -> Dict:
"""Format graph structure for frontend"""
nodes = []
edges = []
node_id = 0
node_map = {}
# Add prerequisite nodes
for prereq in structure.get('prerequisites', []):
node_map[prereq] = f'node_{node_id}'
nodes.append({
'id': f'node_{node_id}',
'label': prereq,
'level': 0,
'type': 'prerequisite'
})
node_id += 1
# Add main concept node
node_map[concept] = f'node_{node_id}'
nodes.append({
'id': f'node_{node_id}',
'label': concept,
'level': 1,
'type': 'main'
})
concept_node_id = f'node_{node_id}'
node_id += 1
# Add core component nodes
for component in structure.get('core_components', []):
node_map[component] = f'node_{node_id}'
nodes.append({
'id': f'node_{node_id}',
'label': component,
'level': 2,
'type': 'component'
})
node_id += 1
# Add edges from relationships
for rel in structure.get('relationships', []):
if len(rel) >= 3:
source_key, target_key, rel_type = rel[0], rel[1], rel[2]
source_id = node_map.get(source_key, node_map.get(concept))
target_id = node_map.get(target_key, concept_node_id)
edges.append({
'source': source_id,
'target': target_id,
'relationship': rel_type
})
# Add default prerequisite edges if none exist
if not edges:
for prereq in structure.get('prerequisites', []):
edges.append({
'source': node_map[prereq],
'target': concept_node_id,
'relationship': 'prerequisite'
})
return {
'nodes': nodes,
'edges': edges,
'concept': concept
}