npcpy 1.3.12__tar.gz → 1.3.14__tar.gz
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-1.3.12/npcpy.egg-info → npcpy-1.3.14}/PKG-INFO +4 -4
- {npcpy-1.3.12 → npcpy-1.3.14}/README.md +3 -3
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/ft/diff.py +45 -30
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/ft/rl.py +134 -51
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/gen/response.py +36 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/memory/knowledge_graph.py +1 -3
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/npc_compiler.py +83 -2
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/serve.py +414 -40
- {npcpy-1.3.12 → npcpy-1.3.14/npcpy.egg-info}/PKG-INFO +4 -4
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy.egg-info/SOURCES.txt +1 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/setup.py +1 -1
- {npcpy-1.3.12 → npcpy-1.3.14}/tests/test_command_history.py +114 -91
- npcpy-1.3.14/tests/test_documentation_examples.py +436 -0
- npcpy-1.3.14/tests/test_load.py +291 -0
- npcpy-1.3.14/tests/test_serve.py +132 -0
- npcpy-1.3.14/tests/test_text.py +215 -0
- npcpy-1.3.14/tests/test_tools.py +211 -0
- npcpy-1.3.12/tests/test_load.py +0 -284
- npcpy-1.3.12/tests/test_serve.py +0 -150
- npcpy-1.3.12/tests/test_text.py +0 -256
- npcpy-1.3.12/tests/test_tools.py +0 -989
- {npcpy-1.3.12 → npcpy-1.3.14}/LICENSE +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/MANIFEST.in +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/__init__.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/build_funcs.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/data/__init__.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/data/audio.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/data/data_models.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/data/image.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/data/load.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/data/text.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/data/video.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/data/web.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/ft/__init__.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/ft/ge.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/ft/memory_trainer.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/ft/model_ensembler.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/ft/sft.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/ft/usft.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/gen/__init__.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/gen/audio_gen.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/gen/embeddings.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/gen/image_gen.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/gen/ocr.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/gen/video_gen.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/gen/world_gen.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/llm_funcs.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/main.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/memory/__init__.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/memory/command_history.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/memory/kg_vis.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/memory/memory_processor.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/memory/search.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/mix/__init__.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/mix/debate.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/ml_funcs.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/npc_array.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/npc_sysenv.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/npcs.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/sql/__init__.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/sql/ai_function_tools.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/sql/database_ai_adapters.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/sql/database_ai_functions.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/sql/model_runner.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/sql/npcsql.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/sql/sql_model_compiler.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/tools.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/work/__init__.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/work/browser.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/work/desktop.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/work/plan.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy/work/trigger.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy.egg-info/dependency_links.txt +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy.egg-info/requires.txt +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/npcpy.egg-info/top_level.txt +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/setup.cfg +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/tests/test_audio.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/tests/test_image.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/tests/test_llm_funcs.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/tests/test_npc_array.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/tests/test_npc_compiler.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/tests/test_npcsql.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/tests/test_response.py +0 -0
- {npcpy-1.3.12 → npcpy-1.3.14}/tests/test_web.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: npcpy
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.14
|
|
4
4
|
Summary: npcpy is the premier open-source library for integrating LLMs and Agents into python systems.
|
|
5
5
|
Home-page: https://github.com/NPC-Worldwide/npcpy
|
|
6
6
|
Author: Christopher Agostino
|
|
@@ -305,7 +305,7 @@ ggm = NPC(
|
|
|
305
305
|
isabel = NPC(
|
|
306
306
|
name='Isabel Allende',
|
|
307
307
|
primary_directive='You are Isabel Allende, weaving stories with emotion and history. Analyze texts and provide insight.',
|
|
308
|
-
model='llama3.2
|
|
308
|
+
model='llama3.2',
|
|
309
309
|
provider='ollama',
|
|
310
310
|
|
|
311
311
|
)
|
|
@@ -359,7 +359,7 @@ LLM responses can be obtained without NPCs as well.
|
|
|
359
359
|
|
|
360
360
|
```python
|
|
361
361
|
from npcpy.llm_funcs import get_llm_response
|
|
362
|
-
response = get_llm_response("Who was the celtic Messenger god?", model='
|
|
362
|
+
response = get_llm_response("Who was the celtic Messenger god?", model='qwen3:4b', provider='ollama')
|
|
363
363
|
print(response['response'])
|
|
364
364
|
```
|
|
365
365
|
|
|
@@ -400,7 +400,7 @@ Return structured outputs by specifying `format='json'` or passing a Pydantic sc
|
|
|
400
400
|
|
|
401
401
|
```python
|
|
402
402
|
from npcpy.llm_funcs import get_llm_response
|
|
403
|
-
response = get_llm_response("What is the sentiment of the american people towards the repeal of Roe v Wade? Return a json object with `sentiment` as the key and a float value from -1 to 1 as the value", model='
|
|
403
|
+
response = get_llm_response("What is the sentiment of the american people towards the repeal of Roe v Wade? Return a json object with `sentiment` as the key and a float value from -1 to 1 as the value", model='deepseek-chat', provider='deepseek', format='json')
|
|
404
404
|
|
|
405
405
|
print(response['response'])
|
|
406
406
|
```
|
|
@@ -209,7 +209,7 @@ ggm = NPC(
|
|
|
209
209
|
isabel = NPC(
|
|
210
210
|
name='Isabel Allende',
|
|
211
211
|
primary_directive='You are Isabel Allende, weaving stories with emotion and history. Analyze texts and provide insight.',
|
|
212
|
-
model='llama3.2
|
|
212
|
+
model='llama3.2',
|
|
213
213
|
provider='ollama',
|
|
214
214
|
|
|
215
215
|
)
|
|
@@ -263,7 +263,7 @@ LLM responses can be obtained without NPCs as well.
|
|
|
263
263
|
|
|
264
264
|
```python
|
|
265
265
|
from npcpy.llm_funcs import get_llm_response
|
|
266
|
-
response = get_llm_response("Who was the celtic Messenger god?", model='
|
|
266
|
+
response = get_llm_response("Who was the celtic Messenger god?", model='qwen3:4b', provider='ollama')
|
|
267
267
|
print(response['response'])
|
|
268
268
|
```
|
|
269
269
|
|
|
@@ -304,7 +304,7 @@ Return structured outputs by specifying `format='json'` or passing a Pydantic sc
|
|
|
304
304
|
|
|
305
305
|
```python
|
|
306
306
|
from npcpy.llm_funcs import get_llm_response
|
|
307
|
-
response = get_llm_response("What is the sentiment of the american people towards the repeal of Roe v Wade? Return a json object with `sentiment` as the key and a float value from -1 to 1 as the value", model='
|
|
307
|
+
response = get_llm_response("What is the sentiment of the american people towards the repeal of Roe v Wade? Return a json object with `sentiment` as the key and a float value from -1 to 1 as the value", model='deepseek-chat', provider='deepseek', format='json')
|
|
308
308
|
|
|
309
309
|
print(response['response'])
|
|
310
310
|
```
|
|
@@ -180,52 +180,66 @@ if TORCH_AVAILABLE:
|
|
|
180
180
|
noise = torch.randn_like(x)
|
|
181
181
|
return sqrt_alpha * x + sqrt_one_minus * noise, noise
|
|
182
182
|
|
|
183
|
-
def train(self, dataloader):
|
|
183
|
+
def train(self, dataloader, progress_callback=None):
|
|
184
184
|
optimizer = torch.optim.AdamW(
|
|
185
|
-
self.model.parameters(),
|
|
185
|
+
self.model.parameters(),
|
|
186
186
|
lr=self.config.learning_rate
|
|
187
187
|
)
|
|
188
|
-
|
|
188
|
+
|
|
189
189
|
os.makedirs(self.config.output_model_path, exist_ok=True)
|
|
190
190
|
checkpoint_dir = os.path.join(
|
|
191
|
-
self.config.output_model_path,
|
|
191
|
+
self.config.output_model_path,
|
|
192
192
|
'checkpoints'
|
|
193
193
|
)
|
|
194
194
|
os.makedirs(checkpoint_dir, exist_ok=True)
|
|
195
|
-
|
|
195
|
+
|
|
196
196
|
global_step = 0
|
|
197
|
-
|
|
197
|
+
total_batches = len(dataloader)
|
|
198
|
+
loss_history = []
|
|
199
|
+
|
|
198
200
|
for epoch in range(self.config.num_epochs):
|
|
199
201
|
self.model.train()
|
|
200
202
|
epoch_loss = 0.0
|
|
201
|
-
|
|
203
|
+
|
|
202
204
|
pbar = tqdm(dataloader, desc=f'Epoch {epoch+1}')
|
|
203
205
|
for batch_idx, (images, captions) in enumerate(pbar):
|
|
204
206
|
images = images.to(self.device)
|
|
205
207
|
batch_size = images.shape[0]
|
|
206
|
-
|
|
208
|
+
|
|
207
209
|
t = torch.randint(
|
|
208
|
-
0,
|
|
209
|
-
self.config.timesteps,
|
|
210
|
-
(batch_size,),
|
|
210
|
+
0,
|
|
211
|
+
self.config.timesteps,
|
|
212
|
+
(batch_size,),
|
|
211
213
|
device=self.device
|
|
212
214
|
).long()
|
|
213
|
-
|
|
215
|
+
|
|
214
216
|
noisy_images, noise = self.add_noise(images, t)
|
|
215
|
-
|
|
217
|
+
|
|
216
218
|
predicted_noise = self.model(noisy_images, t)
|
|
217
|
-
|
|
219
|
+
|
|
218
220
|
loss = F.mse_loss(predicted_noise, noise)
|
|
219
|
-
|
|
221
|
+
|
|
220
222
|
optimizer.zero_grad()
|
|
221
223
|
loss.backward()
|
|
222
224
|
optimizer.step()
|
|
223
|
-
|
|
225
|
+
|
|
224
226
|
epoch_loss += loss.item()
|
|
225
227
|
global_step += 1
|
|
226
|
-
|
|
228
|
+
|
|
227
229
|
pbar.set_postfix({'loss': loss.item()})
|
|
228
|
-
|
|
230
|
+
|
|
231
|
+
# Report progress via callback
|
|
232
|
+
if progress_callback:
|
|
233
|
+
progress_callback({
|
|
234
|
+
'epoch': epoch + 1,
|
|
235
|
+
'total_epochs': self.config.num_epochs,
|
|
236
|
+
'batch': batch_idx + 1,
|
|
237
|
+
'total_batches': total_batches,
|
|
238
|
+
'step': global_step,
|
|
239
|
+
'loss': loss.item(),
|
|
240
|
+
'loss_history': loss_history[-100:], # Last 100 losses
|
|
241
|
+
})
|
|
242
|
+
|
|
229
243
|
if global_step % self.config.checkpoint_frequency == 0:
|
|
230
244
|
ckpt_path = os.path.join(
|
|
231
245
|
checkpoint_dir,
|
|
@@ -238,8 +252,9 @@ if TORCH_AVAILABLE:
|
|
|
238
252
|
'optimizer_state_dict': optimizer.state_dict(),
|
|
239
253
|
'loss': loss.item(),
|
|
240
254
|
}, ckpt_path)
|
|
241
|
-
|
|
255
|
+
|
|
242
256
|
avg_loss = epoch_loss / len(dataloader)
|
|
257
|
+
loss_history.append(avg_loss)
|
|
243
258
|
print(f'Epoch {epoch+1} avg loss: {avg_loss:.6f}')
|
|
244
259
|
|
|
245
260
|
final_path = os.path.join(
|
|
@@ -300,35 +315,35 @@ else:
|
|
|
300
315
|
DiffusionTrainer = None
|
|
301
316
|
|
|
302
317
|
|
|
303
|
-
def train_diffusion(image_paths, captions=None, config=None,
|
|
304
|
-
resume_from=None):
|
|
318
|
+
def train_diffusion(image_paths, captions=None, config=None,
|
|
319
|
+
resume_from=None, progress_callback=None):
|
|
305
320
|
if not TORCH_AVAILABLE:
|
|
306
321
|
raise ImportError(
|
|
307
322
|
"PyTorch not available. Install: pip install torch torchvision"
|
|
308
323
|
)
|
|
309
|
-
|
|
324
|
+
|
|
310
325
|
if config is None:
|
|
311
326
|
config = DiffusionConfig()
|
|
312
|
-
|
|
327
|
+
|
|
313
328
|
if captions is None:
|
|
314
329
|
captions = [''] * len(image_paths)
|
|
315
|
-
|
|
330
|
+
|
|
316
331
|
dataset = ImageDataset(image_paths, captions, config.image_size)
|
|
317
332
|
dataloader = DataLoader(
|
|
318
|
-
dataset,
|
|
319
|
-
batch_size=config.batch_size,
|
|
333
|
+
dataset,
|
|
334
|
+
batch_size=config.batch_size,
|
|
320
335
|
shuffle=True,
|
|
321
336
|
num_workers=0
|
|
322
337
|
)
|
|
323
|
-
|
|
338
|
+
|
|
324
339
|
trainer = DiffusionTrainer(config)
|
|
325
|
-
|
|
340
|
+
|
|
326
341
|
if resume_from and os.path.exists(resume_from):
|
|
327
342
|
checkpoint = torch.load(resume_from, map_location=trainer.device)
|
|
328
343
|
trainer.model.load_state_dict(checkpoint['model_state_dict'])
|
|
329
344
|
print(f'Resumed from {resume_from}')
|
|
330
|
-
|
|
331
|
-
output_path = trainer.train(dataloader)
|
|
345
|
+
|
|
346
|
+
output_path = trainer.train(dataloader, progress_callback=progress_callback)
|
|
332
347
|
|
|
333
348
|
gc.collect()
|
|
334
349
|
if torch.cuda.is_available():
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import List
|
|
2
3
|
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
import glob
|
|
@@ -12,7 +13,8 @@ try:
|
|
|
12
13
|
import torch
|
|
13
14
|
from transformers import (
|
|
14
15
|
AutoModelForCausalLM,
|
|
15
|
-
AutoTokenizer
|
|
16
|
+
AutoTokenizer,
|
|
17
|
+
BitsAndBytesConfig
|
|
16
18
|
)
|
|
17
19
|
from trl import DPOTrainer, DPOConfig
|
|
18
20
|
except:
|
|
@@ -23,6 +25,7 @@ except:
|
|
|
23
25
|
torch = None
|
|
24
26
|
AutoModelForCausalLM = None
|
|
25
27
|
AutoTokenizer = None
|
|
28
|
+
BitsAndBytesConfig = None
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
import random
|
|
@@ -44,6 +47,24 @@ class RLConfig:
|
|
|
44
47
|
beta: float = 0.5
|
|
45
48
|
max_length: int = 512
|
|
46
49
|
max_prompt_length: int = 256
|
|
50
|
+
# Quantization options
|
|
51
|
+
use_4bit: bool = False
|
|
52
|
+
use_8bit: bool = False
|
|
53
|
+
# Precision options
|
|
54
|
+
fp16: bool = False
|
|
55
|
+
bf16: bool = False
|
|
56
|
+
# LoRA configuration
|
|
57
|
+
lora_r: int = 8
|
|
58
|
+
lora_alpha: int = 16
|
|
59
|
+
lora_dropout: float = 0.1
|
|
60
|
+
lora_target_modules: List[str] = field(
|
|
61
|
+
default_factory=lambda: ["q_proj", "k_proj", "v_proj", "o_proj"]
|
|
62
|
+
)
|
|
63
|
+
# Training options
|
|
64
|
+
max_pairs: int = 200
|
|
65
|
+
warmup_steps: int = 5
|
|
66
|
+
logging_steps: int = 5
|
|
67
|
+
save_steps: int = 20
|
|
47
68
|
|
|
48
69
|
|
|
49
70
|
class TaskExecutor:
|
|
@@ -207,8 +228,8 @@ def create_preference_pairs(
|
|
|
207
228
|
f"Warning: Only {len(pairs)} pairs found. "
|
|
208
229
|
"May overfit."
|
|
209
230
|
)
|
|
210
|
-
|
|
211
|
-
return Dataset.from_list(pairs
|
|
231
|
+
|
|
232
|
+
return Dataset.from_list(pairs)
|
|
212
233
|
|
|
213
234
|
|
|
214
235
|
def train_with_dpo(
|
|
@@ -218,84 +239,121 @@ def train_with_dpo(
|
|
|
218
239
|
|
|
219
240
|
if config is None:
|
|
220
241
|
config = RLConfig()
|
|
221
|
-
|
|
242
|
+
|
|
222
243
|
preference_dataset = create_preference_pairs(
|
|
223
244
|
traces,
|
|
224
245
|
min_reward_gap=config.min_reward_gap
|
|
225
246
|
)
|
|
226
|
-
|
|
247
|
+
|
|
227
248
|
if preference_dataset is None or len(preference_dataset) == 0:
|
|
228
249
|
print("No valid preference pairs. Cannot train.")
|
|
229
250
|
return None
|
|
230
|
-
|
|
251
|
+
|
|
252
|
+
# Limit pairs if specified
|
|
253
|
+
if config.max_pairs and len(preference_dataset) > config.max_pairs:
|
|
254
|
+
preference_dataset = preference_dataset.select(range(config.max_pairs))
|
|
255
|
+
|
|
256
|
+
print(f"Training with {len(preference_dataset)} preference pairs")
|
|
257
|
+
|
|
258
|
+
# Build model loading kwargs
|
|
259
|
+
model_kwargs = {
|
|
260
|
+
"device_map": "auto",
|
|
261
|
+
"trust_remote_code": True,
|
|
262
|
+
"low_cpu_mem_usage": True
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
# Handle quantization
|
|
266
|
+
if config.use_4bit:
|
|
267
|
+
if BitsAndBytesConfig is None:
|
|
268
|
+
raise ImportError("bitsandbytes required for 4-bit. pip install bitsandbytes")
|
|
269
|
+
model_kwargs["quantization_config"] = BitsAndBytesConfig(
|
|
270
|
+
load_in_4bit=True,
|
|
271
|
+
bnb_4bit_quant_type="nf4",
|
|
272
|
+
bnb_4bit_compute_dtype=torch.float16,
|
|
273
|
+
bnb_4bit_use_double_quant=True
|
|
274
|
+
)
|
|
275
|
+
print("Using 4-bit quantization")
|
|
276
|
+
elif config.use_8bit:
|
|
277
|
+
if BitsAndBytesConfig is None:
|
|
278
|
+
raise ImportError("bitsandbytes required for 8-bit. pip install bitsandbytes")
|
|
279
|
+
model_kwargs["quantization_config"] = BitsAndBytesConfig(
|
|
280
|
+
load_in_8bit=True
|
|
281
|
+
)
|
|
282
|
+
print("Using 8-bit quantization")
|
|
283
|
+
else:
|
|
284
|
+
# Set dtype based on precision config
|
|
285
|
+
if config.bf16:
|
|
286
|
+
model_kwargs["torch_dtype"] = torch.bfloat16
|
|
287
|
+
elif config.fp16:
|
|
288
|
+
model_kwargs["torch_dtype"] = torch.float16
|
|
289
|
+
else:
|
|
290
|
+
model_kwargs["torch_dtype"] = torch.float32
|
|
291
|
+
|
|
231
292
|
model = AutoModelForCausalLM.from_pretrained(
|
|
232
293
|
config.base_model_name,
|
|
233
|
-
|
|
234
|
-
device_map="auto",
|
|
235
|
-
low_cpu_mem_usage=True
|
|
294
|
+
**model_kwargs
|
|
236
295
|
)
|
|
237
|
-
|
|
296
|
+
|
|
238
297
|
tokenizer = AutoTokenizer.from_pretrained(
|
|
239
298
|
config.base_model_name,
|
|
240
299
|
trust_remote_code=True
|
|
241
300
|
)
|
|
242
|
-
|
|
301
|
+
|
|
243
302
|
if tokenizer.pad_token is None:
|
|
244
303
|
tokenizer.pad_token = tokenizer.eos_token
|
|
245
|
-
|
|
304
|
+
|
|
246
305
|
peft_config = LoraConfig(
|
|
247
|
-
r=
|
|
248
|
-
lora_alpha=
|
|
249
|
-
lora_dropout=
|
|
306
|
+
r=config.lora_r,
|
|
307
|
+
lora_alpha=config.lora_alpha,
|
|
308
|
+
lora_dropout=config.lora_dropout,
|
|
250
309
|
bias="none",
|
|
251
310
|
task_type="CAUSAL_LM",
|
|
252
|
-
target_modules=
|
|
253
|
-
"q_proj",
|
|
254
|
-
"k_proj",
|
|
255
|
-
"v_proj",
|
|
256
|
-
"o_proj"
|
|
257
|
-
]
|
|
311
|
+
target_modules=config.lora_target_modules
|
|
258
312
|
)
|
|
259
|
-
|
|
313
|
+
|
|
314
|
+
# Select optimizer based on quantization
|
|
315
|
+
if config.use_4bit or config.use_8bit:
|
|
316
|
+
optim = "paged_adamw_8bit"
|
|
317
|
+
else:
|
|
318
|
+
optim = "adamw_torch"
|
|
319
|
+
|
|
260
320
|
training_args = DPOConfig(
|
|
261
321
|
output_dir="./dpo_results",
|
|
262
|
-
per_device_train_batch_size=
|
|
263
|
-
|
|
264
|
-
),
|
|
265
|
-
gradient_accumulation_steps=(
|
|
266
|
-
config.gradient_accumulation_steps
|
|
267
|
-
),
|
|
322
|
+
per_device_train_batch_size=config.per_device_train_batch_size,
|
|
323
|
+
gradient_accumulation_steps=config.gradient_accumulation_steps,
|
|
268
324
|
learning_rate=config.learning_rate,
|
|
269
325
|
num_train_epochs=config.num_train_epochs,
|
|
270
326
|
weight_decay=0.1,
|
|
271
327
|
beta=config.beta,
|
|
272
|
-
logging_steps=
|
|
273
|
-
save_steps=
|
|
328
|
+
logging_steps=config.logging_steps,
|
|
329
|
+
save_steps=config.save_steps,
|
|
274
330
|
remove_unused_columns=False,
|
|
275
331
|
max_length=config.max_length,
|
|
276
332
|
max_prompt_length=config.max_prompt_length,
|
|
277
333
|
dataloader_num_workers=0,
|
|
278
|
-
fp16=
|
|
279
|
-
bf16=
|
|
280
|
-
optim=
|
|
281
|
-
warmup_steps=
|
|
334
|
+
fp16=config.fp16 or config.use_4bit,
|
|
335
|
+
bf16=config.bf16,
|
|
336
|
+
optim=optim,
|
|
337
|
+
warmup_steps=config.warmup_steps,
|
|
282
338
|
save_strategy="steps",
|
|
283
|
-
save_total_limit=
|
|
339
|
+
save_total_limit=2
|
|
284
340
|
)
|
|
285
|
-
|
|
341
|
+
|
|
286
342
|
trainer = DPOTrainer(
|
|
287
343
|
model,
|
|
288
344
|
args=training_args,
|
|
289
345
|
train_dataset=preference_dataset,
|
|
290
|
-
peft_config=peft_config
|
|
346
|
+
peft_config=peft_config,
|
|
347
|
+
tokenizer=tokenizer
|
|
291
348
|
)
|
|
292
|
-
|
|
349
|
+
|
|
293
350
|
print("Starting DPO training...")
|
|
294
351
|
trainer.train()
|
|
295
|
-
|
|
352
|
+
|
|
353
|
+
os.makedirs(config.adapter_path, exist_ok=True)
|
|
296
354
|
trainer.save_model(config.adapter_path)
|
|
297
355
|
print(f"Adapter saved to {config.adapter_path}")
|
|
298
|
-
|
|
356
|
+
|
|
299
357
|
return config.adapter_path
|
|
300
358
|
|
|
301
359
|
|
|
@@ -333,28 +391,53 @@ def run_rl_training(
|
|
|
333
391
|
|
|
334
392
|
def load_rl_model(
|
|
335
393
|
base_model_id: str,
|
|
336
|
-
adapter_path: str
|
|
394
|
+
adapter_path: str,
|
|
395
|
+
use_4bit: bool = False,
|
|
396
|
+
use_8bit: bool = False,
|
|
397
|
+
merge_adapter: bool = True
|
|
337
398
|
):
|
|
338
|
-
|
|
339
399
|
print(f"Loading base model: {base_model_id}")
|
|
400
|
+
|
|
401
|
+
model_kwargs = {
|
|
402
|
+
"device_map": "auto",
|
|
403
|
+
"trust_remote_code": True
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if use_4bit:
|
|
407
|
+
if BitsAndBytesConfig is None:
|
|
408
|
+
raise ImportError("bitsandbytes required for 4-bit")
|
|
409
|
+
model_kwargs["quantization_config"] = BitsAndBytesConfig(
|
|
410
|
+
load_in_4bit=True,
|
|
411
|
+
bnb_4bit_quant_type="nf4",
|
|
412
|
+
bnb_4bit_compute_dtype=torch.float16,
|
|
413
|
+
bnb_4bit_use_double_quant=True
|
|
414
|
+
)
|
|
415
|
+
elif use_8bit:
|
|
416
|
+
if BitsAndBytesConfig is None:
|
|
417
|
+
raise ImportError("bitsandbytes required for 8-bit")
|
|
418
|
+
model_kwargs["quantization_config"] = BitsAndBytesConfig(
|
|
419
|
+
load_in_8bit=True
|
|
420
|
+
)
|
|
421
|
+
else:
|
|
422
|
+
model_kwargs["torch_dtype"] = torch.float16
|
|
423
|
+
|
|
340
424
|
model = AutoModelForCausalLM.from_pretrained(
|
|
341
425
|
base_model_id,
|
|
342
|
-
|
|
343
|
-
device_map="auto",
|
|
344
|
-
attn_implementation='eager'
|
|
426
|
+
**model_kwargs
|
|
345
427
|
)
|
|
346
|
-
|
|
428
|
+
|
|
347
429
|
tokenizer = AutoTokenizer.from_pretrained(
|
|
348
430
|
base_model_id,
|
|
349
431
|
trust_remote_code=True
|
|
350
432
|
)
|
|
351
|
-
|
|
433
|
+
|
|
352
434
|
if tokenizer.pad_token is None:
|
|
353
435
|
tokenizer.pad_token = tokenizer.eos_token
|
|
354
|
-
|
|
436
|
+
|
|
355
437
|
if adapter_path and os.path.exists(adapter_path):
|
|
356
438
|
print(f"Loading adapter: {adapter_path}")
|
|
357
439
|
model = PeftModel.from_pretrained(model, adapter_path)
|
|
358
|
-
|
|
359
|
-
|
|
440
|
+
if merge_adapter and not (use_4bit or use_8bit):
|
|
441
|
+
model = model.merge_and_unload()
|
|
442
|
+
|
|
360
443
|
return model, tokenizer
|
|
@@ -259,6 +259,24 @@ def get_ollama_response(
|
|
|
259
259
|
prompt = f"Content from CSV: {os.path.basename(attachment)} (first 100 rows):\n{csv_sample} \n csv description: {csv_data.describe()}"
|
|
260
260
|
except Exception:
|
|
261
261
|
pass
|
|
262
|
+
else:
|
|
263
|
+
# Handle text-based files
|
|
264
|
+
text_extensions = {'.txt', '.text', '.log', '.md', '.markdown', '.rst', '.json', '.yaml', '.yml', '.toml', '.ini', '.conf', '.cfg', '.xml', '.html', '.htm', '.py', '.js', '.ts', '.jsx', '.tsx', '.java', '.c', '.h', '.cpp', '.hpp', '.go', '.rs', '.rb', '.php', '.sh', '.bash', '.sql', '.css', '.scss'}
|
|
265
|
+
filename = os.path.basename(attachment)
|
|
266
|
+
if ext in text_extensions or ext == '':
|
|
267
|
+
try:
|
|
268
|
+
with open(attachment, 'r', encoding='utf-8', errors='replace') as f:
|
|
269
|
+
text_content = f.read()
|
|
270
|
+
max_chars = 50000
|
|
271
|
+
if len(text_content) > max_chars:
|
|
272
|
+
text_content = text_content[:max_chars] + f"\n\n... [truncated]"
|
|
273
|
+
if text_content.strip():
|
|
274
|
+
if prompt:
|
|
275
|
+
prompt += f"\n\nContent from {filename}:\n```\n{text_content}\n```"
|
|
276
|
+
else:
|
|
277
|
+
prompt = f"Content from {filename}:\n```\n{text_content}\n```"
|
|
278
|
+
except Exception:
|
|
279
|
+
pass
|
|
262
280
|
|
|
263
281
|
|
|
264
282
|
if prompt:
|
|
@@ -797,6 +815,24 @@ def get_litellm_response(
|
|
|
797
815
|
prompt = f"Content from CSV: {os.path.basename(attachment)} (first 10 rows):\n{csv_sample}"
|
|
798
816
|
except Exception:
|
|
799
817
|
pass
|
|
818
|
+
else:
|
|
819
|
+
# Handle text-based files
|
|
820
|
+
text_extensions = {'.txt', '.text', '.log', '.md', '.markdown', '.rst', '.json', '.yaml', '.yml', '.toml', '.ini', '.conf', '.cfg', '.xml', '.html', '.htm', '.py', '.js', '.ts', '.jsx', '.tsx', '.java', '.c', '.h', '.cpp', '.hpp', '.go', '.rs', '.rb', '.php', '.sh', '.bash', '.sql', '.css', '.scss'}
|
|
821
|
+
filename = os.path.basename(attachment)
|
|
822
|
+
if ext in text_extensions or ext == '':
|
|
823
|
+
try:
|
|
824
|
+
with open(attachment, 'r', encoding='utf-8', errors='replace') as f:
|
|
825
|
+
text_content = f.read()
|
|
826
|
+
max_chars = 50000
|
|
827
|
+
if len(text_content) > max_chars:
|
|
828
|
+
text_content = text_content[:max_chars] + f"\n\n... [truncated]"
|
|
829
|
+
if text_content.strip():
|
|
830
|
+
if prompt:
|
|
831
|
+
prompt += f"\n\nContent from {filename}:\n```\n{text_content}\n```"
|
|
832
|
+
else:
|
|
833
|
+
prompt = f"Content from {filename}:\n```\n{text_content}\n```"
|
|
834
|
+
except Exception:
|
|
835
|
+
pass
|
|
800
836
|
|
|
801
837
|
if prompt:
|
|
802
838
|
if result['messages'] and result['messages'][-1]["role"] == "user":
|
|
@@ -344,7 +344,6 @@ def kg_evolve_incremental(existing_kg,
|
|
|
344
344
|
|
|
345
345
|
current_gen = existing_kg.get('generation', 0)
|
|
346
346
|
next_gen = current_gen + 1
|
|
347
|
-
print(f"\n--- ABSORBING INFO: Gen {current_gen} -> Gen {next_gen} ---")
|
|
348
347
|
|
|
349
348
|
newly_added_concepts = []
|
|
350
349
|
concept_links = list(existing_kg.get('concept_links', []))
|
|
@@ -359,8 +358,7 @@ def kg_evolve_incremental(existing_kg,
|
|
|
359
358
|
all_concept_names = list(existing_concept_names)
|
|
360
359
|
|
|
361
360
|
all_new_facts = []
|
|
362
|
-
|
|
363
|
-
|
|
361
|
+
|
|
364
362
|
if new_facts:
|
|
365
363
|
all_new_facts = new_facts
|
|
366
364
|
print(f'using pre-approved facts: {len(all_new_facts)}')
|
|
@@ -7,6 +7,41 @@ import sqlite3
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import pandas as pd
|
|
9
9
|
import matplotlib.pyplot as plt
|
|
10
|
+
import matplotlib as mpl
|
|
11
|
+
|
|
12
|
+
# Professional plot styling (from kg-research matplotlibrc)
|
|
13
|
+
mpl.rcParams.update({
|
|
14
|
+
'font.family': 'serif',
|
|
15
|
+
'axes.labelsize': 20,
|
|
16
|
+
'axes.grid.axis': 'both',
|
|
17
|
+
'axes.grid.which': 'major',
|
|
18
|
+
'axes.prop_cycle': mpl.cycler('color', ['k', 'b', 'r', 'g', 'c', 'm', 'y', 'k']),
|
|
19
|
+
'xtick.top': True,
|
|
20
|
+
'xtick.direction': 'in',
|
|
21
|
+
'xtick.major.size': 10,
|
|
22
|
+
'xtick.minor.size': 5,
|
|
23
|
+
'xtick.labelsize': 20,
|
|
24
|
+
'xtick.minor.visible': True,
|
|
25
|
+
'xtick.major.top': True,
|
|
26
|
+
'xtick.major.bottom': True,
|
|
27
|
+
'xtick.minor.top': True,
|
|
28
|
+
'xtick.minor.bottom': True,
|
|
29
|
+
'ytick.left': True,
|
|
30
|
+
'ytick.right': True,
|
|
31
|
+
'ytick.direction': 'in',
|
|
32
|
+
'ytick.major.size': 10,
|
|
33
|
+
'ytick.minor.size': 5,
|
|
34
|
+
'ytick.labelsize': 20,
|
|
35
|
+
'ytick.minor.visible': True,
|
|
36
|
+
'ytick.major.left': True,
|
|
37
|
+
'ytick.major.right': True,
|
|
38
|
+
'ytick.minor.left': True,
|
|
39
|
+
'ytick.minor.right': True,
|
|
40
|
+
'legend.frameon': False,
|
|
41
|
+
'legend.fontsize': 12,
|
|
42
|
+
'image.cmap': 'plasma',
|
|
43
|
+
'errorbar.capsize': 1,
|
|
44
|
+
})
|
|
10
45
|
import re
|
|
11
46
|
import random
|
|
12
47
|
from datetime import datetime
|
|
@@ -31,9 +66,31 @@ from npcpy.npc_sysenv import (
|
|
|
31
66
|
from npcpy.memory.command_history import CommandHistory, generate_message_id
|
|
32
67
|
|
|
33
68
|
class SilentUndefined(Undefined):
|
|
69
|
+
"""Undefined that silently returns empty string instead of raising errors"""
|
|
34
70
|
def _fail_with_undefined_error(self, *args, **kwargs):
|
|
35
71
|
return ""
|
|
36
72
|
|
|
73
|
+
def __str__(self):
|
|
74
|
+
return ""
|
|
75
|
+
|
|
76
|
+
def __repr__(self):
|
|
77
|
+
return ""
|
|
78
|
+
|
|
79
|
+
def __bool__(self):
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def __eq__(self, other):
|
|
83
|
+
return other == "" or other is None or isinstance(other, Undefined)
|
|
84
|
+
|
|
85
|
+
def __ne__(self, other):
|
|
86
|
+
return not self.__eq__(other)
|
|
87
|
+
|
|
88
|
+
def __iter__(self):
|
|
89
|
+
return iter([])
|
|
90
|
+
|
|
91
|
+
def __len__(self):
|
|
92
|
+
return 0
|
|
93
|
+
|
|
37
94
|
import math
|
|
38
95
|
from PIL import Image
|
|
39
96
|
from jinja2 import Environment, ChainableUndefined
|
|
@@ -152,11 +209,35 @@ def get_log_entries(entity_id, entry_type=None, limit=10, db_path="~/npcsh_histo
|
|
|
152
209
|
]
|
|
153
210
|
|
|
154
211
|
|
|
212
|
+
def _json_dumps_with_undefined(obj, **kwargs):
|
|
213
|
+
"""Custom JSON dumps that handles SilentUndefined objects"""
|
|
214
|
+
def default_handler(o):
|
|
215
|
+
if isinstance(o, Undefined):
|
|
216
|
+
return ""
|
|
217
|
+
raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
|
|
218
|
+
return json.dumps(obj, default=default_handler, **kwargs)
|
|
219
|
+
|
|
220
|
+
|
|
155
221
|
def load_yaml_file(file_path):
|
|
156
|
-
"""Load a YAML file with error handling"""
|
|
222
|
+
"""Load a YAML file with error handling, rendering Jinja2 first"""
|
|
157
223
|
try:
|
|
158
224
|
with open(os.path.expanduser(file_path), 'r') as f:
|
|
159
|
-
|
|
225
|
+
content = f.read()
|
|
226
|
+
|
|
227
|
+
# Check if file has Jinja2 control structures that need pre-rendering
|
|
228
|
+
# Only render if there are {% %} blocks, otherwise parse directly
|
|
229
|
+
if '{%' not in content:
|
|
230
|
+
return yaml.safe_load(content)
|
|
231
|
+
|
|
232
|
+
# First pass: render Jinja2 templates to produce valid YAML
|
|
233
|
+
# This allows {% if %} and other control structures to work
|
|
234
|
+
jinja_env = Environment(undefined=SilentUndefined)
|
|
235
|
+
# Configure tojson filter to handle SilentUndefined
|
|
236
|
+
jinja_env.policies['json.dumps_function'] = _json_dumps_with_undefined
|
|
237
|
+
template = jinja_env.from_string(content)
|
|
238
|
+
rendered_content = template.render({})
|
|
239
|
+
|
|
240
|
+
return yaml.safe_load(rendered_content)
|
|
160
241
|
except Exception as e:
|
|
161
242
|
print(f"Error loading YAML file {file_path}: {e}")
|
|
162
243
|
return None
|