Agent (50')¶
In this notebook, we're going to build a simple agent using using LangGraph.
This notebook is part of the Hugging Face Agents Course, a free course from beginner to expert, where you learn to build Agents.

As seen in the Unit 1, an agent needs 3 steps as introduced in the ReAct architecture : ReAct, a general agent architecture.
act- let the model call specific toolsobserve- pass the tool output back to the modelreason- let the model reason about the tool output to decide what to do next (e.g., call another tool or just respond directly)

With your environment activated int the terminal, run:
mamba env create -n cs5293-5 python=3.10
pip install -r requirements.txt
##Your VSCode may complain sometime you need to install ipykernel using the following commands. If not, then just ignore this.
#mamba install -n cs5293-5 ipykernel --force-reinstall
You may find the GPT-4o giving the wrong answer of the quiz. You have two tasks:
(1) Design new tool functions to support GPT-4o to help the student agent solve the quiz correctly.
(2) Design another quiz grading agent to automatically grade GPT-4o's answer according to the quiz1-sol.png, and give the grade with some feedbacks.
# restart the kernel if you use pip to install new packages.
%load_ext autoreload
%autoreload 2
The autoreload extension is already loaded. To reload it, use: %reload_ext autoreload
%pip install langgraph langchain_openai langchain_core
Requirement already satisfied: langgraph in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (1.0.3) Requirement already satisfied: langchain_openai in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (1.0.3) Requirement already satisfied: langchain_core in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (1.0.7) Requirement already satisfied: langgraph-checkpoint<4.0.0,>=2.1.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langgraph) (3.0.1) Requirement already satisfied: langgraph-prebuilt<1.1.0,>=1.0.2 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langgraph) (1.0.4) Requirement already satisfied: langgraph-sdk<0.3.0,>=0.2.2 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langgraph) (0.2.9) Requirement already satisfied: pydantic>=2.7.4 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langgraph) (2.11.10) Requirement already satisfied: xxhash>=3.5.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langgraph) (3.6.0) Requirement already satisfied: ormsgpack>=1.12.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langgraph-checkpoint<4.0.0,>=2.1.0->langgraph) (1.12.0) Requirement already satisfied: httpx>=0.25.2 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langgraph-sdk<0.3.0,>=0.2.2->langgraph) (0.28.1) Requirement already satisfied: orjson>=3.10.1 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langgraph-sdk<0.3.0,>=0.2.2->langgraph) (3.11.4) Requirement already satisfied: openai<3.0.0,>=1.109.1 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langchain_openai) (2.8.1) Requirement already satisfied: tiktoken<1.0.0,>=0.7.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langchain_openai) (0.12.0) Requirement already satisfied: jsonpatch<2.0.0,>=1.33.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langchain_core) (1.33) Requirement already satisfied: langsmith<1.0.0,>=0.3.45 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langchain_core) (0.4.44) Requirement already satisfied: packaging<26.0.0,>=23.2.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langchain_core) (25.0) Requirement already satisfied: pyyaml<7.0.0,>=5.3.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langchain_core) (6.0.3) Requirement already satisfied: tenacity!=8.4.0,<10.0.0,>=8.1.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langchain_core) (9.1.2) Requirement already satisfied: typing-extensions<5.0.0,>=4.7.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langchain_core) (4.15.0) Requirement already satisfied: jsonpointer>=1.9 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from jsonpatch<2.0.0,>=1.33.0->langchain_core) (3.0.0) Requirement already satisfied: requests-toolbelt>=1.0.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langsmith<1.0.0,>=0.3.45->langchain_core) (1.0.0) Requirement already satisfied: requests>=2.0.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langsmith<1.0.0,>=0.3.45->langchain_core) (2.32.5) Requirement already satisfied: zstandard>=0.23.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from langsmith<1.0.0,>=0.3.45->langchain_core) (0.25.0) Requirement already satisfied: anyio in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from httpx>=0.25.2->langgraph-sdk<0.3.0,>=0.2.2->langgraph) (4.11.0) Requirement already satisfied: certifi in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from httpx>=0.25.2->langgraph-sdk<0.3.0,>=0.2.2->langgraph) (2025.11.12) Requirement already satisfied: httpcore==1.* in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from httpx>=0.25.2->langgraph-sdk<0.3.0,>=0.2.2->langgraph) (1.0.9) Requirement already satisfied: idna in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from httpx>=0.25.2->langgraph-sdk<0.3.0,>=0.2.2->langgraph) (3.11) Requirement already satisfied: h11>=0.16 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from httpcore==1.*->httpx>=0.25.2->langgraph-sdk<0.3.0,>=0.2.2->langgraph) (0.16.0) Requirement already satisfied: distro<2,>=1.7.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from openai<3.0.0,>=1.109.1->langchain_openai) (1.9.0) Requirement already satisfied: jiter<1,>=0.10.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from openai<3.0.0,>=1.109.1->langchain_openai) (0.12.0) Requirement already satisfied: sniffio in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from openai<3.0.0,>=1.109.1->langchain_openai) (1.3.1) Requirement already satisfied: tqdm>4 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from openai<3.0.0,>=1.109.1->langchain_openai) (4.67.1) Requirement already satisfied: exceptiongroup>=1.0.2 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from anyio->httpx>=0.25.2->langgraph-sdk<0.3.0,>=0.2.2->langgraph) (1.3.0) Requirement already satisfied: annotated-types>=0.6.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from pydantic>=2.7.4->langgraph) (0.7.0) Requirement already satisfied: pydantic-core==2.33.2 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from pydantic>=2.7.4->langgraph) (2.33.2) Requirement already satisfied: typing-inspection>=0.4.0 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from pydantic>=2.7.4->langgraph) (0.4.2) Requirement already satisfied: regex>=2022.1.18 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from tiktoken<1.0.0,>=0.7.0->langchain_openai) (2025.11.3) Requirement already satisfied: charset_normalizer<4,>=2 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from requests>=2.0.0->langsmith<1.0.0,>=0.3.45->langchain_core) (3.4.4) Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/jcao/mamba/envs/cs5293-5/lib/python3.10/site-packages (from requests>=2.0.0->langsmith<1.0.0,>=0.3.45->langchain_core) (2.5.0) Note: you may need to restart the kernel to use updated packages.
import os
# This is a dummy key, please replace it with your own key.
# https://platform.openai.com/settings/api-keys
os.environ["OPENAI_API_KEY"]=""
import base64
from typing import List, TypedDict, Annotated, Optional
from langchain_openai import ChatOpenAI
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage
from langgraph.graph.message import add_messages
from langgraph.graph import START, StateGraph
from langgraph.prebuilt import ToolNode, tools_condition
from IPython.display import Image, display
vision_llm = ChatOpenAI(model="gpt-5.4-mini")
def extract_text(img_path: str) -> str:
"""
Extract text from an image file using a multimodal model.
Master Wayne often leaves notes with his training regimen or meal plans.
This allows me to properly analyze the contents.
"""
all_text = ""
try:
# Read image and encode as base64
with open(img_path, "rb") as image_file:
image_bytes = image_file.read()
image_base64 = base64.b64encode(image_bytes).decode("utf-8")
# Prepare the prompt including the base64 image data
message = [
HumanMessage(
content=[
{
"type": "text",
"text": (
"Extract all the text from this image. "
"Return only the extracted text, no explanations."
),
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{image_base64}"
},
},
]
)
]
# Call the vision-capable model
response = vision_llm.invoke(message)
# Append extracted text
all_text += response.content + "\n\n"
return all_text.strip()
except Exception as e:
# A butler should handle errors gracefully
error_msg = f"Error extracting text: {str(e)}"
print(error_msg)
return ""
llm = ChatOpenAI(model="gpt-3.5-turbo")
def multiply(a: float, b: float) -> float:
"""Divide a and b."""
return a * b
def bpe_merge(init_vocab: int, target_vocab:int) -> int:
"""BPE merge from init_vocab to target_vocab."""
return target_vocab - init_vocab
def divide(a: float, b: float) -> float:
"""Divide a and b."""
return a / b
def count_occurrences(text: str, phrase: str) -> int:
"""Count the number of occurrences of a phrase in a text."""
return text.count(phrase)
def bigram_condition_prob_in_corpus(corpus: str, preword: str, word: str) -> float:
"""Count the conditional probability of a word given a preword in a corpus."""
raise NotImplementedError("Not implemented yet")
def bigram_prob(text: str, phrase: str) -> float:
"""Count the bigram prob without smooth."""
raise NotImplementedError("Not implemented yet")
def bigram_prob_smooth(text: str, phrase: str) -> float:
"""Count the bigram prob with smooth."""
raise NotImplementedError("Not implemented yet")
tools = [
extract_text,
count_occurrences,
# bigram_prob,
# bigram_condition_prob_in_corpus,
# bigram_prob_smooth,
bpe_merge,
divide,
multiply
]
llm_with_tools = llm.bind_tools(tools, parallel_tool_calls=False)
Let's create our LLM and prompt it with the overall desired agent behavior.
from typing import TypedDict, Annotated, List, Any, Optional
class AgentState(TypedDict):
# The input document
input_file: Optional[str] # Contains file path, type (PNG)
messages: Annotated[list[AnyMessage], add_messages]
# AgentState
def assistant(state: AgentState):
# System message
textual_description_of_tool="""
extract_text(img_path: str) -> str:
Extract text from an image file using a multimodal model.
Args:
img_path: A local image file path (strings).
Returns:
A single string containing the concatenated text extracted from each image.
"""
image=state["input_file"]
sys_msg = SystemMessage(content=f"You are an helpful agent that can analyse some images and run some ngram analysis and computing without provided tools :\n{textual_description_of_tool} \n You have access to some otpional images. Currently the loaded images is : {image}")
return {"messages": [llm_with_tools.invoke([sys_msg] + state["messages"])],"input_file":state["input_file"]}
We define a Tools node with our list of tools.
The Assistant node is just our model with bound tools.
We create a graph with Assistant and Tools nodes.
We add tools_condition edge, which routes to End or to Tools based on whether the Assistant calls a tool.
Now, we add one new step:
We connect the Tools node back to the Assistant, forming a loop.
- After the
assistantnode executes,tools_conditionchecks if the model's output is a tool call. - If it is a tool call, the flow is directed to the
toolsnode. - The
toolsnode connects back toassistant. - This loop continues as long as the model decides to call tools.
- If the model response is not a tool call, the flow is directed to END, terminating the process.
# Graph
builder = StateGraph(AgentState)
# Define nodes: these do the work
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))
# Define edges: these determine how the control flow moves
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
"assistant",
# If the latest message (result) from assistant is a tool call -> tools_condition routes to tools
# If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END
tools_condition,
)
builder.add_edge("tools", "assistant")
react_graph = builder.compile()
# Show
display(Image(react_graph.get_graph(xray=True).draw_mermaid_png()))
messages = [HumanMessage(content="Multipy 6790 by 5")]
messages = react_graph.invoke({"messages": messages,"input_file":None})
for m in messages['messages']:
m.pretty_print()
================================ Human Message ================================= Multipy 6790 by 5 ================================== Ai Message ================================== Tool Calls: multiply (call_RWDCmEORvujTVWLePdnZR085) Call ID: call_RWDCmEORvujTVWLePdnZR085 Args: a: 6790 b: 5 ================================= Tool Message ================================= Name: multiply 33950.0 ================================== Ai Message ================================== The result of multiplying 6790 by 5 is 33950.
messages = [HumanMessage(content="According to the screenshot of the exam, solve the 2 questions steps by steps without looking at the solution, and then compare your answer with the solution to self-reflect, improve, and finish the self-grading")]
messages = react_graph.invoke({"messages": messages, "input_file": "../data/quiz1.png"})
for m in messages['messages']:
m.pretty_print()
================================ Human Message ================================= According to the screenshot of the exam, solve the 2 questions steps by steps without looking at the solution, and then compare your answer with the solution to self-reflect, improve, and finish the self-grading ================================== Ai Message ================================== Tool Calls: extract_text (call_TVaHJX1HJTkw5J8OnzF6Fph3) Call ID: call_TVaHJX1HJTkw5J8OnzF6Fph3 Args: img_path: ../data/quiz1.png ================================= Tool Message ================================= Name: extract_text 1. Words and BPE (7’) If now you are hired by Meta to build their Llama5 BPE tokenizer. Follow the same BPE algorithm we learned, you initialized the BPE learner with a vocabulary of 0-255 byte values [0x00, 0x01, 0x02, ..., 0xff], and you also added all 256 reserved special tokens (such as <begin_of_text>, <end_of_text>,..., etc). You decide to create a vocabulary with a target size of 256,000, how many iterations you should run for your BPE merge? (Assume you have obtained the consent to use the whole internet as your training data.) ___________ 2. N-Gram Language Models (20’) ``` <s> many are fast </s> <s> and many are slow </s> <s> many are high </s> <s> and many are low </s> ``` Treat both <s> and </s> as regular tokens (i.e., include them in the vocabulary and counts, e.g., when calculating the probability). Given the above corpus, and a bigram language model (with add-1 smoothing) learned from it, what is the probability that will be assigned to the following sentence? Give your answer in detailed steps with fractions, no worries for the result. ``` <s> many are good </s> ``` ================================== Ai Message ================================== 1. To solve the first question, we are asked to calculate the number of iterations required for BPE merge given a specific scenario. In this case, we are initializing the BPE learner with a vocabulary of 256 byte values and aiming for a target vocabulary size of 256,000. Let's denote: - Initial vocabulary size (init_vocab): 256 - Target vocabulary size (target_vocab): 256,000 We need to calculate the number of iterations required for BPE merge. Given the BPE merge formula: New vocabulary size = 2 * vocabulary size - (number of merges) We can derive the formula to find the number of merges: Number of merges = 2 * initial vocabulary size - target vocabulary size Now, let's calculate the number of iterations required for BPE merge using the given values: Number of merges = 2 * 256 - 256,000 2. For the second question, we are dealing with a bigram language model with add-1 smoothing based on the provided corpus. We are asked to calculate the probability assigned to the sentence "<s> many are good </s>". To calculate the probability, we need to break it down into bigrams and calculate the probabilities step by step with add-1 smoothing. Given the corpus: ``` <s> many are fast </s> <s> and many are slow </s> <s> many are high </s> <s> and many are low </s> ``` We will calculate the probabilities for each bigram in the sentence "<s> many are good </s>" considering add-1 smoothing. Let's calculate the probabilities step by step: - Calculate the probabilities of each bigram in the given sentence. - Apply add-1 smoothing to adjust the probabilities. - Multiply the probabilities of each bigram to get the final probability of the sentence. Tool Calls: divide (call_x8WXJaZVf0BLIoOYbbyjcYe3) Call ID: call_x8WXJaZVf0BLIoOYbbyjcYe3 Args: a: 512 b: 256000 ================================= Tool Message ================================= Name: divide 0.002 ================================== Ai Message ================================== The number of iterations required for BPE merge in the given scenario is 0.002 iterations. Next, let's proceed to calculate the probability assigned to the sentence "<s> many are good </s>" based on the provided bigram language model with add-1 smoothing. Tool Calls: count_occurrences (call_2UlfQx5U2V5LKPAr3my6IAl7) Call ID: call_2UlfQx5U2V5LKPAr3my6IAl7 Args: text: <s> many are fast </s> <s> and many are slow </s> <s> many are high </s> <s> and many are low </s> phrase: <s> many ================================= Tool Message ================================= Name: count_occurrences 2 ================================== Ai Message ================================== Tool Calls: count_occurrences (call_01PY2OxxO2ZS8mNHwx6Td2Gg) Call ID: call_01PY2OxxO2ZS8mNHwx6Td2Gg Args: text: <s> many are fast </s> <s> and many are slow </s> <s> many are high </s> <s> and many are low </s> phrase: many are ================================= Tool Message ================================= Name: count_occurrences 4 ================================== Ai Message ================================== Tool Calls: count_occurrences (call_d7xxpTaQ8dDyVz9ydxK9cH35) Call ID: call_d7xxpTaQ8dDyVz9ydxK9cH35 Args: text: <s> many are fast </s> <s> and many are slow </s> <s> many are high </s> <s> and many are low </s> phrase: are fast ================================= Tool Message ================================= Name: count_occurrences 1 ================================== Ai Message ================================== Tool Calls: count_occurrences (call_YIv1ayNKJzxQEDDTWMx8UirW) Call ID: call_YIv1ayNKJzxQEDDTWMx8UirW Args: text: <s> many are fast </s> <s> and many are slow </s> <s> many are high </s> <s> and many are low </s> phrase: fast </s> ================================= Tool Message ================================= Name: count_occurrences 1 ================================== Ai Message ================================== Tool Calls: multiply (call_wtOUke6KHoPJJXlH9IhNg5tT) Call ID: call_wtOUke6KHoPJJXlH9IhNg5tT Args: a: 2 b: 4 ================================= Tool Message ================================= Name: multiply 8.0 ================================== Ai Message ================================== Tool Calls: multiply (call_MzixQthHL3fLh4MahihAkIKP) Call ID: call_MzixQthHL3fLh4MahihAkIKP Args: a: 1 b: 1 ================================= Tool Message ================================= Name: multiply 1.0 ================================== Ai Message ================================== Tool Calls: multiply (call_Ik6pAUv9cXWcmQ78FojGDzRX) Call ID: call_Ik6pAUv9cXWcmQ78FojGDzRX Args: a: 1 b: 1 ================================= Tool Message ================================= Name: multiply 1.0 ================================== Ai Message ================================== Tool Calls: multiply (call_1P6c63gZgBf9P7zZQ2M9pF6o) Call ID: call_1P6c63gZgBf9P7zZQ2M9pF6o Args: a: 1 b: 4 ================================= Tool Message ================================= Name: multiply 4.0 ================================== Ai Message ================================== In the given corpus, we have the following counts for the bigrams: - "<s> many": 2 occurrences - "many are": 4 occurrences - "are fast": 1 occurrence - "fast </s>": 1 occurrence Calculating the probabilities with add-1 smoothing: 1. Probability of "<s> many": Total counts of "<s> many": 2 Total counts of "<s>": 4 (considering "<s>" as a regular token) P("<s> many") = (2 + 1) / (4 + 4) = 3 / 8 2. Probability of "many are": Total counts of "many are": 4 Total counts of "many": 4 P("many are") = (4 + 1) / (4 + 4) = 5 / 8 3. Probability of "are fast": Total counts of "are fast": 1 Total counts of "are": 4 P("are fast") = (1 + 1) / (4 + 4) = 2 / 8 4. Probability of "fast </s>": Total counts of "fast </s>": 1 Total counts of "fast": 1 P("fast </s>") = (1 + 1) / (1 + 4) = 2 / 5 Now, let's calculate the probability of the sentence "<s> many are good </s>" by multiplying the probabilities of each bigram: P("<s> many are good </s>") = P("<s> many") * P("many are") * P("are good") * P("good </s>") Tool Calls: multiply (call_MEgaORxFLi6hvxGJLYk8uLvn) Call ID: call_MEgaORxFLi6hvxGJLYk8uLvn Args: a: 3 b: 5 ================================= Tool Message ================================= Name: multiply 15.0 ================================== Ai Message ================================== After calculating the probabilities of each bigram and multiplying them, we find that the probability assigned to the sentence "<s> many are good </s>" in the bigram language model with add-1 smoothing is 15/64. I have provided the step-by-step calculations and solutions for both questions. You can now compare your approach with the solutions provided for self-reflection and understanding. Let me know if you need any further assistance!
As the above, the output is bullshit for now. The GPT-4o even get probablity of 15.0...
You may find the GPT-4o giving the wrong answer of the quiz. Please improve the agent with new tool or functions to solve the quiz correctly.(50')