Lasdw commited on
Commit
0f4e297
·
1 Parent(s): 818fc79

updated python code tool

Browse files
Files changed (2) hide show
  1. agent.py +64 -110
  2. tools.py +16 -1
agent.py CHANGED
@@ -653,125 +653,79 @@ def python_code_node(state: AgentState) -> Dict[str, Any]:
653
  """Node that executes Python code."""
654
  print("Python Code Tool Called...\n\n")
655
 
656
- # Ensure AIMessage is available in this scope
657
- from langchain_core.messages import AIMessage
658
-
659
  # Extract tool arguments
660
  action_input = state.get("action_input", {})
661
  print(f"Python code action_input: {action_input}")
662
  print(f"Action input type: {type(action_input)}")
663
 
664
- # Extract code using specialized functions
665
- # First try the extract_python_code_from_complex_input function from tools
666
- from tools import extract_python_code_from_complex_input
667
-
668
- # Debugging: Print full action_input for analysis
669
- if isinstance(action_input, dict) and "code" in action_input:
670
- print(f"Original code field (first 100 chars): {repr(action_input['code'][:100])}")
671
  elif isinstance(action_input, str):
672
- print(f"Original string input (first 100 chars): {repr(action_input[:100])}")
673
-
674
- # Try to detect and fix double nesting issues
675
- if isinstance(action_input, dict) and "code" in action_input:
676
- code_value = action_input["code"]
677
- if isinstance(code_value, str) and code_value.strip().startswith('{') and '"action"' in code_value:
678
- print("Detected doubly nested JSON structure - attempting to fix")
679
- try:
680
- import json
681
- import re
682
-
683
- # First try to fix common escape issues that might cause JSON decode errors
684
- # Replace escaped single quotes with temporary placeholders
685
- fixed_code = code_value.replace("\\'", "___SQUOTE___")
686
- # Replace escaped double quotes with proper JSON escapes
687
- fixed_code = fixed_code.replace('\\"', '\\\\"')
688
- # Fix newlines
689
- fixed_code = fixed_code.replace('\\n', '\\\\n')
690
-
691
- try:
692
- # Try to parse the fixed JSON
693
- nested_json = json.loads(fixed_code)
694
- except:
695
- # If that fails, try a more aggressive approach with regex
696
- print("JSON parsing failed, trying regex approach")
697
- action_match = re.search(r'"action"\s*:\s*"([^"]+)"', code_value)
698
- code_match = re.search(r'"code"\s*:\s*"((?:[^"\\]|\\.)*)"', code_value)
699
-
700
- if action_match and code_match and action_match.group(1) == "python_code":
701
- # Extract the inner code and unescape it
702
- extracted_code = code_match.group(1)
703
- extracted_code = extracted_code.replace('\\n', '\n').replace('\\"', '"').replace("\\'", "'")
704
- print(f"Extracted Python code using regex: {extracted_code[:100]}")
705
- # Get run_python_code from tools
706
- return {
707
- "messages": state["messages"] + [AIMessage(content=f"Observation: {run_python_code(extracted_code)}")],
708
- "current_tool": None,
709
- "action_input": None
710
- }
711
-
712
- if isinstance(nested_json, dict) and "action" in nested_json and nested_json["action"] == "python_code":
713
- if "action_input" in nested_json and isinstance(nested_json["action_input"], dict) and "code" in nested_json["action_input"]:
714
- # We have a doubly nested structure - extract the innermost code
715
- actual_code = nested_json["action_input"]["code"]
716
- # Replace our placeholders back
717
- if isinstance(actual_code, str):
718
- actual_code = actual_code.replace("___SQUOTE___", "'")
719
- print(f"Successfully extracted code from doubly nested structure. First 100 chars: {repr(actual_code[:100])}")
720
- code = actual_code
721
- else:
722
- code = code_value
723
- else:
724
- code = code_value
725
- except Exception as e:
726
- print(f"Error attempting to fix nested structure: {e}")
727
- # Fall back to direct regex extraction
728
- import re
729
- code_match = re.search(r'code":\s*"((?:[^"\\]|\\.)*)(?<!\\)"', code_value)
730
- if code_match:
731
- extracted_code = code_match.group(1)
732
- # Unescape the extracted code
733
- extracted_code = extracted_code.replace('\\n', '\n').replace('\\"', '"').replace("\\'", "'")
734
- code = extracted_code
735
- print(f"Extracted code using fallback regex: {repr(code[:100])}")
736
- else:
737
- # If all parsing fails, try to use extract_python_code_from_complex_input
738
- from tools import extract_python_code_from_complex_input
739
- code = extract_python_code_from_complex_input(action_input)
740
- else:
741
- code = extract_python_code_from_complex_input(action_input)
742
- else:
743
- code = extract_python_code_from_complex_input(action_input)
744
 
745
- print(f"Final code to execute: {repr(code[:100])}...")
746
 
747
- # Additional validation: check for unmatched braces
748
- open_braces = code.count('{')
749
- close_braces = code.count('}')
750
- if open_braces != close_braces:
751
- result = f"Error: Code contains unmatched braces. Found {open_braces} '{{' and {close_braces} '}}'. Please check your code syntax."
752
- else:
753
- # Call the code execution function, which now also has improved extraction logic
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
754
  result = run_python_code(code)
755
-
756
- print(f"Code execution result: {result[:100]}...") # Print first 100 chars
757
-
758
- # Format the observation to continue the ReAct cycle
759
- # Create a tool message with the result
760
- tool_message = AIMessage(
761
- content=f"Observation: {result}"
762
- )
763
-
764
- # Print the observation that will be sent back to the assistant
765
- print("\n=== TOOL OBSERVATION ===")
766
- print(tool_message.content[:500] + "..." if len(tool_message.content) > 500 else tool_message.content)
767
- print("=== END OBSERVATION ===\n")
768
-
769
- # Return the updated state
770
- return {
771
- "messages": state["messages"] + [tool_message],
772
- "current_tool": None, # Reset the current tool
773
- "action_input": None # Clear the action input
774
- }
 
 
 
 
 
 
 
 
775
 
776
  def webpage_scrape_node(state: AgentState) -> Dict[str, Any]:
777
  """Node that scrapes content from a specific webpage URL."""
 
653
  """Node that executes Python code."""
654
  print("Python Code Tool Called...\n\n")
655
 
 
 
 
656
  # Extract tool arguments
657
  action_input = state.get("action_input", {})
658
  print(f"Python code action_input: {action_input}")
659
  print(f"Action input type: {type(action_input)}")
660
 
661
+ # Get the code string
662
+ code = ""
663
+ if isinstance(action_input, dict):
664
+ code = action_input.get("code", "")
 
 
 
665
  elif isinstance(action_input, str):
666
+ code = action_input
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
 
668
+ print(f"Original code field (first 100 chars): {code[:100]}")
669
 
670
+ def extract_code_from_json(json_str):
671
+ """Recursively extract code from nested JSON structures."""
672
+ try:
673
+ parsed = json.loads(json_str)
674
+ if isinstance(parsed, dict):
675
+ # Check for direct code field
676
+ if "code" in parsed:
677
+ return parsed["code"]
678
+ # Check for nested action_input structure
679
+ if "action_input" in parsed:
680
+ inner_input = parsed["action_input"]
681
+ if isinstance(inner_input, dict):
682
+ if "code" in inner_input:
683
+ return inner_input["code"]
684
+ # If inner_input is also JSON string, recurse
685
+ if isinstance(inner_input.get("code", ""), str) and inner_input["code"].strip().startswith("{"):
686
+ return extract_code_from_json(inner_input["code"])
687
+ return json_str
688
+ except:
689
+ return json_str
690
+
691
+ # Handle nested JSON structures
692
+ if isinstance(code, str) and code.strip().startswith("{"):
693
+ code = extract_code_from_json(code)
694
+ print("Extracted code from JSON structure")
695
+
696
+ print(f"Final code to execute: {code[:100]}...")
697
+
698
+ # Execute the code
699
+ try:
700
  result = run_python_code(code)
701
+ print(f"Code execution result: {result}")
702
+
703
+ # Format the observation
704
+ tool_message = AIMessage(
705
+ content=f"Observation: {result.strip()}"
706
+ )
707
+
708
+ # Print the observation that will be sent back to the assistant
709
+ print("\n=== TOOL OBSERVATION ===")
710
+ content_preview = tool_message.content[:500] + "..." if len(tool_message.content) > 500 else tool_message.content
711
+ print(content_preview)
712
+ print("=== END OBSERVATION ===\n")
713
+
714
+ # Return the updated state
715
+ return {
716
+ "messages": state["messages"] + [tool_message],
717
+ "current_tool": None, # Reset the current tool
718
+ "action_input": None # Clear the action input
719
+ }
720
+ except Exception as e:
721
+ error_message = f"Error executing Python code: {str(e)}"
722
+ print(error_message)
723
+ tool_message = AIMessage(content=f"Observation: {error_message}")
724
+ return {
725
+ "messages": state["messages"] + [tool_message],
726
+ "current_tool": None,
727
+ "action_input": None
728
+ }
729
 
730
  def webpage_scrape_node(state: AgentState) -> Dict[str, Any]:
731
  """Node that scrapes content from a specific webpage URL."""
tools.py CHANGED
@@ -194,8 +194,22 @@ def test_python_execution(code_str):
194
  except SyntaxError as e:
195
  print(f"Syntax error: {str(e)}")
196
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  # Return the captured output
198
- return output.getvalue()
 
199
  def run_python_code(code: str):
200
  """Execute Python code safely using an external Python process."""
201
  try:
@@ -267,6 +281,7 @@ def run_python_code(code: str):
267
  return f"Zero Division Error: {str(e)}"
268
  except Exception as e:
269
  return f"Error executing code: {str(e)}"
 
270
  def scrape_webpage(url: str) -> str:
271
  """
272
  Safely scrape content from a specified URL.
 
194
  except SyntaxError as e:
195
  print(f"Syntax error: {str(e)}")
196
 
197
+ # Get the captured output
198
+ output_text = output.getvalue()
199
+
200
+ # Try to evaluate the last expression if it's not a statement
201
+ try:
202
+ last_line = code_str.strip().split('\n')[-1]
203
+ if not last_line.endswith(':'): # Not a control structure
204
+ last_result = eval(last_line, test_globals, test_locals)
205
+ if last_result is not None:
206
+ return str(last_result)
207
+ except:
208
+ pass # If evaluation fails, just return the output
209
+
210
  # Return the captured output
211
+ return output_text
212
+
213
  def run_python_code(code: str):
214
  """Execute Python code safely using an external Python process."""
215
  try:
 
281
  return f"Zero Division Error: {str(e)}"
282
  except Exception as e:
283
  return f"Error executing code: {str(e)}"
284
+
285
  def scrape_webpage(url: str) -> str:
286
  """
287
  Safely scrape content from a specified URL.