GAIA_Agent / agents /code_agent.py
Delanoe Pirard
First commit
a23082c
raw
history blame
9.42 kB
import os
import logging
from dotenv import load_dotenv
from llama_index.core.agent.workflow import CodeActAgent, ReActAgent
from llama_index.core.tools import FunctionTool
from llama_index.llms.google_genai import GoogleGenAI
from llama_index.llms.openai import OpenAI
from llama_index.tools.code_interpreter import CodeInterpreterToolSpec
# Load environment variables
load_dotenv()
# Setup logging
logger = logging.getLogger(__name__)
# Helper function to load prompt from file
def load_prompt_from_file(filename: str, default_prompt: str) -> str:
"""Loads a prompt from a text file."""
try:
# Assuming the prompt file is in the same directory as the agent script
script_dir = os.path.dirname(__file__)
prompt_path = os.path.join(script_dir, filename)
with open(prompt_path, "r") as f:
prompt = f.read()
logger.info(f"Successfully loaded prompt from {prompt_path}")
return prompt
except FileNotFoundError:
logger.warning(f"Prompt file {filename} not found at {prompt_path}. Using default.")
return default_prompt
except Exception as e:
logger.error(f"Error loading prompt file {filename}: {e}", exc_info=True)
return default_prompt
def generate_python_code(prompt: str) -> str:
"""
Generate valid Python code from a natural language description using a configured LLM.
Args:
prompt (str): A clear description of the desired Python code functionality.
Returns:
str: A string containing the generated Python code.
Raises:
ValueError: If required API key is not set.
Exception: If the LLM call fails.
"""
logger.info(f"Generating Python code for prompt: {prompt[:100]}...")
# Configuration for code generation LLM
gen_llm_model = os.getenv("CODE_GEN_LLM_MODEL", "o4-mini")
gen_api_key_env = os.getenv("CODE_GEN_API_KEY_ENV", "ALPAFLOW_OPENAI_API_KEY")
gen_api_key = os.getenv(gen_api_key_env)
if not gen_api_key:
logger.error(f"{gen_api_key_env} not found in environment variables for code generation LLM.")
raise ValueError(f"{gen_api_key_env} must be set for code generation")
# Load the prompt template
default_gen_prompt_template = ("You are a helpful assistant that writes Python code. "
"You will be given a prompt and you must generate Python code based on that prompt. "
"You must only generate Python code and nothing else. "
"Do not include any explanations or any other text. "
"Do not use any markdown. \n"
"Prompt: {prompt} \n"
"Code:\n")
gen_prompt_template = load_prompt_from_file("../prompts/code_gen_prompt.txt", default_gen_prompt_template)
input_prompt = gen_prompt_template.format(prompt=prompt)
try:
llm = OpenAI(
model=gen_llm_model,
api_key=gen_api_key
)
logger.info(f"Using code generation LLM: {gen_llm_model}")
generated_code = llm.complete(input_prompt)
logger.info("Code generation successful.")
return generated_code.text
except Exception as e:
logger.error(f"LLM call failed during code generation: {e}", exc_info=True)
raise # Re-raise the exception to be handled by the agent/workflow
# --- Tool Definitions ---
python_code_generator_tool = FunctionTool.from_defaults(
fn=generate_python_code,
name="python_code_generator",
description=(
"Generates executable Python code based on a natural language prompt. "
"Input: prompt string. Output: Python code string."
),
)
# Use LlamaIndex's built-in Code Interpreter Tool Spec for safe execution
# This assumes the necessary environment (e.g., docker) for the spec is available
try:
code_interpreter_spec = CodeInterpreterToolSpec()
# Get the tool(s) from the spec. It might return multiple tools.
code_interpreter_tools = code_interpreter_spec.to_tool_list()
if not code_interpreter_tools:
raise RuntimeError("CodeInterpreterToolSpec did not return any tools.")
# Assuming the primary tool is the first one, or find by name if necessary
code_interpreter_tool = next((t for t in code_interpreter_tools if t.metadata.name == "code_interpreter"), None)
if code_interpreter_tool is None:
raise RuntimeError("Could not find 'code_interpreter' tool in CodeInterpreterToolSpec results.")
logger.info("CodeInterpreterToolSpec initialized successfully.")
except Exception as e:
logger.error(f"Failed to initialize CodeInterpreterToolSpec: {e}", exc_info=True)
# Fallback: Define a dummy tool or raise error to prevent agent start?
# For now, let initialization fail if the safe interpreter isn't available.
raise RuntimeError("CodeInterpreterToolSpec failed to initialize. Cannot create code_agent.") from e
# --- REMOVED SimpleCodeExecutor ---
# The SimpleCodeExecutor class that used subprocess has been entirely removed
# due to severe security risks. Execution MUST go through the CodeInterpreterToolSpec.
# --- Agent Initialization ---
def initialize_code_agent() -> ReActAgent:
"""Initializes the CodeActAgent, configured for safe code execution."""
logger.info("Initializing CodeAgent...")
# Configuration for the agent's main LLM
agent_llm_model = os.getenv("CODE_AGENT_LLM_MODEL", "models/gemini-1.5-pro")
gemini_api_key = os.getenv("GEMINI_API_KEY")
if not gemini_api_key:
logger.error("GEMINI_API_KEY not found in environment variables for CodeAgent.")
raise ValueError("GEMINI_API_KEY must be set for CodeAgent")
try:
llm = GoogleGenAI(
api_key=gemini_api_key,
model=agent_llm_model,
)
logger.info(f"Using agent LLM: {agent_llm_model}")
# Load system prompt (consider loading from file)
default_system_prompt = """\
You are CodeAgent, a specialist in generating and executing Python code. Your mission:
1. **Thought**: Think step-by-step before acting and state your reasoning.
2. **Code Generation**: To produce code, call `python_code_generator` with a concise, unambiguous prompt. Review the generated code for correctness and safety.
3. **Execution & Testing**: To execute or test code, call `code_interpreter`. Provide the complete code snippet. Analyze its output (stdout, stderr, result) to verify functionality and debug errors.
4. **Iteration**: If execution fails or the result is incorrect, analyze the error, think about the fix, generate corrected code using `python_code_generator`, and execute again using `code_interpreter`.
5. **Tool Use**: Always adhere strictly to each tool’s input/output format.
6. **Final Output**: Once the code works correctly and achieves the goal, output *only* the final functional code or the final execution result, as appropriate for the task.
7. **Hand-Off**: If further logical reasoning or verification is needed, delegate to **reasoning_agent**. Otherwise, pass your final output to **planner_agent** for synthesis.
"""
# system_prompt = load_prompt_from_file("code_agent_system_prompt.txt", default_system_prompt)
system_prompt = default_system_prompt # Using inline for now
agent = ReActAgent(
name="code_agent",
description=(
"Generates Python code using `python_code_generator` and executes it safely using `code_interpreter`. "
"Iteratively debugs and refines code based on execution results."
),
# REMOVED: code_execute_fn - Execution is handled by the code_interpreter tool via the agent loop.
tools=[
python_code_generator_tool,
code_interpreter_tool, # Use the safe tool from the spec
],
llm=llm,
system_prompt=system_prompt,
can_handoff_to=["planner_agent", "reasoning_agent"],
)
logger.info("CodeAgent initialized successfully.")
return agent
except Exception as e:
logger.error(f"Error during CodeAgent initialization: {e}", exc_info=True)
raise
# Example usage (for testing if run directly)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger.info("Running code_agent.py directly for testing...")
# Ensure API keys are set for testing
required_keys = ["GEMINI_API_KEY", os.getenv("CODE_GEN_API_KEY_ENV", "ALPAFLOW_OPENAI_API_KEY")]
missing_keys = [key for key in required_keys if not os.getenv(key)]
if missing_keys:
print(f"Error: Required environment variable(s) not set: {', '.join(missing_keys)}. Cannot run test.")
else:
try:
test_agent = initialize_code_agent()
print("Code Agent initialized successfully for testing.")
# Example test (requires user interaction or pre-defined task)
# result = test_agent.chat("Write and execute python code to print 'hello world'")
# print(f"Test query result: {result}")
except Exception as e:
print(f"Error during testing: {e}")