Spaces:
Sleeping
Sleeping
updated python code tool
Browse files
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 |
-
#
|
| 665 |
-
|
| 666 |
-
|
| 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 |
-
|
| 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"
|
| 746 |
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 754 |
result = run_python_code(code)
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 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
|
|
|
|
| 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.
|