npcpy 1.2.34__py3-none-any.whl → 1.2.36__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/audio.py +35 -1
- npcpy/data/load.py +149 -7
- npcpy/data/video.py +72 -0
- npcpy/ft/diff.py +332 -71
- npcpy/gen/image_gen.py +120 -23
- npcpy/gen/ocr.py +187 -0
- npcpy/memory/command_history.py +231 -40
- npcpy/npc_compiler.py +64 -22
- npcpy/serve.py +1712 -607
- {npcpy-1.2.34.dist-info → npcpy-1.2.36.dist-info}/METADATA +1 -1
- {npcpy-1.2.34.dist-info → npcpy-1.2.36.dist-info}/RECORD +14 -13
- {npcpy-1.2.34.dist-info → npcpy-1.2.36.dist-info}/WHEEL +0 -0
- {npcpy-1.2.34.dist-info → npcpy-1.2.36.dist-info}/licenses/LICENSE +0 -0
- {npcpy-1.2.34.dist-info → npcpy-1.2.36.dist-info}/top_level.txt +0 -0
npcpy/npc_compiler.py
CHANGED
|
@@ -20,7 +20,8 @@ from sqlalchemy import create_engine, text
|
|
|
20
20
|
import npcpy as npy
|
|
21
21
|
from npcpy.llm_funcs import DEFAULT_ACTION_SPACE
|
|
22
22
|
from npcpy.tools import auto_tools
|
|
23
|
-
|
|
23
|
+
import math
|
|
24
|
+
import random
|
|
24
25
|
from npcpy.npc_sysenv import (
|
|
25
26
|
ensure_dirs_exist,
|
|
26
27
|
init_db_tables,
|
|
@@ -259,16 +260,18 @@ class Jinx:
|
|
|
259
260
|
self._load_from_data(jinx_data)
|
|
260
261
|
else:
|
|
261
262
|
raise ValueError("Either jinx_data or jinx_path must be provided")
|
|
262
|
-
|
|
263
|
+
|
|
264
|
+
# Keep a copy for macro expansion, but retain the executable steps by default
|
|
263
265
|
self._raw_steps = list(self.steps)
|
|
264
|
-
self.steps =
|
|
265
|
-
|
|
266
|
+
self.steps = list(self._raw_steps)
|
|
266
267
|
def _load_from_file(self, path):
|
|
267
268
|
jinx_data = load_yaml_file(path)
|
|
268
269
|
if not jinx_data:
|
|
269
270
|
raise ValueError(f"Failed to load jinx from {path}")
|
|
271
|
+
self._source_path = path
|
|
270
272
|
self._load_from_data(jinx_data)
|
|
271
273
|
|
|
274
|
+
|
|
272
275
|
def _load_from_data(self, jinx_data):
|
|
273
276
|
if not jinx_data or not isinstance(jinx_data, dict):
|
|
274
277
|
raise ValueError("Invalid jinx data provided")
|
|
@@ -281,6 +284,7 @@ class Jinx:
|
|
|
281
284
|
self.description = jinx_data.get("description", "")
|
|
282
285
|
self.npc = jinx_data.get("npc")
|
|
283
286
|
self.steps = jinx_data.get("steps", [])
|
|
287
|
+
self._source_path = jinx_data.get("_source_path", None)
|
|
284
288
|
|
|
285
289
|
def render_first_pass(
|
|
286
290
|
self,
|
|
@@ -450,6 +454,10 @@ class Jinx:
|
|
|
450
454
|
"__builtins__": __builtins__,
|
|
451
455
|
"npc": active_npc,
|
|
452
456
|
"context": context,
|
|
457
|
+
"math": math,
|
|
458
|
+
"random": random,
|
|
459
|
+
"datetime": datetime,
|
|
460
|
+
"Image": Image,
|
|
453
461
|
"pd": pd,
|
|
454
462
|
"plt": plt,
|
|
455
463
|
"sys": sys,
|
|
@@ -611,6 +619,35 @@ def load_jinxs_from_directory(directory):
|
|
|
611
619
|
|
|
612
620
|
return jinxs
|
|
613
621
|
|
|
622
|
+
def jinx_to_tool_def(jinx_obj: 'Jinx') -> Dict[str, Any]:
|
|
623
|
+
"""Convert a Jinx instance into an MCP/LLM-compatible tool schema definition."""
|
|
624
|
+
properties: Dict[str, Any] = {}
|
|
625
|
+
required: List[str] = []
|
|
626
|
+
for inp in jinx_obj.inputs:
|
|
627
|
+
if isinstance(inp, str):
|
|
628
|
+
properties[inp] = {"type": "string"}
|
|
629
|
+
required.append(inp)
|
|
630
|
+
elif isinstance(inp, dict):
|
|
631
|
+
name = list(inp.keys())[0]
|
|
632
|
+
properties[name] = {"type": "string", "default": inp.get(name, "")}
|
|
633
|
+
required.append(name)
|
|
634
|
+
return {
|
|
635
|
+
"type": "function",
|
|
636
|
+
"function": {
|
|
637
|
+
"name": jinx_obj.jinx_name,
|
|
638
|
+
"description": jinx_obj.description or f"Jinx: {jinx_obj.jinx_name}",
|
|
639
|
+
"parameters": {
|
|
640
|
+
"type": "object",
|
|
641
|
+
"properties": properties,
|
|
642
|
+
"required": required
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
def build_jinx_tool_catalog(jinxs: Dict[str, 'Jinx']) -> Dict[str, Dict[str, Any]]:
|
|
648
|
+
"""Helper to build a name->tool_def catalog from a dict of Jinx objects."""
|
|
649
|
+
return {name: jinx_to_tool_def(jinx_obj) for name, jinx_obj in jinxs.items()}
|
|
650
|
+
|
|
614
651
|
def get_npc_action_space(npc=None, team=None):
|
|
615
652
|
"""Get action space for NPC including memory CRUD and core capabilities"""
|
|
616
653
|
actions = DEFAULT_ACTION_SPACE.copy()
|
|
@@ -618,8 +655,9 @@ def get_npc_action_space(npc=None, team=None):
|
|
|
618
655
|
if npc:
|
|
619
656
|
core_tools = [
|
|
620
657
|
npc.think_step_by_step,
|
|
621
|
-
npc.write_code
|
|
622
658
|
]
|
|
659
|
+
if hasattr(npc, "write_code"):
|
|
660
|
+
core_tools.append(npc.write_code)
|
|
623
661
|
|
|
624
662
|
if npc.command_history:
|
|
625
663
|
core_tools.extend([
|
|
@@ -842,6 +880,8 @@ class NPC:
|
|
|
842
880
|
self.tools_schema = []
|
|
843
881
|
self.plain_system_message = plain_system_message
|
|
844
882
|
self.use_global_jinxs = use_global_jinxs
|
|
883
|
+
self.jinx_tool_catalog: Dict[str, Dict[str, Any]] = {}
|
|
884
|
+
self.mcp_servers = []
|
|
845
885
|
|
|
846
886
|
self.memory_length = 20
|
|
847
887
|
self.memory_strategy = 'recent'
|
|
@@ -877,20 +917,20 @@ class NPC:
|
|
|
877
917
|
# If jinxs are explicitly provided *to the NPC* during its standalone creation, load them.
|
|
878
918
|
# This is for NPCs created *outside* a team context initially.
|
|
879
919
|
if jinxs and jinxs != "*":
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
920
|
+
for jinx_item in jinxs:
|
|
921
|
+
if isinstance(jinx_item, Jinx):
|
|
922
|
+
self.jinxs_dict[jinx_item.jinx_name] = jinx_item
|
|
923
|
+
elif isinstance(jinx_item, dict):
|
|
924
|
+
jinx_obj = Jinx(jinx_data=jinx_item)
|
|
925
|
+
self.jinxs_dict[jinx_obj.jinx_name] = jinx_obj
|
|
926
|
+
elif isinstance(jinx_item, str):
|
|
927
|
+
# Try to load from NPC's own directory first
|
|
928
|
+
jinx_path = find_file_path(jinx_item, [self.npc_jinxs_directory], suffix=".jinx")
|
|
929
|
+
if jinx_path:
|
|
930
|
+
jinx_obj = Jinx(jinx_path=jinx_path)
|
|
931
|
+
self.jinxs_dict[jinx_obj.jinx_name] = jinx_obj
|
|
932
|
+
else:
|
|
933
|
+
print(f"Warning: Jinx '{jinx_item}' not found for NPC '{self.name}' during initial load.")
|
|
894
934
|
|
|
895
935
|
self.shared_context = {
|
|
896
936
|
"dataframes": {},
|
|
@@ -968,7 +1008,8 @@ class NPC:
|
|
|
968
1008
|
except Exception as e:
|
|
969
1009
|
print(f"Error performing first-pass rendering for NPC Jinx '{raw_npc_jinx.jinx_name}': {e}")
|
|
970
1010
|
|
|
971
|
-
|
|
1011
|
+
self.jinx_tool_catalog = build_jinx_tool_catalog(self.jinxs_dict)
|
|
1012
|
+
print(f"NPC {self.name} loaded {len(self.jinxs_dict)} jinxs and built catalog with {len(self.jinx_tool_catalog)} tools.")
|
|
972
1013
|
|
|
973
1014
|
def _load_npc_kg(self):
|
|
974
1015
|
"""Load knowledge graph data for this NPC from database"""
|
|
@@ -1995,6 +2036,7 @@ class Team:
|
|
|
1995
2036
|
self.sub_teams: Dict[str, 'Team'] = {}
|
|
1996
2037
|
self.jinxs_dict: Dict[str, 'Jinx'] = {} # This will store first-pass rendered Jinx objects
|
|
1997
2038
|
self._raw_jinxs_list: List['Jinx'] = [] # Temporary storage for raw Team-level Jinx objects
|
|
2039
|
+
self.jinx_tool_catalog: Dict[str, Dict[str, Any]] = {} # Jinx-derived tool defs ready for MCP/LLM
|
|
1998
2040
|
|
|
1999
2041
|
self.jinja_env_for_first_pass = Environment(undefined=SilentUndefined) # Env for macro expansion
|
|
2000
2042
|
|
|
@@ -2050,6 +2092,8 @@ class Team:
|
|
|
2050
2092
|
|
|
2051
2093
|
# Perform first-pass rendering for team-level jinxs
|
|
2052
2094
|
self._perform_first_pass_jinx_rendering()
|
|
2095
|
+
self.jinx_tool_catalog = build_jinx_tool_catalog(self.jinxs_dict)
|
|
2096
|
+
print(f"[TEAM] Built Jinx tool catalog with {len(self.jinx_tool_catalog)} entries for team {self.name}")
|
|
2053
2097
|
|
|
2054
2098
|
# Now, initialize jinxs for all NPCs, as team-level jinxs are ready
|
|
2055
2099
|
for npc_obj in self.npcs.values():
|
|
@@ -2227,8 +2271,6 @@ class Team:
|
|
|
2227
2271
|
self.jinxs_dict[raw_jinx.jinx_name] = raw_jinx # Store the first-pass rendered Jinx
|
|
2228
2272
|
except Exception as e:
|
|
2229
2273
|
print(f"Error performing first-pass rendering for Jinx '{raw_jinx.jinx_name}': {e}")
|
|
2230
|
-
|
|
2231
|
-
self._raw_jinxs_list = [] # Clear temporary storage
|
|
2232
2274
|
|
|
2233
2275
|
|
|
2234
2276
|
def update_context(self, messages: list):
|