Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Lab 1 Exercise: two tools, one loop\n",
"\n",
"This notebook solves the exercise at the end of Lab 1.\n",
"\n",
"Steps:\n",
"\n",
"1. Keep the lab's `get_share_price` tool.\n",
"2. Add a fake `get_exchange_rate` tool.\n",
"3. Bind both tools to the model.\n",
"4. Ask for Amazon's share price in euros.\n",
"5. Run the tool loop by hand until the model gives a final answer.\n",
"\n",
"The answer needs a US dollar price and a USD-to-EUR rate, so both tools must run."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Imports\n",
"\n",
"Use the lab imports. `load_dotenv` finds the `.env` file in the repository root."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from dotenv import load_dotenv\n",
"from langchain_openai import ChatOpenAI\n",
"from langchain_core.messages import HumanMessage, ToolMessage\n",
"from langchain_core.tools import tool\n",
"\n",
"load_dotenv(override=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Model\n",
"\n",
"Use the same model as the lab."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"llm = ChatOpenAI(model=\"gpt-5.4-mini\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Share price tool\n",
"\n",
"The lab tool returns a fake price, or `0.0` for an unknown symbol."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"@tool\n",
"def get_share_price(symbol: str) -> float:\n",
" \"\"\"Return the current share price in US dollars for a given ticker symbol.\"\"\"\n",
" fake_prices = {\"AAPL\": 241.5, \"GOOG\": 168.2, \"AMZN\": 198.0}\n",
" return fake_prices.get(symbol.upper(), 0.0)\n",
"\n",
"print(\"name:\", get_share_price.name)\n",
"print(\"args:\", get_share_price.args)\n",
"print(\"called directly:\", get_share_price.invoke({\"symbol\": \"AMZN\"}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Exchange rate tool\n",
"\n",
"The new tool returns how many units of a currency equal one US dollar."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"@tool\n",
"def get_exchange_rate(currency: str) -> float:\n",
" \"\"\"Return how many units of the given currency equal 1 US dollar.\"\"\"\n",
" fake_rates = {\"EUR\": 0.92, \"GBP\": 0.79, \"JPY\": 156.0}\n",
" return fake_rates.get(currency.upper(), 1.0)\n",
"\n",
"print(\"name:\", get_exchange_rate.name)\n",
"print(\"args:\", get_exchange_rate.args)\n",
"print(\"called directly:\", get_exchange_rate.invoke({\"currency\": \"EUR\"}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Bind both tools to the model\n",
"\n",
"`bind_tools` makes both tools available to the model. The reply may contain requests in `.tool_calls` instead of a final answer."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"llm_with_tools = llm.bind_tools([get_share_price, get_exchange_rate])\n",
"\n",
"question = \"Use both tools to find Amazon's share price in euros.\"\n",
"response = llm_with_tools.invoke(question)\n",
"print(\"content:\", repr(response.content))\n",
"print(\"tool_calls:\", response.tool_calls)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Run the tool loop by hand\n",
"\n",
"The loop:\n",
"\n",
"1. Send the conversation to the model.\n",
"2. Add its reply to the conversation.\n",
"3. Stop if the reply has no tool calls.\n",
"4. Run each tool and add its result as a `ToolMessage`.\n",
"5. Repeat.\n",
"\n",
"The name map selects the right tool. The loop allows the model to request tools in one turn or over several turns."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"tools_by_name = {\n",
" \"get_share_price\": get_share_price,\n",
" \"get_exchange_rate\": get_exchange_rate,\n",
"}\n",
"\n",
"conversation = [HumanMessage(question)]\n",
"\n",
"while True:\n",
" ai_message = llm_with_tools.invoke(conversation)\n",
" conversation.append(ai_message)\n",
"\n",
" if not ai_message.tool_calls:\n",
" break\n",
"\n",
" for call in ai_message.tool_calls:\n",
" tool_to_run = tools_by_name[call[\"name\"]]\n",
" result = tool_to_run.invoke(call[\"args\"])\n",
" print(f\"Ran {call['name']} with {call['args']} and got {result}\")\n",
" conversation.append(ToolMessage(content=str(result), tool_call_id=call[\"id\"]))\n",
"\n",
"print(\"\\nFinal answer:\")\n",
"print(conversation[-1].content)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "agents",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading