File size: 7,584 Bytes
8176754
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
"""
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
        }