Upload agent.ipynb
Browse files- unit2/langgraph/agent.ipynb +560 -0
unit2/langgraph/agent.ipynb
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "98f5e36a-da49-4ae2-8c74-b910a2f992fc",
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"source": [
|
| 8 |
+
"# Agent\n",
|
| 9 |
+
"\n",
|
| 10 |
+
"In this notebook, **we're going to build a simple agent using using LangGraph**.\n",
|
| 11 |
+
"\n",
|
| 12 |
+
"This notebook is part of the <a href=\"https://www.hf.co/learn/agents-course\">Hugging Face Agents Course</a>, a free course from beginner to expert, where you learn to build Agents.\n",
|
| 13 |
+
"\n",
|
| 14 |
+
"\n",
|
| 15 |
+
"\n",
|
| 16 |
+
"As seen in the Unit 1, an agent needs 3 steps as introduced in the ReAct architecture :\n",
|
| 17 |
+
"[ReAct](https://react-lm.github.io/), a general agent architecture.\n",
|
| 18 |
+
" \n",
|
| 19 |
+
"* `act` - let the model call specific tools \n",
|
| 20 |
+
"* `observe` - pass the tool output back to the model \n",
|
| 21 |
+
"* `reason` - let the model reason about the tool output to decide what to do next (e.g., call another tool or just respond directly)\n",
|
| 22 |
+
"\n",
|
| 23 |
+
"\n",
|
| 24 |
+
""
|
| 25 |
+
]
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"cell_type": "code",
|
| 29 |
+
"execution_count": 1,
|
| 30 |
+
"id": "63edff5a-724b-474d-9db8-37f0ae936c76",
|
| 31 |
+
"metadata": {
|
| 32 |
+
"tags": []
|
| 33 |
+
},
|
| 34 |
+
"outputs": [
|
| 35 |
+
{
|
| 36 |
+
"name": "stdout",
|
| 37 |
+
"output_type": "stream",
|
| 38 |
+
"text": [
|
| 39 |
+
"Note: you may need to restart the kernel to use updated packages.\n"
|
| 40 |
+
]
|
| 41 |
+
}
|
| 42 |
+
],
|
| 43 |
+
"source": [
|
| 44 |
+
"%pip install -q -U langchain_openai langchain_core langgraph"
|
| 45 |
+
]
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"cell_type": "code",
|
| 49 |
+
"execution_count": 2,
|
| 50 |
+
"id": "356a6482",
|
| 51 |
+
"metadata": {
|
| 52 |
+
"tags": []
|
| 53 |
+
},
|
| 54 |
+
"outputs": [],
|
| 55 |
+
"source": [
|
| 56 |
+
"import os\n",
|
| 57 |
+
"\n",
|
| 58 |
+
"# Please setp your own key.\n",
|
| 59 |
+
"os.environ[\"OPENAI_API_KEY\"]=\"sk-xxxxxx\""
|
| 60 |
+
]
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"cell_type": "code",
|
| 64 |
+
"execution_count": 65,
|
| 65 |
+
"id": "71795ff1-d6a7-448d-8b55-88bbd1ed3dbe",
|
| 66 |
+
"metadata": {
|
| 67 |
+
"tags": []
|
| 68 |
+
},
|
| 69 |
+
"outputs": [],
|
| 70 |
+
"source": [
|
| 71 |
+
"import base64\n",
|
| 72 |
+
"from typing import List\n",
|
| 73 |
+
"from langchain.schema import HumanMessage\n",
|
| 74 |
+
"from langchain_openai import ChatOpenAI\n",
|
| 75 |
+
"\n",
|
| 76 |
+
"\n",
|
| 77 |
+
"vision_llm = ChatOpenAI(model=\"gpt-4o\")\n",
|
| 78 |
+
"\n",
|
| 79 |
+
"def extract_text(img_path: str) -> str:\n",
|
| 80 |
+
" \"\"\"\n",
|
| 81 |
+
" Extract text from an image file using a multimodal model.\n",
|
| 82 |
+
"\n",
|
| 83 |
+
" Args:\n",
|
| 84 |
+
" img_path: A local image file path (strings).\n",
|
| 85 |
+
"\n",
|
| 86 |
+
" Returns:\n",
|
| 87 |
+
" A single string containing the concatenated text extracted from each image.\n",
|
| 88 |
+
" \"\"\"\n",
|
| 89 |
+
" all_text = \"\"\n",
|
| 90 |
+
" try:\n",
|
| 91 |
+
" \n",
|
| 92 |
+
" # Read image and encode as base64\n",
|
| 93 |
+
" with open(img_path, \"rb\") as image_file:\n",
|
| 94 |
+
" image_bytes = image_file.read()\n",
|
| 95 |
+
"\n",
|
| 96 |
+
" image_base64 = base64.b64encode(image_bytes).decode(\"utf-8\")\n",
|
| 97 |
+
"\n",
|
| 98 |
+
" # Prepare the prompt including the base64 image data\n",
|
| 99 |
+
" message = [\n",
|
| 100 |
+
" HumanMessage(\n",
|
| 101 |
+
" content=[\n",
|
| 102 |
+
" {\n",
|
| 103 |
+
" \"type\": \"text\",\n",
|
| 104 |
+
" \"text\": (\n",
|
| 105 |
+
" \"Extract all the text from this image. \"\n",
|
| 106 |
+
" \"Return only the extracted text, no explanations.\"\n",
|
| 107 |
+
" ),\n",
|
| 108 |
+
" },\n",
|
| 109 |
+
" {\n",
|
| 110 |
+
" \"type\": \"image_url\",\n",
|
| 111 |
+
" \"image_url\": {\n",
|
| 112 |
+
" \"url\": f\"data:image/png;base64,{image_base64}\"\n",
|
| 113 |
+
" },\n",
|
| 114 |
+
" },\n",
|
| 115 |
+
" ]\n",
|
| 116 |
+
" )\n",
|
| 117 |
+
" ]\n",
|
| 118 |
+
"\n",
|
| 119 |
+
" # Call the vision-capable model\n",
|
| 120 |
+
" response = vision_llm.invoke(message)\n",
|
| 121 |
+
"\n",
|
| 122 |
+
" # Append extracted text\n",
|
| 123 |
+
" all_text += response.content + \"\\n\\n\"\n",
|
| 124 |
+
"\n",
|
| 125 |
+
" return all_text.strip()\n",
|
| 126 |
+
" except Exception as e:\n",
|
| 127 |
+
" # You can choose whether to raise or just return an empty string / error message\n",
|
| 128 |
+
" error_msg = f\"Error extracting text: {str(e)}\"\n",
|
| 129 |
+
" print(error_msg)\n",
|
| 130 |
+
" return \"\"\n",
|
| 131 |
+
"\n",
|
| 132 |
+
"llm = ChatOpenAI(model=\"gpt-4o\")\n",
|
| 133 |
+
"\n",
|
| 134 |
+
"def divide(a: int, b: int) -> float:\n",
|
| 135 |
+
" \"\"\"Divide a and b.\"\"\"\n",
|
| 136 |
+
" return a / b\n",
|
| 137 |
+
"\n",
|
| 138 |
+
"tools = [\n",
|
| 139 |
+
" divide,\n",
|
| 140 |
+
" extract_text\n",
|
| 141 |
+
"]\n",
|
| 142 |
+
"llm_with_tools = llm.bind_tools(tools, parallel_tool_calls=False)"
|
| 143 |
+
]
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
"cell_type": "markdown",
|
| 147 |
+
"id": "a2cec014-3023-405c-be79-de8fc7adb346",
|
| 148 |
+
"metadata": {},
|
| 149 |
+
"source": [
|
| 150 |
+
"Let's create our LLM and prompt it with the overall desired agent behavior."
|
| 151 |
+
]
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
"cell_type": "code",
|
| 155 |
+
"execution_count": 66,
|
| 156 |
+
"id": "deb674bc-49b2-485a-b0c3-4d7b05a0bfac",
|
| 157 |
+
"metadata": {
|
| 158 |
+
"tags": []
|
| 159 |
+
},
|
| 160 |
+
"outputs": [],
|
| 161 |
+
"source": [
|
| 162 |
+
"from typing import TypedDict, Annotated, List, Any, Optional\n",
|
| 163 |
+
"from langchain_core.messages import AnyMessage\n",
|
| 164 |
+
"from langgraph.graph.message import add_messages\n",
|
| 165 |
+
"class AgentState(TypedDict):\n",
|
| 166 |
+
" # The input document\n",
|
| 167 |
+
" input_file: Optional[str] # Contains file path, type (PNG)\n",
|
| 168 |
+
" messages: Annotated[list[AnyMessage], add_messages]"
|
| 169 |
+
]
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
"cell_type": "code",
|
| 173 |
+
"execution_count": 76,
|
| 174 |
+
"id": "d061813f-ebc0-432c-91ec-3b42b15c30b6",
|
| 175 |
+
"metadata": {
|
| 176 |
+
"tags": []
|
| 177 |
+
},
|
| 178 |
+
"outputs": [],
|
| 179 |
+
"source": [
|
| 180 |
+
"from langchain_core.messages import HumanMessage, SystemMessage\n",
|
| 181 |
+
"from langchain_core.utils.function_calling import convert_to_openai_tool\n",
|
| 182 |
+
"\n",
|
| 183 |
+
"\n",
|
| 184 |
+
"# AgentState\n",
|
| 185 |
+
"def assistant(state: AgentState):\n",
|
| 186 |
+
" # System message\n",
|
| 187 |
+
" textual_description_of_tool=\"\"\"\n",
|
| 188 |
+
"extract_text(img_path: str) -> str:\n",
|
| 189 |
+
" Extract text from an image file using a multimodal model.\n",
|
| 190 |
+
"\n",
|
| 191 |
+
" Args:\n",
|
| 192 |
+
" img_path: A local image file path (strings).\n",
|
| 193 |
+
"\n",
|
| 194 |
+
" Returns:\n",
|
| 195 |
+
" A single string containing the concatenated text extracted from each image.\n",
|
| 196 |
+
"divide(a: int, b: int) -> float:\n",
|
| 197 |
+
" Divide a and b\n",
|
| 198 |
+
"\"\"\"\n",
|
| 199 |
+
" image=state[\"input_file\"]\n",
|
| 200 |
+
" sys_msg = SystemMessage(content=f\"You are an helpful agent that can analyse some images and run some computatio without provided tools :\\n{textual_description_of_tool} \\n You have access to some otpional images. Currently the loaded images is : {image}\")\n",
|
| 201 |
+
"\n",
|
| 202 |
+
"\n",
|
| 203 |
+
" return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])],\"input_file\":state[\"input_file\"]}"
|
| 204 |
+
]
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"cell_type": "markdown",
|
| 208 |
+
"id": "4eb43343-9a6f-42cb-86e6-4380f928633c",
|
| 209 |
+
"metadata": {},
|
| 210 |
+
"source": [
|
| 211 |
+
"We define a `Tools` node with our list of tools.\n",
|
| 212 |
+
"\n",
|
| 213 |
+
"The `Assistant` node is just our model with bound tools.\n",
|
| 214 |
+
"\n",
|
| 215 |
+
"We create a graph with `Assistant` and `Tools` nodes.\n",
|
| 216 |
+
"\n",
|
| 217 |
+
"We add `tools_condition` edge, which routes to `End` or to `Tools` based on whether the `Assistant` calls a tool.\n",
|
| 218 |
+
"\n",
|
| 219 |
+
"Now, we add one new step:\n",
|
| 220 |
+
"\n",
|
| 221 |
+
"We connect the `Tools` node *back* to the `Assistant`, forming a loop.\n",
|
| 222 |
+
"\n",
|
| 223 |
+
"* After the `assistant` node executes, `tools_condition` checks if the model's output is a tool call.\n",
|
| 224 |
+
"* If it is a tool call, the flow is directed to the `tools` node.\n",
|
| 225 |
+
"* The `tools` node connects back to `assistant`.\n",
|
| 226 |
+
"* This loop continues as long as the model decides to call tools.\n",
|
| 227 |
+
"* If the model response is not a tool call, the flow is directed to END, terminating the process."
|
| 228 |
+
]
|
| 229 |
+
},
|
| 230 |
+
{
|
| 231 |
+
"cell_type": "code",
|
| 232 |
+
"execution_count": 77,
|
| 233 |
+
"id": "aef13cd4-05a6-4084-a620-2e7b91d9a72f",
|
| 234 |
+
"metadata": {
|
| 235 |
+
"tags": []
|
| 236 |
+
},
|
| 237 |
+
"outputs": [
|
| 238 |
+
{
|
| 239 |
+
"data": {
|
| 240 |
+
"image/png": "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",
|
| 241 |
+
"text/plain": [
|
| 242 |
+
"<IPython.core.display.Image object>"
|
| 243 |
+
]
|
| 244 |
+
},
|
| 245 |
+
"metadata": {},
|
| 246 |
+
"output_type": "display_data"
|
| 247 |
+
}
|
| 248 |
+
],
|
| 249 |
+
"source": [
|
| 250 |
+
"from langgraph.graph import START, StateGraph\n",
|
| 251 |
+
"from langgraph.prebuilt import tools_condition\n",
|
| 252 |
+
"from langgraph.prebuilt import ToolNode\n",
|
| 253 |
+
"from IPython.display import Image, display\n",
|
| 254 |
+
"\n",
|
| 255 |
+
"# Graph\n",
|
| 256 |
+
"builder = StateGraph(AgentState)\n",
|
| 257 |
+
"\n",
|
| 258 |
+
"# Define nodes: these do the work\n",
|
| 259 |
+
"builder.add_node(\"assistant\", assistant)\n",
|
| 260 |
+
"builder.add_node(\"tools\", ToolNode(tools))\n",
|
| 261 |
+
"\n",
|
| 262 |
+
"# Define edges: these determine how the control flow moves\n",
|
| 263 |
+
"builder.add_edge(START, \"assistant\")\n",
|
| 264 |
+
"builder.add_conditional_edges(\n",
|
| 265 |
+
" \"assistant\",\n",
|
| 266 |
+
" # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n",
|
| 267 |
+
" # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n",
|
| 268 |
+
" tools_condition,\n",
|
| 269 |
+
")\n",
|
| 270 |
+
"builder.add_edge(\"tools\", \"assistant\")\n",
|
| 271 |
+
"react_graph = builder.compile()\n",
|
| 272 |
+
"\n",
|
| 273 |
+
"# Show\n",
|
| 274 |
+
"display(Image(react_graph.get_graph(xray=True).draw_mermaid_png()))"
|
| 275 |
+
]
|
| 276 |
+
},
|
| 277 |
+
{
|
| 278 |
+
"cell_type": "code",
|
| 279 |
+
"execution_count": 78,
|
| 280 |
+
"id": "75602459-d8ca-47b4-9518-3f38343ebfe4",
|
| 281 |
+
"metadata": {
|
| 282 |
+
"tags": []
|
| 283 |
+
},
|
| 284 |
+
"outputs": [],
|
| 285 |
+
"source": [
|
| 286 |
+
"messages = [HumanMessage(content=\"Divide 6790 by 5\")]\n",
|
| 287 |
+
"\n",
|
| 288 |
+
"messages = react_graph.invoke({\"messages\": messages,\"input_file\":None})"
|
| 289 |
+
]
|
| 290 |
+
},
|
| 291 |
+
{
|
| 292 |
+
"cell_type": "code",
|
| 293 |
+
"execution_count": 79,
|
| 294 |
+
"id": "b517142d-c40c-48bf-a5b8-c8409427aa79",
|
| 295 |
+
"metadata": {
|
| 296 |
+
"tags": []
|
| 297 |
+
},
|
| 298 |
+
"outputs": [
|
| 299 |
+
{
|
| 300 |
+
"name": "stdout",
|
| 301 |
+
"output_type": "stream",
|
| 302 |
+
"text": [
|
| 303 |
+
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
| 304 |
+
"\n",
|
| 305 |
+
"Divide 6790 by 5\n",
|
| 306 |
+
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
| 307 |
+
"Tool Calls:\n",
|
| 308 |
+
" divide (call_s0G5ewtIQyHUCOv0fClsCpgh)\n",
|
| 309 |
+
" Call ID: call_s0G5ewtIQyHUCOv0fClsCpgh\n",
|
| 310 |
+
" Args:\n",
|
| 311 |
+
" a: 6790\n",
|
| 312 |
+
" b: 5\n",
|
| 313 |
+
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
| 314 |
+
"Name: divide\n",
|
| 315 |
+
"\n",
|
| 316 |
+
"1358.0\n",
|
| 317 |
+
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
| 318 |
+
"\n",
|
| 319 |
+
"The result of dividing 6790 by 5 is 1358.0.\n"
|
| 320 |
+
]
|
| 321 |
+
}
|
| 322 |
+
],
|
| 323 |
+
"source": [
|
| 324 |
+
"for m in messages['messages']:\n",
|
| 325 |
+
" m.pretty_print()"
|
| 326 |
+
]
|
| 327 |
+
},
|
| 328 |
+
{
|
| 329 |
+
"cell_type": "markdown",
|
| 330 |
+
"id": "08386393-c270-43a5-bde2-2b4075238971",
|
| 331 |
+
"metadata": {},
|
| 332 |
+
"source": [
|
| 333 |
+
"## Training program\n",
|
| 334 |
+
"MR Wayne left a note with his training program for the week. I came up with a recipe for dinner leaft in a note.\n",
|
| 335 |
+
"\n",
|
| 336 |
+
"you can find the document [HERE](https://huggingface.co/datasets/agents-course/course-images/blob/main/en/unit2/LangGraph/Batman_training_and_meals.png), so download it and upload it in the local folder.\n",
|
| 337 |
+
"\n",
|
| 338 |
+
""
|
| 339 |
+
]
|
| 340 |
+
},
|
| 341 |
+
{
|
| 342 |
+
"cell_type": "code",
|
| 343 |
+
"execution_count": 82,
|
| 344 |
+
"id": "f6e97e84-3b05-4aaf-a38f-1de9b73cd37f",
|
| 345 |
+
"metadata": {
|
| 346 |
+
"tags": []
|
| 347 |
+
},
|
| 348 |
+
"outputs": [],
|
| 349 |
+
"source": [
|
| 350 |
+
"messages = [HumanMessage(content=\"According the note provided by MR wayne in the provided images. What's the list of items I should buy for the dinner menu ?\")]\n",
|
| 351 |
+
"\n",
|
| 352 |
+
"messages = react_graph.invoke({\"messages\": messages,\"input_file\":\"Batman_training_and_meals.png\"})"
|
| 353 |
+
]
|
| 354 |
+
},
|
| 355 |
+
{
|
| 356 |
+
"cell_type": "code",
|
| 357 |
+
"execution_count": 83,
|
| 358 |
+
"id": "17686d52-c7ba-407b-a13f-f6c37668e5b0",
|
| 359 |
+
"metadata": {
|
| 360 |
+
"tags": []
|
| 361 |
+
},
|
| 362 |
+
"outputs": [
|
| 363 |
+
{
|
| 364 |
+
"name": "stdout",
|
| 365 |
+
"output_type": "stream",
|
| 366 |
+
"text": [
|
| 367 |
+
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
| 368 |
+
"\n",
|
| 369 |
+
"According the note provided by MR wayne in the provided images. What's the list of tiems I should buy for the dinner menu ?\n",
|
| 370 |
+
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
| 371 |
+
"Tool Calls:\n",
|
| 372 |
+
" extract_text (call_JalVBOR82hwRknFcplnLoTtG)\n",
|
| 373 |
+
" Call ID: call_JalVBOR82hwRknFcplnLoTtG\n",
|
| 374 |
+
" Args:\n",
|
| 375 |
+
" img_path: Batman_training_and_meals.png\n",
|
| 376 |
+
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
|
| 377 |
+
"Name: extract_text\n",
|
| 378 |
+
"\n",
|
| 379 |
+
"TRAINING SCHEDULE\n",
|
| 380 |
+
"For the week of 2/20-2/26\n",
|
| 381 |
+
"\n",
|
| 382 |
+
"SUNDAY 2/20\n",
|
| 383 |
+
"MORNING\n",
|
| 384 |
+
"30 minute jog\n",
|
| 385 |
+
"30 minute meditation\n",
|
| 386 |
+
"\n",
|
| 387 |
+
"EVENING\n",
|
| 388 |
+
"clean and jerk lifts—3 reps/8 sets. 262 lbs.\n",
|
| 389 |
+
"5 sets metabolic conditioning:\n",
|
| 390 |
+
"10 mile run\n",
|
| 391 |
+
"12 kettlebell swings\n",
|
| 392 |
+
"12 pull-ups\n",
|
| 393 |
+
"30 minutes flexibility\n",
|
| 394 |
+
"30 minutes sparring\n",
|
| 395 |
+
"\n",
|
| 396 |
+
"MONDAY 2/21\n",
|
| 397 |
+
"MORNING\n",
|
| 398 |
+
"30 minute jog\n",
|
| 399 |
+
"30 minutes traditional kata (focus on Japanese forms)\n",
|
| 400 |
+
"\n",
|
| 401 |
+
"EVENING\n",
|
| 402 |
+
"5 sets 20 foot rope climb\n",
|
| 403 |
+
"30 minutes gymnastics (work on muscle ups in\n",
|
| 404 |
+
"particular)\n",
|
| 405 |
+
"high bar jumps—12 reps/8 sets\n",
|
| 406 |
+
"crunches—50 reps/5 sets\n",
|
| 407 |
+
"30 minutes heavy bag\n",
|
| 408 |
+
"30 minutes flexibility\n",
|
| 409 |
+
"20 minutes target practice\n",
|
| 410 |
+
"\n",
|
| 411 |
+
"TUESDAY 2/22\n",
|
| 412 |
+
"MORNING\n",
|
| 413 |
+
"30 minute jog\n",
|
| 414 |
+
"30 minutes yoga\n",
|
| 415 |
+
"\n",
|
| 416 |
+
"EVENING\n",
|
| 417 |
+
"off day\n",
|
| 418 |
+
"leg heavy dead lift—5 reps/7 sets. 600 lbs.\n",
|
| 419 |
+
"clean and jerk lift—3 reps/10 sets\n",
|
| 420 |
+
"30 minutes sparring\n",
|
| 421 |
+
"\n",
|
| 422 |
+
"WEDNESDAY 2/23\n",
|
| 423 |
+
"OFF DAY\n",
|
| 424 |
+
"\n",
|
| 425 |
+
"MORNING\n",
|
| 426 |
+
"20-mile run—last week’s time was 4:50 per mile.\n",
|
| 427 |
+
"Need to better that time by a half a minute.\n",
|
| 428 |
+
"\n",
|
| 429 |
+
"EVENING\n",
|
| 430 |
+
"skill training only\n",
|
| 431 |
+
"30 minutes yoga\n",
|
| 432 |
+
"30 minutes meditation\n",
|
| 433 |
+
"30 minutes body basics\n",
|
| 434 |
+
"30 minutes bow basics\n",
|
| 435 |
+
"30 minutes sword basics\n",
|
| 436 |
+
"30 minutes observational\n",
|
| 437 |
+
"exercise\n",
|
| 438 |
+
"30 minutes kata\n",
|
| 439 |
+
"30 minutes pressure points\n",
|
| 440 |
+
"30 minutes modus and pressure points\n",
|
| 441 |
+
"\n",
|
| 442 |
+
"THURSDAY 2/24\n",
|
| 443 |
+
"MORNING\n",
|
| 444 |
+
"30 minute jog\n",
|
| 445 |
+
"30 minute meditation\n",
|
| 446 |
+
"30 minutes traditional kata\n",
|
| 447 |
+
"(focus on Japanese forms)\n",
|
| 448 |
+
"\n",
|
| 449 |
+
"EVENING\n",
|
| 450 |
+
"squats—10 reps/5 sets. 525 lbs.\n",
|
| 451 |
+
"30 minutes flexibility\n",
|
| 452 |
+
"crunches—50 reps/5 sets\n",
|
| 453 |
+
"20 minutes target practice\n",
|
| 454 |
+
"30 minutes heavy bag\n",
|
| 455 |
+
"\n",
|
| 456 |
+
"FRIDAY 2/25\n",
|
| 457 |
+
"MORNING\n",
|
| 458 |
+
"30 minute jog\n",
|
| 459 |
+
"30 minute meditation\n",
|
| 460 |
+
"\n",
|
| 461 |
+
"EVENING\n",
|
| 462 |
+
"clean and jerk lifts—3 reps/8 sets. 262 lbs.\n",
|
| 463 |
+
"5 sets metabolic conditioning:\n",
|
| 464 |
+
"10 mile run\n",
|
| 465 |
+
"12 kettlebell swings\n",
|
| 466 |
+
"12 pull-ups\n",
|
| 467 |
+
"30 minutes flexibility\n",
|
| 468 |
+
"30 minutes sparring\n",
|
| 469 |
+
"\n",
|
| 470 |
+
"SATURDAY 2/26)\n",
|
| 471 |
+
"MORNING\n",
|
| 472 |
+
"30 minute jog\n",
|
| 473 |
+
"30 minutes yoga\n",
|
| 474 |
+
"\n",
|
| 475 |
+
"EVENING\n",
|
| 476 |
+
"crunches—50 reps/5 sets\n",
|
| 477 |
+
"squats—(5 reps/10 sets. 525 lbs.\n",
|
| 478 |
+
"push-ups—60 reps/sets\n",
|
| 479 |
+
"30 minutes monkey bars\n",
|
| 480 |
+
"30 minute pommel horse\n",
|
| 481 |
+
"30 minutes heavy bag\n",
|
| 482 |
+
"2 mile swim\n",
|
| 483 |
+
"\n",
|
| 484 |
+
"In an effort to inspire the all- important Dark Knight to take time out of his busy schedule and actually consume a reasonable amount of sustenance, I have taken the liberty of composing a menu for today's scheduled natal to its my hope that these elegantly prepared courses will not share the fate of their predecessors -mated cold and untouched on a computer console.\n",
|
| 485 |
+
"-A\n",
|
| 486 |
+
"\n",
|
| 487 |
+
"W A Y N E M A N O R\n",
|
| 488 |
+
"\n",
|
| 489 |
+
"Tuesday's Menu\n",
|
| 490 |
+
"\n",
|
| 491 |
+
"Breakfast\n",
|
| 492 |
+
"six poached eggs laid over artichoke bottoms with a sage pesto sauce\n",
|
| 493 |
+
"thinly sliced baked ham\n",
|
| 494 |
+
"mixed organic fresh fruit bowl\n",
|
| 495 |
+
"freshly squeezed orange juice\n",
|
| 496 |
+
"organic, grass-fed milk\n",
|
| 497 |
+
"4 grams branched-chain amino acid\n",
|
| 498 |
+
"2 grams fish oil\n",
|
| 499 |
+
"\n",
|
| 500 |
+
"Lunch\n",
|
| 501 |
+
"local salmon with a ginger glaze\n",
|
| 502 |
+
"organic asparagus with lemon garlic dusting\n",
|
| 503 |
+
"Asian yam soup with diced onions\n",
|
| 504 |
+
"2 grams fish oil\n",
|
| 505 |
+
"\n",
|
| 506 |
+
"Dinner\n",
|
| 507 |
+
"grass-fed local sirloin steak\n",
|
| 508 |
+
"bed of organic spinach and piquillo peppers\n",
|
| 509 |
+
"oven-baked golden herb potato\n",
|
| 510 |
+
"2 grams fish oil\n",
|
| 511 |
+
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
| 512 |
+
"\n",
|
| 513 |
+
"For the dinner menu, you should buy the following items:\n",
|
| 514 |
+
"\n",
|
| 515 |
+
"1. Grass-fed local sirloin steak\n",
|
| 516 |
+
"2. Organic spinach\n",
|
| 517 |
+
"3. Piquillo peppers\n",
|
| 518 |
+
"4. Potatoes (for oven-baked golden herb potato)\n",
|
| 519 |
+
"5. Fish oil (2 grams)\n",
|
| 520 |
+
"\n",
|
| 521 |
+
"Ensure the steak is grass-fed and the spinach and peppers are organic for the best quality meal.\n"
|
| 522 |
+
]
|
| 523 |
+
}
|
| 524 |
+
],
|
| 525 |
+
"source": [
|
| 526 |
+
"for m in messages['messages']:\n",
|
| 527 |
+
" m.pretty_print()"
|
| 528 |
+
]
|
| 529 |
+
},
|
| 530 |
+
{
|
| 531 |
+
"cell_type": "code",
|
| 532 |
+
"execution_count": null,
|
| 533 |
+
"id": "b96c8456-4093-4cd6-bc5a-f611967ab709",
|
| 534 |
+
"metadata": {},
|
| 535 |
+
"outputs": [],
|
| 536 |
+
"source": []
|
| 537 |
+
}
|
| 538 |
+
],
|
| 539 |
+
"metadata": {
|
| 540 |
+
"kernelspec": {
|
| 541 |
+
"display_name": "Python 3 (ipykernel)",
|
| 542 |
+
"language": "python",
|
| 543 |
+
"name": "python3"
|
| 544 |
+
},
|
| 545 |
+
"language_info": {
|
| 546 |
+
"codemirror_mode": {
|
| 547 |
+
"name": "ipython",
|
| 548 |
+
"version": 3
|
| 549 |
+
},
|
| 550 |
+
"file_extension": ".py",
|
| 551 |
+
"mimetype": "text/x-python",
|
| 552 |
+
"name": "python",
|
| 553 |
+
"nbconvert_exporter": "python",
|
| 554 |
+
"pygments_lexer": "ipython3",
|
| 555 |
+
"version": "3.9.5"
|
| 556 |
+
}
|
| 557 |
+
},
|
| 558 |
+
"nbformat": 4,
|
| 559 |
+
"nbformat_minor": 5
|
| 560 |
+
}
|