npcpy 1.2.37__tar.gz → 1.3.1__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.2.37/npcpy.egg-info → npcpy-1.3.1}/PKG-INFO +3 -4
- {npcpy-1.2.37 → npcpy-1.3.1}/README.md +2 -3
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/npc_compiler.py +202 -84
- {npcpy-1.2.37 → npcpy-1.3.1/npcpy.egg-info}/PKG-INFO +3 -4
- {npcpy-1.2.37 → npcpy-1.3.1}/setup.py +1 -1
- {npcpy-1.2.37 → npcpy-1.3.1}/LICENSE +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/MANIFEST.in +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/__init__.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/data/__init__.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/data/audio.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/data/data_models.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/data/image.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/data/load.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/data/text.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/data/video.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/data/web.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/ft/__init__.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/ft/diff.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/ft/ge.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/ft/memory_trainer.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/ft/model_ensembler.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/ft/rl.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/ft/sft.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/ft/usft.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/gen/__init__.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/gen/audio_gen.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/gen/embeddings.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/gen/image_gen.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/gen/ocr.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/gen/response.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/gen/video_gen.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/llm_funcs.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/main.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/memory/__init__.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/memory/command_history.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/memory/kg_vis.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/memory/knowledge_graph.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/memory/memory_processor.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/memory/search.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/mix/__init__.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/mix/debate.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/ml_funcs.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/npc_array.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/npc_sysenv.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/npcs.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/serve.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/sql/__init__.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/sql/ai_function_tools.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/sql/database_ai_adapters.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/sql/database_ai_functions.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/sql/model_runner.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/sql/npcsql.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/sql/sql_model_compiler.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/tools.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/work/__init__.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/work/desktop.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/work/plan.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy/work/trigger.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy.egg-info/SOURCES.txt +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy.egg-info/dependency_links.txt +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy.egg-info/requires.txt +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/npcpy.egg-info/top_level.txt +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/setup.cfg +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/tests/test_audio.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/tests/test_command_history.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/tests/test_image.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/tests/test_llm_funcs.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/tests/test_load.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/tests/test_npc_array.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/tests/test_npc_compiler.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/tests/test_npcsql.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/tests/test_response.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/tests/test_serve.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/tests/test_text.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/tests/test_tools.py +0 -0
- {npcpy-1.2.37 → npcpy-1.3.1}/tests/test_web.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: npcpy
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
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
|
|
@@ -275,9 +275,8 @@ output = context['research_summary']
|
|
|
275
275
|
"code": '''
|
|
276
276
|
# Access outputs from previous steps.
|
|
277
277
|
research_summary = context['initial_llm_research']
|
|
278
|
-
# The
|
|
279
|
-
|
|
280
|
-
file_summary = context['read_and_process_source_file'].get('output', 'No file summary available.')
|
|
278
|
+
# The file_reader jinx returns its output directly; also keep a fallback of file_raw_content.
|
|
279
|
+
file_summary = context.get('read_and_process_source_file', '') or context.get('file_raw_content', 'No file summary available.')
|
|
281
280
|
|
|
282
281
|
prompt = f"""Based on the following information:
|
|
283
282
|
1. Comprehensive Research Summary:
|
|
@@ -179,9 +179,8 @@ output = context['research_summary']
|
|
|
179
179
|
"code": '''
|
|
180
180
|
# Access outputs from previous steps.
|
|
181
181
|
research_summary = context['initial_llm_research']
|
|
182
|
-
# The
|
|
183
|
-
|
|
184
|
-
file_summary = context['read_and_process_source_file'].get('output', 'No file summary available.')
|
|
182
|
+
# The file_reader jinx returns its output directly; also keep a fallback of file_raw_content.
|
|
183
|
+
file_summary = context.get('read_and_process_source_file', '') or context.get('file_raw_content', 'No file summary available.')
|
|
185
184
|
|
|
186
185
|
prompt = f"""Based on the following information:
|
|
187
186
|
1. Comprehensive Research Summary:
|
|
@@ -384,7 +384,6 @@ def write_yaml_file(file_path, data):
|
|
|
384
384
|
print(f"Error writing YAML file {file_path}: {e}")
|
|
385
385
|
return False
|
|
386
386
|
|
|
387
|
-
|
|
388
387
|
class Jinx:
|
|
389
388
|
'''
|
|
390
389
|
Jinx represents a workflow template with Jinja-rendered steps.
|
|
@@ -394,7 +393,8 @@ class Jinx:
|
|
|
394
393
|
- inputs: list of input parameters
|
|
395
394
|
- description: what the jinx does
|
|
396
395
|
- npc: optional NPC to execute with
|
|
397
|
-
- steps: list of step definitions with code
|
|
396
|
+
- steps: list of step definitions with code. This section can now be a Jinja template itself.
|
|
397
|
+
- file_context: optional list of file patterns to include as context
|
|
398
398
|
|
|
399
399
|
Execution:
|
|
400
400
|
- Renders Jinja templates in step code with input values
|
|
@@ -409,9 +409,13 @@ class Jinx:
|
|
|
409
409
|
else:
|
|
410
410
|
raise ValueError("Either jinx_data or jinx_path must be provided")
|
|
411
411
|
|
|
412
|
-
#
|
|
412
|
+
# _raw_steps will now hold the original, potentially templated, steps definition
|
|
413
413
|
self._raw_steps = list(self.steps)
|
|
414
|
-
self.steps =
|
|
414
|
+
self.steps = [] # Will be populated after first-pass rendering
|
|
415
|
+
self.parsed_files = {}
|
|
416
|
+
if self.file_context:
|
|
417
|
+
self.parsed_files = self._parse_file_patterns(self.file_context)
|
|
418
|
+
|
|
415
419
|
def _load_from_file(self, path):
|
|
416
420
|
jinx_data = load_yaml_file(path)
|
|
417
421
|
if not jinx_data:
|
|
@@ -431,7 +435,8 @@ class Jinx:
|
|
|
431
435
|
self.inputs = jinx_data.get("inputs", [])
|
|
432
436
|
self.description = jinx_data.get("description", "")
|
|
433
437
|
self.npc = jinx_data.get("npc")
|
|
434
|
-
self.steps = jinx_data.get("steps", [])
|
|
438
|
+
self.steps = jinx_data.get("steps", []) # This can now be a Jinja templated list
|
|
439
|
+
self.file_context = jinx_data.get("file_context", [])
|
|
435
440
|
self._source_path = jinx_data.get("_source_path", None)
|
|
436
441
|
|
|
437
442
|
def to_tool_def(self) -> Dict[str, Any]:
|
|
@@ -469,21 +474,52 @@ class Jinx:
|
|
|
469
474
|
):
|
|
470
475
|
"""
|
|
471
476
|
Performs the first-pass Jinja rendering on the Jinx's raw steps.
|
|
472
|
-
This expands
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
Args:
|
|
477
|
-
jinja_env_for_macros: The Jinja Environment configured with
|
|
478
|
-
Jinx callables in its globals.
|
|
479
|
-
all_jinx_callables: A dictionary of Jinx names to their
|
|
480
|
-
callable functions (from create_jinx_callable).
|
|
477
|
+
This expands Jinja control flow (for, if) to generate step structures,
|
|
478
|
+
then expands nested Jinx calls (e.g., {{ sh(...) }} or engine: jinx_name)
|
|
479
|
+
and inline macros.
|
|
481
480
|
"""
|
|
482
|
-
|
|
481
|
+
# 1. Join the list of raw steps (which are individual YAML lines) into a single string.
|
|
482
|
+
# This single string is the complete Jinja template for the 'steps' section.
|
|
483
|
+
raw_steps_template_string = "\n".join(self._raw_steps)
|
|
484
|
+
|
|
485
|
+
# 2. Render this single string as a Jinja template.
|
|
486
|
+
# Jinja will now process the {% for %} and {% if %} directives,
|
|
487
|
+
# dynamically generating the YAML structure.
|
|
488
|
+
try:
|
|
489
|
+
steps_template = jinja_env_for_macros.from_string(raw_steps_template_string)
|
|
490
|
+
# Pass globals (like num_tasks, include_greeting from Jinx inputs)
|
|
491
|
+
# to the Jinja rendering context for structural templating.
|
|
492
|
+
rendered_steps_yaml_string = steps_template.render(**jinja_env_for_macros.globals)
|
|
493
|
+
except Exception as e:
|
|
494
|
+
# In a real Jinx, this would go to a proper logger.
|
|
495
|
+
# For this context, we handle the error gracefully.
|
|
496
|
+
# self._log_debug(f"Warning: Error during first-pass templating of Jinx '{self.jinx_name}' steps YAML: {e}")
|
|
497
|
+
self.steps = list(self._raw_steps) # Fallback to original raw steps
|
|
498
|
+
return
|
|
483
499
|
|
|
484
|
-
|
|
500
|
+
# 3. Parse the rendered YAML string back into a list of step dictionaries.
|
|
501
|
+
# This step will now correctly interpret the YAML structure generated by Jinja.
|
|
502
|
+
try:
|
|
503
|
+
structurally_expanded_steps = yaml.safe_load(rendered_steps_yaml_string)
|
|
504
|
+
if not isinstance(structurally_expanded_steps, list):
|
|
505
|
+
# Handle cases where the rendered YAML might be empty or not a list
|
|
506
|
+
if structurally_expanded_steps is None:
|
|
507
|
+
structurally_expanded_steps = []
|
|
508
|
+
else:
|
|
509
|
+
raise ValueError(f"Rendered steps YAML did not result in a list: {type(structurally_expanded_steps)}")
|
|
510
|
+
self.steps = structurally_expanded_steps
|
|
511
|
+
except Exception as e:
|
|
512
|
+
# self._log_debug(f"Warning: Error re-parsing structurally expanded steps YAML for Jinx '{self.jinx_name}': {e}")
|
|
513
|
+
self.steps = list(self._raw_steps) # Fallback
|
|
514
|
+
return
|
|
515
|
+
|
|
516
|
+
# 4. Now, iterate through these `structurally_expanded_steps` to expand
|
|
517
|
+
# declarative Jinx calls (engine: jinx_name) and inline macros.
|
|
518
|
+
# This is the second phase of the first-pass rendering.
|
|
519
|
+
final_rendered_steps = []
|
|
520
|
+
for raw_step in structurally_expanded_steps:
|
|
485
521
|
if not isinstance(raw_step, dict):
|
|
486
|
-
|
|
522
|
+
final_rendered_steps.append(raw_step)
|
|
487
523
|
continue
|
|
488
524
|
|
|
489
525
|
engine_name = raw_step.get('engine')
|
|
@@ -502,51 +538,58 @@ class Jinx:
|
|
|
502
538
|
expanded_steps = yaml.safe_load(expanded_yaml_string)
|
|
503
539
|
|
|
504
540
|
if isinstance(expanded_steps, list):
|
|
505
|
-
|
|
506
|
-
|
|
541
|
+
final_rendered_steps.extend(expanded_steps)
|
|
507
542
|
elif expanded_steps is not None:
|
|
508
|
-
|
|
509
|
-
|
|
543
|
+
final_rendered_steps.append(expanded_steps)
|
|
510
544
|
except Exception as e:
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
)
|
|
516
|
-
|
|
517
|
-
#
|
|
545
|
+
# self._log_debug(
|
|
546
|
+
# f"Warning: Error expanding Jinx '{engine_name}' "
|
|
547
|
+
# f"within Jinx '{self.jinx_name}' "
|
|
548
|
+
# f"(declarative): {e}"
|
|
549
|
+
# )
|
|
550
|
+
final_rendered_steps.append(raw_step)
|
|
551
|
+
# For python/bash engine steps, only inline macro expansion happens in the next block.
|
|
552
|
+
# The code content itself is preserved for runtime Jinja rendering.
|
|
518
553
|
elif raw_step.get('engine') in ['python', 'bash']:
|
|
519
|
-
|
|
554
|
+
processed_step = {}
|
|
555
|
+
for key, value in raw_step.items():
|
|
556
|
+
if isinstance(value, str):
|
|
557
|
+
try:
|
|
558
|
+
template = jinja_env_for_macros.from_string(value)
|
|
559
|
+
# Render with empty context for inline macros/static values
|
|
560
|
+
rendered_value = template.render({})
|
|
561
|
+
try:
|
|
562
|
+
loaded_value = yaml.safe_load(rendered_value)
|
|
563
|
+
processed_step[key] = loaded_value
|
|
564
|
+
except yaml.YAMLError:
|
|
565
|
+
processed_step[key] = rendered_value
|
|
566
|
+
except Exception as e:
|
|
567
|
+
# self._log_debug(f"Warning: Error during first-pass rendering of Jinx '{self.jinx_name}' step field '{key}' (inline macro): {e}")
|
|
568
|
+
processed_step[key] = value
|
|
569
|
+
else:
|
|
570
|
+
processed_step[key] = value
|
|
571
|
+
final_rendered_steps.append(processed_step)
|
|
520
572
|
else:
|
|
521
|
-
# For other steps,
|
|
573
|
+
# For other steps (e.g., custom engines, or just data), perform inline macro expansion
|
|
522
574
|
processed_step = {}
|
|
523
575
|
for key, value in raw_step.items():
|
|
524
576
|
if isinstance(value, str):
|
|
525
577
|
try:
|
|
526
|
-
template = jinja_env_for_macros.from_string(
|
|
527
|
-
value
|
|
528
|
-
)
|
|
578
|
+
template = jinja_env_for_macros.from_string(value)
|
|
529
579
|
rendered_value = template.render({})
|
|
530
|
-
|
|
531
580
|
try:
|
|
532
|
-
loaded_value = yaml.safe_load(
|
|
533
|
-
rendered_value
|
|
534
|
-
)
|
|
581
|
+
loaded_value = yaml.safe_load(rendered_value)
|
|
535
582
|
processed_step[key] = loaded_value
|
|
536
583
|
except yaml.YAMLError:
|
|
537
584
|
processed_step[key] = rendered_value
|
|
538
585
|
except Exception as e:
|
|
539
|
-
|
|
540
|
-
f"Warning: Error during first-pass "
|
|
541
|
-
f"rendering of Jinx '{self.jinx_name}' "
|
|
542
|
-
f"step field '{key}' (inline macro): {e}"
|
|
543
|
-
)
|
|
586
|
+
# self._log_debug(f"Warning: Error during first-pass rendering of Jinx '{self.jinx_name}' step field '{key}' (inline macro): {e}")
|
|
544
587
|
processed_step[key] = value
|
|
545
588
|
else:
|
|
546
589
|
processed_step[key] = value
|
|
547
|
-
|
|
590
|
+
final_rendered_steps.append(processed_step)
|
|
548
591
|
|
|
549
|
-
self.steps =
|
|
592
|
+
self.steps = final_rendered_steps
|
|
550
593
|
|
|
551
594
|
def execute(self,
|
|
552
595
|
input_values: Dict[str, Any],
|
|
@@ -575,6 +618,11 @@ class Jinx:
|
|
|
575
618
|
"messages": messages,
|
|
576
619
|
"npc": active_npc
|
|
577
620
|
})
|
|
621
|
+
|
|
622
|
+
# Add parsed file content to the context
|
|
623
|
+
if self.parsed_files:
|
|
624
|
+
context['file_context'] = self._format_parsed_files_context(self.parsed_files)
|
|
625
|
+
context['files'] = self.parsed_files # Also make raw dict available
|
|
578
626
|
|
|
579
627
|
for i, step in enumerate(self.steps):
|
|
580
628
|
context = self._execute_step(
|
|
@@ -585,9 +633,19 @@ class Jinx:
|
|
|
585
633
|
messages=messages,
|
|
586
634
|
extra_globals=extra_globals
|
|
587
635
|
)
|
|
636
|
+
# If an error occurred in a step, propagate it and stop execution
|
|
637
|
+
if "error" in context.get("output", ""):
|
|
638
|
+
self._log_debug(f"DEBUG: Jinx '{self.jinx_name}' execution stopped due to error in step '{step.get('name', 'unnamed_step')}': {context['output']}")
|
|
639
|
+
break
|
|
588
640
|
|
|
589
641
|
return context
|
|
590
642
|
|
|
643
|
+
def _log_debug(self, msg: str):
|
|
644
|
+
"""Helper for logging debug messages to a file."""
|
|
645
|
+
log_file_path = os.path.expanduser("~/jinx_debug_log.txt")
|
|
646
|
+
with open(log_file_path, "a") as f:
|
|
647
|
+
f.write(f"[{datetime.now().isoformat()}] {msg}\n")
|
|
648
|
+
|
|
591
649
|
def _execute_step(self,
|
|
592
650
|
step: Dict[str, Any],
|
|
593
651
|
context: Dict[str, Any],
|
|
@@ -596,40 +654,31 @@ class Jinx:
|
|
|
596
654
|
messages: Optional[List[Dict[str, str]]] = None,
|
|
597
655
|
extra_globals: Optional[Dict[str, Any]] = None):
|
|
598
656
|
|
|
599
|
-
def _log_debug(msg):
|
|
600
|
-
log_file_path = os.path.expanduser("~/jinx_debug_log.txt")
|
|
601
|
-
with open(log_file_path, "a") as f:
|
|
602
|
-
f.write(f"[{datetime.now().isoformat()}] {msg}\n")
|
|
603
|
-
|
|
604
657
|
code_content = step.get("code", "")
|
|
605
658
|
step_name = step.get("name", "unnamed_step")
|
|
606
659
|
step_npc = step.get("npc")
|
|
607
660
|
|
|
608
661
|
active_npc = step_npc if step_npc else npc
|
|
609
662
|
|
|
663
|
+
# Second pass Jinja rendering: render the step's code with the current runtime context
|
|
610
664
|
try:
|
|
611
665
|
template = jinja_env.from_string(code_content)
|
|
612
666
|
rendered_code = template.render(**context)
|
|
613
667
|
except Exception as e:
|
|
614
|
-
|
|
615
|
-
f"Error rendering template for step {step_name} "
|
|
616
|
-
f"(second pass): {e}"
|
|
668
|
+
error_msg = (
|
|
669
|
+
f"Error rendering template for step '{step_name}' "
|
|
670
|
+
f"(second pass): {type(e).__name__}: {e}"
|
|
617
671
|
)
|
|
618
|
-
|
|
672
|
+
context['output'] = error_msg
|
|
673
|
+
self._log_debug(error_msg)
|
|
674
|
+
return context
|
|
619
675
|
|
|
620
|
-
_log_debug(f"rendered
|
|
621
|
-
_log_debug(
|
|
622
|
-
f"DEBUG: Before exec - rendered_code: {rendered_code}"
|
|
623
|
-
)
|
|
624
|
-
_log_debug(
|
|
625
|
-
f"DEBUG: Before exec - context['output'] before step: "
|
|
626
|
-
f"{context.get('output')}"
|
|
627
|
-
)
|
|
676
|
+
self._log_debug(f"DEBUG: Executing step '{step_name}' with rendered code: {rendered_code}")
|
|
628
677
|
|
|
629
678
|
exec_globals = {
|
|
630
679
|
"__builtins__": __builtins__,
|
|
631
680
|
"npc": active_npc,
|
|
632
|
-
"context": context,
|
|
681
|
+
"context": context, # Pass context by reference
|
|
633
682
|
"math": math,
|
|
634
683
|
"random": random,
|
|
635
684
|
"datetime": datetime,
|
|
@@ -653,43 +702,27 @@ class Jinx:
|
|
|
653
702
|
if extra_globals:
|
|
654
703
|
exec_globals.update(extra_globals)
|
|
655
704
|
|
|
656
|
-
exec_locals = {}
|
|
657
|
-
|
|
705
|
+
exec_locals = {} # Locals for this specific exec call
|
|
706
|
+
|
|
658
707
|
try:
|
|
659
708
|
exec(rendered_code, exec_globals, exec_locals)
|
|
660
709
|
except Exception as e:
|
|
661
710
|
error_msg = (
|
|
662
|
-
f"Error executing step {step_name}: "
|
|
711
|
+
f"Error executing step '{step_name}': "
|
|
663
712
|
f"{type(e).__name__}: {e}"
|
|
664
713
|
)
|
|
665
714
|
context['output'] = error_msg
|
|
666
|
-
_log_debug(error_msg)
|
|
715
|
+
self._log_debug(error_msg)
|
|
667
716
|
return context
|
|
668
717
|
|
|
669
|
-
|
|
670
|
-
_log_debug(
|
|
671
|
-
f"DEBUG: After exec - 'output' in exec_locals: "
|
|
672
|
-
f"{'output' in exec_locals}"
|
|
673
|
-
)
|
|
674
|
-
|
|
718
|
+
# Update the main context with any variables set in exec_locals
|
|
675
719
|
context.update(exec_locals)
|
|
676
720
|
|
|
677
|
-
_log_debug(
|
|
678
|
-
f"DEBUG: After context.update(exec_locals) - "
|
|
679
|
-
f"context['output']: {context.get('output')}"
|
|
680
|
-
)
|
|
681
|
-
_log_debug(f"context after jinx ex: {context}")
|
|
682
|
-
|
|
683
721
|
if "output" in exec_locals:
|
|
684
722
|
outp = exec_locals["output"]
|
|
685
723
|
context["output"] = outp
|
|
686
724
|
context[step_name] = outp
|
|
687
725
|
|
|
688
|
-
_log_debug(
|
|
689
|
-
f"DEBUG: Inside 'output' in exec_locals block - "
|
|
690
|
-
f"context['output']: {context.get('output')}"
|
|
691
|
-
)
|
|
692
|
-
|
|
693
726
|
if messages is not None:
|
|
694
727
|
messages.append({
|
|
695
728
|
'role':'assistant',
|
|
@@ -702,13 +735,95 @@ class Jinx:
|
|
|
702
735
|
|
|
703
736
|
return context
|
|
704
737
|
|
|
738
|
+
def _parse_file_patterns(self, patterns_config):
|
|
739
|
+
"""Parse file patterns configuration and load matching files into KV cache"""
|
|
740
|
+
if not patterns_config:
|
|
741
|
+
return {}
|
|
742
|
+
|
|
743
|
+
file_cache = {}
|
|
744
|
+
|
|
745
|
+
for pattern_entry in patterns_config:
|
|
746
|
+
if isinstance(pattern_entry, str):
|
|
747
|
+
pattern_entry = {"pattern": pattern_entry}
|
|
748
|
+
|
|
749
|
+
pattern = pattern_entry.get("pattern", "")
|
|
750
|
+
recursive = pattern_entry.get("recursive", False)
|
|
751
|
+
base_path = pattern_entry.get("base_path", ".")
|
|
752
|
+
|
|
753
|
+
if not pattern:
|
|
754
|
+
continue
|
|
755
|
+
|
|
756
|
+
# Resolve base_path relative to jinx's source path or current working directory
|
|
757
|
+
if self._source_path:
|
|
758
|
+
base_path = os.path.join(os.path.dirname(self._source_path), base_path)
|
|
759
|
+
base_path = os.path.expanduser(base_path)
|
|
760
|
+
|
|
761
|
+
if not os.path.isabs(base_path):
|
|
762
|
+
base_path = os.path.join(os.getcwd(), base_path)
|
|
763
|
+
|
|
764
|
+
matching_files = self._find_matching_files(pattern, base_path, recursive)
|
|
765
|
+
|
|
766
|
+
for file_path in matching_files:
|
|
767
|
+
file_content = self._load_file_content(file_path)
|
|
768
|
+
if file_content:
|
|
769
|
+
relative_path = os.path.relpath(file_path, base_path)
|
|
770
|
+
file_cache[relative_path] = file_content
|
|
771
|
+
|
|
772
|
+
return file_cache
|
|
773
|
+
|
|
774
|
+
def _find_matching_files(self, pattern, base_path, recursive=False):
|
|
775
|
+
"""Find files matching the given pattern"""
|
|
776
|
+
matching_files = []
|
|
777
|
+
|
|
778
|
+
if not os.path.exists(base_path):
|
|
779
|
+
return matching_files
|
|
780
|
+
|
|
781
|
+
if recursive:
|
|
782
|
+
for root, dirs, files in os.walk(base_path):
|
|
783
|
+
for filename in files:
|
|
784
|
+
if fnmatch.fnmatch(filename, pattern):
|
|
785
|
+
matching_files.append(os.path.join(root, filename))
|
|
786
|
+
else:
|
|
787
|
+
try:
|
|
788
|
+
for item in os.listdir(base_path):
|
|
789
|
+
item_path = os.path.join(base_path, item)
|
|
790
|
+
if os.path.isfile(item_path) and fnmatch.fnmatch(item, pattern):
|
|
791
|
+
matching_files.append(item_path)
|
|
792
|
+
except PermissionError:
|
|
793
|
+
print(f"Permission denied accessing {base_path}")
|
|
794
|
+
|
|
795
|
+
return matching_files
|
|
796
|
+
|
|
797
|
+
def _load_file_content(self, file_path):
|
|
798
|
+
"""Load content from a file with error handling"""
|
|
799
|
+
try:
|
|
800
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
801
|
+
return f.read()
|
|
802
|
+
except Exception as e:
|
|
803
|
+
print(f"Error reading {file_path}: {e}")
|
|
804
|
+
return None
|
|
805
|
+
|
|
806
|
+
def _format_parsed_files_context(self, parsed_files):
|
|
807
|
+
"""Format parsed files into context string"""
|
|
808
|
+
if not parsed_files:
|
|
809
|
+
return ""
|
|
810
|
+
|
|
811
|
+
context_parts = ["Additional context from files:"]
|
|
812
|
+
|
|
813
|
+
for file_path, content in parsed_files.items():
|
|
814
|
+
context_parts.append(f"\n--- {file_path} ---")
|
|
815
|
+
context_parts.append(content)
|
|
816
|
+
context_parts.append("")
|
|
817
|
+
|
|
818
|
+
return "\n".join(context_parts)
|
|
705
819
|
|
|
706
820
|
def to_dict(self):
|
|
707
821
|
result = {
|
|
708
822
|
"jinx_name": self.jinx_name,
|
|
709
823
|
"description": self.description,
|
|
710
824
|
"inputs": self.inputs,
|
|
711
|
-
"steps": self._raw_steps
|
|
825
|
+
"steps": self._raw_steps, # Save the original raw steps, which might be templated
|
|
826
|
+
"file_context": self.file_context
|
|
712
827
|
}
|
|
713
828
|
|
|
714
829
|
if self.npc:
|
|
@@ -754,6 +869,7 @@ class Jinx:
|
|
|
754
869
|
"jinx_name": name,
|
|
755
870
|
"description": doc.strip(),
|
|
756
871
|
"inputs": inputs,
|
|
872
|
+
"file_context": [],
|
|
757
873
|
"steps": [
|
|
758
874
|
{
|
|
759
875
|
"name": "mcp_function_call",
|
|
@@ -775,6 +891,8 @@ output = {mcp_tool.__module__}.{name}(
|
|
|
775
891
|
except:
|
|
776
892
|
pass
|
|
777
893
|
|
|
894
|
+
|
|
895
|
+
|
|
778
896
|
def load_jinxs_from_directory(directory):
|
|
779
897
|
"""Load all jinxs from a directory recursively"""
|
|
780
898
|
jinxs = []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: npcpy
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
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
|
|
@@ -275,9 +275,8 @@ output = context['research_summary']
|
|
|
275
275
|
"code": '''
|
|
276
276
|
# Access outputs from previous steps.
|
|
277
277
|
research_summary = context['initial_llm_research']
|
|
278
|
-
# The
|
|
279
|
-
|
|
280
|
-
file_summary = context['read_and_process_source_file'].get('output', 'No file summary available.')
|
|
278
|
+
# The file_reader jinx returns its output directly; also keep a fallback of file_raw_content.
|
|
279
|
+
file_summary = context.get('read_and_process_source_file', '') or context.get('file_raw_content', 'No file summary available.')
|
|
281
280
|
|
|
282
281
|
prompt = f"""Based on the following information:
|
|
283
282
|
1. Comprehensive Research Summary:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|