npcpy 1.3.4__py3-none-any.whl → 1.3.6__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/llm_funcs.py CHANGED
@@ -6,7 +6,10 @@ import random
6
6
  import subprocess
7
7
  import copy
8
8
  import itertools
9
+ import logging
9
10
  from typing import List, Dict, Any, Optional, Union
11
+
12
+ logger = logging.getLogger("npcpy.llm_funcs")
10
13
  from npcpy.npc_sysenv import (
11
14
  print_and_process_stream_with_markdown,
12
15
  render_markdown,
@@ -243,7 +246,7 @@ def get_llm_response(
243
246
  ctx_suffix = _context_suffix(run_context)
244
247
  run_messages = _build_messages(messages, system_message, prompt, ctx_suffix)
245
248
  return get_litellm_response(
246
- prompt + ctx_suffix,
249
+ (prompt + ctx_suffix) if prompt else None,
247
250
  messages=run_messages,
248
251
  model=run_model,
249
252
  provider=run_provider,
@@ -580,16 +583,17 @@ def check_llm_command(
580
583
  if jinxs is None:
581
584
  jinxs = _get_jinxs(npc, team)
582
585
 
583
- # Only prepare tools if model supports them
584
- tools = None
585
- if tool_capable is not False and jinxs:
586
- tools = _jinxs_to_tools(jinxs)
587
-
588
- # Keep full message history, only truncate for API calls to reduce tokens
586
+ # Keep full message history
589
587
  full_messages = messages.copy() if messages else []
590
588
 
591
- # Make LLM call (with or without tools based on tool_capable)
589
+ # If we have jinxs, use ReAct fallback (JSON prompting) instead of tool_calls
590
+ if jinxs:
591
+ return _react_fallback(
592
+ command, model, provider, api_url, api_key, npc, team,
593
+ full_messages, images, stream, context, jinxs, extra_globals, max_iterations
594
+ )
592
595
 
596
+ # No jinxs - just get a direct response
593
597
  try:
594
598
  response = get_llm_response(
595
599
  command,
@@ -597,13 +601,12 @@ def check_llm_command(
597
601
  provider=provider,
598
602
  api_url=api_url,
599
603
  api_key=api_key,
600
- messages=messages[-10:], # Truncate for API call only
604
+ messages=messages[-10:],
601
605
  npc=npc,
602
606
  team=team,
603
607
  images=images,
604
608
  stream=stream,
605
609
  context=context,
606
- tools=tools,
607
610
  )
608
611
  except Exception as e:
609
612
  print(f"[check_llm_command] EXCEPTION in get_llm_response: {type(e).__name__}: {e}", "red")
@@ -614,156 +617,17 @@ def check_llm_command(
614
617
  "usage": total_usage,
615
618
  }
616
619
 
617
-
618
620
  if response.get("usage"):
619
621
  total_usage["input_tokens"] += response["usage"].get("input_tokens", 0)
620
622
  total_usage["output_tokens"] += response["usage"].get("output_tokens", 0)
621
623
 
622
- # Check if tool calls were made
623
- tool_calls = response.get("tool_calls", [])
624
- if not tool_calls:
625
- # Direct answer - append user message to full messages
626
- full_messages.append({"role": "user", "content": command})
627
- assistant_response = response.get("response", "")
628
- # Only append assistant message if it's a string (not a stream)
629
- # For streaming, the caller (process_result) handles appending after consumption
630
- if assistant_response and isinstance(assistant_response, str):
631
- full_messages.append({"role": "assistant", "content": assistant_response})
632
- return {
633
- "messages": full_messages,
634
- "output": assistant_response,
635
- "usage": total_usage,
636
- }
637
-
638
- # Helper to serialize tool_calls for message history
639
- def _serialize_tool_calls(tcs):
640
- """Convert tool_call objects to dicts for message history."""
641
- serialized = []
642
- for tc in tcs:
643
- if hasattr(tc, 'id'):
644
- # It's an object, convert to dict
645
- func = tc.function
646
- serialized.append({
647
- "id": tc.id,
648
- "type": "function",
649
- "function": {
650
- "name": func.name if hasattr(func, 'name') else func.get('name'),
651
- "arguments": func.arguments if hasattr(func, 'arguments') else func.get('arguments', '{}')
652
- }
653
- })
654
- else:
655
- # Already a dict
656
- serialized.append(tc)
657
- return serialized
658
-
659
- # Execute tool calls in a loop - start with full messages
660
624
  full_messages.append({"role": "user", "content": command})
661
- # Add assistant message with tool_calls before executing them
662
- assistant_msg = {"role": "assistant", "content": response.get("response", "")}
663
- if tool_calls:
664
- assistant_msg["tool_calls"] = _serialize_tool_calls(tool_calls)
665
- full_messages.append(assistant_msg)
666
- current_messages = full_messages
667
- for _ in range(max_iterations):
668
- for tc in tool_calls:
669
- # Handle both dict and object formats
670
- if hasattr(tc, 'function'):
671
- func = tc.function
672
- jinx_name = func.name if hasattr(func, 'name') else func.get('name')
673
- args_str = func.arguments if hasattr(func, 'arguments') else func.get('arguments', '{}')
674
- tc_id = tc.id if hasattr(tc, 'id') else tc.get('id', '')
675
- else:
676
- func = tc.get("function", {})
677
- jinx_name = func.get("name")
678
- args_str = func.get("arguments", "{}")
679
- tc_id = tc.get("id", "")
680
-
681
- try:
682
- inputs = json.loads(args_str) if isinstance(args_str, str) else args_str
683
- except:
684
- inputs = {}
685
-
686
- if jinx_name in jinxs:
687
- try:
688
-
689
- print((f" ⚡ {jinx_name}", "cyan"), end="", flush=True)
690
- except:
691
- pass
692
- output = _execute_jinx(jinxs[jinx_name], inputs, npc, team, current_messages, extra_globals)
693
-
694
-
695
- # Add tool result to messages
696
- # Include name for Gemini compatibility
697
- current_messages.append({
698
- "role": "tool",
699
- "tool_call_id": tc_id,
700
- "name": jinx_name,
701
- "content": str(output)
702
- })
703
-
704
- # Get next response - truncate carefully to not orphan tool responses
705
- # Find a safe truncation point that doesn't split tool_call/tool_response pairs
706
- truncated = current_messages
707
- if len(current_messages) > 15:
708
- # Start from -15 and walk back to find a user message or start
709
- start_idx = len(current_messages) - 15
710
- while start_idx > 0:
711
- msg = current_messages[start_idx]
712
- # Don't start on a tool response - would orphan it
713
- if msg.get("role") == "tool":
714
- start_idx -= 1
715
- # Don't start on assistant with tool_calls unless next is tool response
716
- elif msg.get("role") == "assistant" and msg.get("tool_calls"):
717
- start_idx -= 1
718
- else:
719
- break
720
- truncated = current_messages[start_idx:]
721
-
722
- try:
723
- response = get_llm_response(
724
- "", # continuation
725
- model=model,
726
- provider=provider,
727
- api_url=api_url,
728
- api_key=api_key,
729
- messages=truncated,
730
- npc=npc,
731
- team=team,
732
- stream=stream,
733
- context=context,
734
- tools=tools,
735
- )
736
- except Exception as e:
737
- # If continuation fails, return what we have so far
738
- return {
739
- "messages": current_messages,
740
- "output": f"Tool executed successfully. (Continuation error: {type(e).__name__})",
741
- "usage": total_usage,
742
- }
743
-
744
- if response.get("usage"):
745
- total_usage["input_tokens"] += response["usage"].get("input_tokens", 0)
746
- total_usage["output_tokens"] += response["usage"].get("output_tokens", 0)
747
-
748
- tool_calls = response.get("tool_calls", [])
749
- # Append assistant response to full messages with tool_calls if present
750
- assistant_response = response.get("response", "")
751
- assistant_msg = {"role": "assistant", "content": assistant_response}
752
- if tool_calls:
753
- assistant_msg["tool_calls"] = _serialize_tool_calls(tool_calls)
754
- current_messages.append(assistant_msg)
755
-
756
- if not tool_calls:
757
- # Done - return full message history
758
- return {
759
- "messages": current_messages,
760
- "output": assistant_response,
761
- "usage": total_usage,
762
- }
763
-
625
+ assistant_response = response.get("response", "")
626
+ if assistant_response and isinstance(assistant_response, str):
627
+ full_messages.append({"role": "assistant", "content": assistant_response})
764
628
  return {
765
- "messages": current_messages,
766
- "output": response.get("response", "Max iterations reached"),
629
+ "messages": full_messages,
630
+ "output": assistant_response,
767
631
  "usage": total_usage,
768
632
  }
769
633
 
@@ -775,10 +639,12 @@ def _react_fallback(
775
639
  """ReAct-style fallback for models without tool calling."""
776
640
  import logging
777
641
  logger = logging.getLogger("npcpy.llm_funcs")
778
- logger.debug(f"[_react_fallback] Starting with {len(messages) if messages else 0} messages")
642
+ logger.debug(f"[_react_fallback] Starting with {len(messages) if messages else 0} messages, jinxs: {list(jinxs.keys()) if jinxs else None}")
779
643
 
780
644
  total_usage = {"input_tokens": 0, "output_tokens": 0}
781
645
  current_messages = messages.copy() if messages else []
646
+ jinx_executions = [] # Track jinx calls for UI display
647
+ generated_images = [] # Track images generated by jinxs for subsequent LLM calls
782
648
  logger.debug(f"[_react_fallback] current_messages initialized with {len(current_messages)} messages")
783
649
 
784
650
  # Build jinx list with input parameters
@@ -789,17 +655,32 @@ def _react_fallback(
789
655
 
790
656
  jinx_list = "\n".join(_jinx_info(n, j) for n, j in jinxs.items()) if jinxs else "None"
791
657
 
792
- for iteration in range(max_iterations):
658
+ # Cap iterations - after this, return to orchestrator for review/compression
659
+ effective_max = min(max_iterations, 7)
660
+
661
+ for iteration in range(effective_max):
662
+ # Build history of what's been tried
663
+ history_text = ""
664
+ if jinx_executions:
665
+ history_text = "\n\nPrevious tool calls this session:\n" + "\n".join(
666
+ f"- {h['name']}({h['inputs']}) -> {h['output']}"
667
+ for h in jinx_executions[-5:]
668
+ )
669
+
793
670
  prompt = f"""Request: {command}
794
671
 
795
- Tools:
672
+ Available Tools:
796
673
  {jinx_list}
797
674
 
798
- Return JSON: {{"action": "answer", "response": "..."}} OR {{"action": "jinx", "jinx_name": "...", "inputs": {{"param_name": "value"}}}}
799
- Use EXACT parameter names from the tool definitions above."""
675
+ Instructions:
676
+ 1. Analyze the request and determine the best tool to use
677
+ 2. If you have enough information to answer, use {{"action": "answer", "response": "your answer"}}
678
+ 3. If you need to use a tool, use {{"action": "jinx", "jinx_name": "tool_name", "inputs": {{"param": "value"}}}}
679
+ 4. Use EXACT parameter names from tool definitions
680
+ 5. Do NOT repeat the same tool call with the same inputs{history_text}"""
800
681
 
801
682
  if context:
802
- prompt += f"\nContext: {context}"
683
+ prompt += f"\n\nCurrent context: {context}"
803
684
 
804
685
  response = get_llm_response(
805
686
  prompt,
@@ -810,7 +691,7 @@ Use EXACT parameter names from the tool definitions above."""
810
691
  messages=current_messages[-10:],
811
692
  npc=npc,
812
693
  team=team,
813
- images=images if iteration == 0 else None,
694
+ images=((images or []) if iteration == 0 else []) + generated_images or None,
814
695
  format="json",
815
696
  context=context,
816
697
  )
@@ -820,14 +701,32 @@ Use EXACT parameter names from the tool definitions above."""
820
701
  total_usage["output_tokens"] += response["usage"].get("output_tokens", 0)
821
702
 
822
703
  decision = response.get("response", {})
704
+ logger.debug(f"[_react_fallback] Raw decision: {str(decision)[:200]}")
705
+ print(f"[REACT-DEBUG] Full response keys: {response.keys()}")
706
+ print(f"[REACT-DEBUG] Raw response['response']: {str(response.get('response', 'NONE'))[:500]}")
707
+ print(f"[REACT-DEBUG] Raw decision type: {type(decision)}, value: {str(decision)[:500]}")
823
708
  if isinstance(decision, str):
824
709
  try:
825
710
  decision = json.loads(decision)
826
711
  except:
827
- return {"messages": current_messages, "output": decision, "usage": total_usage}
712
+ logger.debug(f"[_react_fallback] Could not parse JSON, returning as text")
713
+ return {"messages": current_messages, "output": decision, "usage": total_usage, "jinx_executions": jinx_executions}
828
714
 
715
+ logger.debug(f"[_react_fallback] Parsed decision action: {decision.get('action')}")
829
716
  if decision.get("action") == "answer":
830
717
  output = decision.get("response", "")
718
+
719
+ # Prepend any image URLs from jinx executions to the output
720
+ import re
721
+ for jexec in jinx_executions:
722
+ joutput = jexec.get("output", "")
723
+ if joutput:
724
+ # Find image URLs in jinx output
725
+ img_urls = re.findall(r'/uploads/[^\s,\'"]+\.(?:png|jpg|jpeg|webp|gif)', str(joutput), re.IGNORECASE)
726
+ for url in img_urls:
727
+ if url not in output:
728
+ output = f"![Generated Image]({url})\n\n{output}"
729
+
831
730
  # Add user message to full history
832
731
  current_messages.append({"role": "user", "content": command})
833
732
  if stream:
@@ -838,35 +737,140 @@ Use EXACT parameter names from the tool definitions above."""
838
737
  total_usage["output_tokens"] += final["usage"].get("output_tokens", 0)
839
738
  # Return full messages, not truncated from final
840
739
  logger.debug(f"[_react_fallback] Answer (stream) - returning {len(current_messages)} messages")
841
- return {"messages": current_messages, "output": final.get("response", output), "usage": total_usage}
740
+ return {"messages": current_messages, "output": final.get("response", output), "usage": total_usage, "jinx_executions": jinx_executions}
842
741
  # Non-streaming: add assistant response to full history
843
742
  if output and isinstance(output, str):
844
743
  current_messages.append({"role": "assistant", "content": output})
845
744
  logger.debug(f"[_react_fallback] Answer - returning {len(current_messages)} messages")
846
- return {"messages": current_messages, "output": output, "usage": total_usage}
847
-
848
- elif decision.get("action") == "jinx":
849
- jinx_name = decision.get("jinx_name")
745
+ return {"messages": current_messages, "output": output, "usage": total_usage, "jinx_executions": jinx_executions}
746
+
747
+ elif decision.get("action") == "jinx" or decision.get("action") in jinxs:
748
+ # Handle multiple formats:
749
+ # 1. {"action": "jinx", "jinx_name": "foo", "inputs": {...}}
750
+ # 2. {"action": "foo", "inputs": {...}}
751
+ # 3. {"action": "foo", "param1": "...", "param2": "..."} - params at top level
752
+ jinx_name = decision.get("jinx_name") or decision.get("action")
850
753
  inputs = decision.get("inputs", {})
851
754
 
755
+ # If inputs is empty, check if params are at top level
756
+ if not inputs:
757
+ # Extract all keys except 'action', 'jinx_name', 'inputs' as potential inputs
758
+ inputs = {k: v for k, v in decision.items() if k not in ('action', 'jinx_name', 'inputs', 'response')}
759
+ logger.debug(f"[_react_fallback] Jinx action: {jinx_name} with inputs: {inputs}")
760
+ print(f"[REACT-DEBUG] Chose jinx: {jinx_name}, inputs: {str(inputs)[:200]}")
761
+
852
762
  if jinx_name not in jinxs:
853
763
  context = f"Error: '{jinx_name}' not found. Available: {list(jinxs.keys())}"
764
+ logger.debug(f"[_react_fallback] Jinx not found: {jinx_name}")
854
765
  continue
855
766
 
856
-
857
-
767
+ # Validate required parameters before executing
768
+ jinx_obj = jinxs[jinx_name]
769
+ # Handle both dict and Jinx object
770
+ if hasattr(jinx_obj, 'inputs'):
771
+ required_inputs = jinx_obj.inputs or []
772
+ elif isinstance(jinx_obj, dict):
773
+ required_inputs = jinx_obj.get('inputs', [])
774
+ else:
775
+ required_inputs = []
776
+
777
+ if required_inputs:
778
+ # Get parameter names, distinguishing required (string) from optional with defaults (dict)
779
+ required_names = [] # Params without defaults - truly required
780
+ optional_names = [] # Params with defaults - not required
781
+ for inp in required_inputs:
782
+ if isinstance(inp, str):
783
+ # String inputs have no default, so they're required
784
+ required_names.append(inp)
785
+ elif isinstance(inp, dict):
786
+ # Dict inputs have defaults (e.g., "backup: true"), so they're optional
787
+ optional_names.extend(inp.keys())
788
+
789
+ # Only check truly required params (those without defaults)
790
+ missing = [p for p in required_names if p not in inputs or not inputs.get(p)]
791
+ provided = list(inputs.keys())
792
+ if missing:
793
+ all_params = required_names + optional_names
794
+ context = f"Error: jinx '{jinx_name}' requires parameters {required_names} but got {provided}. Missing: {missing}. Optional params with defaults: {optional_names}. Please retry with correct parameter names."
795
+ logger.debug(f"[_react_fallback] Missing required params: {missing}")
796
+ print(f"[REACT-DEBUG] Missing params for {jinx_name}: {missing}, got: {provided}")
797
+ continue
798
+
799
+ logger.debug(f"[_react_fallback] Executing jinx: {jinx_name}")
858
800
  output = _execute_jinx(jinxs[jinx_name], inputs, npc, team, current_messages, extra_globals)
801
+ logger.debug(f"[_react_fallback] Jinx output: {str(output)[:200]}")
802
+ jinx_executions.append({
803
+ "name": jinx_name,
804
+ "inputs": inputs,
805
+ "output": str(output) if output else None
806
+ })
859
807
 
860
-
861
- context = f"Tool '{jinx_name}' returned: {output}"
808
+ # Extract generated image paths from output for subsequent LLM calls
809
+ import re
810
+ image_paths = re.findall(r'/uploads/[^\s,\'"]+\.(?:png|jpg|jpeg|webp|gif)', str(output), re.IGNORECASE)
811
+ if image_paths:
812
+ # Convert relative URLs to absolute file paths
813
+ for img_path in image_paths:
814
+ # Remove leading slash
815
+ local_path = img_path.lstrip('/')
816
+ # Check various possible locations
817
+ if os.path.exists(local_path):
818
+ generated_images.append(local_path)
819
+ print(f"[REACT-DEBUG] Added generated image: {local_path}")
820
+ elif os.path.exists(os.path.join(os.getcwd(), local_path)):
821
+ full_path = os.path.join(os.getcwd(), local_path)
822
+ generated_images.append(full_path)
823
+ print(f"[REACT-DEBUG] Added generated image (cwd): {full_path}")
824
+ else:
825
+ # Just add the URL path anyway - let get_llm_response handle it
826
+ generated_images.append(local_path)
827
+ print(f"[REACT-DEBUG] Added generated image (not found, using anyway): {local_path}")
828
+
829
+ # Truncate output for context to avoid sending huge base64 data back to LLM
830
+ output_for_context = str(output)[:8000] + "..." if len(str(output)) > 8000 else str(output)
831
+ context = f"Tool '{jinx_name}' returned: {output_for_context}"
862
832
  command = f"{command}\n\nPrevious: {context}"
863
833
 
864
834
  else:
865
835
  logger.debug(f"[_react_fallback] Unknown action - returning {len(current_messages)} messages")
866
- return {"messages": current_messages, "output": str(decision), "usage": total_usage}
836
+ # If we have jinx executions, return the last output instead of empty decision
837
+ if jinx_executions and jinx_executions[-1].get("output"):
838
+ return {"messages": current_messages, "output": jinx_executions[-1]["output"], "usage": total_usage, "jinx_executions": jinx_executions}
839
+ # If decision is empty {}, retry with clearer prompt if jinxs are available
840
+ if not decision or decision == {}:
841
+ if jinxs and iteration < max_iterations - 1:
842
+ # Retry with explicit instruction to use a jinx
843
+ print(f"[REACT-DEBUG] Empty decision on iteration {iteration}, retrying with clearer prompt")
844
+ context = f"You MUST use one of these tools to complete the task: {list(jinxs.keys())}. Return JSON with action and inputs."
845
+ continue
846
+ else:
847
+ # Last resort: get a text response
848
+ print(f"[REACT-DEBUG] Empty decision, getting text response instead")
849
+ current_messages.append({"role": "user", "content": command})
850
+ fallback_response = get_llm_response(
851
+ command,
852
+ model=model,
853
+ provider=provider,
854
+ messages=current_messages[-10:],
855
+ npc=npc,
856
+ team=team,
857
+ stream=stream,
858
+ context=context,
859
+ )
860
+ if fallback_response.get("usage"):
861
+ total_usage["input_tokens"] += fallback_response["usage"].get("input_tokens", 0)
862
+ total_usage["output_tokens"] += fallback_response["usage"].get("output_tokens", 0)
863
+ output = fallback_response.get("response", "")
864
+ if output and isinstance(output, str):
865
+ current_messages.append({"role": "assistant", "content": output})
866
+ return {"messages": current_messages, "output": output, "usage": total_usage, "jinx_executions": jinx_executions}
867
+ return {"messages": current_messages, "output": str(decision), "usage": total_usage, "jinx_executions": jinx_executions}
867
868
 
868
869
  logger.debug(f"[_react_fallback] Max iterations - returning {len(current_messages)} messages")
869
- return {"messages": current_messages, "output": f"Max iterations reached. Last: {context}", "usage": total_usage}
870
+ # If we have jinx executions, return the last output
871
+ if jinx_executions and jinx_executions[-1].get("output"):
872
+ return {"messages": current_messages, "output": jinx_executions[-1]["output"], "usage": total_usage, "jinx_executions": jinx_executions}
873
+ return {"messages": current_messages, "output": f"Max iterations reached. Last: {context}", "usage": total_usage, "jinx_executions": jinx_executions}
870
874
 
871
875
 
872
876
 
@@ -1231,104 +1235,6 @@ def abstract(groups,
1231
1235
  return response["response"].get("groups", [])
1232
1236
 
1233
1237
 
1234
- def extract_facts(
1235
- text: str,
1236
- model: str,
1237
- provider: str,
1238
- npc = None,
1239
- context: str = None
1240
- ) -> List[str]:
1241
- """Extract concise facts from text using LLM (as defined earlier)"""
1242
-
1243
- prompt = """Extract concise facts from this text.
1244
- A fact is a piece of information that makes a statement about the world.
1245
- A fact is typically a sentence that is true or false.
1246
- Facts may be simple or complex. They can also be conflicting with each other, usually
1247
- because there is some hidden context that is not mentioned in the text.
1248
- In any case, it is simply your job to extract a list of facts that could pertain to
1249
- an individual's personality.
1250
-
1251
- For example, if a message says:
1252
- "since I am a doctor I am often trying to think up new ways to help people.
1253
- Can you help me set up a new kind of software to help with that?"
1254
- You might extract the following facts:
1255
- - The individual is a doctor
1256
- - They are helpful
1257
-
1258
- Another example:
1259
- "I am a software engineer who loves to play video games. I am also a huge fan of the
1260
- Star Wars franchise and I am a member of the 501st Legion."
1261
- You might extract the following facts:
1262
- - The individual is a software engineer
1263
- - The individual loves to play video games
1264
- - The individual is a huge fan of the Star Wars franchise
1265
- - The individual is a member of the 501st Legion
1266
-
1267
- Another example:
1268
- "The quantum tunneling effect allows particles to pass through barriers
1269
- that classical physics says they shouldn't be able to cross. This has
1270
- huge implications for semiconductor design."
1271
- You might extract these facts:
1272
- - Quantum tunneling enables particles to pass through barriers that are
1273
- impassable according to classical physics
1274
- - The behavior of quantum tunneling has significant implications for
1275
- how semiconductors must be designed
1276
-
1277
- Another example:
1278
- "People used to think the Earth was flat. Now we know it's spherical,
1279
- though technically it's an oblate spheroid due to its rotation."
1280
- You might extract these facts:
1281
- - People historically believed the Earth was flat
1282
- - It is now known that the Earth is an oblate spheroid
1283
- - The Earth's oblate spheroid shape is caused by its rotation
1284
-
1285
- Another example:
1286
- "My research on black holes suggests they emit radiation, but my professor
1287
- says this conflicts with Einstein's work. After reading more papers, I
1288
- learned this is actually Hawking radiation and doesn't conflict at all."
1289
- You might extract the following facts:
1290
- - Black holes emit radiation
1291
- - The professor believes this radiation conflicts with Einstein's work
1292
- - The radiation from black holes is called Hawking radiation
1293
- - Hawking radiation does not conflict with Einstein's work
1294
-
1295
- Another example:
1296
- "During the pandemic, many developers switched to remote work. I found
1297
- that I'm actually more productive at home, though my company initially
1298
- thought productivity would drop. Now they're keeping remote work permanent."
1299
- You might extract the following facts:
1300
- - The pandemic caused many developers to switch to remote work
1301
- - The individual discovered higher productivity when working from home
1302
- - The company predicted productivity would decrease with remote work
1303
- - The company decided to make remote work a permanent option
1304
-
1305
- Thus, it is your mission to reliably extract lists of facts.
1306
-
1307
- Return a JSON object with the following structure:
1308
- {
1309
- "fact_list": "a list containing the facts where each fact is a string",
1310
- }
1311
- """
1312
- if context and len(context) > 0:
1313
- prompt+=f""" Here is some relevant user context: {context}"""
1314
-
1315
- prompt+="""
1316
- Return only the JSON object.
1317
- Do not include any additional markdown formatting.
1318
- """
1319
-
1320
- response = get_llm_response(
1321
- prompt + f"HERE BEGINS THE TEXT TO INVESTIGATE:\n\nText: {text}",
1322
- model=model,
1323
- provider=provider,
1324
- format="json",
1325
- npc=npc,
1326
- context=context,
1327
- )
1328
- response = response["response"]
1329
- return response.get("fact_list", [])
1330
-
1331
-
1332
1238
  def get_facts(content_text,
1333
1239
  model= None,
1334
1240
  provider = None,