npcpy 1.3.15__tar.gz → 1.3.17__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.15/npcpy.egg-info → npcpy-1.3.17}/PKG-INFO +1 -1
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/ft/rl.py +27 -14
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/gen/response.py +160 -2
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/npc_sysenv.py +27 -2
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/serve.py +809 -38
- {npcpy-1.3.15 → npcpy-1.3.17/npcpy.egg-info}/PKG-INFO +1 -1
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy.egg-info/SOURCES.txt +13 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/setup.py +1 -1
- npcpy-1.3.17/tests/test_browser.py +233 -0
- npcpy-1.3.17/tests/test_build_funcs.py +274 -0
- npcpy-1.3.17/tests/test_data_models.py +251 -0
- npcpy-1.3.17/tests/test_diff.py +360 -0
- npcpy-1.3.17/tests/test_genetic_evolver.py +447 -0
- npcpy-1.3.17/tests/test_memory_processor.py +268 -0
- npcpy-1.3.17/tests/test_ml_funcs.py +288 -0
- npcpy-1.3.17/tests/test_model_runner.py +329 -0
- npcpy-1.3.17/tests/test_npc_sysenv.py +173 -0
- npcpy-1.3.17/tests/test_sql_adapters.py +171 -0
- npcpy-1.3.17/tests/test_sql_compiler.py +287 -0
- npcpy-1.3.17/tests/test_sql_functions.py +221 -0
- npcpy-1.3.17/tests/test_video.py +133 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/LICENSE +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/MANIFEST.in +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/README.md +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/build_funcs.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/data/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/data/audio.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/data/data_models.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/data/image.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/data/load.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/data/text.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/data/video.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/data/web.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/ft/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/ft/diff.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/ft/ge.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/ft/memory_trainer.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/ft/model_ensembler.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/ft/sft.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/ft/usft.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/gen/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/gen/audio_gen.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/gen/embeddings.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/gen/image_gen.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/gen/ocr.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/gen/video_gen.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/gen/world_gen.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/llm_funcs.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/main.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/memory/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/memory/command_history.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/memory/kg_vis.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/memory/knowledge_graph.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/memory/memory_processor.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/memory/search.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/mix/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/mix/debate.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/ml_funcs.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/npc_array.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/npc_compiler.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/npcs.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/sql/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/sql/ai_function_tools.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/sql/database_ai_adapters.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/sql/database_ai_functions.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/sql/model_runner.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/sql/npcsql.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/sql/sql_model_compiler.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/tools.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/work/__init__.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/work/browser.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/work/desktop.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/work/plan.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy/work/trigger.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy.egg-info/dependency_links.txt +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy.egg-info/requires.txt +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/npcpy.egg-info/top_level.txt +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/setup.cfg +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_audio.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_command_history.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_documentation_examples.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_image.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_llm_funcs.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_load.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_npc_array.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_npc_compiler.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_npcsql.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_response.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_serve.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_text.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_tools.py +0 -0
- {npcpy-1.3.15 → npcpy-1.3.17}/tests/test_web.py +0 -0
|
@@ -6,27 +6,40 @@ import glob
|
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
8
|
import pandas as pd
|
|
9
|
+
# Core imports that should always work
|
|
9
10
|
try:
|
|
10
|
-
from datasets import Dataset
|
|
11
|
-
|
|
12
|
-
from peft import LoraConfig, PeftModel
|
|
13
11
|
import torch
|
|
14
|
-
|
|
15
|
-
AutoModelForCausalLM,
|
|
16
|
-
AutoTokenizer,
|
|
17
|
-
BitsAndBytesConfig
|
|
18
|
-
)
|
|
19
|
-
from trl import DPOTrainer, DPOConfig
|
|
20
|
-
except:
|
|
21
|
-
Dataset = None
|
|
22
|
-
PeftModel = None
|
|
23
|
-
DPOConfig = None
|
|
24
|
-
DPOTrainer = None
|
|
12
|
+
except ImportError:
|
|
25
13
|
torch = None
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
17
|
+
except ImportError:
|
|
26
18
|
AutoModelForCausalLM = None
|
|
27
19
|
AutoTokenizer = None
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from transformers import BitsAndBytesConfig
|
|
23
|
+
except ImportError:
|
|
28
24
|
BitsAndBytesConfig = None
|
|
29
25
|
|
|
26
|
+
try:
|
|
27
|
+
from datasets import Dataset
|
|
28
|
+
except ImportError:
|
|
29
|
+
Dataset = None
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
from peft import LoraConfig, PeftModel
|
|
33
|
+
except ImportError:
|
|
34
|
+
LoraConfig = None
|
|
35
|
+
PeftModel = None
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
from trl import DPOTrainer, DPOConfig
|
|
39
|
+
except ImportError:
|
|
40
|
+
DPOTrainer = None
|
|
41
|
+
DPOConfig = None
|
|
42
|
+
|
|
30
43
|
|
|
31
44
|
import random
|
|
32
45
|
from typing import List, Dict, Any, Optional, Callable
|
|
@@ -588,6 +588,148 @@ def get_ollama_response(
|
|
|
588
588
|
import time
|
|
589
589
|
|
|
590
590
|
|
|
591
|
+
def get_lora_response(
|
|
592
|
+
prompt: str = None,
|
|
593
|
+
model: str = None,
|
|
594
|
+
tools: list = None,
|
|
595
|
+
tool_map: Dict = None,
|
|
596
|
+
format: str = None,
|
|
597
|
+
messages: List[Dict[str, str]] = None,
|
|
598
|
+
stream: bool = False,
|
|
599
|
+
auto_process_tool_calls: bool = False,
|
|
600
|
+
**kwargs,
|
|
601
|
+
) -> Dict[str, Any]:
|
|
602
|
+
"""
|
|
603
|
+
Generate response using a LoRA adapter on top of a base model.
|
|
604
|
+
The adapter path should contain adapter_config.json with base_model_name_or_path.
|
|
605
|
+
"""
|
|
606
|
+
print(f"🎯 get_lora_response called with model={model}, prompt={prompt[:50] if prompt else 'None'}...")
|
|
607
|
+
|
|
608
|
+
result = {
|
|
609
|
+
"response": None,
|
|
610
|
+
"messages": messages.copy() if messages else [],
|
|
611
|
+
"raw_response": None,
|
|
612
|
+
"tool_calls": [],
|
|
613
|
+
"tool_results": []
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
try:
|
|
617
|
+
import torch
|
|
618
|
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
|
619
|
+
from peft import PeftModel
|
|
620
|
+
print("🎯 Successfully imported torch, transformers, peft")
|
|
621
|
+
except ImportError as e:
|
|
622
|
+
print(f"🎯 Import error: {e}")
|
|
623
|
+
return {
|
|
624
|
+
"response": "",
|
|
625
|
+
"messages": messages or [],
|
|
626
|
+
"error": f"Missing dependencies for LoRA. Install with: pip install transformers peft torch. Error: {e}"
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
adapter_path = os.path.expanduser(model)
|
|
630
|
+
adapter_config_path = os.path.join(adapter_path, 'adapter_config.json')
|
|
631
|
+
|
|
632
|
+
if not os.path.exists(adapter_config_path):
|
|
633
|
+
return {
|
|
634
|
+
"response": "",
|
|
635
|
+
"messages": messages or [],
|
|
636
|
+
"error": f"No adapter_config.json found at {adapter_path}"
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
# Read base model from adapter config
|
|
640
|
+
try:
|
|
641
|
+
with open(adapter_config_path, 'r') as f:
|
|
642
|
+
adapter_config = json.load(f)
|
|
643
|
+
base_model_id = adapter_config.get('base_model_name_or_path')
|
|
644
|
+
if not base_model_id:
|
|
645
|
+
return {
|
|
646
|
+
"response": "",
|
|
647
|
+
"messages": messages or [],
|
|
648
|
+
"error": "adapter_config.json missing base_model_name_or_path"
|
|
649
|
+
}
|
|
650
|
+
except Exception as e:
|
|
651
|
+
return {
|
|
652
|
+
"response": "",
|
|
653
|
+
"messages": messages or [],
|
|
654
|
+
"error": f"Failed to read adapter config: {e}"
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if prompt:
|
|
658
|
+
if result['messages'] and result['messages'][-1]["role"] == "user":
|
|
659
|
+
result['messages'][-1]["content"] = prompt
|
|
660
|
+
else:
|
|
661
|
+
result['messages'].append({"role": "user", "content": prompt})
|
|
662
|
+
|
|
663
|
+
if format == "json":
|
|
664
|
+
json_instruction = """If you are returning a json object, begin directly with the opening {.
|
|
665
|
+
Do not include any additional markdown formatting or leading ```json tags in your response."""
|
|
666
|
+
if result["messages"] and result["messages"][-1]["role"] == "user":
|
|
667
|
+
result["messages"][-1]["content"] += "\n" + json_instruction
|
|
668
|
+
|
|
669
|
+
try:
|
|
670
|
+
logger.info(f"Loading base model: {base_model_id}")
|
|
671
|
+
tokenizer = AutoTokenizer.from_pretrained(base_model_id, trust_remote_code=True)
|
|
672
|
+
base_model = AutoModelForCausalLM.from_pretrained(
|
|
673
|
+
base_model_id,
|
|
674
|
+
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
|
|
675
|
+
device_map="auto" if torch.cuda.is_available() else None,
|
|
676
|
+
trust_remote_code=True
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
if tokenizer.pad_token is None:
|
|
680
|
+
tokenizer.pad_token = tokenizer.eos_token
|
|
681
|
+
|
|
682
|
+
logger.info(f"Loading LoRA adapter: {adapter_path}")
|
|
683
|
+
model_with_adapter = PeftModel.from_pretrained(base_model, adapter_path)
|
|
684
|
+
|
|
685
|
+
# Apply chat template
|
|
686
|
+
chat_text = tokenizer.apply_chat_template(
|
|
687
|
+
result["messages"],
|
|
688
|
+
tokenize=False,
|
|
689
|
+
add_generation_prompt=True
|
|
690
|
+
)
|
|
691
|
+
device = next(model_with_adapter.parameters()).device
|
|
692
|
+
inputs = tokenizer(chat_text, return_tensors="pt", padding=True, truncation=True)
|
|
693
|
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
|
694
|
+
|
|
695
|
+
max_new_tokens = kwargs.get("max_tokens", 512)
|
|
696
|
+
temperature = kwargs.get("temperature", 0.7)
|
|
697
|
+
|
|
698
|
+
with torch.no_grad():
|
|
699
|
+
outputs = model_with_adapter.generate(
|
|
700
|
+
**inputs,
|
|
701
|
+
max_new_tokens=max_new_tokens,
|
|
702
|
+
temperature=temperature,
|
|
703
|
+
do_sample=True,
|
|
704
|
+
pad_token_id=tokenizer.eos_token_id,
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
response_content = tokenizer.decode(
|
|
708
|
+
outputs[0][inputs['input_ids'].shape[1]:],
|
|
709
|
+
skip_special_tokens=True
|
|
710
|
+
).strip()
|
|
711
|
+
|
|
712
|
+
result["response"] = response_content
|
|
713
|
+
result["raw_response"] = response_content
|
|
714
|
+
result["messages"].append({"role": "assistant", "content": response_content})
|
|
715
|
+
|
|
716
|
+
if format == "json":
|
|
717
|
+
try:
|
|
718
|
+
if response_content.startswith("```json"):
|
|
719
|
+
response_content = response_content.replace("```json", "").replace("```", "").strip()
|
|
720
|
+
parsed_response = json.loads(response_content)
|
|
721
|
+
result["response"] = parsed_response
|
|
722
|
+
except json.JSONDecodeError:
|
|
723
|
+
result["error"] = f"Invalid JSON response: {response_content}"
|
|
724
|
+
|
|
725
|
+
except Exception as e:
|
|
726
|
+
logger.error(f"LoRA inference error: {e}")
|
|
727
|
+
result["error"] = f"LoRA inference error: {str(e)}"
|
|
728
|
+
result["response"] = ""
|
|
729
|
+
|
|
730
|
+
return result
|
|
731
|
+
|
|
732
|
+
|
|
591
733
|
def get_llamacpp_response(
|
|
592
734
|
prompt: str = None,
|
|
593
735
|
model: str = None,
|
|
@@ -730,7 +872,7 @@ def get_litellm_response(
|
|
|
730
872
|
auto_process_tool_calls=auto_process_tool_calls,
|
|
731
873
|
**kwargs
|
|
732
874
|
)
|
|
733
|
-
elif provider=='transformers':
|
|
875
|
+
elif provider == 'transformers':
|
|
734
876
|
return get_transformers_response(
|
|
735
877
|
prompt,
|
|
736
878
|
model,
|
|
@@ -745,8 +887,24 @@ def get_litellm_response(
|
|
|
745
887
|
attachments=attachments,
|
|
746
888
|
auto_process_tool_calls=auto_process_tool_calls,
|
|
747
889
|
**kwargs
|
|
748
|
-
|
|
749
890
|
)
|
|
891
|
+
elif provider == 'lora':
|
|
892
|
+
print(f"🔧 LoRA provider detected, calling get_lora_response with model: {model}")
|
|
893
|
+
result = get_lora_response(
|
|
894
|
+
prompt=prompt,
|
|
895
|
+
model=model,
|
|
896
|
+
tools=tools,
|
|
897
|
+
tool_map=tool_map,
|
|
898
|
+
format=format,
|
|
899
|
+
messages=messages,
|
|
900
|
+
stream=stream,
|
|
901
|
+
auto_process_tool_calls=auto_process_tool_calls,
|
|
902
|
+
**kwargs
|
|
903
|
+
)
|
|
904
|
+
print(f"🔧 LoRA response: {result.get('response', 'NO RESPONSE')[:200] if result.get('response') else 'EMPTY'}")
|
|
905
|
+
if result.get('error'):
|
|
906
|
+
print(f"🔧 LoRA error: {result.get('error')}")
|
|
907
|
+
return result
|
|
750
908
|
elif provider == 'llamacpp':
|
|
751
909
|
return get_llamacpp_response(
|
|
752
910
|
prompt,
|
|
@@ -358,6 +358,25 @@ def get_locally_available_models(project_directory, airplane_mode=False):
|
|
|
358
358
|
except Exception as e:
|
|
359
359
|
logging.debug(f"llama.cpp server not available: {e}")
|
|
360
360
|
|
|
361
|
+
# Scan for LoRA adapters (fine-tuned models with adapter_config.json)
|
|
362
|
+
lora_dirs = [
|
|
363
|
+
os.path.expanduser('~/.npcsh/models'),
|
|
364
|
+
]
|
|
365
|
+
for scan_dir in lora_dirs:
|
|
366
|
+
if not os.path.isdir(scan_dir):
|
|
367
|
+
continue
|
|
368
|
+
try:
|
|
369
|
+
for item in os.listdir(scan_dir):
|
|
370
|
+
item_path = os.path.join(scan_dir, item)
|
|
371
|
+
if os.path.isdir(item_path):
|
|
372
|
+
adapter_config = os.path.join(item_path, 'adapter_config.json')
|
|
373
|
+
if os.path.exists(adapter_config):
|
|
374
|
+
# This is a LoRA adapter
|
|
375
|
+
available_models[item_path] = "lora"
|
|
376
|
+
logging.debug(f"Found LoRA adapter: {item_path}")
|
|
377
|
+
except Exception as e:
|
|
378
|
+
logging.debug(f"Error scanning LoRA directory {scan_dir}: {e}")
|
|
379
|
+
|
|
361
380
|
return available_models
|
|
362
381
|
|
|
363
382
|
|
|
@@ -959,13 +978,19 @@ def lookup_provider(model: str) -> str:
|
|
|
959
978
|
"""
|
|
960
979
|
Determine the provider based on the model name.
|
|
961
980
|
Checks custom providers first, then falls back to known providers.
|
|
962
|
-
|
|
981
|
+
|
|
963
982
|
Args:
|
|
964
983
|
model: The model name
|
|
965
|
-
|
|
984
|
+
|
|
966
985
|
Returns:
|
|
967
986
|
The provider name or None if not found
|
|
968
987
|
"""
|
|
988
|
+
# Check if model is a LoRA adapter path
|
|
989
|
+
if model and os.path.isdir(os.path.expanduser(model)):
|
|
990
|
+
adapter_config = os.path.join(os.path.expanduser(model), 'adapter_config.json')
|
|
991
|
+
if os.path.exists(adapter_config):
|
|
992
|
+
return "lora"
|
|
993
|
+
|
|
969
994
|
custom_providers = load_custom_providers()
|
|
970
995
|
|
|
971
996
|
for provider_name, config in custom_providers.items():
|