David
commited on
Commit
·
edf3100
1
Parent(s):
095d02f
Implementing agent tools and logic
Browse files- agent.py +35 -1
- requirements.txt +5 -1
- tools.py +124 -0
agent.py
CHANGED
|
@@ -1,4 +1,13 @@
|
|
| 1 |
from llama_index.llms.google_genai import GoogleGenAI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import os
|
| 3 |
|
| 4 |
GEMINI_API_KEY = os.getenv("GEMINI_TOKEN")
|
|
@@ -6,11 +15,36 @@ GEMINI_MODEL_NAME = "gemini-2.5-flash-preview-04-17"
|
|
| 6 |
|
| 7 |
class FinalAgent:
|
| 8 |
def __init__(self):
|
|
|
|
| 9 |
self.llm = GoogleGenAI(model=GEMINI_MODEL_NAME, api_key=GEMINI_API_KEY)
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
print("FinalAgent initialized.")
|
| 12 |
def __call__(self, question: str) -> str:
|
|
|
|
| 13 |
print(f"Agent received question (first 50 chars): {question[:50]}...")
|
| 14 |
fixed_answer = "This is a default answer."
|
| 15 |
print(f"Agent returning fixed answer: {fixed_answer}")
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from llama_index.llms.google_genai import GoogleGenAI
|
| 2 |
+
from llama_index.tools.arxiv import ArxivToolSpec
|
| 3 |
+
from llama_index.tools.wikipedia import WikipediaToolSpec
|
| 4 |
+
from llama_index.tools.duckduckgo import DuckDuckGoSearchResultsToolSpec
|
| 5 |
+
from llama_index.core.tools import FunctionTool
|
| 6 |
+
from llama_index.core.agent.workflow import AgentWorkflow
|
| 7 |
+
|
| 8 |
+
from tools import interpret_python_math_code
|
| 9 |
+
from gaia_system_prompt import GAIA_SYSTEM_PROMPT
|
| 10 |
+
|
| 11 |
import os
|
| 12 |
|
| 13 |
GEMINI_API_KEY = os.getenv("GEMINI_TOKEN")
|
|
|
|
| 15 |
|
| 16 |
class FinalAgent:
|
| 17 |
def __init__(self):
|
| 18 |
+
# LLM Initialization
|
| 19 |
self.llm = GoogleGenAI(model=GEMINI_MODEL_NAME, api_key=GEMINI_API_KEY)
|
| 20 |
|
| 21 |
+
# Tool Initialization
|
| 22 |
+
self.tools = [
|
| 23 |
+
FunctionTool.from_defaults(
|
| 24 |
+
func=interpret_python_math_code,
|
| 25 |
+
name="InterpretPythonMathCode",
|
| 26 |
+
description="Interprets Python code for mathematical expressions."
|
| 27 |
+
),
|
| 28 |
+
DuckDuckGoSearchResultsToolSpec(),
|
| 29 |
+
WikipediaToolSpec(),
|
| 30 |
+
ArxivToolSpec()
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
# Agent Workflow Initialization
|
| 34 |
+
self.agent = AgentWorkflow(
|
| 35 |
+
llm=self.llm,
|
| 36 |
+
tools=self.tools,
|
| 37 |
+
system_prompt=GAIA_SYSTEM_PROMPT
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
print("FinalAgent initialized.")
|
| 41 |
def __call__(self, question: str) -> str:
|
| 42 |
+
# Example
|
| 43 |
print(f"Agent received question (first 50 chars): {question[:50]}...")
|
| 44 |
fixed_answer = "This is a default answer."
|
| 45 |
print(f"Agent returning fixed answer: {fixed_answer}")
|
| 46 |
+
|
| 47 |
+
# Implement agent logic here
|
| 48 |
+
response = self.agent.run(question)
|
| 49 |
+
|
| 50 |
+
return response
|
requirements.txt
CHANGED
|
@@ -2,7 +2,11 @@ gradio[oauth]
|
|
| 2 |
requests
|
| 3 |
numpy
|
| 4 |
pandas
|
|
|
|
| 5 |
llama-index
|
| 6 |
llama-index-llms-gemini
|
| 7 |
llama-index-llms-google-genai
|
| 8 |
-
llama-index-utils-workflow
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
requests
|
| 3 |
numpy
|
| 4 |
pandas
|
| 5 |
+
scipy
|
| 6 |
llama-index
|
| 7 |
llama-index-llms-gemini
|
| 8 |
llama-index-llms-google-genai
|
| 9 |
+
llama-index-utils-workflow
|
| 10 |
+
llama-index-tools-duckduckgo
|
| 11 |
+
llama-index-tools-arxiv
|
| 12 |
+
llama-index-tools-wikipedia
|
tools.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import ast
|
| 3 |
+
import io
|
| 4 |
+
import sys
|
| 5 |
+
import numpy as np
|
| 6 |
+
import pandas as pd
|
| 7 |
+
import scipy
|
| 8 |
+
|
| 9 |
+
ALLOWED_MODULES = {"numpy", "pandas", "scipy"}
|
| 10 |
+
|
| 11 |
+
def interpret_python_math_code(python_code: str) -> str:
|
| 12 |
+
"""
|
| 13 |
+
Interprets a string of Python code to perform math calculations.
|
| 14 |
+
|
| 15 |
+
Security Note: This function uses exec(). While it attempts to restrict
|
| 16 |
+
imports to numpy, pandas, and scipy, and runs with a restricted
|
| 17 |
+
global scope, executing arbitrary code always carries risks. Ensure
|
| 18 |
+
that input code is from a trusted source or properly sanitized.
|
| 19 |
+
|
| 20 |
+
The code must only import modules from the allowed list: numpy, pandas, scipy.
|
| 21 |
+
Submodules of these (e.g., numpy.linalg, scipy.stats) are permitted.
|
| 22 |
+
For example:
|
| 23 |
+
'import numpy as np' is allowed.
|
| 24 |
+
'from scipy.stats import norm' is allowed.
|
| 25 |
+
'import os' is NOT allowed.
|
| 26 |
+
|
| 27 |
+
To return a result, the code should either:
|
| 28 |
+
1. End with an expression (e.g., '1 + 1' or 'np.array([1,2,3]).sum()').
|
| 29 |
+
2. Assign the result to a variable named '_result' (e.g., '_result = my_calculation').
|
| 30 |
+
|
| 31 |
+
Print statements will also be captured and returned along with the result.
|
| 32 |
+
"""
|
| 33 |
+
# 1. Validate imports using AST
|
| 34 |
+
try:
|
| 35 |
+
tree = ast.parse(python_code)
|
| 36 |
+
for node in tree.body:
|
| 37 |
+
if isinstance(node, ast.Import):
|
| 38 |
+
for alias in node.names:
|
| 39 |
+
root_module = alias.name.split('.')[0]
|
| 40 |
+
if root_module not in ALLOWED_MODULES:
|
| 41 |
+
return (f"Error: Import of '{alias.name}' is not allowed. "
|
| 42 |
+
f"Only modules from {list(ALLOWED_MODULES)} are permitted.")
|
| 43 |
+
elif isinstance(node, ast.ImportFrom):
|
| 44 |
+
if node.module: # Handles cases like 'from . import something' where module is None
|
| 45 |
+
root_module = node.module.split('.')[0]
|
| 46 |
+
if root_module not in ALLOWED_MODULES:
|
| 47 |
+
return (f"Error: Import from '{node.module}' is not allowed. "
|
| 48 |
+
f"Only modules from {list(ALLOWED_MODULES)} are permitted.")
|
| 49 |
+
except SyntaxError as e:
|
| 50 |
+
return f"Syntax Error in input code: {e}"
|
| 51 |
+
|
| 52 |
+
# 2. Prepare execution environment
|
| 53 |
+
restricted_globals = {
|
| 54 |
+
"__builtins__": {
|
| 55 |
+
"print": print,
|
| 56 |
+
"abs": abs, "round": round, "min": min, "max": max, "sum": sum, "len": len,
|
| 57 |
+
"range": range, "zip": zip, "enumerate": enumerate,
|
| 58 |
+
"int": int, "float": float, "str": str, "list": list, "dict": dict, "tuple": tuple, "set": set,
|
| 59 |
+
"True": True, "False": False, "None": None,
|
| 60 |
+
"__import__": __import__, # Add this line
|
| 61 |
+
}
|
| 62 |
+
# numpy, pandas, scipy are NOT pre-loaded here.
|
| 63 |
+
# The user's code `import numpy` will use Python's import mechanism.
|
| 64 |
+
# The AST check above is the primary guard.
|
| 65 |
+
}
|
| 66 |
+
local_vars = {}
|
| 67 |
+
|
| 68 |
+
# 3. Capture stdout
|
| 69 |
+
old_stdout = sys.stdout
|
| 70 |
+
redirected_output = io.StringIO()
|
| 71 |
+
sys.stdout = redirected_output
|
| 72 |
+
|
| 73 |
+
# 4. Execute code and retrieve result
|
| 74 |
+
calculated_value = None
|
| 75 |
+
result_source = ""
|
| 76 |
+
output_str = ""
|
| 77 |
+
|
| 78 |
+
try:
|
| 79 |
+
compiled_code = compile(python_code, '<string>', 'exec')
|
| 80 |
+
exec(compiled_code, restricted_globals, local_vars)
|
| 81 |
+
|
| 82 |
+
# Priority 1: Check for '_result' variable
|
| 83 |
+
if "_result" in local_vars:
|
| 84 |
+
calculated_value = local_vars["_result"]
|
| 85 |
+
result_source = "variable '_result'"
|
| 86 |
+
# Priority 2: If no _result, and the last AST node was an expression, evaluate it.
|
| 87 |
+
elif tree.body and isinstance(tree.body[-1], ast.Expr):
|
| 88 |
+
# Ensure the expression node's value is a valid AST object for ast.Expression
|
| 89 |
+
if isinstance(tree.body[-1].value, ast.AST):
|
| 90 |
+
last_expr_ast = ast.Expression(body=tree.body[-1].value)
|
| 91 |
+
# Compile the expression in 'eval' mode
|
| 92 |
+
compiled_expr = compile(last_expr_ast, '<string>', 'eval')
|
| 93 |
+
# Evaluate in the context of restricted_globals and local_vars (which holds state from exec)
|
| 94 |
+
calculated_value = eval(compiled_expr, restricted_globals, local_vars)
|
| 95 |
+
result_source = "last expression"
|
| 96 |
+
|
| 97 |
+
sys.stdout = old_stdout # Restore stdout before getting its value
|
| 98 |
+
output_str = redirected_output.getvalue()
|
| 99 |
+
|
| 100 |
+
if calculated_value is not None:
|
| 101 |
+
return f"Result (from {result_source}):\n{calculated_value}\n\nCaptured Output:\n{output_str}".strip()
|
| 102 |
+
else:
|
| 103 |
+
return f"Executed successfully.\n\nCaptured Output:\n{output_str}\n(No specific result value found via '_result' variable or last expression evaluation.)".strip()
|
| 104 |
+
|
| 105 |
+
except Exception as e:
|
| 106 |
+
if sys.stdout == redirected_output: # Ensure stdout is restored on error too
|
| 107 |
+
sys.stdout = old_stdout
|
| 108 |
+
output_str = redirected_output.getvalue() # Get any output captured before the error
|
| 109 |
+
return f"Execution Error: {type(e).__name__}: {e}\n\nCaptured Output:\n{output_str}".strip()
|
| 110 |
+
finally:
|
| 111 |
+
# Ensure stdout is always restored
|
| 112 |
+
if sys.stdout == redirected_output:
|
| 113 |
+
sys.stdout = old_stdout
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
# Example usage:
|
| 117 |
+
if __name__ == "__main__":
|
| 118 |
+
code = """
|
| 119 |
+
import numpy as np
|
| 120 |
+
# import os # This should trigger an error since 'os' is not allowed
|
| 121 |
+
arr = np.array([1, 2, 3, 4, 5])
|
| 122 |
+
_result = arr.mean()
|
| 123 |
+
"""
|
| 124 |
+
print(interpret_python_math_code(code))
|