helene-rousset's picture
Update agent.py
3c1a783 verified
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
from langchain_core.messages import AnyMessage, HumanMessage, AIMessage, SystemMessage
from langgraph.prebuilt import ToolNode
from langgraph.graph import START, StateGraph, END
from langchain_community.tools import DuckDuckGoSearchRun
from langchain_openai import ChatOpenAI
import requests
import base64
import json
import io
import contextlib
import os
import subprocess
import tempfile
OPENAI = os.getenv("OPENAI")
def replace(_, new_value):
return new_value
search_tool = DuckDuckGoSearchRun()
tools = [search_tool]
llm = ChatOpenAI(
model="gpt-4o",
openai_api_key=OPENAI,
)
llm_with_tools = llm.bind_tools(tools)
# Generate the AgentState and Agent graph
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
question: Annotated[str, replace]
task_id: Annotated[str, replace]
file_name: Annotated[str, replace]
def get_question(state: AgentState):
print("get_question")
question_dict = json.loads(state['messages'][-1].content)
print(question_dict)
state['question'] = question_dict["question"]
state['task_id'] = question_dict["task_id"]
state['file_name'] = question_dict["file_name"]
return state
def reformulate_question(state: AgentState):
print("reformulate_question")
sys_msg = SystemMessage(content=f"You are a detective and you have to answer a hard question. The first step is to understand the question. Can you reformulate it to make it clear and concise ? The question is : {state['question']}")
state['messages'] = state['messages'] + [llm_with_tools.invoke([sys_msg])]
return state
def assistant(state: AgentState):
print("assistant")
# System message
sys_msg="""
You are a helpful assistant that can answer questions about the world. You can use the internet through the search tool to find information.
"""
sys_msg = SystemMessage(content=sys_msg)
state['messages'] = state['messages'] + [llm_with_tools.invoke([sys_msg] + state["messages"])]
return state
def get_final_answer(state: AgentState):
print("get_final_answer")
sys_msg = SystemMessage(content=f"""
Reply the answer and only the answer of the question. Do not make a sentence, just the answer. If the answer is a number, return it in numeric form.
If the question specifies a format, please return it in the specified format. Do not add unnecessary uppercasing or punctuation. Here is the question again: {state['question']} and here is the answer: {state['messages'][-1].content}
""")
final_answer = state["messages"][-1].content # The last assistant message
print(f"final answer: {final_answer}")
state['messages'] = state['messages'] + [llm.invoke([sys_msg])]
return state
def python_interpreter(state: AgentState):
print("python interpreter")
question_id = state['task_id']
url = f"https://agents-course-unit4-scoring.hf.space/files/{question_id}"
# Step 1: Download the code
response = requests.get(url)
python_code = response.content.decode("utf-8")
print("Running user code with subprocess...")
# Step 2: Write code to temp file
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as tmp_file:
tmp_file.write(python_code)
tmp_file_path = tmp_file.name
try:
# Step 3: Run using subprocess (set timeout if needed)
result = subprocess.run(
["python3", tmp_file_path],
capture_output=True,
text=True,
timeout=30 # seconds
)
if result.returncode != 0:
output = f"⚠️ Error:\n{result.stderr.strip()}"
else:
output = result.stdout.strip() or "Code ran but produced no output."
except subprocess.TimeoutExpired:
output = "Execution timed out."
except Exception as e:
output = f"Unexpected error: {e}"
# Step 4: Update agent state
state['messages'] = state['messages'] + [
AIMessage(content=f"The output of the Python code is:\n```\n{output}\n```")
]
return state
# def python_interpreter(state: AgentState):
# question_id = state['task_id']
# url = f"https://agents-course-unit4-scoring.hf.space/files/{question_id}"
# # Step 1: Fetch Python code as bytes and decode
# response = requests.get(url)
# python_code = response.content.decode("utf-8")
# # print(python_code)
# # Step 2: Capture stdout
# stdout = io.StringIO()
# with contextlib.redirect_stdout(stdout):
# try:
# exec(python_code) # Use empty dict for isolated global context
# except Exception as e:
# output = f"Error during execution: {e}"
# else:
# output = stdout.getvalue().strip()
# print("Code output : ", output)
# # Step 3: Save output as message
# state['messages'] = state['messages'] + [
# AIMessage(content=f"The output of the python code is : {output}")
# ]
# return state
def image_interpreter(state: AgentState):
print("image interpreter")
"""Answers a question about an image. Provide raw image bytes and a question."""
question_id = state['task_id']
url = f"https://agents-course-unit4-scoring.hf.space/files/{question_id}"
image = requests.get(url).content
image_base64 = base64.b64encode(image).decode("utf-8")
messages=[
{"role": "system", "content": "You are a helpful assistant that can answer questions based on images."},
{
"role": "human",
"content": [
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"}},
{"type": "text", "text": state['question']}
],
},
]
resp = llm.invoke(messages)
print("Image response : ", resp)
state['messages'] = state['messages'] + [resp]
return state
def not_handled(state: AgentState):
print("not_handled")
state['messages'] = state['messages'] + [AIMessage(content="I cannot handle this question.")]
return state
def tools_condition(state: AgentState) -> str:
print("tools_condition")
last_msg = state["messages"][-1]
if "tool_calls" in last_msg.additional_kwargs:
# LLM is requesting a tool
return "tools"
# No tool requested → go to final summary
return "get_final_answer"
def files_condition(state: AgentState) -> str:
print("files_condition")
if state["file_name"].endswith(".py"):
return "python_interpreter"
elif state["file_name"].endswith(".png"):
return "image_interpreter"
elif state["file_name"]=="":
return "reformulate_question"
else:
return "not_handled"
def build_agent():
## The graph
builder = StateGraph(AgentState)
# Define nodes: these do the work
builder.add_node("get_question", get_question)
builder.add_node("reformulate_question", reformulate_question)
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))
builder.add_node("get_final_answer", get_final_answer)
builder.add_node("python_interpreter", python_interpreter)
builder.add_node("image_interpreter", image_interpreter)
builder.add_node("not_handled", not_handled)
# Define edges: these determine how the control flow moves
builder.add_edge(START, "get_question")
builder.add_conditional_edges(
"get_question",
files_condition,
{
"python_interpreter": "python_interpreter",
"image_interpreter": "image_interpreter",
"reformulate_question": "reformulate_question",
"not_handled": "not_handled"
}
)
builder.add_edge("python_interpreter", "get_final_answer")
builder.add_edge("image_interpreter", "get_final_answer")
builder.add_edge("reformulate_question", "assistant")
builder.add_conditional_edges(
"assistant",
tools_condition,
{
"tools": "tools",
"get_final_answer": "get_final_answer"
}
)
builder.add_edge("tools", "assistant")
builder.add_edge("get_final_answer", END)
builder.add_edge("not_handled", END)
mySuperAgent = builder.compile()
return mySuperAgent