Lab 7 — The Same Agent, with Frameworks¶
⏱️ ~35 min
Part A: rebuild Lab 5's agent with LangChain. Part B: a 2-agent CrewAI team. Uses OpenAI.
Requires an OpenAI API key — these labs use OpenAI embeddings / function-calling specifically. (Labs 1–3 work with OpenAI or Anthropic.)
In [ ]:
Copied!
# Tip: pin these to exact versions in real projects — these frameworks ship breaking changes often.
!pip install -q langchain langchain-openai crewai
import os, getpass
if not os.environ.get("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API key: ")
# Tip: pin these to exact versions in real projects — these frameworks ship breaking changes often.
!pip install -q langchain langchain-openai crewai
import os, getpass
if not os.environ.get("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API key: ")
Part A — LangChain agent with two tools¶
In [ ]:
Copied!
import ast, operator
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
_OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul,
ast.Div: operator.truediv, ast.Pow: operator.pow, ast.Mod: operator.mod,
ast.USub: operator.neg}
def _safe_arith(node):
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
return node.value
if isinstance(node, ast.BinOp) and type(node.op) in _OPS:
return _OPS[type(node.op)](_safe_arith(node.left), _safe_arith(node.right))
if isinstance(node, ast.UnaryOp) and type(node.op) in _OPS:
return _OPS[type(node.op)](_safe_arith(node.operand))
raise ValueError("unsupported expression")
@tool
def web_search(query: str) -> str:
"""Search the web for a fact."""
facts = {"eiffel tower height": "The Eiffel Tower is 330 meters tall."}
for k, v in facts.items():
if k in query.lower():
return v
return "No results found."
@tool
def calculator(expression: str) -> str:
"""Safely evaluate a simple arithmetic expression.
NOTE: never call Python eval() on model-generated text — that's how you get
code injection. We parse a tiny arithmetic grammar with ast instead."""
try:
return str(_safe_arith(ast.parse(expression, mode="eval").body))
except Exception as e:
return f"Error: {e}"
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful agent. Use tools when needed."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_tool_calling_agent(llm, [web_search, calculator], prompt)
executor = AgentExecutor(agent=agent, tools=[web_search, calculator], verbose=True)
import ast, operator
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
_OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul,
ast.Div: operator.truediv, ast.Pow: operator.pow, ast.Mod: operator.mod,
ast.USub: operator.neg}
def _safe_arith(node):
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
return node.value
if isinstance(node, ast.BinOp) and type(node.op) in _OPS:
return _OPS[type(node.op)](_safe_arith(node.left), _safe_arith(node.right))
if isinstance(node, ast.UnaryOp) and type(node.op) in _OPS:
return _OPS[type(node.op)](_safe_arith(node.operand))
raise ValueError("unsupported expression")
@tool
def web_search(query: str) -> str:
"""Search the web for a fact."""
facts = {"eiffel tower height": "The Eiffel Tower is 330 meters tall."}
for k, v in facts.items():
if k in query.lower():
return v
return "No results found."
@tool
def calculator(expression: str) -> str:
"""Safely evaluate a simple arithmetic expression.
NOTE: never call Python eval() on model-generated text — that's how you get
code injection. We parse a tiny arithmetic grammar with ast instead."""
try:
return str(_safe_arith(ast.parse(expression, mode="eval").body))
except Exception as e:
return f"Error: {e}"
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful agent. Use tools when needed."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_tool_calling_agent(llm, [web_search, calculator], prompt)
executor = AgentExecutor(agent=agent, tools=[web_search, calculator], verbose=True)
In [ ]:
Copied!
executor.invoke({"input": "How tall is the Eiffel Tower, and what is that doubled?"})
executor.invoke({"input": "How tall is the Eiffel Tower, and what is that doubled?"})
Compare to Lab 5: no manual message list, no tool-dispatch loop — the executor runs it.
Part B — A two-agent team with CrewAI¶
A researcher finds the fact; a math analyst doubles it. Shows multi-agent orchestration.
In [ ]:
Copied!
from crewai import Agent, Task, Crew
researcher = Agent(
role="Researcher", goal="Find the height of the Eiffel Tower",
backstory="You find accurate facts.", verbose=True, allow_delegation=False)
analyst = Agent(
role="Math Analyst", goal="Double any number you are given",
backstory="You do precise arithmetic.", verbose=True, allow_delegation=False)
t1 = Task(description="State the Eiffel Tower's height in meters.",
expected_output="A single number in meters.", agent=researcher)
t2 = Task(description="Double the height the researcher found.",
expected_output="The doubled number.", agent=analyst, context=[t1])
crew = Crew(agents=[researcher, analyst], tasks=[t1, t2], verbose=True)
print(crew.kickoff())
from crewai import Agent, Task, Crew
researcher = Agent(
role="Researcher", goal="Find the height of the Eiffel Tower",
backstory="You find accurate facts.", verbose=True, allow_delegation=False)
analyst = Agent(
role="Math Analyst", goal="Double any number you are given",
backstory="You do precise arithmetic.", verbose=True, allow_delegation=False)
t1 = Task(description="State the Eiffel Tower's height in meters.",
expected_output="A single number in meters.", agent=researcher)
t2 = Task(description="Double the height the researcher found.",
expected_output="The doubled number.", agent=analyst, context=[t1])
crew = Crew(agents=[researcher, analyst], tasks=[t1, t2], verbose=True)
print(crew.kickoff())
Takeaway: LangChain abstracts the single-agent loop; CrewAI coordinates multiple specialist agents. Frameworks trade some control for far less boilerplate.
✅ Did it work?¶
- Part A: the verbose AgentExecutor log showed it calling
web_searchthencalculator, ending at 660 meters. - Part B: the CrewAI run printed the researcher's height (330 m) handed to the analyst, whose final output is the doubled value.