npcpy 1.2.31__py3-none-any.whl → 1.2.33__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/gen/response.py +73 -20
- npcpy/llm_funcs.py +58 -38
- npcpy/npc_compiler.py +549 -350
- npcpy/npc_sysenv.py +44 -20
- npcpy/serve.py +1 -0
- {npcpy-1.2.31.dist-info → npcpy-1.2.33.dist-info}/METADATA +97 -34
- {npcpy-1.2.31.dist-info → npcpy-1.2.33.dist-info}/RECORD +10 -10
- {npcpy-1.2.31.dist-info → npcpy-1.2.33.dist-info}/WHEEL +0 -0
- {npcpy-1.2.31.dist-info → npcpy-1.2.33.dist-info}/licenses/LICENSE +0 -0
- {npcpy-1.2.31.dist-info → npcpy-1.2.33.dist-info}/top_level.txt +0 -0
npcpy/gen/response.py
CHANGED
|
@@ -378,21 +378,50 @@ def get_ollama_response(
|
|
|
378
378
|
|
|
379
379
|
result["response"] = ollama.chat(**stream_api_params, options=options)
|
|
380
380
|
else:
|
|
381
|
-
|
|
381
|
+
|
|
382
382
|
if format == "json":
|
|
383
383
|
try:
|
|
384
|
-
if isinstance(
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
384
|
+
if isinstance(llm_response, str):
|
|
385
|
+
llm_response = llm_response.strip()
|
|
386
|
+
|
|
387
|
+
if '```json' in llm_response:
|
|
388
|
+
start = llm_response.find('```json') + 7
|
|
389
|
+
end = llm_response.rfind('```')
|
|
390
|
+
if end > start:
|
|
391
|
+
llm_response = llm_response[start:end].strip()
|
|
392
|
+
|
|
393
|
+
first_brace = llm_response.find('{')
|
|
394
|
+
first_bracket = llm_response.find('[')
|
|
395
|
+
|
|
396
|
+
if first_brace == -1 and first_bracket == -1:
|
|
397
|
+
result["response"] = {}
|
|
398
|
+
result["error"] = "No JSON found in response"
|
|
399
|
+
return result
|
|
400
|
+
|
|
401
|
+
if first_brace != -1 and (first_bracket == -1 or first_brace < first_bracket):
|
|
402
|
+
llm_response = llm_response[first_brace:]
|
|
403
|
+
last_brace = llm_response.rfind('}')
|
|
404
|
+
if last_brace != -1:
|
|
405
|
+
llm_response = llm_response[:last_brace+1]
|
|
406
|
+
else:
|
|
407
|
+
llm_response = llm_response[first_bracket:]
|
|
408
|
+
last_bracket = llm_response.rfind(']')
|
|
409
|
+
if last_bracket != -1:
|
|
410
|
+
llm_response = llm_response[:last_bracket+1]
|
|
411
|
+
|
|
412
|
+
parsed_json = json.loads(llm_response, strict=False)
|
|
413
|
+
|
|
414
|
+
if "json" in parsed_json:
|
|
415
|
+
result["response"] = parsed_json["json"]
|
|
416
|
+
else:
|
|
417
|
+
result["response"] = parsed_json
|
|
418
|
+
|
|
419
|
+
except (json.JSONDecodeError, TypeError) as e:
|
|
420
|
+
print(f"JSON parsing error: {str(e)}")
|
|
421
|
+
print(f"Raw response: {llm_response[:500]}")
|
|
422
|
+
result["response"] = {}
|
|
423
|
+
result["error"] = "Invalid JSON response"
|
|
424
|
+
|
|
396
425
|
return result
|
|
397
426
|
|
|
398
427
|
import time
|
|
@@ -553,7 +582,7 @@ def get_litellm_response(
|
|
|
553
582
|
litellm.include_cost_in_streaming_usage = True
|
|
554
583
|
api_params['stream_options'] = {"include_usage": True}
|
|
555
584
|
|
|
556
|
-
if api_url is not None and (provider == "openai-like" or provider == "openai"):
|
|
585
|
+
if api_url is not None and ('openai-like' in provider or provider == "openai-like" or provider == "openai"):
|
|
557
586
|
api_params["api_base"] = api_url
|
|
558
587
|
provider = "openai"
|
|
559
588
|
|
|
@@ -609,14 +638,37 @@ def get_litellm_response(
|
|
|
609
638
|
|
|
610
639
|
if hasattr(resp.choices[0].message, 'tool_calls') and resp.choices[0].message.tool_calls:
|
|
611
640
|
result["tool_calls"] = resp.choices[0].message.tool_calls
|
|
612
|
-
|
|
613
|
-
|
|
614
641
|
if format == "json":
|
|
615
642
|
try:
|
|
616
643
|
if isinstance(llm_response, str):
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
644
|
+
llm_response = llm_response.strip()
|
|
645
|
+
|
|
646
|
+
if '```json' in llm_response:
|
|
647
|
+
start = llm_response.find('```json') + 7
|
|
648
|
+
end = llm_response.rfind('```')
|
|
649
|
+
if end > start:
|
|
650
|
+
llm_response = llm_response[start:end].strip()
|
|
651
|
+
|
|
652
|
+
first_brace = llm_response.find('{')
|
|
653
|
+
first_bracket = llm_response.find('[')
|
|
654
|
+
|
|
655
|
+
if first_brace == -1 and first_bracket == -1:
|
|
656
|
+
result["response"] = {}
|
|
657
|
+
result["error"] = "No JSON found in response"
|
|
658
|
+
return result
|
|
659
|
+
|
|
660
|
+
if first_brace != -1 and (first_bracket == -1 or first_brace < first_bracket):
|
|
661
|
+
llm_response = llm_response[first_brace:]
|
|
662
|
+
last_brace = llm_response.rfind('}')
|
|
663
|
+
if last_brace != -1:
|
|
664
|
+
llm_response = llm_response[:last_brace+1]
|
|
665
|
+
else:
|
|
666
|
+
llm_response = llm_response[first_bracket:]
|
|
667
|
+
last_bracket = llm_response.rfind(']')
|
|
668
|
+
if last_bracket != -1:
|
|
669
|
+
llm_response = llm_response[:last_bracket+1]
|
|
670
|
+
|
|
671
|
+
parsed_json = json.loads(llm_response, strict=False)
|
|
620
672
|
|
|
621
673
|
if "json" in parsed_json:
|
|
622
674
|
result["response"] = parsed_json["json"]
|
|
@@ -625,7 +677,8 @@ def get_litellm_response(
|
|
|
625
677
|
|
|
626
678
|
except (json.JSONDecodeError, TypeError) as e:
|
|
627
679
|
print(f"JSON parsing error: {str(e)}")
|
|
628
|
-
print(f"Raw response: {llm_response}")
|
|
680
|
+
print(f"Raw response: {llm_response[:500]}")
|
|
681
|
+
result["response"] = {}
|
|
629
682
|
result["error"] = "Invalid JSON response"
|
|
630
683
|
|
|
631
684
|
return result
|
npcpy/llm_funcs.py
CHANGED
|
@@ -379,6 +379,8 @@ def execute_llm_command(
|
|
|
379
379
|
"messages": messages,
|
|
380
380
|
"output": "Max attempts reached. Unable to execute the command successfully.",
|
|
381
381
|
}
|
|
382
|
+
|
|
383
|
+
# --- START OF CORRECTED handle_jinx_call ---
|
|
382
384
|
def handle_jinx_call(
|
|
383
385
|
command: str,
|
|
384
386
|
jinx_name: str,
|
|
@@ -391,7 +393,7 @@ def handle_jinx_call(
|
|
|
391
393
|
n_attempts=3,
|
|
392
394
|
attempt=0,
|
|
393
395
|
context=None,
|
|
394
|
-
extra_globals=None,
|
|
396
|
+
extra_globals=None,
|
|
395
397
|
**kwargs
|
|
396
398
|
) -> Union[str, Dict[str, Any]]:
|
|
397
399
|
"""This function handles a jinx call.
|
|
@@ -411,10 +413,13 @@ def handle_jinx_call(
|
|
|
411
413
|
if npc is None and team is None:
|
|
412
414
|
return f"No jinxs are available. "
|
|
413
415
|
else:
|
|
416
|
+
jinx = None
|
|
417
|
+
if npc and hasattr(npc, 'jinxs_dict') and jinx_name in npc.jinxs_dict:
|
|
418
|
+
jinx = npc.jinxs_dict[jinx_name]
|
|
419
|
+
elif team and hasattr(team, 'jinxs_dict') and jinx_name in team.jinxs_dict:
|
|
420
|
+
jinx = team.jinxs_dict[jinx_name]
|
|
414
421
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if jinx_name not in npc.jinxs_dict and jinx_name not in team.jinxs_dict:
|
|
422
|
+
if not jinx:
|
|
418
423
|
print(f"Jinx {jinx_name} not available")
|
|
419
424
|
if attempt < n_attempts:
|
|
420
425
|
print(f"attempt {attempt+1} to generate jinx name failed, trying again")
|
|
@@ -442,16 +447,11 @@ def handle_jinx_call(
|
|
|
442
447
|
"messages": messages,
|
|
443
448
|
}
|
|
444
449
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
elif jinx_name in npc.jinxs_dict:
|
|
449
|
-
jinx = npc.jinxs_dict[jinx_name]
|
|
450
|
-
elif jinx_name in team.jinxs_dict:
|
|
451
|
-
jinx = team.jinxs_dict[jinx_name]
|
|
452
|
-
|
|
453
450
|
render_markdown(f"jinx found: {jinx.jinx_name}")
|
|
454
|
-
|
|
451
|
+
|
|
452
|
+
# This jinja_env is for parsing the Jinx's *inputs* from the LLM response, not for Jinx.execute's second pass.
|
|
453
|
+
local_jinja_env_for_input_parsing = Environment(loader=FileSystemLoader("."), undefined=Undefined)
|
|
454
|
+
|
|
455
455
|
example_format = {}
|
|
456
456
|
for inp in jinx.inputs:
|
|
457
457
|
if isinstance(inp, str):
|
|
@@ -563,20 +563,32 @@ def handle_jinx_call(
|
|
|
563
563
|
|
|
564
564
|
render_markdown( "\n".join(['\n - ' + str(key) + ': ' +str(val) for key, val in input_values.items()]))
|
|
565
565
|
|
|
566
|
+
# Initialize jinx_output before the try block to prevent UnboundLocalError
|
|
567
|
+
jinx_output = {"output": "Jinx execution did not complete."}
|
|
568
|
+
|
|
566
569
|
try:
|
|
570
|
+
# --- CRITICAL FIX HERE ---
|
|
571
|
+
# Pass arguments as keyword arguments to avoid positional confusion
|
|
572
|
+
# Use npc.jinja_env for the second-pass rendering
|
|
567
573
|
jinx_output = jinx.execute(
|
|
568
|
-
input_values,
|
|
569
|
-
|
|
570
|
-
npc=npc,
|
|
574
|
+
input_values=input_values,
|
|
575
|
+
npc=npc, # This is the orchestrating NPC
|
|
571
576
|
messages=messages,
|
|
572
|
-
extra_globals=extra_globals
|
|
573
|
-
|
|
577
|
+
extra_globals=extra_globals,
|
|
578
|
+
jinja_env=npc.jinja_env if npc else (team.forenpc.jinja_env if team and team.forenpc else None) # Use NPC's or Team's forenpc's jinja_env
|
|
574
579
|
)
|
|
580
|
+
# Ensure jinx_output is a dict with an 'output' key
|
|
581
|
+
if jinx_output is None:
|
|
582
|
+
jinx_output = {"output": "Jinx executed, but returned no explicit output."}
|
|
583
|
+
elif not isinstance(jinx_output, dict):
|
|
584
|
+
jinx_output = {"output": str(jinx_output)}
|
|
585
|
+
|
|
575
586
|
except Exception as e:
|
|
576
587
|
print(f"An error occurred while executing the jinx: {e}")
|
|
577
588
|
print(f"trying again, attempt {attempt+1}")
|
|
578
589
|
print('command', command)
|
|
579
590
|
if attempt < n_attempts:
|
|
591
|
+
# Recursively call handle_jinx_call for retry
|
|
580
592
|
jinx_output = handle_jinx_call(
|
|
581
593
|
command,
|
|
582
594
|
jinx_name,
|
|
@@ -589,12 +601,18 @@ def handle_jinx_call(
|
|
|
589
601
|
attempt=attempt + 1,
|
|
590
602
|
n_attempts=n_attempts,
|
|
591
603
|
context=f""" \n \n \n "jinx failed: {e} \n \n \n here was the previous attempt: {input_values}""",
|
|
604
|
+
extra_globals=extra_globals
|
|
592
605
|
)
|
|
606
|
+
else:
|
|
607
|
+
# If max attempts reached, set a clear error output
|
|
608
|
+
jinx_output = {"output": f"Jinx '{jinx_name}' failed after {n_attempts} attempts: {e}", "error": True}
|
|
609
|
+
|
|
610
|
+
|
|
593
611
|
if not stream and len(messages) > 0 :
|
|
594
|
-
render_markdown(f""" ## jinx OUTPUT FROM CALLING {jinx_name} \n \n output:{jinx_output
|
|
612
|
+
render_markdown(f""" ## jinx OUTPUT FROM CALLING {jinx_name} \n \n output:{jinx_output.get('output', 'No output.')}""" )
|
|
595
613
|
response = get_llm_response(f"""
|
|
596
614
|
The user had the following request: {command}.
|
|
597
|
-
Here were the jinx outputs from calling {jinx_name}: {jinx_output}
|
|
615
|
+
Here were the jinx outputs from calling {jinx_name}: {jinx_output.get('output', '')}
|
|
598
616
|
|
|
599
617
|
Given the jinx outputs and the user request, please format a simple answer that
|
|
600
618
|
provides the answer without requiring the user to carry out any further steps.
|
|
@@ -610,7 +628,9 @@ def handle_jinx_call(
|
|
|
610
628
|
response = response.get("response", {})
|
|
611
629
|
return {'messages':messages, 'output':response}
|
|
612
630
|
|
|
613
|
-
return {'messages': messages, 'output': jinx_output
|
|
631
|
+
return {'messages': messages, 'output': jinx_output.get('output', 'No output.')} # Ensure 'output' key exists
|
|
632
|
+
|
|
633
|
+
# --- END OF CORRECTED handle_jinx_call ---
|
|
614
634
|
|
|
615
635
|
|
|
616
636
|
def handle_request_input(
|
|
@@ -670,7 +690,7 @@ def jinx_handler(command, extracted_data, **kwargs):
|
|
|
670
690
|
team=kwargs.get('team'),
|
|
671
691
|
stream=kwargs.get('stream'),
|
|
672
692
|
context=kwargs.get('context'),
|
|
673
|
-
extra_globals=kwargs.get('extra_globals')
|
|
693
|
+
extra_globals=kwargs.get('extra_globals')
|
|
674
694
|
)
|
|
675
695
|
|
|
676
696
|
def answer_handler(command, extracted_data, **kwargs):
|
|
@@ -976,8 +996,8 @@ def execute_multi_step_plan(
|
|
|
976
996
|
images: list = None,
|
|
977
997
|
stream=False,
|
|
978
998
|
context=None,
|
|
979
|
-
|
|
980
999
|
actions: Dict[str, Dict] = None,
|
|
1000
|
+
extra_globals=None,
|
|
981
1001
|
**kwargs,
|
|
982
1002
|
):
|
|
983
1003
|
"""
|
|
@@ -1045,7 +1065,7 @@ def execute_multi_step_plan(
|
|
|
1045
1065
|
render_markdown(
|
|
1046
1066
|
f"- Executing Action: {action_name} \n- Explanation: {action_data.get('explanation')}\n "
|
|
1047
1067
|
)
|
|
1048
|
-
|
|
1068
|
+
|
|
1049
1069
|
result = handler(
|
|
1050
1070
|
command=command,
|
|
1051
1071
|
extracted_data=action_data,
|
|
@@ -1059,7 +1079,7 @@ def execute_multi_step_plan(
|
|
|
1059
1079
|
stream=stream,
|
|
1060
1080
|
context=context+step_context,
|
|
1061
1081
|
images=images,
|
|
1062
|
-
extra_globals=
|
|
1082
|
+
extra_globals=extra_globals
|
|
1063
1083
|
)
|
|
1064
1084
|
except KeyError as e:
|
|
1065
1085
|
|
|
@@ -1862,7 +1882,7 @@ def zoom_in(facts,
|
|
|
1862
1882
|
npc=npc,
|
|
1863
1883
|
context=context,
|
|
1864
1884
|
attempt_number=attempt_number+1,
|
|
1865
|
-
n_tries=n_tries
|
|
1885
|
+
n_tries=n_attempts, # Corrected from n_tries to n_attempts
|
|
1866
1886
|
**kwargs)
|
|
1867
1887
|
return facts
|
|
1868
1888
|
def generate_groups(facts,
|
|
@@ -1945,7 +1965,6 @@ def remove_redundant_groups(groups,
|
|
|
1945
1965
|
response = get_llm_response(prompt,
|
|
1946
1966
|
model=model,
|
|
1947
1967
|
provider=provider,
|
|
1948
|
-
format="json",
|
|
1949
1968
|
npc=npc,
|
|
1950
1969
|
context=context,
|
|
1951
1970
|
**kwargs)
|
|
@@ -2056,17 +2075,18 @@ def get_related_facts_llm(new_fact_statement,
|
|
|
2056
2075
|
npc=npc,
|
|
2057
2076
|
context=context,
|
|
2058
2077
|
**kwargs)
|
|
2059
|
-
if attempt_number
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2078
|
+
if attempt_number <= n_attempts: # Corrected logic: retry if attempt_number is within limits
|
|
2079
|
+
if not response["response"].get("related_facts", []): # Only retry if no related facts found
|
|
2080
|
+
print(f" Attempt {attempt_number} to find related facts yielded no results. Retrying...")
|
|
2081
|
+
return get_related_facts_llm(new_fact_statement,
|
|
2082
|
+
existing_fact_statements,
|
|
2083
|
+
model=model,
|
|
2084
|
+
provider=provider,
|
|
2085
|
+
npc=npc,
|
|
2086
|
+
attempt_number=attempt_number+1,
|
|
2087
|
+
n_attempts=n_attempts,
|
|
2088
|
+
context=context,
|
|
2089
|
+
**kwargs)
|
|
2070
2090
|
|
|
2071
2091
|
return response["response"].get("related_facts", [])
|
|
2072
2092
|
|