npcpy 1.2.21__py3-none-any.whl → 1.2.23__py3-none-any.whl

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.
npcpy/data/load.py CHANGED
@@ -132,7 +132,7 @@ def load_file_contents(file_path, chunk_size=None):
132
132
  elif file_ext in ['XLS', 'XLSX']:
133
133
  df = load_excel(file_path)
134
134
  full_content = df.to_string()
135
- elif file_ext in ['TXT', 'MD', 'PY', 'JSX', 'TSX', 'TS', 'JS', 'JSON', 'SQL', 'NPC', 'JINX', 'LINE', 'YAML']:
135
+ elif file_ext in ['TXT', 'MD', 'PY', 'JSX', 'TSX', 'TS', 'JS', 'JSON', 'SQL', 'NPC', 'JINX', 'LINE', 'YAML', 'DART', 'JAVA']:
136
136
  full_content = load_txt(file_path)
137
137
  elif file_ext == 'JSON':
138
138
  data = load_json(file_path)
npcpy/ft/diff.py CHANGED
@@ -1 +1,110 @@
1
- # finetuning diffuser models
1
+ # finetuning diffuser models
2
+ try:
3
+ import torch
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+ from torch.utils.data import DataLoader, Dataset as TorchDataset
7
+ from transformers import CLIPTextModel, CLIPTokenizer
8
+ except:
9
+ torch = None
10
+ nn = None
11
+ F = None
12
+ DataLoader = None
13
+ TorchDataset = None
14
+ CLIPTextModel = None
15
+ CLIPTokenizer = None
16
+ import math
17
+ from dataclasses import dataclass, field
18
+ from typing import List, Optional, Callable
19
+ import numpy as np
20
+ from PIL import Image
21
+ import os
22
+ from tqdm import tqdm
23
+ import gc
24
+
25
+
26
+ @dataclass
27
+ class DiffusionConfig:
28
+ image_size: int = 128
29
+ channels: int = 256
30
+ time_emb_dim: int = 128
31
+ timesteps: int = 1000
32
+ beta_start: float = 1e-4
33
+ beta_end: float = 0.02
34
+ num_epochs: int = 100
35
+ batch_size: int = 4
36
+ learning_rate: float = 1e-5
37
+ checkpoint_frequency: int = 1000
38
+ output_dir: str = "diffusion_model"
39
+ use_clip: bool = True
40
+ num_channels: int = 1
41
+
42
+
43
+ class SinusoidalPositionEmbeddings(nn.Module):
44
+
45
+ def __init__(self, dim):
46
+ super().__init__()
47
+ self.dim = dim
48
+
49
+ def forward(self, time):
50
+ device = time.device
51
+ half_dim = self.dim // 2
52
+ embeddings = math.log(10000) / (half_dim - 1)
53
+ embeddings = torch.exp(
54
+ torch.arange(half_dim, device=device) * -embeddings
55
+ )
56
+ embeddings = time[:, None] * embeddings[None, :]
57
+ embeddings = torch.cat(
58
+ (embeddings.sin(), embeddings.cos()),
59
+ dim=-1
60
+ )
61
+ return embeddings
62
+
63
+
64
+ class SimpleUNet(nn.Module):
65
+
66
+ def __init__(
67
+ self,
68
+ image_size=128,
69
+ channels=256,
70
+ time_emb_dim=128,
71
+ num_channels=1
72
+ ):
73
+ super().__init__()
74
+
75
+ self.image_size = image_size
76
+
77
+ self.time_mlp = nn.Sequential(
78
+ SinusoidalPositionEmbeddings(time_emb_dim),
79
+ nn.Linear(time_emb_dim, time_emb_dim * 4),
80
+ nn.GELU(),
81
+ nn.Linear(time_emb_dim * 4, channels),
82
+ )
83
+
84
+ self.text_mlp = nn.Sequential(
85
+ nn.Linear(768, time_emb_dim),
86
+ nn.GELU(),
87
+ nn.Linear(time_emb_dim, time_emb_dim),
88
+ nn.GELU(),
89
+ nn.Linear(time_emb_dim, channels),
90
+ )
91
+
92
+ self.conv_in = nn.Conv2d(num_channels, channels, 1, padding=0)
93
+
94
+ self.down1 = nn.Sequential(
95
+ nn.Conv2d(channels, channels * 2, 4, 2, 1),
96
+ nn.GroupNorm(8, channels * 2),
97
+ nn.GELU(),
98
+ )
99
+
100
+ self.down2 = nn.Sequential(
101
+ nn.Conv2d(channels * 2, channels * 4, 4, 2, 1),
102
+ nn.GroupNorm(8, channels * 4),
103
+ nn.GELU(),
104
+ )
105
+
106
+ self.down3 = nn.Sequential(
107
+ nn.Conv2d(channels * 4, channels * 8, 4, 2, 1),
108
+ nn.GroupNorm(8, channels * 8),
109
+ nn.GELU(),
110
+ )
npcpy/ft/ge.py CHANGED
@@ -1 +1,115 @@
1
- # genetic engineering for using genetic algorithms with LLMs
1
+ import random
2
+ from dataclasses import dataclass
3
+ from typing import Callable, Optional, List
4
+
5
+
6
+ @dataclass
7
+ class GAConfig:
8
+ population_size: int = 20
9
+ mutation_rate: float = 0.15
10
+ crossover_rate: float = 0.7
11
+ tournament_size: int = 3
12
+ elitism_count: int = 2
13
+ generations: int = 50
14
+
15
+
16
+ class GeneticEvolver:
17
+ """
18
+ Generic GA that takes fitness, mutation, crossover
19
+ and initialization functions to evolve any population
20
+ """
21
+ def __init__(
22
+ self,
23
+ fitness_fn: Callable,
24
+ mutate_fn: Callable,
25
+ crossover_fn: Callable,
26
+ initialize_fn: Callable,
27
+ config: Optional[GAConfig] = None
28
+ ):
29
+ self.fitness_fn = fitness_fn
30
+ self.mutate_fn = mutate_fn
31
+ self.crossover_fn = crossover_fn
32
+ self.initialize_fn = initialize_fn
33
+ self.config = config or GAConfig()
34
+ self.population = []
35
+ self.history = []
36
+
37
+ def initialize_population(self):
38
+ self.population = [
39
+ self.initialize_fn()
40
+ for _ in range(self.config.population_size)
41
+ ]
42
+
43
+ def evaluate_population(self) -> List[float]:
44
+ return [
45
+ self.fitness_fn(individual)
46
+ for individual in self.population
47
+ ]
48
+
49
+ def tournament_select(self, fitness_scores: List[float]):
50
+ indices = random.sample(
51
+ range(len(self.population)),
52
+ self.config.tournament_size
53
+ )
54
+ tournament_fitness = [fitness_scores[i] for i in indices]
55
+ winner_idx = indices[
56
+ tournament_fitness.index(max(tournament_fitness))
57
+ ]
58
+ return self.population[winner_idx]
59
+
60
+ def evolve_generation(self):
61
+ fitness_scores = self.evaluate_population()
62
+
63
+ sorted_pop = sorted(
64
+ zip(self.population, fitness_scores),
65
+ key=lambda x: x[1],
66
+ reverse=True
67
+ )
68
+
69
+ new_population = [
70
+ ind for ind, _ in sorted_pop[:self.config.elitism_count]
71
+ ]
72
+
73
+ while len(new_population) < self.config.population_size:
74
+ parent1 = self.tournament_select(fitness_scores)
75
+ parent2 = self.tournament_select(fitness_scores)
76
+
77
+ if random.random() < self.config.crossover_rate:
78
+ child = self.crossover_fn(parent1, parent2)
79
+ else:
80
+ child = parent1
81
+
82
+ if random.random() < self.config.mutation_rate:
83
+ child = self.mutate_fn(child)
84
+
85
+ new_population.append(child)
86
+
87
+ self.population = new_population[:self.config.population_size]
88
+
89
+ best_fitness = max(fitness_scores)
90
+ avg_fitness = sum(fitness_scores) / len(fitness_scores)
91
+
92
+ return {
93
+ 'best_fitness': best_fitness,
94
+ 'avg_fitness': avg_fitness,
95
+ 'best_individual': sorted_pop[0][0]
96
+ }
97
+
98
+ def run(self, generations: Optional[int] = None):
99
+ if not self.population:
100
+ self.initialize_population()
101
+
102
+ gens = generations or self.config.generations
103
+
104
+ for gen in range(gens):
105
+ gen_stats = self.evolve_generation()
106
+ self.history.append(gen_stats)
107
+
108
+ if gen % 10 == 0:
109
+ print(
110
+ f"Gen {gen}: "
111
+ f"Best={gen_stats['best_fitness']:.3f}, "
112
+ f"Avg={gen_stats['avg_fitness']:.3f}"
113
+ )
114
+
115
+ return self.history[-1]['best_individual']
@@ -0,0 +1,357 @@
1
+ import time
2
+ import copy
3
+ import random
4
+ from dataclasses import dataclass, field
5
+ from typing import List, Dict, Any, Optional
6
+ from npcpy.llm_funcs import get_llm_response
7
+
8
+ try:
9
+ from npcpy.ft.sft import predict_sft, load_sft_model
10
+ except:
11
+ pass
12
+
13
+ @dataclass
14
+ class ModelGene:
15
+ """
16
+ Represents a specialized model with trigger patterns
17
+ and confidence threshold
18
+ """
19
+ sft_path: Optional[str] = None
20
+ rl_path: Optional[str] = None
21
+ base_model: str = "Qwen/Qwen3-0.6B"
22
+ specialization: str = "general"
23
+ trigger_patterns: List[str] = field(default_factory=list)
24
+ confidence_threshold: float = 0.7
25
+
26
+
27
+ def generate_trigger_patterns(specialization: str) -> List[str]:
28
+ """
29
+ Generate trigger patterns for a given specialization domain
30
+ """
31
+ patterns = {
32
+ 'math': ['calculate', 'solve', 'equation', 'number'],
33
+ 'code': ['function', 'class', 'bug', 'debug', 'code'],
34
+ 'creative': ['story', 'poem', 'creative', 'imagine'],
35
+ 'factual': ['what is', 'who is', 'when did', 'where is'],
36
+ 'analysis': ['analyze', 'compare', 'evaluate', 'assess']
37
+ }
38
+
39
+ return patterns.get(specialization, ['general'])
40
+
41
+
42
+ def create_model_genome(
43
+ specializations: List[str],
44
+ base_model: str = "Qwen/Qwen3-0.6B"
45
+ ) -> List[ModelGene]:
46
+ """
47
+ Initialize a genome of specialized models
48
+ """
49
+ genome = []
50
+
51
+ for spec in specializations:
52
+ gene = ModelGene(
53
+ base_model=base_model,
54
+ specialization=spec,
55
+ trigger_patterns=generate_trigger_patterns(spec),
56
+ confidence_threshold=random.uniform(0.6, 0.9)
57
+ )
58
+ genome.append(gene)
59
+
60
+ return genome
61
+
62
+
63
+ def mutate_model_genome(
64
+ genome: List[ModelGene],
65
+ mutation_type: str = 'random'
66
+ ) -> List[ModelGene]:
67
+ """
68
+ Apply genetic mutation to model genome
69
+ """
70
+ new_genome = copy.deepcopy(genome)
71
+
72
+ mutations = [
73
+ 'adjust_threshold',
74
+ 'add_trigger',
75
+ 'remove_gene',
76
+ 'duplicate_gene'
77
+ ]
78
+
79
+ if mutation_type == 'random':
80
+ mutation_type = random.choice(mutations)
81
+
82
+ if mutation_type == 'adjust_threshold':
83
+ gene = random.choice(new_genome)
84
+ gene.confidence_threshold += random.uniform(-0.1, 0.1)
85
+ gene.confidence_threshold = max(
86
+ 0.5,
87
+ min(0.95, gene.confidence_threshold)
88
+ )
89
+
90
+ elif mutation_type == 'add_trigger':
91
+ gene = random.choice(new_genome)
92
+ new_trigger = f"pattern_{random.randint(1, 100)}"
93
+ if new_trigger not in gene.trigger_patterns:
94
+ gene.trigger_patterns.append(new_trigger)
95
+
96
+ elif mutation_type == 'remove_gene' and len(new_genome) > 1:
97
+ new_genome.pop(random.randint(0, len(new_genome) - 1))
98
+
99
+ elif mutation_type == 'duplicate_gene':
100
+ gene = random.choice(new_genome)
101
+ new_gene = copy.deepcopy(gene)
102
+ new_gene.specialization = f"{gene.specialization}_variant"
103
+ new_genome.append(new_gene)
104
+
105
+ return new_genome
106
+
107
+
108
+ def crossover_model_genomes(
109
+ genome1: List[ModelGene],
110
+ genome2: List[ModelGene]
111
+ ) -> List[ModelGene]:
112
+ """
113
+ Crossover two model genomes to create child genome
114
+ """
115
+ if not genome1 or not genome2:
116
+ return genome1 or genome2
117
+
118
+ split = random.randint(1, min(len(genome1), len(genome2)) - 1)
119
+
120
+ child = genome1[:split] + genome2[split:]
121
+
122
+ return child
123
+
124
+
125
+ def evaluate_model_genome(
126
+ genome: List[ModelGene],
127
+ test_cases: List[Dict[str, Any]],
128
+ router: 'ResponseRouter'
129
+ ) -> float:
130
+ """
131
+ Evaluate fitness of a model genome based on accuracy,
132
+ speed and efficiency
133
+ """
134
+ correct = 0
135
+ total_time = 0
136
+ fast_responses = 0
137
+
138
+ for test_case in test_cases:
139
+ result = router.route_query(
140
+ test_case['query'],
141
+ genome,
142
+ test_case.get('ground_truth')
143
+ )
144
+
145
+ if result['correct']:
146
+ correct += 1
147
+
148
+ total_time += result['response_time']
149
+
150
+ if result['used_fast_path']:
151
+ fast_responses += 1
152
+
153
+ accuracy = correct / len(test_cases)
154
+ speed_bonus = fast_responses / len(test_cases)
155
+ efficiency = 1.0 / (total_time / len(test_cases))
156
+
157
+ fitness = (
158
+ accuracy * 0.6 +
159
+ speed_bonus * 0.2 +
160
+ efficiency * 0.2
161
+ )
162
+
163
+ return fitness
164
+
165
+
166
+ class ResponseRouter:
167
+ """
168
+ Routes queries through fast path, ensemble or full reasoning
169
+ based on confidence thresholds
170
+ """
171
+ def __init__(
172
+ self,
173
+ fast_threshold: float = 0.8,
174
+ ensemble_threshold: float = 0.6
175
+ ):
176
+ self.fast_threshold = fast_threshold
177
+ self.ensemble_threshold = ensemble_threshold
178
+ self.response_cache = {}
179
+
180
+ def route_query(
181
+ self,
182
+ query: str,
183
+ genome: List[ModelGene],
184
+ ground_truth: Optional[str] = None
185
+ ) -> Dict[str, Any]:
186
+ """
187
+ Route query through system 1 fast path,
188
+ ensemble or system 2 reasoning
189
+ """
190
+ start_time = time.time()
191
+
192
+ fast_response = self._try_fast_path(query, genome)
193
+
194
+ if fast_response and fast_response['confidence'] > (
195
+ self.fast_threshold
196
+ ):
197
+ response_time = time.time() - start_time
198
+
199
+ return {
200
+ 'response': fast_response['answer'],
201
+ 'confidence': fast_response['confidence'],
202
+ 'used_fast_path': True,
203
+ 'response_time': response_time,
204
+ 'correct': (
205
+ ground_truth is None or
206
+ self._check_correctness(
207
+ fast_response['answer'],
208
+ ground_truth
209
+ )
210
+ )
211
+ }
212
+
213
+ ensemble_response = self._try_ensemble(query, genome)
214
+
215
+ if ensemble_response['confidence'] > (
216
+ self.ensemble_threshold
217
+ ):
218
+ response_time = time.time() - start_time
219
+
220
+ return {
221
+ 'response': ensemble_response['answer'],
222
+ 'confidence': ensemble_response['confidence'],
223
+ 'used_fast_path': False,
224
+ 'used_ensemble': True,
225
+ 'response_time': response_time,
226
+ 'correct': (
227
+ ground_truth is None or
228
+ self._check_correctness(
229
+ ensemble_response['answer'],
230
+ ground_truth
231
+ )
232
+ )
233
+ }
234
+
235
+ full_response = self._full_reasoning(query)
236
+ response_time = time.time() - start_time
237
+
238
+ return {
239
+ 'response': full_response,
240
+ 'confidence': 0.5,
241
+ 'used_fast_path': False,
242
+ 'used_ensemble': False,
243
+ 'response_time': response_time,
244
+ 'correct': (
245
+ ground_truth is None or
246
+ self._check_correctness(
247
+ full_response,
248
+ ground_truth
249
+ )
250
+ )
251
+ }
252
+
253
+ def _try_fast_path(
254
+ self,
255
+ query: str,
256
+ genome: List[ModelGene]
257
+ ) -> Optional[Dict[str, Any]]:
258
+ """
259
+ Try fast system 1 gut reaction using pattern matching
260
+ """
261
+ query_lower = query.lower()
262
+
263
+ for gene in genome:
264
+ if any(
265
+ pattern in query_lower
266
+ for pattern in gene.trigger_patterns
267
+ ):
268
+ if gene.sft_path:
269
+ model, tokenizer = load_sft_model(gene.sft_path)
270
+
271
+ response = predict_sft(
272
+ model,
273
+ tokenizer,
274
+ query,
275
+ temperature=0.1
276
+ )
277
+
278
+ return {
279
+ 'answer': response,
280
+ 'confidence': gene.confidence_threshold
281
+ }
282
+
283
+ return None
284
+
285
+ def _try_ensemble(
286
+ self,
287
+ query: str,
288
+ genome: List[ModelGene]
289
+ ) -> Dict[str, Any]:
290
+ """
291
+ Try ensemble voting across specialized models
292
+ """
293
+ responses = []
294
+
295
+ for gene in genome:
296
+ if gene.sft_path or gene.rl_path:
297
+ model_path = gene.rl_path or gene.sft_path
298
+
299
+ model, tokenizer = load_sft_model(model_path)
300
+
301
+ response = predict_sft(
302
+ model,
303
+ tokenizer,
304
+ query,
305
+ temperature=0.3
306
+ )
307
+
308
+ responses.append({
309
+ 'answer': response,
310
+ 'weight': gene.confidence_threshold
311
+ })
312
+
313
+ if not responses:
314
+ return {'answer': '', 'confidence': 0.0}
315
+
316
+ best_response = max(responses, key=lambda x: x['weight'])
317
+
318
+ avg_confidence = sum(
319
+ r['weight'] for r in responses
320
+ ) / len(responses)
321
+
322
+ return {
323
+ 'answer': best_response['answer'],
324
+ 'confidence': avg_confidence
325
+ }
326
+
327
+ def _full_reasoning(
328
+ self,
329
+ query: str,
330
+ model: str = "qwen3:1.7b",
331
+ provider: str = "ollama"
332
+ ) -> str:
333
+ """
334
+ Fall back to full system 2 reasoning
335
+ """
336
+ response = get_llm_response(
337
+ query,
338
+ model=model,
339
+ provider=provider
340
+ )
341
+
342
+ return response.get('response', '')
343
+
344
+ def _check_correctness(
345
+ self,
346
+ response: str,
347
+ ground_truth: str
348
+ ) -> bool:
349
+ """
350
+ Check if response matches ground truth
351
+ """
352
+ response_lower = response.lower().strip()
353
+ truth_lower = ground_truth.lower().strip()
354
+
355
+ return response_lower == truth_lower or (
356
+ truth_lower in response_lower
357
+ )