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 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(response_content, str):
385
- if response_content.startswith("```json"):
386
- response_content = (
387
- response_content.replace("```json", "")
388
- .replace("```", "")
389
- .strip()
390
- )
391
- parsed_response = json.loads(response_content)
392
- result["response"] = parsed_response
393
- except json.JSONDecodeError:
394
- result["error"] = f"Invalid JSON response: {response_content}"
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
- if llm_response.startswith("```json"):
618
- llm_response = llm_response.replace("```json", "").replace("```", "").strip()
619
- parsed_json = json.loads(llm_response)
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, # ADD THIS
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
- jinja_env = Environment(loader=FileSystemLoader("."), undefined=Undefined)
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
- jinja_env,
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 # ADD THIS
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['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['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') # ADD THIS
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=kwargs.get('extra_globals') # ADD THIS
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 > n_attempts:
2060
- print(f" Attempt {attempt_number} to find related facts yielded no results. Giving up.")
2061
- return get_related_facts_llm(new_fact_statement,
2062
- existing_fact_statements,
2063
- model=model,
2064
- provider=provider,
2065
- npc=npc,
2066
- attempt_number=attempt_number+1,
2067
- n_attempts=n_attempts,
2068
- context=context,
2069
- **kwargs)
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