Loading the chapter…
%23%20%2F%2F%2F%20script%0A%23%20requires-python%20%3D%20%22%3E%3D3.12%2C%3C3.15%22%0A%23%20dependencies%20%3D%20%5B%0A%23%20%20%20%20%20%22marimo%22%2C%0A%23%20%20%20%20%20%22requests%22%2C%0A%23%20%20%20%20%20%22yfinance%22%2C%0A%23%20%5D%0A%23%20%2F%2F%2F%0A%22%22%22Python%20Handbook%2C%20Chapter%20D%3A%20Agents%20and%20APIs.%0A%22%22%22%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.25.2%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22%2C%20sql_output%3D%22polars%22)%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20json%0A%20%20%20%20import%20random%0A%20%20%20%20import%20re%0A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20requests%0A%20%20%20%20import%20yfinance%20as%20yf%0A%0A%20%20%20%20return%20json%2C%20mo%2C%20random%2C%20re%2C%20requests%2C%20yf%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Agents%20and%20APIs%0A%0A%20%20%20%20Chapter%20D%20of%20the%20Python%20Handbook.%20Nine%20sections%2C%20each%20one%20concept.%20Read%20this%20chapter%0A%20%20%20%20and%20you%20can%20send%20a%20request%20to%20a%20model%2C%20read%20what%20comes%20back%2C%20give%20it%20a%20tool%2C%20run%20the%0A%20%20%20%20loop%2C%20and%20calculate%20what%20the%20whole%20thing%20cost.%0A%0A%20%20%20%20Everything%20in%20this%20chapter%20is%20code%20you%20already%20know.%20A%20request%20is%20a%20dictionary.%20A%0A%20%20%20%20reply%20is%20a%20dictionary.%20The%20conversation%20is%20a%20list%20of%20dictionaries.%20The%20loop%20is%20a%0A%20%20%20%20%60while%60%2C%20covered%20in%0A%20%20%20%20%5Bsection%2012%5D(%2Fhandbooks%2Fpython%2F02-collections%2F%2312-iterating-over-a-list-with-for).%0A%20%20%20%20There%20is%20no%20new%20syntax%20here%2C%20and%20the%20difficulty%20is%20entirely%20in%20knowing%20which%0A%20%20%20%20dictionary%20holds%20what.%0A%0A%20%20%20%20**What%20you%20need%20to%20run%20the%20model%20code.**%20A%20key%2C%20which%20Babson%20issues%20and%0A%20%20%20%20%5BYour%20AI%20Tools%20and%20Babson%20Key%5D(%2Fguides%2Fbabson-ai-key%2F)%20explains%2C%20and%20the%20Anthropic%20library%2C%0A%20%20%20%20which%20is%20one%20command%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20uv%20add%20anthropic%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20**Which%20code%20on%20this%20page%20runs%2C%20and%20which%20is%20quoted.**%20No%20cell%20here%20calls%20a%20model%2C%0A%20%20%20%20because%20a%20published%20page%20cannot%20hold%20a%20key.%20Every%20model%20call%20sits%20in%20a%20grey%20block%20for%0A%20%20%20%20you%20to%20copy.%20What%20the%20model%20would%20send%20back%20is%20on%20this%20page%20as%20the%20real%20JSON%20the%20API%0A%20%20%20%20returns%2C%20built%20in%20a%20real%20cell%2C%20and%20every%20cell%20that%20reads%20it%2C%20dispatches%20it%2C%20loops%20over%0A%20%20%20%20it%20or%20prices%20it%20is%20live.%20Section%2031%20and%20section%2039%20run%20end%20to%20end%20with%20nothing%0A%20%20%20%20installed%20but%20this%20notebook.%0A%0A%20%20%20%20To%20run%20the%20code%20yourself%2C%20download%20the%20%60.py%60%20into%20your%20project%20and%20open%20it%20with%0A%20%20%20%20%60uv%20run%20marimo%20edit%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2031.%20HTTP%20requests%0A%0A%20%20%20%20An%20API%20is%20a%20web%20address%20that%20answers%20with%20data%20instead%20of%20a%20page.%20You%20send%20a%20request%2C%0A%20%20%20%20a%20server%20sends%20back%20a%20response%2C%20and%20your%20job%20is%20to%20read%20the%20response%20before%20you%20assume%0A%20%20%20%20anything%20about%20it.%20Every%20model%20call%20later%20in%20this%20chapter%20is%20one%20of%20these%20underneath.%0A%0A%20%20%20%20The%20first%20requests%20below%20go%20to%20Kraken%2C%20a%20cryptocurrency%20exchange%20whose%20public%20price%0A%20%20%20%20data%20needs%20no%20key.%20Each%20one%20is%20wrapped%20in%20a%20%60try%60%20so%20the%20page%20still%20renders%20when%20there%0A%20%20%20%20is%20no%20network%2C%20in%20which%20case%20it%20falls%20back%20to%20a%20body%20recorded%20on%202026-09-15%20and%20says%20so.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20Getting%20data%20from%20an%20API%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Requests%20and%20responses%0A%0A%20%20%20%20%60requests.get(url)%60%20sends%20the%20request%20and%20hands%20you%20a%20response%20object.%20Three%20things%20on%0A%20%20%20%20it%20matter%3A%20the%20**status%20code**%2C%20the%20**headers**%2C%20and%20the%20**body**.%0A%0A%20%20%20%20%60timeout%3D10%60%20says%20how%20long%20to%20wait%20before%20giving%20up.%20Leave%20it%20out%20and%20a%20server%20that%0A%20%20%20%20never%20answers%20hangs%20your%20notebook%20until%20you%20kill%20it.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(requests)%3A%0A%20%20%20%20TICKER_URL%20%3D%20%22https%3A%2F%2Fapi.kraken.com%2F0%2Fpublic%2FTicker%3Fpair%3DXBTUSD%22%0A%20%20%20%20TICKER_SAVED%20%3D%20'%7B%22error%22%3A%5B%5D%2C%22result%22%3A%7B%22XXBTZUSD%22%3A%7B%22a%22%3A%5B%2276386.40000%22%2C%221%22%2C%221.000%22%5D%2C%22b%22%3A%5B%2276386.30000%22%2C%223%22%2C%223.000%22%5D%2C%22c%22%3A%5B%2276386.40000%22%2C%220.00049007%22%5D%2C%22v%22%3A%5B%222309.89255277%22%2C%223625.12550720%22%5D%2C%22p%22%3A%5B%2276741.04546%22%2C%2277539.45124%22%5D%2C%22t%22%3A%5B78754%2C118300%5D%2C%22l%22%3A%5B%2275561.00000%22%2C%2275561.00000%22%5D%2C%22h%22%3A%5B%2278242.10000%22%2C%2279581.20000%22%5D%2C%22o%22%3A%2278184.20000%22%7D%7D%7D'%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20ticker_reply%20%3D%20requests.get(TICKER_URL%2C%20timeout%3D10)%0A%20%20%20%20%20%20%20%20ticker_status%20%3D%20ticker_reply.status_code%0A%20%20%20%20%20%20%20%20ticker_type%20%3D%20ticker_reply.headers%5B%22content-type%22%5D%0A%20%20%20%20%20%20%20%20ticker_body%20%3D%20ticker_reply.text%0A%20%20%20%20%20%20%20%20ticker_source%20%3D%20%22live%20request%22%0A%20%20%20%20except%20requests.RequestException%3A%0A%20%20%20%20%20%20%20%20ticker_status%20%3D%20200%0A%20%20%20%20%20%20%20%20ticker_type%20%3D%20%22application%2Fjson%22%0A%20%20%20%20%20%20%20%20ticker_body%20%3D%20TICKER_SAVED%0A%20%20%20%20%20%20%20%20ticker_source%20%3D%20%22saved%20copy%2C%20no%20network%22%0A%0A%20%20%20%20f%22%7Bticker_source%7D%20%7C%20status%20%7Bticker_status%7D%20%7C%20%7Bticker_type%7D%20%7C%20%7Blen(ticker_body)%7D%20characters%22%0A%20%20%20%20return%20(ticker_body%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20**200**%20means%20the%20server%20understood%20and%20answered.%20The%20content%20type%20says%20what%20the%20body%0A%20%20%20%20is.%20The%20body%20itself%20is%20text%2C%20however%20much%20it%20looks%20like%20a%20Python%20dictionary.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20print%20%60ticker_body%5B%3A80%5D%60.%20Then%20check%20%60type(ticker_body)%60%20and%20explain%20why%20the%0A%20%20%20%20next%20knowledge%20point%20exists.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60.json()%60%20turns%20the%20body%20into%20Python%0A%0A%20%20%20%20The%20body%20arrives%20as%20one%20long%20string.%20%60json.loads()%60%20parses%20it%20into%20dictionaries%20and%0A%20%20%20%20lists.%20The%20%60requests%60%20library%20wraps%20this%20as%20%60response.json()%60%2C%20which%20is%20the%20form%20you%0A%20%20%20%20will%20see%20everywhere.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(json%2C%20ticker_body)%3A%0A%20%20%20%20ticker%20%3D%20json.loads(ticker_body)%0A%0A%20%20%20%20type(ticker)%2C%20list(ticker.keys())%0A%20%20%20%20return%20(ticker%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20ticker)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20The%20reply%20carries%20**%7Blen(ticker)%7D**%20keys%2C%20and%20Kraken%20sends%20%60error%60%20on%20every%20answer%3B%0A%20%20%20%20%60result%60%20comes%20only%20when%20the%20request%20worked.%0A%20%20%20%20%60error%60%20is%20a%20list%2C%20and%20here%20it%20holds%20**%7Blen(ticker%5B'error'%5D)%7D**%20items.%20%60result%60%20holds%0A%20%20%20%20the%20prices.%20A%20reply%20is%20good%20only%20when%20%60error%60%20is%20empty%20and%20%60result%60%20is%20there.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20put%20%60ticker%5B%22result%22%5D%60%20in%20a%20cell%20of%20its%20own%20and%20read%20what%20comes%20back%0A%20%20%20%20before%20you%20go%20any%20deeper.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Inspect%20before%20you%20index%0A%0A%20%20%20%20You%20asked%20for%20%60XBTUSD%60.%20Look%20at%20the%20name%20the%20answer%20is%20filed%20under.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ticker)%3A%0A%20%20%20%20result_keys%20%3D%20list(ticker%5B%22result%22%5D.keys())%0A%0A%20%20%20%20result_keys%0A%20%20%20%20return%20(result_keys%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20result_keys)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20The%20answer%20is%20filed%20under%20**%7Bresult_keys%5B0%5D%7D**.%20You%20sent%20%60XBTUSD%60%2C%20and%20Kraken%20renamed%0A%20%20%20%20the%20pair%20into%20its%20own%20internal%20code%20on%20the%20way%20back.%0A%0A%20%20%20%20**The%20trap.**%20%60ticker%5B%22result%22%5D%5B%22XBTUSD%22%5D%60%20raises%20a%20%60KeyError%60%2C%20although%20the%20request%0A%20%20%20%20worked%20and%20%60XBTUSD%60%20is%20exactly%20what%20you%20typed.%20Nothing%20in%20your%20code%20tells%20you%20what%20the%0A%20%20%20%20key%20is%20called.%20The%20response%20does%2C%20and%20%60list(reply.keys())%60%20costs%20one%20line.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20change%20the%20request%20to%20%60pair%3DXBTUSD%2CETHUSD%60%20and%20predict%20both%20keys%20of%0A%20%20%20%20%60result%60%20before%20you%20list%20them.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20status%20code%20of%20200%20is%20not%20an%20answer%0A%0A%20%20%20%20Misspell%20the%20pair%20and%20send%20the%20request%20again.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(requests)%3A%0A%20%20%20%20WRONG_PAIR_URL%20%3D%20%22https%3A%2F%2Fapi.kraken.com%2F0%2Fpublic%2FTicker%3Fpair%3DNOPE%22%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20wrong_pair_reply%20%3D%20requests.get(WRONG_PAIR_URL%2C%20timeout%3D10)%0A%20%20%20%20%20%20%20%20wrong_pair_status%20%3D%20wrong_pair_reply.status_code%0A%20%20%20%20%20%20%20%20wrong_pair_body%20%3D%20wrong_pair_reply.text%0A%20%20%20%20%20%20%20%20wrong_pair_source%20%3D%20%22live%20request%22%0A%20%20%20%20except%20requests.RequestException%3A%0A%20%20%20%20%20%20%20%20wrong_pair_status%20%3D%20200%0A%20%20%20%20%20%20%20%20wrong_pair_body%20%3D%20'%7B%22error%22%3A%5B%22EQuery%3AUnknown%20asset%20pair%22%5D%7D'%0A%20%20%20%20%20%20%20%20wrong_pair_source%20%3D%20%22saved%20copy%2C%20no%20network%22%0A%0A%20%20%20%20wrong_pair_source%2C%20wrong_pair_status%2C%20wrong_pair_body%0A%20%20%20%20return%20wrong_pair_body%2C%20wrong_pair_status%0A%0A%0A%40app.cell%0Adef%20_(json%2C%20wrong_pair_body)%3A%0A%20%20%20%20wrong_pair%20%3D%20json.loads(wrong_pair_body)%0A%0A%20%20%20%20list(wrong_pair.keys())%2C%20wrong_pair%5B%22error%22%5D%0A%20%20%20%20return%20(wrong_pair%2C)%0A%0A%0A%40app.cell%0Adef%20_(requests)%3A%0A%20%20%20%20WRONG_PATH_URL%20%3D%20%22https%3A%2F%2Fapi.kraken.com%2F0%2Fpublic%2FTickers%3Fpair%3DXBTUSD%22%0A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20wrong_path_status%20%3D%20requests.get(WRONG_PATH_URL%2C%20timeout%3D10).status_code%0A%20%20%20%20%20%20%20%20wrong_path_source%20%3D%20%22live%20request%22%0A%20%20%20%20except%20requests.RequestException%3A%0A%20%20%20%20%20%20%20%20wrong_path_status%20%3D%20404%0A%20%20%20%20%20%20%20%20wrong_path_source%20%3D%20%22saved%20copy%2C%20no%20network%22%0A%0A%20%20%20%20wrong_path_source%2C%20wrong_path_status%0A%20%20%20%20return%20(wrong_path_status%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20wrong_pair%2C%20wrong_pair_status%2C%20wrong_path_status)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20The%20status%20is%20**%7Bwrong_pair_status%7D**.%20The%20body%20parses%20cleanly%20into%20a%20dictionary%20with%0A%20%20%20%20**%7Blen(wrong_pair)%7D**%20key%2C%20%60error%60%2C%20and%20the%20message%20inside%20it%20is%0A%20%20%20%20%60%7Bwrong_pair%5B'error'%5D%5B0%5D%7D%60.%20There%20is%20no%20%60result%60%2C%20so%20the%20next%20line%20falls%20over%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20wrong_pair%5B%22result%22%5D%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20Traceback%20(most%20recent%20call%20last)%3A%0A%20%20%20%20%20%20File%20%22ticker.py%22%2C%20line%204%2C%20in%20%3Cmodule%3E%0A%20%20%20%20%20%20%20%20wrong_pair%5B%22result%22%5D%0A%20%20%20%20%20%20%20%20~~~~~~~~~~%5E%5E%5E%5E%5E%5E%5E%5E%5E%5E%0A%20%20%20%20KeyError%3A%20'result'%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20Misspell%20the%20method%20instead%2C%20%60Tickers%60%20for%20%60Ticker%60%2C%20and%20the%20status%20is%0A%20%20%20%20**%7Bwrong_path_status%7D**.%20This%20server%20uses%20the%20status%20code%20for%20a%20wrong%20address%20and%20the%0A%20%20%20%20%60error%60%20list%20for%20a%20wrong%20question%2C%20and%20code%20that%20checks%20only%20one%20of%20them%20misses%20the%0A%20%20%20%20other.%0A%0A%20%20%20%20**The%20trap.**%20A%20%60KeyError%60%20is%20not%20a%20parsing%20failure.%20The%20JSON%20parsed.%20What%20was%20wrong%20was%0A%20%20%20%20the%20assumption%20about%20what%20was%20in%20it.%20Last%20year's%20course%20materials%20caught%20this%20exception%0A%20%20%20%20and%20printed%20*JSON%20parsing%20error*%2C%20which%20sent%20everybody%20who%20read%20it%20looking%20in%20the%20wrong%0A%20%20%20%20place.%20Check%20the%20status%20code%2C%20then%20check%20%60error%60%2C%20and%20only%20then%20index.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20write%20the%20lines%20that%20check%20%60status_code%20%3D%3D%20200%60%20and%20an%20empty%20%60error%60%0A%20%20%20%20before%20reading%20%60result%60%2C%20and%20print%20something%20useful%20when%20either%20check%20fails.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20price%20can%20arrive%20as%20text%0A%0A%20%20%20%20Look%20at%20what%20a%20price%20is%20here%2C%20and%20at%20what%20a%20count%20of%20trades%20is.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(result_keys%2C%20ticker)%3A%0A%20%20%20%20last_trade%20%3D%20ticker%5B%22result%22%5D%5Bresult_keys%5B0%5D%5D%5B%22c%22%5D%0A%20%20%20%20trade_counts%20%3D%20ticker%5B%22result%22%5D%5Bresult_keys%5B0%5D%5D%5B%22t%22%5D%0A%0A%20%20%20%20last_trade%2C%20trade_counts%0A%20%20%20%20return%20last_trade%2C%20trade_counts%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(last_trade%2C%20mo%2C%20trade_counts)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20%60c%60%20is%20Kraken's%20name%20for%20the%20last%20trade%2C%20and%20it%20holds%20**%7Blen(last_trade)%7D**%20values%3A%20the%0A%20%20%20%20price%2C%20then%20the%20volume.%20The%20price%20is%20%60%7Blast_trade%5B0%5D%7D%60%2C%20and%20its%20type%20is%0A%20%20%20%20%60%7Btype(last_trade%5B0%5D).__name__%7D%60.%20In%20the%20same%20reply%20%60t%60%2C%20the%20number%20of%20trades%20today%20and%0A%20%20%20%20over%20the%20last%2024%20hours%2C%20holds%20%60%7Btrade_counts%7D%60%2C%20and%20those%20are%0A%20%20%20%20%60%7Btype(trade_counts%5B0%5D).__name__%7D%60.%20The%20server%20picked%20a%20type%20for%20each%20field%2C%20and%20it%20did%0A%20%20%20%20not%20pick%20the%20same%20one.%0A%0A%20%20%20%20Chapter%20A%20section%202%20is%20where%20this%20bites.%20%60last_trade%5B0%5D%20*%202%60%20repeats%20the%20text%20instead%0A%20%20%20%20of%20doubling%20the%20price%2C%20and%20%60last_trade%5B0%5D%20%2B%201%60%20raises%20a%20%60TypeError%60.%20Check%20the%20type%20of%20a%0A%20%20%20%20value%20before%20you%20do%20arithmetic%20with%20it%2C%20whatever%20it%20looks%20like.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20convert%20the%20price%20to%20a%20float%20and%20work%20out%20what%200.1%20bitcoin%20cost%20at%20that%0A%20%20%20%20price.%20Then%20find%20out%20what%20%60a%60%20and%20%60b%60%20hold%20in%20the%20same%20reply.%0A%0A%20%20%20%20%23%23%23%20In%20Excel%20terms%0A%0A%20%20%20%20Power%20Query's%20**Get%20Data%20from%20Web**%20opens%20a%20JSON%20response%2C%20flattens%20it%20into%20columns%20and%0A%20%20%20%20guesses%20a%20type%20for%20each%20one.%20Python%20does%20none%20of%20that.%20You%20get%20nested%20dictionaries%20and%0A%20%20%20%20lists%20in%20the%20shape%20the%20server%20chose%2C%20and%20every%20value%20keeps%20the%20type%20the%20server%20sent%2C%0A%20%20%20%20which%20for%20a%20lot%20of%20APIs%20means%20prices%20as%20text.%20The%20Excel%20habit%20that%20misleads%20here%20is%0A%20%20%20%20expecting%20the%20shape%20to%20arrive%20as%20a%20table.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Parameters%20and%20headers%0A%0A%20%20%20%20**Query%20parameters**%20are%20the%20part%20after%20the%20%60%3F%60.%20Passing%20%60params%3D%60%20lets%20%60requests%60%0A%20%20%20%20build%20them%2C%20which%20handles%20spaces%20and%20punctuation%20for%20you.%0A%0A%20%20%20%20**Headers**%20carry%20information%20about%20the%20request%20instead%20of%20information%20in%20it.%20This%20is%0A%20%20%20%20where%20a%20key%20belongs%2C%20and%20section%2032%20is%20about%20why.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(requests)%3A%0A%20%20%20%20prepared%20%3D%20requests.Request(%0A%20%20%20%20%20%20%20%20%22GET%22%2C%0A%20%20%20%20%20%20%20%20%22https%3A%2F%2Fapi.kraken.com%2F0%2Fpublic%2FTicker%22%2C%0A%20%20%20%20%20%20%20%20params%3D%7B%22pair%22%3A%20%22XBTUSD%2CETHUSD%22%2C%20%22note%22%3A%20%22oim%207510%20handbook%22%7D%2C%0A%20%20%20%20%20%20%20%20headers%3D%7B%22User-Agent%22%3A%20%22oim7510-handbook%22%7D%2C%0A%20%20%20%20).prepare()%0A%0A%20%20%20%20prepared.url%0A%20%20%20%20return%20(prepared%2C)%0A%0A%0A%40app.cell%0Adef%20_(prepared)%3A%0A%20%20%20%20dict(prepared.headers)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20prepared)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20The%20URL%20came%20out%20as%20%60%7Bprepared.url%7D%60.%20The%20comma%20between%20the%20two%20pairs%20became%20%60%252C%60%2C%0A%20%20%20%20the%20spaces%20in%20the%20note%20became%20%60%2B%60%2C%20and%20%60requests%60%20joined%20the%20parameters%20with%20%60%26%60.%0A%20%20%20%20Nothing%20was%20sent%20here.%20%60.prepare()%60%20builds%20the%20request%20so%20you%20can%20look%20at%20it%2C%20which%20is%0A%20%20%20%20a%20useful%20habit%20when%20a%20URL%20is%20not%20doing%20what%20you%20expect.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20a%20parameter%20whose%20value%20contains%20a%20%60%26%60%20and%20read%20what%20%60requests%60%20did%0A%20%20%20%20to%20it.%20Then%20work%20out%20what%20would%20have%20happened%20if%20you%20had%20built%20the%20URL%20by%20pasting%0A%20%20%20%20strings%20together.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20library%20can%20make%20the%20request%20for%20you%0A%0A%20%20%20%20Many%20services%20come%20with%20a%20Python%20library%20that%20sends%20these%20requests%20and%20hands%20back%0A%20%20%20%20something%20more%20convenient.%20%60yfinance%60%20does%20it%20for%20Yahoo%20Finance's%20stock%20prices%20and%20gives%0A%20%20%20%20you%20a%20DataFrame.%20It%20is%20an%20open-source%20project%20with%20no%20connection%20to%20Yahoo%20itself%2C%20and%0A%20%20%20%20the%20one%20thing%20it%20does%20not%20hand%20you%20is%20the%20status%20code.%0A%0A%20%20%20%20The%20cell%20below%20asks%20for%20five%20days%20of%20Apple's%20prices%2C%20and%20then%20for%20a%20ticker%20that%20does%20not%0A%20%20%20%20exist.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(yf)%3A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20apple_history%20%3D%20yf.Ticker(%22AAPL%22).history(period%3D%225d%22)%0A%20%20%20%20%20%20%20%20missing_history%20%3D%20yf.Ticker(%22NOTATICKERXYZ%22).history(period%3D%225d%22)%0A%20%20%20%20%20%20%20%20apple_rows%20%3D%20len(apple_history)%0A%20%20%20%20%20%20%20%20missing_rows%20%3D%20len(missing_history)%0A%20%20%20%20%20%20%20%20history_source%20%3D%20%22live%20request%22%0A%20%20%20%20except%20Exception%3A%0A%20%20%20%20%20%20%20%20apple_rows%20%3D%200%0A%20%20%20%20%20%20%20%20missing_rows%20%3D%200%0A%20%20%20%20%20%20%20%20history_source%20%3D%20%22no%20network%22%0A%0A%20%20%20%20if%20apple_rows%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20apple_rows%20%3D%205%0A%20%20%20%20%20%20%20%20missing_rows%20%3D%200%0A%20%20%20%20%20%20%20%20history_source%20%3D%20%22saved%20counts%20from%202026-09-15%2C%20Yahoo%20did%20not%20answer%22%0A%0A%20%20%20%20f%22%7Bhistory_source%7D%20%7C%20AAPL%20%7Bapple_rows%7D%20rows%20%7C%20NOTATICKERXYZ%20%7Bmissing_rows%7D%20rows%22%0A%20%20%20%20return%20apple_rows%2C%20missing_rows%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(apple_rows%2C%20missing_rows%2C%20mo)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20Apple%20came%20back%20with%20**%7Bapple_rows%7D**%20rows%20and%20the%20ticker%20that%20does%20not%20exist%20with%0A%20%20%20%20**%7Bmissing_rows%7D**.%20Underneath%2C%20Yahoo%20answered%20that%20second%20request%20with%20HTTP%20404.%0A%20%20%20%20%60yfinance%60%20printed%20a%20message%20about%20it%2C%20returned%20an%20empty%20DataFrame%20and%20raised%20nothing%2C%0A%20%20%20%20so%20your%20code%20carries%20on%20holding%20no%20data.%0A%0A%20%20%20%20**The%20trap.**%20The%20failure%20turns%20up%20later%20and%20somewhere%20else.%20Ask%20the%20empty%20frame%20for%20its%0A%20%20%20%20latest%20closing%20price%20and%20you%20get%20this%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20missing_history%5B%22Close%22%5D.iloc%5B-1%5D%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20IndexError%3A%20single%20positional%20indexer%20is%20out-of-bounds%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20Nothing%20in%20that%20message%20mentions%20a%20ticker.%20A%20library%20that%20hides%20the%20status%20code%20leaves%0A%20%20%20%20the%20checking%20to%20you%2C%20so%20look%20at%20%60history.empty%60%20before%20you%20index.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20write%20a%20function%20that%20takes%20a%20ticker%2C%20fetches%20five%20days%20and%20returns%20the%0A%20%20%20%20latest%20close%2C%20or%20returns%20%60None%60%20and%20prints%20a%20warning%20when%20the%20frame%20is%20empty.%20Run%20it%20on%0A%20%20%20%20a%20real%20ticker%20and%20on%20a%20misspelled%20one.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%E2%9C%94%20**Check%20your%20understanding**%0A%0A%20%20%20%201.%20A%20request%20returns%20status%20200%20and%20%60.json()%60%20works%2C%20and%20the%20answer%20is%20still%20missing.%0A%20%20%20%20%20%20%20How%20can%20that%20happen%2C%20and%20where%20in%20the%20reply%20do%20you%20look%3F%0A%20%20%20%202.%20You%20have%20a%20response%20from%20an%20API%20you%20have%20never%20seen%20before.%20Name%20the%20two%20lines%20you%0A%20%20%20%20%20%20%20write%20before%20you%20index%20into%20it.%0A%20%20%20%203.%20A%20price%20in%20the%20response%20is%20%60%2276386.40000%22%60.%20What%20does%20%60price%20*%202%60%20give%20you%2C%20and%20why%0A%20%20%20%20%20%20%20does%20that%20not%20raise%20anything%3F%0A%20%20%20%204.%20%60yfinance%60%20hands%20you%20an%20empty%20DataFrame%20for%20a%20ticker.%20What%20happened%20at%20the%20HTTP%0A%20%20%20%20%20%20%20level%2C%20and%20why%20did%20your%20code%20not%20notice%3F%0A%20%20%20%205.%20Where%20does%20an%20API%20key%20go%20on%20a%20request%2C%20and%20where%20does%20it%20not%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2032.%20API%20keys%20and%20environment%20variables%0A%0A%20%20%20%20A%20key%20is%20a%20password%20that%20identifies%20you%20to%20a%20service.%20Yours%20arrives%20by%20email%20from%0A%20%20%20%20Babson.%20You%20do%20not%20create%20this%20key%20and%20it%20is%20not%20yours%20in%20the%20sense%20a%20password%20is%2C%20and%0A%20%20%20%20every%20request%20made%20with%20it%20is%20billed%20to%20Babson%20and%20attributed%20to%20you.%0A%0A%20%20%20%20So%20it%20must%20never%20enter%20your%20repository.%20Your%20repositories%20in%20this%20course%20are%0A%20%20%20%20published%2C%20so%20a%20committed%20key%20is%20a%20key%20that%20strangers%20can%20spend%2C%20and%20deleting%20it%20in%20a%0A%20%20%20%20later%20commit%20does%20not%20remove%20it%20from%20the%20history.%0A%0A%20%20%20%20**This%20section%20is%20about%20code%20you%20write.**%20The%20key%20you%20pasted%20into%20marimo's%20settings%0A%20%20%20%20screen%20is%20used%20by%20marimo's%20own%20chat%20panel%2C%20and%20code%20in%20a%20cell%20cannot%20see%20it.%20When%20your%0A%20%20%20%20own%20code%20has%20to%20make%20the%20call%2C%20the%20key%20reaches%20it%20through%20the%20environment%2C%20and%20a%20%60.env%60%0A%20%20%20%20file%20is%20what%20puts%20it%20there.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20How%20an%20LLM%20and%20an%20agent%20actually%20work%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20environment%20is%20a%20set%20of%20names%20your%20program%20can%20read%0A%0A%20%20%20%20Every%20program%20starts%20with%20a%20set%20of%20name%20and%20value%20pairs%20handed%20to%20it%20by%20the%20operating%0A%20%20%20%20system.%20%60os.environ%60%20is%20that%20set%2C%20and%20%60os.getenv%60%20reads%20one%20name%20out%20of%20it.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20os%0A%0A%20%20%20%20key_present%20%3D%20os.getenv(%22ANTHROPIC_API_KEY%22)%20is%20not%20None%0A%0A%20%20%20%20f%22ANTHROPIC_API_KEY%20is%20%7B'set'%20if%20key_present%20else%20'not%20set'%7D%20in%20this%20environment%22%0A%20%20%20%20return%20(os%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Note%20what%20that%20cell%20does%20not%20do.%20It%20never%20prints%20the%20value%2C%20and%20neither%20should%20any%20cell%0A%20%20%20%20you%20write.%20A%20key%20printed%20into%20a%20notebook%20output%20is%20a%20key%20committed%20to%20your%20repository%2C%0A%20%20%20%20because%20the%20output%20is%20saved%20in%20the%20file.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20print%20%60len(os.environ)%60%20and%20then%20%60sorted(os.environ)%5B%3A10%5D%60.%20Your%20machine%0A%20%20%20%20already%20carries%20dozens%20of%20these%20and%20none%20of%20them%20came%20from%20you.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20files%20that%20hold%20a%20key%0A%0A%20%20%20%20Each%20of%20the%20three%20does%20one%20job%2C%20and%20none%20of%20them%20works%20without%20the%20other%20two.%0A%0A%20%20%20%20**%60.env%60**%20sits%20in%20your%20project%20folder%20and%20holds%20the%20real%20value.%20One%20name%20per%20line%2C%20no%0A%20%20%20%20quotes%2C%20no%20spaces%20around%20the%20%60%3D%60%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20ANTHROPIC_API_KEY%3Dthe-value-from-your-email%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20**%60.gitignore%60**%20tells%20git%20to%20pretend%20%60.env%60%20is%20not%20there%2C%20with%20the%20reason%20written%0A%20%20%20%20where%20the%20next%20person%20will%20read%20it%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20%23%20Environment%20variables%20(contains%20API%20keys)%0A%20%20%20%20.env%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20**Write%20%60.gitignore%60%20first.**%20It%20only%20affects%20files%20git%20is%20not%20tracking%20yet%2C%20so%20a%20%60.env%60%0A%20%20%20%20committed%20even%20once%20stays%20tracked%2C%20and%20its%20value%20stays%20in%20the%20history%20whatever%0A%20%20%20%20%60.gitignore%60%20says%20afterwards.%0A%0A%20%20%20%20**%60.env.example%60**%20is%20committed%2C%20and%20it%20is%20the%20same%20file%20with%20the%20values%20removed.%20It%0A%20%20%20%20tells%20somebody%20who%20clones%20your%20work%20which%20names%20they%20have%20to%20supply%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20%23%20Copy%20this%20file%20to%20.env%20and%20fill%20in%20your%20own%20values.%0A%20%20%20%20ANTHROPIC_API_KEY%3D%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20Setting%20up%20a%20clone%20is%20then%20one%20command%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20cp%20.env.example%20.env%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20create%20all%20three%20files%20in%20a%20scratch%20folder%2C%20put%20a%20made-up%20value%20in%0A%20%20%20%20%60.env%60%2C%20run%20%60git%20status%60%2C%20and%20confirm%20that%20git%20offers%20you%20%60.env.example%60%20and%20says%0A%20%20%20%20nothing%20about%20%60.env%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60load_dotenv()%60%20copies%20the%20file%20into%20the%20environment%0A%0A%20%20%20%20A%20%60.env%60%20file%20does%20nothing%20on%20its%20own.%20The%20%60python-dotenv%60%20library%20reads%20it%20and%20puts%0A%20%20%20%20each%20name%20into%20%60os.environ%60%2C%20after%20which%20%60os.getenv%60%20finds%20it.%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20from%20dotenv%20import%20load_dotenv%0A%20%20%20%20import%20os%0A%0A%20%20%20%20load_dotenv()%0A%20%20%20%20api_key%20%3D%20os.getenv(%22ANTHROPIC_API_KEY%22)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20Two%20lines%20of%20setup%2C%20and%20one%20line%20that%20gets%20the%20value.%20The%20Anthropic%20library%20reads%0A%20%20%20%20%60ANTHROPIC_API_KEY%60%20by%20itself%2C%20so%20with%20those%20two%20lines%20run%20you%20can%20construct%20a%20client%0A%20%20%20%20with%20no%20arguments%20at%20all%20and%20never%20touch%20the%20value%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20import%20anthropic%0A%0A%20%20%20%20client%20%3D%20anthropic.Anthropic()%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20**The%20trap.**%20%60load_dotenv()%60%20returns%20%60False%60%20when%20it%20found%20no%20file%2C%20and%20it%20does%20not%0A%20%20%20%20raise.%20So%20a%20missing%20%60.env%60%20gives%20you%20a%20program%20that%20runs%2C%20reads%20%60None%60%20for%20the%20key%2C%20and%0A%20%20%20%20fails%20much%20later%20with%20an%20authentication%20error%20that%20says%20nothing%20about%20a%20missing%20file.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20%60uv%20add%20python-dotenv%60%2C%20then%20print%20what%20%60load_dotenv()%60%20returns%20in%20a%0A%20%20%20%20folder%20with%20a%20%60.env%60%20and%20in%20a%20folder%20without%20one.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Fail%20loudly%20when%20the%20key%20is%20missing%0A%0A%20%20%20%20The%20fix%20for%20the%20trap%20above%20is%20four%20lines%2C%20and%20they%20belong%20before%20anything%20else%20runs.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(os)%3A%0A%20%20%20%20def%20key_status()%3A%0A%20%20%20%20%20%20%20%20%22%22%22Report%20whether%20the%20key%20is%20available%2C%20without%20ever%20showing%20it.%22%22%22%0A%20%20%20%20%20%20%20%20value%20%3D%20os.getenv(%22ANTHROPIC_API_KEY%22)%0A%20%20%20%20%20%20%20%20if%20value%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%22ANTHROPIC_API_KEY%20is%20missing.%20Check%20that%20.env%20exists%20and%20load_dotenv()%20ran.%22%0A%20%20%20%20%20%20%20%20if%20not%20value.strip()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%22ANTHROPIC_API_KEY%20is%20set%20but%20empty.%20Check%20for%20a%20blank%20value%20in%20.env.%22%0A%20%20%20%20%20%20%20%20return%20f%22ANTHROPIC_API_KEY%20is%20set%2C%20%7Blen(value)%7D%20characters.%22%0A%0A%20%20%20%20key_status()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20In%20real%20code%20the%20missing%20branch%20raises%20instead%20of%20returning%20a%20string%2C%20so%20that%20nothing%0A%20%20%20%20downstream%20runs%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20if%20not%20os.getenv(%22ANTHROPIC_API_KEY%22)%3A%0A%20%20%20%20%20%20%20%20raise%20RuntimeError(%22ANTHROPIC_API_KEY%20is%20missing.%20Copy%20.env.example%20to%20.env.%22)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20Reporting%20the%20length%20is%20safe%20and%20useful%2C%20because%20a%20key%20pasted%20with%20a%20trailing%20newline%0A%20%20%20%20or%20half%20a%20key%20pasted%20from%20a%20wrapped%20email%20both%20show%20up%20as%20a%20wrong%20number.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20call%20%60key_status()%60%20on%20your%20own%20machine%20before%20and%20after%20you%20create%0A%20%20%20%20%60.env%60.%20Then%20paste%20a%20value%20with%20a%20space%20at%20the%20end%20and%20see%20whether%20the%20length%20tells%20you.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Where%20a%20key%20must%20never%20go%0A%0A%20%20%20%20**Not%20in%20%60pyproject.toml%60.**%20That%20file%20is%20part%20of%20your%20repository%20and%20it%20gets%0A%20%20%20%20published.%20If%20an%20agent%20suggests%20putting%20a%20key%20there%2C%20decline.%20This%20is%20the%20one%20an%20agent%0A%20%20%20%20gets%20wrong%20often%20enough%20that%20%5BYour%20AI%20Tools%20and%20Babson%20Key%5D(%2Fguides%2Fbabson-ai-key%2F)%20warns%20about%20it%20separately.%0A%0A%20%20%20%20**Not%20in%20a%20notebook%20cell.**%20%60client%20%3D%20anthropic.Anthropic(api_key%3D%22sk-...%22)%60%20runs%0A%20%20%20%20perfectly%20and%20commits%20the%20key%20with%20the%20notebook.%0A%0A%20%20%20%20**Not%20in%20a%20URL.**%20Last%20year's%20weather%20example%20put%20its%20key%20in%20a%20query%20string%2C%20because%0A%20%20%20%20that%20is%20what%20that%20particular%20service%20wanted.%20A%20model%20API%20wants%20it%20in%20a%20header%2C%20and%20the%0A%20%20%20%20library%20puts%20it%20there%20for%20you.%20Query%20strings%20turn%20up%20in%20server%20logs%20and%20browser%0A%20%20%20%20history%3B%20headers%20do%20not.%0A%0A%20%20%20%20**Not%20in%20a%20printed%20output.**%20Including%20in%20an%20error%20message%20you%20paste%20into%20a%20chat%20when%0A%20%20%20%20asking%20for%20help.%0A%0A%20%20%20%20**When%20a%20key%20does%20leak%2C%20the%20fix%20is%20a%20new%20key.**%20Rewriting%20git%20history%20is%20slow%2C%0A%20%20%20%20incomplete%2C%20and%20beside%20the%20point%20once%20the%20value%20has%20been%20public%20for%20any%20length%20of%20time.%0A%20%20%20%20Mail%20ai%40babson.edu%2C%20get%20a%20replacement%2C%20and%20put%20the%20old%20one%20out%20of%20use.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20open%20the%20last%20repository%20you%20pushed%20and%20run%20%60git%20log%20-p%20%7C%20grep%20-i%20%22api_key%22%60.%0A%20%20%20%20Do%20this%20before%20you%20need%20to.%0A%0A%20%20%20%20%E2%9C%94%20**Check%20your%20understanding**%0A%0A%20%20%20%201.%20You%20committed%20a%20key%2C%20noticed%20an%20hour%20later%2C%20and%20deleted%20the%20line%20in%20a%20new%20commit.%0A%20%20%20%20%20%20%20What%20is%20still%20true%3F%0A%20%20%20%202.%20%60.env%60%20is%20gitignored%20and%20%60.env.example%60%20is%20committed.%20What%20does%20each%20one%20contain%2C%0A%20%20%20%20%20%20%20and%20why%20is%20that%20the%20way%20round%20it%20is%3F%0A%20%20%20%203.%20Your%20code%20runs%20and%20gets%20an%20authentication%20error.%20Name%20two%20causes%20that%20have%20nothing%0A%20%20%20%20%20%20%20to%20do%20with%20the%20key%20being%20wrong.%0A%20%20%20%204.%20Why%20does%20a%20key%20belong%20in%20a%20header%20instead%20of%20in%20the%20URL%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2033.%20What%20a%20request%20to%20a%20model%20contains%0A%0A%20%20%20%20A%20request%20to%20a%20model%20is%20a%20dictionary%20with%20four%20things%20in%20it%2C%20and%20once%20you%20have%20seen%0A%20%20%20%20them%20the%20rest%20of%20this%20chapter%20is%20variations%20on%20the%20same%20shape.%20There%20is%20no%20session%2C%20no%0A%20%20%20%20connection%2C%20and%20nothing%20on%20the%20far%20side%20that%20remembers%20you.%20You%20send%20everything%2C%20every%0A%20%20%20%20time.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20How%20an%20LLM%20and%20an%20agent%20actually%20work%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20parts%20of%20a%20request%20to%20a%20model%0A%0A%20%20%20%20%60model%60%20says%20which%20model.%20%60max_tokens%60%20caps%20how%20long%20the%20reply%20may%20be%2C%20and%20it%20is%0A%20%20%20%20**required**%3A%20leave%20it%20out%20and%20the%20request%20is%20rejected.%20%60system%60%20sets%20standing%0A%20%20%20%20instructions.%20%60messages%60%20is%20the%20conversation.%0A%0A%20%20%20%20Here%20is%20the%20request%20as%20a%20plain%20dictionary%2C%20so%20you%20can%20read%20it%20before%20you%20send%20one.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20request_body%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22model%22%3A%20%22MODEL%22%2C%0A%20%20%20%20%20%20%20%20%22max_tokens%22%3A%201024%2C%0A%20%20%20%20%20%20%20%20%22system%22%3A%20%22You%20are%20helping%20an%20MBA%20student%20check%20a%20freight%20calculation.%20Be%20brief.%22%2C%0A%20%20%20%20%20%20%20%20%22messages%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20%22Order%2010248%20had%20freight%20of%2016.75.%20Is%20that%20high%3F%22%7D%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20sorted(request_body)%0A%20%20%20%20return%20(request_body%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20request_body)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20Four%20keys%2C%20and%20%60messages%60%20holds%20**%7Blen(request_body%5B%22messages%22%5D)%7D**%20entry%20so%20far.%20The%0A%20%20%20%20library%20sends%20this%20to%20the%20server%20as%20JSON%2C%20so%20what%20goes%20over%20the%20wire%20is%20the%20dictionary%0A%20%20%20%20you%20just%20read.%0A%0A%20%20%20%20%60%22MODEL%22%60%20is%20a%20placeholder.%20The%20real%20model%20id%20comes%20from%20the%20email%20that%20issued%20your%0A%20%20%20%20key%2C%20and%20it%20goes%20in%20a%20name%20at%20the%20top%20of%20your%20file%20so%20that%20changing%20it%20is%20one%20edit%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20MODEL%20%3D%20%22the%20model%20id%20from%20your%20email%22%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20build%20the%20same%20dictionary%20with%20your%20own%20question%20in%20it%2C%20then%20run%0A%20%20%20%20%60json.dumps(request_body%2C%20indent%3D2)%60%20and%20read%20what%20the%20server%20will%20actually%20receive.%0A%0A%20%20%20%20%F0%9F%91%89%20**Then%20send%20it**%2C%20with%20the%20key%20from%20your%20Babson%20email%2C%20and%20read%20the%20reply's%20own%0A%20%20%20%20shape%20rather%20than%20this%20page's%20description%20of%20it.%20Your%20base%20URL%20carries%20no%20%60%2Fv1%60%2C%20so%0A%20%20%20%20add%20%60%2Fv1%2Fmessages%60%20to%20it.%20%60-i%60%20prints%20the%20status%20line%3B%20%60-s%60%20hides%20the%20errors%20worth%0A%20%20%20%20seeing.%0A%0A%20%20%20%20%60%60%60bash%0A%20%20%20%20curl%20-i%20%22%24BASE%2Fv1%2Fmessages%22%20-H%20%22x-api-key%3A%20%24KEY%22%20-H%20%22anthropic-version%3A%202023-06-01%22%20-H%20%22content-type%3A%20application%2Fjson%22%20-d%20'%7B%7B%22model%22%3A%22claude-sonnet-5%22%2C%22max_tokens%22%3A16%2C%22system%22%3A%22Answer%20in%20one%20word.%22%2C%22messages%22%3A%5B%7B%7B%22role%22%3A%22user%22%2C%22content%22%3A%22hi%22%7D%7D%5D%7D%7D'%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20Three%20things%20to%20look%20for%2C%20because%20they%20are%20what%20this%20section%20claims%3A%20%60system%60%20went%20in%0A%20%20%20%20at%20the%20top%20level%20and%20not%20inside%20%60messages%60%2C%20%60content%60%20came%20back%20as%20a%20**list**%20of%0A%20%20%20%20blocks%20rather%20than%20a%20string%2C%20and%20there%20is%20a%20%60usage%60%20object%20counting%20both%20directions.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60system%60%20is%20a%20top-level%20parameter%0A%0A%20%20%20%20In%20the%20Anthropic%20API%2C%20%60system%60%20sits%20beside%20%60model%60%20and%20%60max_tokens%60.%20It%20is%20not%20an%20entry%0A%20%20%20%20in%20%60messages%60.%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20response%20%3D%20client.messages.create(%0A%20%20%20%20%20%20%20%20model%3DMODEL%2C%0A%20%20%20%20%20%20%20%20max_tokens%3D1024%2C%0A%20%20%20%20%20%20%20%20system%3D%22You%20are%20helping%20an%20MBA%20student%20check%20a%20freight%20calculation.%20Be%20brief.%22%2C%0A%20%20%20%20%20%20%20%20messages%3D%5B%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20%22Order%2010248%20had%20freight%20of%2016.75.%20Is%20that%20high%3F%22%7D%5D%2C%0A%20%20%20%20)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20**The%20trap%2C%20and%20it%20is%20the%20one%20an%20agent%20will%20hand%20you.**%20Most%20example%20code%20on%20the%0A%20%20%20%20internet%20was%20written%20for%20a%20different%20provider%2C%20where%20the%20system%20prompt%20is%20the%20first%0A%20%20%20%20entry%20in%20%60messages%60%20with%20%60%22role%22%3A%20%22system%22%60.%20Ported%20across%20without%20changing%20it%2C%20the%0A%20%20%20%20request%20is%20still%20valid%20and%20still%20runs.%20The%20instruction%20is%20now%20a%20user%20turn%2C%20the%20model%0A%20%20%20%20treats%20it%20as%20something%20you%20wrote%2C%20the%20standing%20rule%20you%20meant%20it%20to%20be%20is%20gone%2C%20and%0A%20%20%20%20nothing%20warns%20you.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20ask%20your%20agent%20to%20write%20you%20a%20Claude%20API%20call.%20Look%20at%20where%20it%20put%20the%0A%20%20%20%20system%20prompt%20before%20you%20look%20at%20anything%20else.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20shape%20of%20%60messages%60%0A%0A%20%20%20%20Every%20entry%20has%20a%20%60role%60%20and%20a%20%60content%60.%20The%20role%20is%20%60user%60%20for%20your%20turns%20and%0A%20%20%20%20%60assistant%60%20for%20the%20model's%2C%20and%20those%20are%20the%20literal%20strings%20the%20API%20uses.%0A%0A%20%20%20%20The%20first%20entry%20has%20to%20be%20a%20%60user%60%20turn.%20After%20that%20they%20alternate.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20three_turns%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20%22Order%2010248%20had%20freight%20of%2016.75.%20Is%20that%20high%3F%22%7D%2C%0A%20%20%20%20%20%20%20%20%7B%22role%22%3A%20%22assistant%22%2C%20%22content%22%3A%20%22It%20depends%20what%20you%20compare%20it%20with.%22%7D%2C%0A%20%20%20%20%20%20%20%20%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20%22Compare%20it%20with%20the%20other%20four%20orders%20that%20week.%22%7D%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20%5Bentry%5B%22role%22%5D%20for%20entry%20in%20three_turns%5D%0A%20%20%20%20return%20(three_turns%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20three_turns)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20The%20list%20holds%20three%20entries%2C%20with%20roles%0A%20%20%20%20**%7B%22%2C%20%22.join(e%5B%22role%22%5D%20for%20e%20in%20three_turns)%7D**.%20That%20list%20is%20the%20whole%20conversation%2C%20it%0A%20%20%20%20lives%20in%20your%20program%2C%20and%20section%2034%20is%20about%20what%20happens%20when%20you%20forget%20to%20add%20to%0A%20%20%20%20it.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20build%20a%20list%20where%20two%20%60user%60%20turns%20sit%20next%20to%20each%20other.%20It%20is%20legal%3B%0A%20%20%20%20the%20API%20joins%20them%20into%20one%20turn.%20Then%20build%20one%20that%20starts%20with%20an%20%60assistant%60%20turn%0A%20%20%20%20and%20find%20out%20what%20the%20library%20says.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20What%20comes%20back%20is%20a%20list%20of%20blocks%0A%0A%20%20%20%20The%20reply%20is%20not%20a%20string.%20%60response.content%60%20is%20a%20**list**%2C%20and%20each%20entry%20has%20a%0A%20%20%20%20%60type%60.%20Today%20it%20is%20usually%20one%20text%20block%3B%20with%20a%20tool%20in%20play%20there%20are%20two%2C%20and%0A%20%20%20%20section%2036%20is%20where%20that%20matters.%0A%0A%20%20%20%20Below%20is%20the%20JSON%20the%20API%20returns%2C%20built%20as%20a%20dictionary%20so%20you%20can%20read%20it%20with%20code%0A%20%20%20%20you%20already%20have.%20The%20library%20hands%20you%20objects%20with%20the%20same%20field%20names%2C%20so%0A%20%20%20%20%60reply%5B%22stop_reason%22%5D%60%20here%20is%20%60response.stop_reason%60%20there.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20reply%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22id%22%3A%20%22msg_example%22%2C%0A%20%20%20%20%20%20%20%20%22type%22%3A%20%22message%22%2C%0A%20%20%20%20%20%20%20%20%22role%22%3A%20%22assistant%22%2C%0A%20%20%20%20%20%20%20%20%22content%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22text%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22text%22%3A%20%22Freight%20of%2032.38%20is%20the%20highest%20of%20the%20five%20orders%20placed%20that%20week.%20The%20others%20range%20from%203.05%20to%2051.30%2C%20so%20it%20is%20high%20but%20not%20an%20outlier.%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%22stop_reason%22%3A%20%22end_turn%22%2C%0A%20%20%20%20%20%20%20%20%22stop_details%22%3A%20None%2C%0A%20%20%20%20%20%20%20%20%22usage%22%3A%20%7B%22input_tokens%22%3A%201284%2C%20%22output_tokens%22%3A%20412%7D%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20sorted(reply)%0A%20%20%20%20return%20(reply%2C)%0A%0A%0A%40app.cell%0Adef%20_(reply)%3A%0A%20%20%20%20def%20first_text(message)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Return%20the%20text%20of%20the%20first%20text%20block%2C%20or%20None%20when%20there%20is%20not%20one.%22%22%22%0A%20%20%20%20%20%20%20%20for%20block%20in%20message%5B%22content%22%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20block%5B%22type%22%5D%20%3D%3D%20%22text%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20block%5B%22text%22%5D%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20first_text(reply)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20reply)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20The%20reply%20carries%20**%7Blen(reply%5B%22content%22%5D)%7D**%20block%2C%20of%20type%0A%20%20%20%20%60%7Breply%5B%22content%22%5D%5B0%5D%5B%22type%22%5D%7D%60%2C%20and%20it%20stopped%20because%20of%0A%20%20%20%20%60%7Breply%5B%22stop_reason%22%5D%7D%60.%20%60usage%60%20says%20the%20request%20cost%0A%20%20%20%20**%7Breply%5B%22usage%22%5D%5B%22input_tokens%22%5D%7D**%20input%20tokens%20and%0A%20%20%20%20**%7Breply%5B%22usage%22%5D%5B%22output_tokens%22%5D%7D**%20output%20tokens%2C%20which%20section%2038%20turns%20into%20money.%0A%0A%20%20%20%20**The%20trap.**%20%60response.content%5B0%5D.text%60%20works%20most%20of%20the%20time%20and%20is%20wrong.%20It%0A%20%20%20%20assumes%20there%20is%20at%20least%20one%20block%20and%20that%20the%20first%20one%20is%20text%2C%20and%20both%20of%20those%0A%20%20%20%20stop%20being%20true%20the%20moment%20you%20add%20a%20tool.%20Loop%20over%20the%20blocks%20and%20check%20the%20type%2C%20the%0A%20%20%20%20way%20%60first_text%60%20does.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20a%20second%20block%20of%20type%20%60tool_use%60%20to%20the%20front%20of%20%60reply%5B%22content%22%5D%60%0A%20%20%20%20and%20run%20%60first_text%60%20again.%20Then%20run%20%60reply%5B%22content%22%5D%5B0%5D%5B%22text%22%5D%60%20and%20read%20the%20error.%0A%0A%20%20%20%20%E2%9C%94%20**Check%20your%20understanding**%0A%0A%20%20%20%201.%20Name%20the%20four%20parts%20of%20a%20request%20and%20state%20which%20one%20is%20required%20and%20easy%20to%20forget.%0A%20%20%20%202.%20Somebody%20hands%20you%20code%20with%20%60%7B%7B%22role%22%3A%20%22system%22%2C%20...%7D%7D%60%20as%20the%20first%20entry%20in%0A%20%20%20%20%20%20%20%60messages%60.%20What%20happens%20when%20you%20run%20it%20against%20the%20Anthropic%20API%3F%0A%20%20%20%203.%20%60response.content%60%20is%20a%20list.%20Give%20a%20case%20where%20it%20has%20two%20entries.%0A%20%20%20%204.%20Where%20does%20%60stop_reason%60%20live%2C%20and%20what%20is%20it%20for%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2034.%20The%20API%20is%20stateless%0A%0A%20%20%20%20**The%20API%20is%20stateless.**%20Nothing%20on%20the%20server%20keeps%20track%20of%20your%20conversation%0A%20%20%20%20between%20one%20request%20and%20the%20next.%0A%0A%20%20%20%20So%20when%20a%20model%20appears%20to%20remember%20what%20you%20wrote%20three%20turns%20ago%2C%20it%20is%20because%20your%0A%20%20%20%20program%20sent%20all%20three%20turns%20again.%20The%20memory%20is%20a%20Python%20list%2C%20it%20lives%0A%20%20%20%20on%20your%20machine%2C%20and%20you%20are%20responsible%20for%20it.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20How%20an%20LLM%20and%20an%20agent%20actually%20work%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20loop%20that%20works%0A%0A%20%20%20%20Here%20is%20the%20whole%20shape.%20Line%20by%20line%3A%20read%20a%20question%2C%20add%20it%20to%20the%20list%2C%20send%20**the%0A%20%20%20%20entire%20list**%2C%20add%20the%20reply%20to%20the%20list%2C%20print%20it.%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20messages%20%3D%20%5B%5D%0A%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20user_input%20%3D%20input(%22You%3A%20%22)%0A%20%20%20%20%20%20%20%20if%20user_input.lower()%20in%20%5B%22quit%22%2C%20%22exit%22%2C%20%22bye%22%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20%20%20%20%20messages.append(%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20user_input%7D)%0A%0A%20%20%20%20%20%20%20%20response%20%3D%20client.messages.create(%0A%20%20%20%20%20%20%20%20%20%20%20%20model%3DMODEL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_tokens%3D1024%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20system%3D%22You%20are%20a%20Python%20expert.%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20messages%3Dmessages%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20answer%20%3D%20next(b.text%20for%20b%20in%20response.content%20if%20b.type%20%3D%3D%20%22text%22)%0A%20%20%20%20%20%20%20%20messages.append(%7B%22role%22%3A%20%22assistant%22%2C%20%22content%22%3A%20answer%7D)%20%20%20%20%23%20%3C-%20this%20line%0A%0A%20%20%20%20%20%20%20%20print(f%22Model%3A%20%7Banswer%7D%22)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20The%20marked%20line%20is%20the%20one%20that%20matters.%20Delete%20it%20and%20the%20program%20runs%2C%20prints%2C%20never%0A%20%20%20%20raises%2C%20never%20warns%2C%20and%20the%20model%20forgets%20everything%20after%20every%20turn.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20copy%20this%20into%20a%20file%2C%20run%20it%20with%20your%20key%2C%20and%20ask%20it%20two%20questions%0A%20%20%20%20where%20the%20second%20one%20depends%20on%20the%20first.%20Then%20delete%20the%20marked%20line%20and%20ask%20the%20same%0A%20%20%20%20two%20questions.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20conversations%20side%20by%20side%0A%0A%20%20%20%20A%20model%20call%20needs%20a%20key%2C%20so%20the%20cells%20below%20use%20a%20stand-in%20that%20gives%20back%20a%20fixed%0A%20%20%20%20reply%20and%20reports%20how%20many%20turns%20it%20was%20handed.%20That%20is%20enough%20to%20show%20the%20difference%2C%0A%20%20%20%20because%20the%20difference%20is%20entirely%20on%20your%20side%20of%20the%20wire.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20def%20stand_in_reply(sent)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Stand%20in%20for%20a%20model%20call.%20Report%20how%20many%20turns%20the%20request%20carried.%22%22%22%0A%20%20%20%20%20%20%20%20return%20f%22(a%20reply%2C%20written%20after%20reading%20%7Blen(sent)%7D%20turns)%22%0A%0A%20%20%20%20questions%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%22My%20name%20is%20Dana%20and%20I%20work%20in%20logistics.%22%2C%0A%20%20%20%20%20%20%20%20%22Which%20order%20that%20week%20had%20the%20highest%20freight%3F%22%2C%0A%20%20%20%20%20%20%20%20%22What%20was%20my%20name%20again%3F%22%2C%0A%20%20%20%20%5D%0A%20%20%20%20return%20questions%2C%20stand_in_reply%0A%0A%0A%40app.cell%0Adef%20_(questions%2C%20stand_in_reply)%3A%0A%20%20%20%20messages%20%3D%20%5B%5D%0A%20%20%20%20for%20question%20in%20questions%3A%0A%20%20%20%20%20%20%20%20messages.append(%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20question%7D)%0A%20%20%20%20%20%20%20%20answer%20%3D%20stand_in_reply(messages)%0A%20%20%20%20%20%20%20%20messages.append(%7B%22role%22%3A%20%22assistant%22%2C%20%22content%22%3A%20answer%7D)%0A%0A%20%20%20%20len(messages)%0A%20%20%20%20return%20(messages%2C)%0A%0A%0A%40app.cell%0Adef%20_(questions%2C%20stand_in_reply)%3A%0A%20%20%20%20forgetful%20%3D%20%5B%5D%0A%20%20%20%20for%20forgetful_question%20in%20questions%3A%0A%20%20%20%20%20%20%20%20forgetful.append(%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20forgetful_question%7D)%0A%20%20%20%20%20%20%20%20stand_in_reply(forgetful)%0A%20%20%20%20%20%20%20%20%23%20the%20reply%20is%20never%20appended%0A%0A%20%20%20%20len(forgetful)%0A%20%20%20%20return%20(forgetful%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(forgetful%2C%20messages%2C%20mo)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20After%20three%20questions%20the%20working%20version%20holds%20**%7Blen(messages)%7D**%20entries%20and%20the%0A%20%20%20%20broken%20one%20holds%20**%7Blen(forgetful)%7D**.%20The%20last%20request%20in%20the%20working%20version%20carried%0A%20%20%20%20**%7Blen(messages)%20-%201%7D**%20turns%3B%20in%20the%20broken%20one%20it%20carried%20**%7Blen(forgetful)%7D**%2C%20all%20of%0A%20%20%20%20them%20questions%2C%20with%20no%20sign%20that%20anything%20was%20ever%20answered.%0A%0A%20%20%20%20Neither%20program%20raised%20anything.%20Neither%20printed%20a%20warning.%20The%20only%20visible%20difference%0A%20%20%20%20is%20that%20the%20third%20answer%20is%20wrong%2C%20and%20only%20if%20you%20happened%20to%20ask%20a%20question%20whose%0A%20%20%20%20answer%20depended%20on%20the%20first%20two.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20print%20both%20lists.%20Then%20work%20out%20what%20the%20model%20saw%20on%20the%20third%20request%0A%20%20%20%20in%20the%20broken%20version%2C%20and%20what%20it%20would%20have%20to%20guess.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Sending%20everything%20every%20time%20has%20a%20price%0A%0A%20%20%20%20Each%20turn%20resends%20every%20turn%20before%20it.%20So%20the%20tenth%20request%20is%20not%20the%20same%20size%20as%0A%20%20%20%20the%20first.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(messages)%3A%0A%20%20%20%20def%20characters_sent(conversation)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Total%20characters%20sent%2C%20if%20every%20turn%20resends%20the%20whole%20conversation.%22%22%22%0A%20%20%20%20%20%20%20%20running%20%3D%200%0A%20%20%20%20%20%20%20%20for%20turn%20in%20range(1%2C%20len(conversation)%20%2B%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20running%20%2B%3D%20sum(len(entry%5B%22content%22%5D)%20for%20entry%20in%20conversation%5B%3Aturn%5D)%0A%20%20%20%20%20%20%20%20return%20running%0A%0A%20%20%20%20characters_sent(messages)%2C%20sum(len(e%5B%22content%22%5D)%20for%20e%20in%20messages)%0A%20%20%20%20return%20(characters_sent%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(characters_sent%2C%20messages%2C%20mo)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20The%20conversation%20is%20**%7Bsum(len(e%5B%22content%22%5D)%20for%20e%20in%20messages)%7D**%20characters%20long%2C%20and%0A%20%20%20%20holding%20it%20for%20six%20turns%20meant%20sending%20**%7Bcharacters_sent(messages)%7D**%20characters%2C%20which%0A%20%20%20%20is%20**%7Bcharacters_sent(messages)%20%2F%20sum(len(e%5B%22content%22%5D)%20for%20e%20in%20messages)%3A.1f%7D**%20times%0A%20%20%20%20as%20many.%20That%20ratio%20grows%20with%20the%20length%20of%20the%20conversation%2C%20and%20section%2038%20puts%20a%0A%20%20%20%20price%20on%20it.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20%60characters_sent%60%20on%20a%20list%20of%2020%20turns%20and%20compare%20it%20with%20the%0A%20%20%20%20length%20of%20the%20conversation.%20Then%20work%20out%20roughly%20what%20the%20ratio%20would%20be%20at%2050%20turns.%0A%0A%20%20%20%20%E2%9C%94%20**Check%20your%20understanding**%0A%0A%20%20%20%201.%20The%20model%20answered%20a%20question%20that%20depended%20on%20something%20you%20wrote%20two%20turns%20ago.%0A%20%20%20%20%20%20%20Where%20was%20that%20information%20stored%3F%0A%20%20%20%202.%20You%20delete%20the%20line%20that%20appends%20the%20reply.%20Name%20everything%20that%20goes%20wrong%2C%20and%0A%20%20%20%20%20%20%20everything%20that%20reports%20it.%0A%20%20%20%203.%20Why%20does%20turn%2010%20of%20a%20conversation%20cost%20more%20than%20turn%201%2C%20given%20that%20you%20typed%20the%0A%20%20%20%20%20%20%20same%20amount%3F%0A%20%20%20%204.%20Two%20people%20run%20your%20chat%20program%20at%20once.%20How%20many%20conversations%20does%20the%20server%0A%20%20%20%20%20%20%20keep%20track%20of%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2035.%20The%20same%20prompt%20twice%0A%0A%20%20%20%20Send%20a%20model%20the%20same%20request%20twice%20and%20you%20can%20get%20two%20different%20answers.%20Two%0A%20%20%20%20wordings%20of%20one%20answer%20is%20the%20harmless%20version%20of%20this.%20The%20version%20that%20costs%20you%0A%20%20%20%20something%20is%20two%20different%20numbers%2C%20or%20two%20different%20thresholds%2C%20or%20two%20decisions%20that%0A%20%20%20%20contradict%20each%20other.%0A%0A%20%20%20%20This%20is%20a%20property%20of%20how%20these%20models%20produce%20text%2C%20and%20no%20setting%20available%20to%20you%0A%20%20%20%20removes%20it.%20What%20you%20can%20do%20about%20it%20is%20run%20the%20request%20more%20than%20once%20and%20compare%20the%0A%20%20%20%20answers.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20How%20an%20LLM%20and%20an%20agent%20actually%20work%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20same%20code%20gives%20a%20different%20total%0A%0A%20%20%20%20Before%20the%20model%2C%20here%20is%20the%20same%20behavior%20in%20three%20lines%20you%20can%20read%20completely.%0A%20%20%20%20Roll%20a%20die%20100%20times.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(random)%3A%0A%20%20%20%20def%20roll(times)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Roll%20a%20six-sided%20die%20and%20report%20the%20total%20and%20the%20average.%22%22%22%0A%20%20%20%20%20%20%20%20rolls%20%3D%20%5Brandom.randint(1%2C%206)%20for%20_%20in%20range(times)%5D%0A%20%20%20%20%20%20%20%20return%20sum(rolls)%2C%20sum(rolls)%20%2F%20times%0A%0A%20%20%20%20roll(100)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Run%20that%20cell%20again%20and%20the%20total%20changes.%20Run%20it%2010%20times%20and%20the%20average%20stays%20near%0A%20%20%20%203.5%20while%20the%20total%20keeps%20moving.%20The%20code%20did%20not%20change%20and%20neither%20did%20the%20input%2C%20and%0A%20%20%20%20you%20can%20still%20rely%20on%20the%20average.%0A%0A%20%20%20%20A%20model%20is%20not%20a%20die%20and%20the%20analogy%20stops%20quickly.%20What%20carries%20over%20is%20the%20working%0A%20%20%20%20habit.%20One%20run%20tells%20you%20very%20little%2C%20and%20several%20runs%20tell%20you%20what%20is%20stable.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20call%20%60roll(10)%60%20five%20times%20and%20%60roll(10000)%60%20five%20times.%20Which%20average%0A%20%20%20%20moves%20more%2C%20and%20why%20does%20that%20matter%20for%20how%20many%20times%20you%20should%20run%20a%20prompt%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20There%20is%20no%20setting%20that%20makes%20it%20repeat%0A%0A%20%20%20%20%60temperature%60%20is%20the%20parameter%20people%20reach%20for.%20It%20takes%20a%20number%20from%200%20to%201%20and%20low%0A%20%20%20%20values%20make%20the%20model%20pick%20the%20likeliest%20next%20piece%20of%20text%20more%20often.%0A%0A%20%20%20%20Two%20things%20about%20it%2C%20and%20the%20first%20one%20is%20permanent.%0A%0A%20%20%20%20**Temperature%200%20does%20not%20make%20output%20deterministic.**%20It%20narrows%20the%20spread.%20Identical%0A%20%20%20%20requests%20at%20temperature%200%20still%20come%20back%20different%2C%20and%20any%20workflow%20built%20on%20the%0A%20%20%20%20assumption%20that%20they%20will%20not%20is%20built%20on%20sand.%0A%0A%20%20%20%20**On%20some%20newer%20models%20the%20parameter%20is%20gone.**%20Sending%20%60temperature%60%20to%20a%20model%20that%0A%20%20%20%20no%20longer%20takes%20it%20returns%20a%20400%2C%20and%20the%20request%20never%20runs%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20anthropic.BadRequestError%3A%20Error%20code%3A%20400%20-%20%7B'type'%3A%20'error'%2C%20'error'%3A%0A%20%20%20%20%7B'type'%3A%20'invalid_request_error'%2C%20'message'%3A%20'temperature%3A%20Extra%20inputs%20are%20not%20permitted'%7D%7D%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20Other%20models%20still%20accept%20it.%20Whether%20the%20model%20behind%20your%20key%20does%20is%20worth%20one%0A%20%20%20%20request%20to%20find%20out%2C%20and%20the%20answer%20changes%20nothing%20about%20the%20paragraph%20above.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20send%20the%20same%20request%20twice%20with%20your%20key%20and%20%60diff%60%20the%20two%20answers.%0A%20%20%20%20Then%2C%20if%20your%20model%20accepts%20%60temperature%60%2C%20set%20it%20to%200%20and%20do%20it%20again.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Reading%20the%20spread%20across%20runs%0A%0A%20%20%20%20Here%20are%20five%20replies%20to%20one%20request%2C%20recorded%20from%20five%20runs%20of%20*write%20Python%20that%0A%20%20%20%20tiers%20an%20order%20value%20into%20A%2C%20B%20or%20C*.%20Nothing%20about%20the%20request%20changed%20between%20them.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20five_runs%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%22Orders%20of%201000%20or%20more%20are%20tier%20A%2C%20500%20or%20more%20are%20tier%20B%2C%20and%20below%20500%20is%20tier%20C.%22%2C%0A%20%20%20%20%20%20%20%20%22Tier%20A%20starts%20at%201000.%20Tier%20B%20covers%20250%20up%20to%201000.%20Everything%20under%20250%20is%20tier%20C.%22%2C%0A%20%20%20%20%20%20%20%20%22I%20used%201000%20and%20500%20as%20the%20cutoffs%2C%20so%20A%20is%201000%20and%20above%2C%20B%20is%20500%20and%20above.%22%2C%0A%20%20%20%20%20%20%20%20%22Tier%20A%20is%20above%205000%2C%20tier%20B%20is%20above%201000%2C%20and%20the%20rest%20are%20tier%20C.%22%2C%0A%20%20%20%20%20%20%20%20%22A%20for%201000%20or%20more%2C%20B%20for%20500%20or%20more%2C%20C%20otherwise.%22%2C%0A%20%20%20%20%5D%0A%20%20%20%20len(five_runs)%0A%20%20%20%20return%20(five_runs%2C)%0A%0A%0A%40app.cell%0Adef%20_(five_runs%2C%20re)%3A%0A%20%20%20%20thresholds%20%3D%20%5Btuple(sorted(%7Bint(n)%20for%20n%20in%20re.findall(r%22%5Cd%2B%22%2C%20run)%7D))%20for%20run%20in%20five_runs%5D%0A%20%20%20%20distinct_sets%20%3D%20sorted(set(thresholds))%0A%20%20%20%20top_boundaries%20%3D%20sorted(%7Bmax(cutoffs)%20for%20cutoffs%20in%20thresholds%7D)%0A%0A%20%20%20%20thresholds%2C%20distinct_sets%2C%20top_boundaries%0A%20%20%20%20return%20distinct_sets%2C%20top_boundaries%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(distinct_sets%2C%20mo%2C%20top_boundaries)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20Five%20runs%20produced%20**%7Blen(distinct_sets)%7D**%20different%20sets%20of%20cutoffs%2C%20and%20the%20highest%0A%20%20%20%20boundary%20alone%20took%20**%7Blen(top_boundaries)%7D**%20different%20values%3A%0A%20%20%20%20%7B%22%20and%20%22.join(f%22%7Bvalue%3A%2C%7D%22%20for%20value%20in%20top_boundaries)%7D.%20One%20run%20of%20five%20put%20tier%20A%20at%0A%20%20%20%20%7Bmax(top_boundaries)%3A%2C%7D%2C%20which%20is%20**%7Bmax(top_boundaries)%20%2F%2F%20min(top_boundaries)%7D**%20times%0A%20%20%20%20the%20value%20the%20other%20four%20chose%20and%20would%20be%20invisible%20if%20you%20had%20run%20it%20once.%0A%0A%20%20%20%20**The%20trap.**%20Reading%20one%20output%20and%20finding%20it%20reasonable%20is%20not%20a%20check.%20The%20output%0A%20%20%20%20you%20happened%20to%20get%20is%20one%20draw%2C%20and%20the%20run%20that%20disagrees%20with%20the%20other%20four%20is%20the%0A%20%20%20%20one%20you%20needed%20to%20see.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20send%20*write%20Python%20that%20tiers%20an%20order%20value%20into%20A%2C%20B%20or%20C*%20five%20times%0A%20%20%20%20with%20your%20key%2C%20pull%20the%20numbers%20out%20of%20each%20answer%2C%20and%20count%20the%20distinct%20sets.%20Then%0A%20%20%20%20look%20at%20whether%20the%20boundary%20is%20%60%3E%60%20or%20%60%3E%3D%60%20in%20each%20one.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20What%20to%20verify%0A%0A%20%20%20%20Verify%20the%20number%2C%20and%20let%20the%20wording%20vary.%20Five%20answers%20that%20word%20the%20same%20threshold%0A%20%20%20%20differently%20are%20fine.%20Five%20answers%20with%20three%20different%20thresholds%20mean%20the%20model%20was%0A%20%20%20%20filling%20in%20a%20decision%20you%20never%20made%2C%20and%20the%20fix%20is%20to%20make%20it%20and%20state%20it%20in%20the%0A%20%20%20%20request.%0A%0A%20%20%20%20Every%20deliverable%20in%20this%20course%20carries%20a%20**How%20I%20know%20these%20numbers%20are%20right**%0A%20%20%20%20section%2C%20and%20that%20section%20needs%20a%20check%20that%20runs.%20A%20number%20you%20can%20recompute%20is%20a%0A%20%20%20%20number%20that%20survives%20the%20model%20producing%20a%20different%20answer%20tomorrow.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20take%20one%20of%20the%20five%20replies%20above%2C%20write%20the%20%60assert%60%20that%20would%20have%0A%20%20%20%20caught%20the%205000%20version%2C%20and%20state%20what%20you%20had%20to%20decide%20in%20order%20to%20write%20it.%0A%0A%20%20%20%20%E2%9C%94%20**Check%20your%20understanding**%0A%0A%20%20%20%201.%20You%20set%20%60temperature%60%20to%200%20and%20get%20the%20same%20answer%20twice.%20What%20have%20you%20established%3F%0A%20%20%20%202.%20Your%20agent%20gave%20you%20a%20working%20function%20and%20you%20read%20it%20and%20it%20looked%20right.%20What%0A%20%20%20%20%20%20%20have%20you%20checked%2C%20and%20what%20have%20you%20not%3F%0A%20%20%20%203.%20Five%20runs%20give%20five%20wordings%20of%20the%20same%20threshold.%20Is%20that%20a%20problem%3F%0A%20%20%20%204.%20Name%20something%20about%20a%20model's%20answer%20that%20is%20worth%20verifying%2C%20and%20something%20that%20is%0A%20%20%20%20%20%20%20not%20worth%20arguing%20with.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2036.%20Tools%0A%0A%20%20%20%20A%20model%20can%20read%20and%20write%20text.%20It%20cannot%20look%20anything%20up%2C%20run%20anything%2C%20or%20touch%0A%20%20%20%20anything.%20A%20**tool**%20is%20how%20you%20close%20that%20gap%2C%20and%20the%20mechanism%20is%20smaller%20than%20it%0A%20%20%20%20sounds.%20You%20describe%20a%20function%20you%20already%20have%2C%20the%20model%20asks%20you%20to%20call%20it%2C%20you%0A%20%20%20%20call%20it%2C%20and%20you%20send%20back%20the%20answer.%0A%0A%20%20%20%20The%20model%20never%20runs%20your%20code.%20It%20asks.%20Everything%20that%20happens%2C%20happens%20because%20your%0A%20%20%20%20program%20did%20it.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20How%20an%20LLM%20and%20an%20agent%20actually%20work%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20function%20first%0A%0A%20%20%20%20Nothing%20about%20this%20function%20knows%20a%20model%20exists.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20ORDER_FREIGHT%20%3D%20%7B10248%3A%2032.38%2C%2010249%3A%2011.61%2C%2010250%3A%2065.83%2C%2010251%3A%2041.34%2C%2010252%3A%2051.30%7D%0A%0A%20%20%20%20def%20order_freight(order_id)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Return%20the%20freight%20charge%20for%20one%20order%2C%20or%20None%20when%20the%20order%20is%20unknown.%22%22%22%0A%20%20%20%20%20%20%20%20return%20ORDER_FREIGHT.get(order_id)%0A%0A%20%20%20%20order_freight(10250)%0A%20%20%20%20return%20(order_freight%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20call%20it%20with%20an%20order%20id%20that%20is%20not%20in%20the%20dictionary.%20Chapter%20B%20section%0A%20%20%20%2015%20is%20why%20the%20answer%20is%20%60None%60%20instead%20of%20a%20%60KeyError%60%2C%20and%20that%20choice%20matters%20here%2C%0A%20%20%20%20because%20a%20tool%20that%20raises%20takes%20the%20whole%20loop%20down%20with%20it.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20tool%20definition%20is%20a%20description%20of%20it%0A%0A%20%20%20%20Three%20keys.%20%60name%60%20has%20to%20match%20what%20your%20code%20dispatches%20on.%20%60description%60%20is%20what%20the%0A%20%20%20%20model%20reads%20to%20decide%20whether%20this%20is%20the%20right%20tool%2C%20so%20it%20is%20prose%20and%20it%20is%20worth%0A%20%20%20%20writing%20properly.%20%60input_schema%60%20is%20JSON%20Schema%2C%20which%20is%20a%20dictionary%20describing%20the%0A%20%20%20%20arguments.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20freight_tool%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22name%22%3A%20%22order_freight%22%2C%0A%20%20%20%20%20%20%20%20%22description%22%3A%20%22Look%20up%20the%20freight%20charge%20for%20one%20order%20by%20its%20order%20id.%20Returns%20the%20charge%20in%20dollars%2C%20or%20nothing%20when%20the%20order%20is%20unknown.%22%2C%0A%20%20%20%20%20%20%20%20%22input_schema%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22object%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22properties%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22order_id%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22integer%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22description%22%3A%20%22The%20order%20id%2C%20for%20example%2010248.%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22required%22%3A%20%5B%22order_id%22%5D%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20sorted(freight_tool)%2C%20freight_tool%5B%22input_schema%22%5D%5B%22required%22%5D%0A%20%20%20%20return%20(freight_tool%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(freight_tool%2C%20mo)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20The%20definition%20goes%20on%20the%20request%20as%20%60tools%3D%5Bfreight_tool%5D%60%2C%20beside%20%60model%60%2C%0A%20%20%20%20%60max_tokens%60%2C%20%60system%60%20and%20%60messages%60%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20response%20%3D%20client.messages.create(%0A%20%20%20%20%20%20%20%20model%3DMODEL%2C%0A%20%20%20%20%20%20%20%20max_tokens%3D1024%2C%0A%20%20%20%20%20%20%20%20tools%3D%5Bfreight_tool%5D%2C%0A%20%20%20%20%20%20%20%20messages%3D%5B%7B%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20%22What%20was%20the%20freight%20on%20order%2010250%3F%22%7D%7D%5D%2C%0A%20%20%20%20)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20**The%20description%20is%20the%20whole%20interface.**%20The%20model%20chooses%20this%20tool%20on%20the%20strength%0A%20%20%20%20of%20**%7Blen(freight_tool%5B%22description%22%5D)%7D**%20characters%20of%20prose%20and%20nothing%20else.%20A%20tool%0A%20%20%20%20that%20never%20gets%20called%20and%20a%20tool%20that%20gets%20called%20for%20the%20wrong%20thing%20are%20usually%20both%0A%20%20%20%20description%20problems.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20write%20a%20definition%20for%20a%20second%20tool%20that%20takes%20two%20arguments%2C%20one%20of%0A%20%20%20%20them%20optional.%20Look%20at%20what%20changes%20in%20%60required%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20model%20asks%20in%20a%20block%20of%20its%20own%0A%0A%20%20%20%20When%20the%20model%20wants%20the%20tool%2C%20the%20reply%20comes%20back%20with%20%60stop_reason%60%20of%20%60tool_use%60%0A%20%20%20%20and%20a%20block%20of%20type%20%60tool_use%60%20in%20the%20content%20list.%20Here%20is%20that%20reply.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20tool_request%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22id%22%3A%20%22msg_example_tool%22%2C%0A%20%20%20%20%20%20%20%20%22role%22%3A%20%22assistant%22%2C%0A%20%20%20%20%20%20%20%20%22content%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%22type%22%3A%20%22text%22%2C%20%22text%22%3A%20%22Let%20me%20look%20that%20up.%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22tool_use%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22id%22%3A%20%22toolu_example%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22name%22%3A%20%22order_freight%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22input%22%3A%20%7B%22order_id%22%3A%2010250%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%22stop_reason%22%3A%20%22tool_use%22%2C%0A%20%20%20%20%20%20%20%20%22usage%22%3A%20%7B%22input_tokens%22%3A%20512%2C%20%22output_tokens%22%3A%2074%7D%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20%5Bblock%5B%22type%22%5D%20for%20block%20in%20tool_request%5B%22content%22%5D%5D%0A%20%20%20%20return%20(tool_request%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20tool_request)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20Two%20blocks%2C%20and%20%60content%5B0%5D%60%20is%20text%2C%20which%20is%20why%20section%2033%20said%20not%20to%20reach%20for%0A%20%20%20%20%60content%5B0%5D%5B%22text%22%5D%60%20as%20if%20it%20were%20the%20answer.%20The%20%60tool_use%60%20block%20carries%20three%20things%0A%20%20%20%20you%20need%3A%20an%20%60id%60%20of%20%60%7Btool_request%5B%22content%22%5D%5B1%5D%5B%22id%22%5D%7D%60%2C%20a%20%60name%60%20of%0A%20%20%20%20%60%7Btool_request%5B%22content%22%5D%5B1%5D%5B%22name%22%5D%7D%60%2C%20and%20an%20%60input%60%20dictionary%20holding%0A%20%20%20%20%60%7Btool_request%5B%22content%22%5D%5B1%5D%5B%22input%22%5D%7D%60.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20write%20the%20expression%20that%20pulls%20out%20every%20%60tool_use%60%20block%20from%20a%20reply%2C%0A%20%20%20%20without%20assuming%20how%20many%20there%20are%20or%20where%20they%20sit.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Running%20the%20tool%20the%20model%20chose%0A%0A%20%20%20%20The%20model%20gave%20you%20a%20name%20and%20a%20dictionary%20of%20arguments.%20Your%20code%20turns%20that%20into%20a%0A%20%20%20%20call.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(order_freight%2C%20tool_request)%3A%0A%20%20%20%20TOOLS%20%3D%20%7B%22order_freight%22%3A%20order_freight%7D%0A%0A%20%20%20%20def%20run_tool(block)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Run%20the%20tool%20the%20model%20asked%20for%20and%20return%20its%20result%20as%20text.%22%22%22%0A%20%20%20%20%20%20%20%20function%20%3D%20TOOLS.get(block%5B%22name%22%5D)%0A%20%20%20%20%20%20%20%20if%20function%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20f%22No%20tool%20named%20%7Bblock%5B'name'%5D%7D.%22%0A%20%20%20%20%20%20%20%20return%20str(function(**block%5B%22input%22%5D))%0A%0A%20%20%20%20tool_answer%20%3D%20run_tool(tool_request%5B%22content%22%5D%5B1%5D)%0A%20%20%20%20tool_answer%0A%20%20%20%20return%20run_tool%2C%20tool_answer%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60**block%5B%22input%22%5D%60%20unpacks%20the%20dictionary%20into%20keyword%20arguments%2C%20so%0A%20%20%20%20%60%7B%22order_id%22%3A%2010250%7D%60%20becomes%20%60order_freight(order_id%3D10250)%60.%20That%20works%20because%20the%0A%20%20%20%20schema%20named%20the%20argument%20%60order_id%60%20and%20so%20does%20the%20function.%20When%20those%20two%20names%0A%20%20%20%20drift%20apart%20you%20get%20a%20%60TypeError%60%20about%20an%20unexpected%20keyword%20argument%2C%20and%20the%20schema%0A%20%20%20%20is%20where%20to%20look.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20change%20the%20schema's%20property%20name%20to%20%60order%60.%20A%20model%20fills%20in%20the%20names%0A%20%20%20%20the%20schema%20gives%20it%2C%20so%20change%20the%20%60input%60%20in%20%60tool_request%60%20to%20%60%7B%22order%22%3A%2010250%7D%60%20as%0A%20%20%20%20well%2C%20and%20leave%20the%20function%20alone.%20Read%20the%20error%20and%20decide%20which%20of%20the%20two%20you%0A%20%20%20%20would%20fix.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20You%20send%20the%20result%20back%20as%20a%20user%20turn%0A%0A%20%20%20%20The%20result%20goes%20into%20the%20conversation%20as%20a%20%60tool_result%60%20block%20inside%20a%20message%20with%0A%20%20%20%20role%20%60user%60.%20That%20surprises%20people%2C%20and%20it%20is%20consistent.%20Everything%20that%20comes%20from%0A%20%20%20%20your%20side%20of%20the%20wire%20is%20a%20user%20turn%2C%20including%20a%20number%20your%20own%20function%20produced.%0A%0A%20%20%20%20Two%20rules.%20%60tool_use_id%60%20has%20to%20match%20the%20%60id%60%20from%20the%20request.%20And%20every%20result%20for%0A%20%20%20%20one%20reply%20goes%20back%20in%20one%20message%2C%20however%20many%20tools%20were%20asked%20for.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(tool_answer%2C%20tool_request)%3A%0A%20%20%20%20tool_conversation%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20%22What%20was%20the%20freight%20on%20order%2010250%3F%22%7D%2C%0A%20%20%20%20%20%20%20%20%7B%22role%22%3A%20%22assistant%22%2C%20%22content%22%3A%20tool_request%5B%22content%22%5D%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22role%22%3A%20%22user%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22content%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22tool_result%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22tool_use_id%22%3A%20tool_request%5B%22content%22%5D%5B1%5D%5B%22id%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22content%22%3A%20tool_answer%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20%5Bentry%5B%22role%22%5D%20for%20entry%20in%20tool_conversation%5D%0A%20%20%20%20return%20(tool_conversation%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20tool_conversation)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20The%20list%20holds%20three%20entries%2C%20with%20roles%0A%20%20%20%20**%7B%22%2C%20%22.join(e%5B%22role%22%5D%20for%20e%20in%20tool_conversation)%7D**.%20It%20goes%20back%20to%20the%20model%20as%0A%20%20%20%20%60messages%60%2C%20and%20the%20next%20reply%20is%20the%20answer%20to%20the%20original%20question%2C%20written%20with%20the%0A%20%20%20%20number%20in%20hand.%0A%0A%20%20%20%20**The%20trap%2C%20and%20it%20fails%20silently.**%20If%20your%20code%20reads%20the%20%60tool_use%60%20block%20and%20never%0A%20%20%20%20sends%20a%20%60tool_result%60%20back%2C%20nothing%20raises.%20The%20model%20asked%2C%20you%20did%20not%20answer%2C%20and%0A%20%20%20%20the%20conversation%20stops%20with%20an%20unanswered%20request%20in%20it.%20The%20tell%20is%20a%20reply%20whose%0A%20%20%20%20%60stop_reason%60%20is%20%60tool_use%60%20and%20a%20program%20that%20printed%20nothing.%0A%0A%20%20%20%20**When%20the%20tool%20fails**%2C%20send%20the%20failure%20back.%20Add%0A%20%20%20%20%60%22is_error%22%3A%20True%60%20to%20the%20block%20and%20put%20the%20message%20in%20%60content%60.%20The%20model%20can%20work%0A%20%20%20%20with%20a%20tool%20that%20said%20no%3B%20it%20cannot%20work%20with%20silence.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20build%20the%20%60tool_result%60%20block%20for%20a%20failed%20call%2C%20with%20%60is_error%60%20set.%0A%20%20%20%20Then%20build%20the%20message%20that%20would%20carry%20two%20results%20at%20once.%0A%0A%20%20%20%20%E2%9C%94%20**Check%20your%20understanding**%0A%0A%20%20%20%201.%20The%20model%20decided%20to%20use%20your%20tool.%20What%20ran%20on%20the%20server%2C%20and%20what%20ran%20on%20your%0A%20%20%20%20%20%20%20machine%3F%0A%20%20%20%202.%20Your%20tool%20is%20never%20chosen.%20Name%20the%20first%20thing%20to%20look%20at.%0A%20%20%20%203.%20What%20has%20to%20match%20between%20the%20%60tool_use%60%20block%20and%20the%20%60tool_result%60%20block%2C%20and%20what%0A%20%20%20%20%20%20%20happens%20when%20it%20does%20not%3F%0A%20%20%20%204.%20Why%20is%20a%20tool%20result%20carried%20in%20a%20message%20with%20role%20%60user%60%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2037.%20The%20agent%20loop%0A%0A%20%20%20%20Section%2036%20did%20one%20round%20trip%20by%20hand.%20An%20agent%20is%20that%20round%20trip%20in%20a%20%60while%60%2C%20run%0A%20%20%20%20until%20the%20model%20stops%20asking%20for%20tools.%20That%20loop%20is%20the%20entire%20difference%20between%20a%0A%20%20%20%20chat%20window%20and%20an%20agent%2C%20and%20it%20is%20under%2020%20lines.%0A%0A%20%20%20%20It%20also%20raises%20the%20one%20question%20the%20API%20cannot%20answer%20for%20you.%20When%20does%20it%20stop%3F%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20How%20an%20LLM%20and%20an%20agent%20actually%20work%20%C2%B7%20Building%20your%20product%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60stop_reason%60%20is%20how%20the%20model%20tells%20you%20what%20it%20wants%0A%0A%20%20%20%20Every%20reply%20carries%20one.%20Four%20of%20them%20matter%20here.%0A%0A%20%20%20%20%7C%20%60stop_reason%60%20%7C%20what%20it%20means%20%7C%20what%20you%20do%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20%60end_turn%60%20%7C%20the%20model%20finished%20%7C%20stop%2C%20and%20read%20the%20answer%20%7C%0A%20%20%20%20%7C%20%60tool_use%60%20%7C%20it%20wants%20a%20tool%20run%20%7C%20run%20it%2C%20send%20the%20result%2C%20go%20again%20%7C%0A%20%20%20%20%7C%20%60max_tokens%60%20%7C%20it%20hit%20the%20ceiling%20you%20set%20and%20was%20cut%20off%20%7C%20the%20answer%20is%20incomplete%2C%20raise%20%60max_tokens%60%20and%20retry%20%7C%0A%20%20%20%20%7C%20%60refusal%60%20%7C%20it%20declined%20%7C%20read%20%60stop_details%60%2C%20do%20not%20retry%20the%20same%20request%20%7C%0A%0A%20%20%20%20**%60max_tokens%60%20is%20the%20one%20that%20catches%20people.**%20The%20reply%20arrives%2C%20it%20reads%20like%20an%0A%20%20%20%20answer%2C%20and%20it%20stopped%20mid-sentence%20because%20the%20cap%20was%20too%20low.%20Nothing%20raises.%20Check%0A%20%20%20%20%60stop_reason%60%20before%20you%20use%20a%20reply%20for%20anything.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20send%20a%20request%20with%20%60max_tokens%3D20%60%20and%20a%20question%20that%20needs%20a%0A%20%20%20%20paragraph.%20Print%20%60response.stop_reason%60%20and%20then%20print%20the%20text.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20loop%0A%0A%20%20%20%20Here%20it%20is%20against%20a%20real%20client.%20It%20is%20section%2036%20four%20times%20over.%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20messages%20%3D%20%5B%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20question%7D%5D%0A%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20response%20%3D%20client.messages.create(%0A%20%20%20%20%20%20%20%20%20%20%20%20model%3DMODEL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_tokens%3D1024%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tools%3D%5Bfreight_tool%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20messages%3Dmessages%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20if%20response.stop_reason%20!%3D%20%22tool_use%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20%20%20%20%20messages.append(%7B%22role%22%3A%20%22assistant%22%2C%20%22content%22%3A%20response.content%7D)%0A%20%20%20%20%20%20%20%20results%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%22type%22%3A%20%22tool_result%22%2C%20%22tool_use_id%22%3A%20b.id%2C%20%22content%22%3A%20run_tool(b)%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20b%20in%20response.content%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20b.type%20%3D%3D%20%22tool_use%22%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20messages.append(%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20results%7D)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20Below%2C%20the%20same%20loop%20runs%20against%20a%20stand-in%20that%20returns%20replies%20from%20a%20list%2C%20so%20you%0A%20%20%20%20can%20watch%20it%20go%20round%20without%20a%20key.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(run_tool)%3A%0A%20%20%20%20def%20build_stand_in(script)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Return%20a%20function%20that%20hands%20back%20the%20next%20scripted%20reply%20each%20time%20it%20is%20called.%22%22%22%0A%20%20%20%20%20%20%20%20remaining%20%3D%20list(script)%0A%0A%20%20%20%20%20%20%20%20def%20send(_sent)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20remaining.pop(0)%20if%20remaining%20else%20%7B%22stop_reason%22%3A%20%22end_turn%22%2C%20%22content%22%3A%20%5B%5D%7D%0A%0A%20%20%20%20%20%20%20%20return%20send%0A%0A%20%20%20%20def%20agent_loop(send%2C%20question%2C%20limit)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Run%20the%20tool%20loop.%20Stop%20on%20end_turn%2C%20or%20at%20the%20limit%2C%20and%20report%20which.%22%22%22%0A%20%20%20%20%20%20%20%20conversation%20%3D%20%5B%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20question%7D%5D%0A%20%20%20%20%20%20%20%20log%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20for%20step%20in%20range(1%2C%20limit%20%2B%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20response%20%3D%20send(conversation)%0A%20%20%20%20%20%20%20%20%20%20%20%20log.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22step%20%7Bstep%7D%3A%20entries%20sent%20%7Blen(conversation)%7D%2C%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22stop_reason%20%7Bresponse%5B'stop_reason'%5D%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20response%5B%22stop_reason%22%5D%20!%3D%20%22tool_use%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20conversation%2C%20log%2C%20%22the%20model%20said%20it%20was%20done%22%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20conversation.append(%7B%22role%22%3A%20%22assistant%22%2C%20%22content%22%3A%20response%5B%22content%22%5D%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20results%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%22type%22%3A%20%22tool_result%22%2C%20%22tool_use_id%22%3A%20b%5B%22id%22%5D%2C%20%22content%22%3A%20run_tool(b)%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20b%20in%20response%5B%22content%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20b%5B%22type%22%5D%20%3D%3D%20%22tool_use%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20conversation.append(%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20results%7D)%0A%0A%20%20%20%20%20%20%20%20return%20conversation%2C%20log%2C%20f%22the%20limit%20of%20%7Blimit%7D%20steps%20was%20reached%22%0A%0A%20%20%20%20return%20agent_loop%2C%20build_stand_in%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20def%20asks_for(order_id)%3A%0A%20%20%20%20%20%20%20%20%22%22%22A%20scripted%20reply%20in%20which%20the%20model%20asks%20for%20one%20order's%20freight.%22%22%22%0A%20%20%20%20%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22stop_reason%22%3A%20%22tool_use%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22content%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22tool_use%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22id%22%3A%20f%22toolu_%7Border_id%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22name%22%3A%20%22order_freight%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22input%22%3A%20%7B%22order_id%22%3A%20order_id%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20FINISHES%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22stop_reason%22%3A%20%22end_turn%22%2C%0A%20%20%20%20%20%20%20%20%22content%22%3A%20%5B%7B%22type%22%3A%20%22text%22%2C%20%22text%22%3A%20%22Order%2010250%20is%20the%20highest%20of%20the%20three%20at%2065.83.%22%7D%5D%2C%0A%20%20%20%20%7D%0A%20%20%20%20return%20FINISHES%2C%20asks_for%0A%0A%0A%40app.cell%0Adef%20_(FINISHES%2C%20agent_loop%2C%20asks_for%2C%20build_stand_in)%3A%0A%20%20%20%20good_conversation%2C%20good_log%2C%20good_reason%20%3D%20agent_loop(%0A%20%20%20%20%20%20%20%20build_stand_in(%5Basks_for(10248)%2C%20asks_for(10249)%2C%20asks_for(10250)%2C%20FINISHES%5D)%2C%0A%20%20%20%20%20%20%20%20%22Which%20of%20orders%2010248%2C%2010249%20and%2010250%20had%20the%20highest%20freight%3F%22%2C%0A%20%20%20%20%20%20%20%20limit%3D10%2C%0A%20%20%20%20)%0A%0A%20%20%20%20good_log%0A%20%20%20%20return%20good_conversation%2C%20good_reason%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(good_conversation%2C%20good_reason%2C%20mo)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20Four%20steps%2C%20three%20of%20them%20tool%20calls%2C%20and%20it%20stopped%20because%20**%7Bgood_reason%7D**.%20The%0A%20%20%20%20conversation%20ended%20up%20**%7Blen(good_conversation)%7D**%20entries%20long%20from%20one%20question%2C%0A%20%20%20%20because%20every%20tool%20call%20adds%20two.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20change%20the%20script%20so%20the%20model%20asks%20for%20a%20fourth%20order%20and%20then%20finishes.%0A%20%20%20%20Predict%20the%20number%20of%20entries%20before%20you%20run%20it.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Print%20what%20the%20model%20receives%0A%0A%20%20%20%20The%20model%20sees%20the%20list%20you%20send%20and%20nothing%20else.%20So%20the%20most%20useful%20line%20in%20an%20agent%0A%20%20%20%20loop%20is%20one%20that%20prints%20that%20list%20before%20each%20call.%20Against%20a%20real%20client%20it%20is%0A%20%20%20%20%60print(messages)%60%2C%20directly%20above%20%60client.messages.create(...)%60.%20The%20output%20is%20long%2C%0A%20%20%20%20and%20it%20is%20everything%20the%20model%20will%20see.%0A%0A%20%20%20%20Here%20is%20the%20conversation%20from%20the%20run%20above%2C%20one%20entry%20per%20line.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(good_conversation)%3A%0A%20%20%20%20def%20describe(entry)%3A%0A%20%20%20%20%20%20%20%20%22%22%22One%20line%20for%20one%20entry%20in%20the%20conversation%3A%20who%20sent%20it%20and%20what%20it%20carries.%22%22%22%0A%20%20%20%20%20%20%20%20if%20isinstance(entry%5B%22content%22%5D%2C%20str)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20f%22%7Bentry%5B'role'%5D%7D%3A%20%7Bentry%5B'content'%5D%7D%22%0A%20%20%20%20%20%20%20%20parts%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20block%20in%20entry%5B%22content%22%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20block%5B%22type%22%5D%20%3D%3D%20%22text%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20parts.append(block%5B%22text%22%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20block%5B%22type%22%5D%20%3D%3D%20%22tool_use%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20parts.append(f%22asks%20for%20%7Bblock%5B'name'%5D%7D(%7Bblock%5B'input'%5D%7D)%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20block%5B%22type%22%5D%20%3D%3D%20%22tool_result%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20parts.append(f%22tool%20returned%20%7Bblock%5B'content'%5D%7D%22)%0A%20%20%20%20%20%20%20%20return%20f%22%7Bentry%5B'role'%5D%7D%3A%20%22%20%2B%20%22%20%7C%20%22.join(parts)%0A%0A%20%20%20%20%5Bdescribe(entry)%20for%20entry%20in%20good_conversation%5D%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Step%201%20was%20sent%20the%20first%20line.%20Step%202%20was%20sent%20the%20first%20three%2C%20and%20step%204%20was%20sent%20all%0A%20%20%20%20seven.%20**Every%20call%20resends%20everything%20before%20it**%2C%20which%20is%20why%20the%20log%20above%20shows%0A%20%20%20%20the%20entry%20count%20climbing%20by%20two%20per%20tool%20call.%20The%20tool%20results%20arrive%20as%20%60user%60%20lines%2C%0A%20%20%20%20as%20section%2036%20said%20they%20would.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20give%20%60describe%60%20one%20more%20%60elif%60%20that%20marks%20a%20%60tool_result%60%20carrying%0A%20%20%20%20%60is_error%60.%20Then%20build%20a%20conversation%20with%20a%20failed%20call%20in%20it%20and%20read%20the%20line.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20The%20stop%20condition%20the%20API%20will%20not%20give%20you%0A%0A%20%20%20%20The%20loop%20above%20stops%20when%20the%20model%20says%20it%20is%20done.%20Nothing%20guarantees%20the%20model%20will%0A%20%20%20%20say%20it%20is%20done.%20A%20tool%20that%20keeps%20returning%20something%20unsatisfying%2C%20a%20question%20with%20no%0A%20%20%20%20answer%20in%20the%20data%2C%20an%20agent%20that%20decides%20to%20look%20one%20more%20thing%20up.%20Any%20of%20those%0A%20%20%20%20turns%20%60while%20True%60%20into%20a%20loop%20that%20runs%20until%20your%20budget%20stops%20it.%0A%0A%20%20%20%20So%20the%20limit%20is%20yours%20to%20set.%20Here%20is%20the%20identical%20loop%20against%20a%20model%20that%20never%0A%20%20%20%20finishes.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(agent_loop%2C%20asks_for%2C%20build_stand_in)%3A%0A%20%20%20%20stuck_conversation%2C%20stuck_log%2C%20stuck_reason%20%3D%20agent_loop(%0A%20%20%20%20%20%20%20%20build_stand_in(%5Basks_for(10248)%5D%20*%2050)%2C%0A%20%20%20%20%20%20%20%20%22Which%20of%20orders%2010248%2C%2010249%20and%2010250%20had%20the%20highest%20freight%3F%22%2C%0A%20%20%20%20%20%20%20%20limit%3D5%2C%0A%20%20%20%20)%0A%0A%20%20%20%20stuck_log%0A%20%20%20%20return%20stuck_conversation%2C%20stuck_reason%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(good_conversation%2C%20mo%2C%20stuck_conversation%2C%20stuck_reason)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20It%20stopped%20because%20**%7Bstuck_reason%7D**%2C%20after%20building%20a%20conversation%20of%0A%20%20%20%20**%7Blen(stuck_conversation)%7D**%20entries%20against%20**%7Blen(good_conversation)%7D**%20for%20the%0A%20%20%20%20working%20one.%20Without%20the%20limit%20that%20loop%20would%20have%20run%2050%20times%2C%20and%20against%20a%20live%0A%20%20%20%20model%20it%20would%20have%20run%20until%20something%20else%20stopped%20it.%0A%0A%20%20%20%20**A%20step%20limit%20is%20what%20keeps%20a%20runaway%20loop%20off%20your%20budget.**%20%5BYour%20AI%20Tools%20and%20Babson%20Key%5D(%2Fguides%2Fbabson-ai-key%2F)%20says%20the%0A%20%20%20%20monthly%20budget%20covers%20this%20course%20comfortably%2C%20and%20it%20stops%20covering%20it%20when%20an%20agent%0A%20%20%20%20is%20left%20rerunning%20a%20failing%20step%2020%20times%20with%20nobody%20reading%20the%20error.%20A%20%60for%60%20with%20a%0A%20%20%20%20ceiling%20instead%20of%20a%20%60while%20True%60%20is%20the%20whole%20defense.%0A%0A%20%20%20%20**And%20every%20step%20costs%20more%20than%20the%20one%20before%20it**%2C%20because%20the%20conversation%20it%0A%20%20%20%20resends%20is%20longer.%20Step%205%20of%20a%20stuck%20loop%20is%20the%20most%20expensive%20one%20in%20it.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20raise%20the%20limit%20to%2020%20and%20watch%20the%20conversation%20length.%20Then%20add%20a%0A%20%20%20%20counter%20to%20the%20loop%20that%20stops%20when%20the%20same%20tool%20is%20called%20with%20the%20same%20input%20twice%0A%20%20%20%20in%20a%20row%2C%20and%20name%20what%20that%20would%20have%20caught.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20check%20you%20write%20decides%20when%20it%20is%20done%0A%0A%20%20%20%20%60end_turn%60%20means%20the%20model%20judged%20its%20own%20answer%20finished.%20That%20is%20a%20claim%2C%20and%20the%0A%20%20%20%20loop%20can%20test%20it%20before%20accepting%20it.%20Write%20a%20function%20that%20reads%20the%20answer%20and%0A%20%20%20%20returns%20a%20problem%2C%20or%20%60None%60%20when%20there%20is%20none.%20Only%20the%20branch%20for%20a%20finished%20reply%0A%20%20%20%20changes%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20if%20response%5B%22stop_reason%22%5D%20!%3D%20%22tool_use%22%3A%0A%20%20%20%20%20%20%20%20answer%20%3D%20%22%20%22.join(b%5B%22text%22%5D%20for%20b%20in%20response%5B%22content%22%5D%20if%20b%5B%22type%22%5D%20%3D%3D%20%22text%22)%0A%20%20%20%20%20%20%20%20problem%20%3D%20find_problem(answer)%0A%20%20%20%20%20%20%20%20if%20problem%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20conversation%2C%20log%2C%20%22the%20check%20passed%22%0A%20%20%20%20%20%20%20%20conversation.append(%7B%22role%22%3A%20%22assistant%22%2C%20%22content%22%3A%20response%5B%22content%22%5D%7D)%0A%20%20%20%20%20%20%20%20conversation.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%22role%22%3A%20%22user%22%2C%20%22content%22%3A%20f%22Your%20answer%20did%20not%20pass%20my%20check%3A%20%7Bproblem%7D%22%7D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20A%20problem%20goes%20back%20to%20the%20model%20as%20a%20user%20turn%2C%20and%20%60continue%60%20sends%20the%20loop%20round%0A%20%20%20%20again.%20So%20the%20loop%20stops%20in%20one%20of%20two%20ways%3A%20the%20check%20passed%2C%20or%20the%20limit%20was%0A%20%20%20%20reached.%20The%20model%20reporting%20that%20it%20is%20done%20no%20longer%20ends%20it.%0A%0A%20%20%20%20For%20the%20freight%20question%2C%20a%20check%20can%20recompute%20the%20answer%20with%20the%20tool%20itself%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20def%20find_problem(answer)%3A%0A%20%20%20%20%20%20%20%20highest%20%3D%20max(%5B10248%2C%2010249%2C%2010250%5D%2C%20key%3Dorder_freight)%0A%20%20%20%20%20%20%20%20if%20str(highest)%20not%20in%20answer%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20f%22it%20does%20not%20name%20order%20%7Bhighest%7D%22%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20A%20check%20like%20this%20needs%20a%20question%20whose%20answer%20you%20already%20know%2C%20which%20is%20how%20you%0A%20%20%20%20test%20an%20agent%20before%20you%20trust%20it%20with%20a%20question%20you%20do%20not.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20%60find_problem%60%20as%20a%20fourth%20parameter%20of%20%60agent_loop%60%20and%20put%20the%0A%20%20%20%20branch%20above%20in%20it.%20Script%20a%20stand-in%20whose%20first%20reply%20names%20order%2010249%2C%20and%20read%20the%0A%20%20%20%20log.%20After%20section%2038%2C%20add%20a%20second%20ceiling%3A%20stop%20when%20the%20tokens%20in%20%60usage%60%20add%20up%0A%20%20%20%20past%20a%20budget%20you%20choose.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%3E%20**Advanced**%0A%20%20%20%20%3E%0A%20%20%20%20%3E%20The%20Anthropic%20library%20ships%20this%20loop%2C%20so%20you%20can%20hand%20it%20your%20functions%20and%20let%20it%0A%20%20%20%20%3E%20run%3A%20%60client.beta.messages.tool_runner(...)%60%2C%20with%20each%20tool%20marked%20by%20a%20%60%40beta_tool%60%0A%20%20%20%20%3E%20decorator%20that%20builds%20the%20schema%20from%20the%20function%20signature.%20It%20is%20in%20beta%2C%20and%20the%0A%20%20%20%20%3E%20reason%20to%20write%20the%20loop%20by%20hand%20at%20least%20once%20is%20that%20the%20runner's%20stopping%20behavior%0A%20%20%20%20%3E%20is%20then%20something%20you%20can%20reason%20about%20instead%20of%20something%20you%20hope%20about.%0A%0A%20%20%20%20%E2%9C%94%20**Check%20your%20understanding**%0A%0A%20%20%20%201.%20What%20is%20the%20difference%20between%20a%20chat%20program%20and%20an%20agent%2C%20in%20code%3F%0A%20%20%20%202.%20A%20reply%20comes%20back%20with%20%60stop_reason%60%20of%20%60max_tokens%60.%20Is%20the%20answer%20usable%3F%0A%20%20%20%203.%20Your%20loop%20has%20been%20going%20for%2040%20steps.%20Name%20two%20things%20that%20are%20growing%20and%20one%0A%20%20%20%20%20%20%20thing%20that%20is%20not.%0A%20%20%20%204.%20Where%20does%20the%20stop%20condition%20come%20from%2C%20and%20what%20happens%20when%20you%20leave%20it%20out%3F%0A%20%20%20%205.%20The%20model%20replied%20with%20%60end_turn%60.%20What%20has%20to%20be%20true%20before%20you%20treat%20its%20answer%0A%20%20%20%20%20%20%20as%20finished%2C%20and%20where%20in%20the%20loop%20does%20that%20get%20tested%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2038.%20Tokens%20and%20cost%0A%0A%20%20%20%20A%20model%20does%20not%20read%20characters%20or%20words.%20It%20reads%20**tokens**%2C%20which%20are%20pieces%20of%0A%20%20%20%20text%20of%20roughly%20a%20few%20characters%2C%20and%20it%20is%20billed%20by%20the%20token%20in%20both%20directions.%0A%0A%20%20%20%20Two%20numbers%20come%20back%20on%20every%20reply.%0A%0A%20%20%20%20%F0%9F%93%96%20**Used%20in%3A**%20How%20an%20LLM%20and%20an%20agent%20actually%20work%20%C2%B7%20Building%20your%20product%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20%60usage%60%20is%20on%20every%20reply%0A%0A%20%20%20%20You%20already%20have%20it.%20The%20reply%20built%20in%20section%2033%20carries%20a%20%60usage%60%20dictionary%20with%0A%20%20%20%20two%20counts%20in%20it.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(reply)%3A%0A%20%20%20%20reply%5B%22usage%22%5D%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20reply)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20**%7Breply%5B%22usage%22%5D%5B%22input_tokens%22%5D%7D**%20tokens%20went%20in%20and%0A%20%20%20%20**%7Breply%5B%22usage%22%5D%5B%22output_tokens%22%5D%7D**%20came%20out.%20Input%20is%20everything%20you%20sent%3A%20the%0A%20%20%20%20system%20prompt%2C%20the%20tool%20definitions%2C%20and%20every%20turn%20of%20the%20conversation.%20Output%20is%20what%0A%20%20%20%20the%20model%20wrote.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20send%20any%20request%20with%20your%20key%20and%20print%20%60response.usage%60.%20Then%20send%20the%0A%20%20%20%20same%20request%20with%20a%20longer%20system%20prompt%20and%20print%20it%20again.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Counting%20tokens%20before%20you%20send%0A%0A%20%20%20%20A%20token%20count%20is%20a%20property%20of%20the%20model's%20tokenizer%2C%20so%20the%20only%20accurate%20count%20comes%0A%20%20%20%20from%20the%20model's%20own%20API.%20One%20call%2C%20and%20it%20does%20not%20run%20the%20model%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20count%20%3D%20client.messages.count_tokens(%0A%20%20%20%20%20%20%20%20model%3DMODEL%2C%0A%20%20%20%20%20%20%20%20system%3Dsystem_prompt%2C%0A%20%20%20%20%20%20%20%20messages%3Dmessages%2C%0A%20%20%20%20)%0A%20%20%20%20print(count.input_tokens)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20**Do%20not%20use%20%60tiktoken%60%20for%20this.**%20It%20is%20a%20different%20company's%20tokenizer%2C%20it%20undercounts%0A%20%20%20%20Claude%20by%20roughly%2015%20to%2020%20percent%20on%20ordinary%20text%2C%20and%20by%20more%20on%20code.%20A%20cost%0A%20%20%20%20estimate%20built%20on%20it%20is%20wrong%20in%20the%20direction%20that%20costs%20you%20money.%0A%0A%20%20%20%20Rules%20of%20thumb%20for%20characters%20per%20token%20float%20around%20and%20none%20of%20them%20are%20worth%0A%20%20%20%20memorizing%2C%20because%20the%20ratio%20depends%20on%20the%20text.%20Measure%20your%20own.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20run%20%60count_tokens%60%20on%20a%20paragraph%20of%20your%20own%20writing%2C%20and%20again%20on%2020%0A%20%20%20%20lines%20of%20your%20own%20Python.%20Divide%20each%20by%20its%20character%20count.%20The%20two%20ratios%20will%20not%0A%20%20%20%20match%2C%20and%20that%20is%20the%20point.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Tokens%20times%20a%20price%0A%0A%20%20%20%20Prices%20are%20quoted%20per%20million%20tokens%2C%20and%20output%20costs%20several%20times%20what%20input%20does.%0A%20%20%20%20Put%20both%20in%20names%20at%20the%20top%20so%20that%20a%20price%20change%20is%20one%20edit.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(reply)%3A%0A%20%20%20%20INPUT_PER_MILLION%20%3D%202.00%0A%20%20%20%20OUTPUT_PER_MILLION%20%3D%2010.00%0A%0A%20%20%20%20def%20cost_of(usage)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Cost%20in%20dollars%20for%20one%20reply%2C%20from%20its%20usage%20counts.%22%22%22%0A%20%20%20%20%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20usage%5B%22input_tokens%22%5D%20%2F%201_000_000%20*%20INPUT_PER_MILLION%0A%20%20%20%20%20%20%20%20%20%20%20%20%2B%20usage%5B%22output_tokens%22%5D%20%2F%201_000_000%20*%20OUTPUT_PER_MILLION%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20cost_of(reply%5B%22usage%22%5D)%0A%20%20%20%20return%20INPUT_PER_MILLION%2C%20OUTPUT_PER_MILLION%2C%20cost_of%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(INPUT_PER_MILLION%2C%20OUTPUT_PER_MILLION%2C%20cost_of%2C%20mo%2C%20reply)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20That%20request%20cost%20**%7Bcost_of(reply%5B%22usage%22%5D)%3A.5f%7D**%20dollars%2C%20which%20is%20under%20a%20cent.%20The%0A%20%20%20%20two%20prices%20used%20are%20**%7BINPUT_PER_MILLION%3A.2f%7D**%20and%20**%7BOUTPUT_PER_MILLION%3A.2f%7D**%20dollars%0A%20%20%20%20per%20million%2C%20published%20for%20the%20model%20this%20course%20runs%20on%20and%20checked%20on%202026-09-14.%0A%20%20%20%20Prices%20move.%20Put%20the%20current%20ones%20in%20those%20two%20names%20before%20you%20trust%20a%20total.%0A%0A%20%20%20%20**%60max_tokens%60%20is%20a%20ceiling%20and%20not%20a%20target.**%20Setting%20it%20to%204096%20does%20not%20cost%20you%0A%20%20%20%204096%20output%20tokens.%20You%20are%20billed%20for%20what%20came%20back.%20Set%20it%20high%20enough%20that%20answers%0A%20%20%20%20are%20not%20cut%20off%2C%20and%20use%20%60stop_reason%60%20to%20find%20out%20when%20it%20was%20too%20low.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20work%20out%20what%201%2C000%20requests%20of%20this%20size%20would%20cost.%20Then%20work%20out%20how%0A%20%20%20%20much%20of%20that%20is%20output.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20A%20conversation%20costs%20more%20than%20the%20sum%20of%20its%20turns%0A%0A%20%20%20%20Section%2034%20established%20that%20every%20request%20resends%20the%20whole%20conversation.%20Here%20is%20what%0A%20%20%20%20that%20does%20to%20a%20bill.%0A%0A%20%20%20%20Suppose%20each%20turn%20adds%20900%20tokens%2C%20and%20you%20have%2010%20turns.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20TOKENS_PER_TURN%20%3D%20900%0A%20%20%20%20TURNS%20%3D%2010%0A%0A%20%20%20%20naive_input%20%3D%20TOKENS_PER_TURN%20*%20TURNS%0A%20%20%20%20actual_input%20%3D%20sum(TOKENS_PER_TURN%20*%20turn%20for%20turn%20in%20range(1%2C%20TURNS%20%2B%201))%0A%0A%20%20%20%20naive_input%2C%20actual_input%2C%20actual_input%20%2F%20naive_input%0A%20%20%20%20return%20TURNS%2C%20actual_input%2C%20naive_input%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(INPUT_PER_MILLION%2C%20TURNS%2C%20actual_input%2C%20mo%2C%20naive_input)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20Typing%20**%7Bnaive_input%3A%2C%7D**%20tokens%20of%20conversation%20sends%20**%7Bactual_input%3A%2C%7D**%20of%20them%2C%0A%20%20%20%20which%20is%20**%7Bactual_input%20%2F%20naive_input%3A.1f%7D**%20times%20as%20many%2C%20and%20costs%0A%20%20%20%20**%7Bactual_input%20%2F%201_000_000%20*%20INPUT_PER_MILLION%3A.4f%7D**%20dollars%20against%0A%20%20%20%20**%7Bnaive_input%20%2F%201_000_000%20*%20INPUT_PER_MILLION%3A.4f%7D**.%0A%0A%20%20%20%20The%20ratio%20is%20not%20a%20constant.%20At%20%7BTURNS%7D%20turns%20it%20is%0A%20%20%20%20**%7Bactual_input%20%2F%20naive_input%3A.1f%7D**%3B%20the%20longer%20the%20conversation%20runs%20the%20worse%20it%0A%20%20%20%20gets%2C%20because%20turn%2040%20resends%2039%20turns.%20Long%20agent%20runs%20are%20expensive%20for%20this%20reason%0A%20%20%20%20before%20anything%20goes%20wrong.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20change%20%60TURNS%60%20to%2040%20and%20read%20the%20ratio.%20Then%20find%20the%20number%20of%20turns%0A%20%20%20%20at%20which%20the%20conversation%20costs%2010%20times%20what%20typing%20it%20would%20suggest.%0A%0A%20%20%20%20%E2%9C%94%20**Check%20your%20understanding**%0A%0A%20%20%20%201.%20You%20raised%20%60max_tokens%60%20from%201024%20to%208192%20and%20sent%20the%20same%20request.%20What%20happened%20to%0A%20%20%20%20%20%20%20the%20cost%3F%0A%20%20%20%202.%20Name%20three%20things%20that%20count%20as%20input%20tokens%20on%20a%20request%20with%20a%20tool%20in%20it.%0A%20%20%20%203.%20Why%20does%20a%2040-turn%20conversation%20cost%20more%20than%20four%2010-turn%20conversations%20carrying%0A%20%20%20%20%20%20%20the%20same%20text%3F%0A%20%20%20%204.%20Somebody%20estimates%20their%20bill%20with%20%60tiktoken%60%20and%20gets%205%20dollars.%20Which%20direction%20is%0A%20%20%20%20%20%20%20the%20real%20number%20in%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%2039.%20Embeddings%20and%20similarity%0A%0A%20%20%20%20%3E%20**Advanced**%0A%20%20%20%20%3E%0A%20%20%20%20%3E%20Nothing%20in%20this%20chapter%2C%20and%20nothing%20in%20any%20assignment%2C%20needs%20this%20section.%20It%20is%0A%20%20%20%20%3E%20here%20because%20*find%20the%20documents%20that%20are%20about%20the%20same%20thing%20as%20this%20one*%20is%20a%0A%20%20%20%20%3E%20question%20people%20expect%20a%20model%20to%20answer%2C%20and%20the%20machinery%20underneath%20is%20two%20ideas%0A%20%20%20%20%3E%20you%20can%20read%20completely.%0A%0A%20%20%20%20An%20**embedding**%20is%20a%20list%20of%20numbers%20standing%20for%20a%20piece%20of%20text%2C%20arranged%20so%20that%0A%20%20%20%20texts%20about%20the%20same%20thing%20get%20similar%20lists.%20Once%20text%20is%20numbers%2C%20*similar*%20becomes%0A%20%20%20%20arithmetic.%0A%0A%20%20%20%20To%20see%20the%20arithmetic%20without%20a%20model%2C%20the%20section%20below%20builds%20the%20numbers%20by%20counting%0A%20%20%20%20words.%20That%20is%20a%20poor%20embedding%20and%20a%20perfectly%20good%20demonstration%2C%20because%20the%0A%20%20%20%20similarity%20step%20is%20identical%20either%20way.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Text%20becomes%20a%20vector%0A%0A%20%20%20%20Take%20the%20list%20of%20every%20word%20that%20appears%20anywhere%2C%20then%20count%20each%20word%20in%20each%0A%20%20%20%20sentence.%20Each%20sentence%20becomes%20one%20row%20of%20counts.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20SENTENCES%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%22the%20order%20shipped%20late%22%2C%0A%20%20%20%20%20%20%20%20%22the%20shipment%20arrived%20late%22%2C%0A%20%20%20%20%20%20%20%20%22the%20order%20shipped%20on%20time%22%2C%0A%20%20%20%20%20%20%20%20%22freight%20charges%20rose%20in%20July%22%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20VOCABULARY%20%3D%20sorted(%7Bword%20for%20sentence%20in%20SENTENCES%20for%20word%20in%20sentence.split()%7D)%0A%0A%20%20%20%20def%20to_vector(sentence)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Count%20each%20vocabulary%20word%20in%20one%20sentence.%22%22%22%0A%20%20%20%20%20%20%20%20words%20%3D%20sentence.split()%0A%20%20%20%20%20%20%20%20return%20%5Bwords.count(term)%20for%20term%20in%20VOCABULARY%5D%0A%0A%20%20%20%20VOCABULARY%0A%20%20%20%20return%20SENTENCES%2C%20VOCABULARY%2C%20to_vector%0A%0A%0A%40app.cell%0Adef%20_(SENTENCES%2C%20to_vector)%3A%0A%20%20%20%20vectors%20%3D%20%5Bto_vector(sentence)%20for%20sentence%20in%20SENTENCES%5D%0A%0A%20%20%20%20vectors%5B0%5D%2C%20vectors%5B1%5D%0A%20%20%20%20return%20(vectors%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(VOCABULARY%2C%20mo%2C%20vectors)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20The%20vocabulary%20holds%20**%7Blen(VOCABULARY)%7D**%20words%2C%20so%20every%20sentence%20became%20a%20list%20of%0A%20%20%20%20**%7Blen(vectors%5B0%5D)%7D**%20numbers%2C%20most%20of%20them%20zero.%20The%20first%20two%20sentences%20differ%20in%0A%20%20%20%20**%7Bsum(1%20for%20x%2C%20y%20in%20zip(vectors%5B0%5D%2C%20vectors%5B1%5D)%20if%20x%20!%3D%20y)%7D**%20positions%2C%20and%20the%20two%0A%20%20%20%20words%20they%20share%20are%20*the*%20and%20*late*.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20add%20a%20sentence%20of%20your%20own%20and%20print%20the%20vocabulary%20length%20before%20and%0A%20%20%20%20after.%20Then%20explain%20what%20happens%20to%20the%20length%20of%20every%20existing%20vector.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Cosine%20similarity%0A%0A%20%20%20%20The%20measure%20everybody%20uses%20is%20the%20cosine%20of%20the%20angle%20between%20two%20vectors.%20It%20runs%20from%0A%20%20%20%200%20for%20nothing%20in%20common%20to%201%20for%20pointing%20the%20same%20way%2C%20and%20it%20ignores%20length%2C%20which%20is%0A%20%20%20%20what%20stops%20a%20long%20document%20from%20looking%20similar%20to%20everything.%0A%0A%20%20%20%20Multiply%20the%20pairs%20and%20add%20them%20up%2C%20then%20divide%20by%20the%20two%20lengths.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20def%20cosine(a%2C%20b)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Cosine%20similarity%20of%20two%20vectors%20of%20the%20same%20length.%22%22%22%0A%20%20%20%20%20%20%20%20dot%20%3D%20sum(x%20*%20y%20for%20x%2C%20y%20in%20zip(a%2C%20b))%0A%20%20%20%20%20%20%20%20return%20dot%20%2F%20((sum(x%20*%20x%20for%20x%20in%20a)%20**%200.5)%20*%20(sum(y%20*%20y%20for%20y%20in%20b)%20**%200.5))%0A%0A%20%20%20%20cosine(%5B1%2C%201%2C%200%5D%2C%20%5B1%2C%201%2C%200%5D)%2C%20cosine(%5B1%2C%200%2C%200%5D%2C%20%5B0%2C%201%2C%200%5D)%0A%20%20%20%20return%20(cosine%2C)%0A%0A%0A%40app.cell%0Adef%20_(SENTENCES%2C%20cosine%2C%20vectors)%3A%0A%20%20%20%20similarity_table%20%3D%20%22%5Cn%22.join(%0A%20%20%20%20%20%20%20%20f%22%7Bcosine(vectors%5B0%5D%2C%20vectors%5Bindex%5D)%3A.2f%7D%20%20%7BSENTENCES%5Bindex%5D%7D%22%0A%20%20%20%20%20%20%20%20for%20index%20in%20range(len(SENTENCES))%0A%20%20%20%20)%0A%0A%20%20%20%20print(f%22similarity%20to%3A%20%7BSENTENCES%5B0%5D%7D%5Cn%5Cn%7Bsimilarity_table%7D%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(SENTENCES%2C%20cosine%2C%20mo%2C%20vectors)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20Against%20*%7BSENTENCES%5B0%5D%7D*%2C%20the%20paraphrase%20scores%0A%20%20%20%20**%7Bcosine(vectors%5B0%5D%2C%20vectors%5B1%5D)%3A.2f%7D**%2C%20the%20opposite%20meaning%20scores%0A%20%20%20%20**%7Bcosine(vectors%5B0%5D%2C%20vectors%5B2%5D)%3A.2f%7D**%2C%20and%20the%20unrelated%20sentence%20scores%0A%20%20%20%20**%7Bcosine(vectors%5B0%5D%2C%20vectors%5B3%5D)%3A.2f%7D**.%0A%0A%20%20%20%20Read%20the%20first%20two%20numbers%20again.%20*%7BSENTENCES%5B2%5D%7D*%20means%20the%20opposite%20of%0A%20%20%20%20*%7BSENTENCES%5B0%5D%7D*%20and%20it%20scores%20**%7Bcosine(vectors%5B0%5D%2C%20vectors%5B2%5D)%20-%20cosine(vectors%5B0%5D%2C%20vectors%5B1%5D)%3A.2f%7D**%0A%20%20%20%20higher%20than%20the%20sentence%20that%20means%20the%20same%20thing%2C%20because%20it%20shares%20three%20words%20out%0A%20%20%20%20of%20four.%20Counting%20words%20measures%20overlap%20and%20has%20no%20access%20to%20meaning.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20work%20out%20by%20hand%20why%20the%20last%20sentence%20scores%20what%20it%20does%2C%20then%20check%0A%20%20%20%20it.%20Then%20write%20two%20sentences%20that%20mean%20the%20same%20thing%20and%20score%200.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20What%20an%20embedding%20model%20changes%0A%0A%20%20%20%20Here%20are%20two%20sentences%20that%20mean%20the%20same%20thing%20with%20no%20word%20in%20common.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(cosine%2C%20to_vector%2C%20vectors)%3A%0A%20%20%20%20delayed%20%3D%20to_vector(%22delivery%20was%20delayed%22)%0A%20%20%20%20delayed_similarity%20%3D%200.0%20if%20not%20any(delayed)%20else%20cosine(vectors%5B0%5D%2C%20delayed)%0A%0A%20%20%20%20delayed%2C%20delayed_similarity%0A%20%20%20%20return%20(delayed_similarity%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(delayed_similarity%2C%20mo)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20Their%20similarity%20is%20**%7Bdelayed_similarity%3A.2f%7D**%2C%20because%20the%20vocabulary%20above%20has%20no%0A%20%20%20%20place%20for%20words%20that%20never%20appeared.%20That%20is%20the%20ceiling%20on%20counting.%0A%0A%20%20%20%20An%20embedding%20model%20produces%20the%20vector%20instead%20of%20counting%2C%20and%20it%20is%20trained%20so%20that%0A%20%20%20%20*delivery%20was%20delayed*%20lands%20near%20*the%20order%20shipped%20late*%20without%20sharing%20a%20word.%20The%0A%20%20%20%20vector%20is%20longer%2C%20a%20few%20hundred%20to%20a%20few%20thousand%20numbers%2C%20and%20none%20of%20the%20positions%0A%20%20%20%20means%20anything%20you%20can%20name.%0A%0A%20%20%20%20**What%20does%20not%20change%20is%20everything%20after%20that.**%20The%20vectors%20go%20into%20the%20same%0A%20%20%20%20%60cosine%60%20function%20above%2C%20unmodified.%20Search%20over%20documents%2C%20clustering%2C%20and%20*find%20me%0A%20%20%20%20ones%20like%20this*%20are%20all%20this%20measure%20applied%20to%20better%20vectors.%0A%0A%20%20%20%20**Where%20the%20vectors%20come%20from%20is%20a%20separate%20question.**%20The%20Claude%20API%20has%20no%0A%20%20%20%20embeddings%20endpoint%2C%20so%20an%20embedding%20comes%20from%20a%20different%20provider's%20API%20or%20from%20a%0A%20%20%20%20model%20you%20run%20locally.%20That%20is%20a%20decision%20with%20a%20cost%20and%20a%20dependency%20attached%2C%20which%0A%20%20%20%20is%20one%20reason%20nothing%20in%20this%20course%20requires%20it.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20ask%20your%20agent%20which%20local%20Python%20libraries%20produce%20sentence%20embeddings%0A%20%20%20%20with%20no%20API%20key%2C%20and%20what%20one%20of%20them%20would%20add%20to%20your%20project's%20install%20size.%20Then%0A%20%20%20%20decide%20whether%20you%20want%20it.%0A%0A%20%20%20%20%E2%9C%94%20**Check%20your%20understanding**%0A%0A%20%20%20%201.%20Two%20sentences%20share%20three%20words%20out%20of%20four%20and%20mean%20opposite%20things.%20What%20does%0A%20%20%20%20%20%20%20counting%20words%20give%20you%2C%20and%20why%3F%0A%20%20%20%202.%20Cosine%20similarity%20ignores%20the%20length%20of%20a%20vector.%20What%20problem%20does%20that%20solve%3F%0A%20%20%20%203.%20You%20swap%20counted%20vectors%20for%20a%20real%20embedding%20model.%20Which%20part%20of%20the%20code%20changes%3F%0A%20%20%20%204.%20Give%20a%20business%20question%20you%20could%20answer%20with%20document%20similarity%2C%20and%20one%20you%0A%20%20%20%20%20%20%20could%20not.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%20Chapter%20challenge%0A%0A%20%20%20%20**An%20agent%20that%20looks%20something%20up.**%20Nothing%20here%20is%20due.%20It%20exists%20because%20every%20section%20above%20teaches%20one%20part%20of%20a%20round%0A%20%20%20%20trip%2C%20and%20a%20working%20agent%20needs%20all%20of%20them%20at%20once.%0A%0A%20%20%20%20**Build%20an%20agent%20with%20one%20tool%2C%20and%20let%20the%20model%20decide%20when%20to%20call%20it.**%20The%20tool%0A%20%20%20%20calls%20a%20public%20API%20that%20needs%20no%20key.%20Kraken's%20ticker%20from%20section%2031%20works%3B%20so%20does%0A%20%20%20%20anything%20else%20you%20can%20reach%20without%20signing%20up.%0A%0A%20%20%20%20Done%20looks%20like%20a%20program%20that%3A%0A%0A%20%20%20%20-%20reads%20a%20key%20from%20%60.env%60%20and%20fails%20loudly%20when%20it%20is%20missing%0A%20%20%20%20-%20defines%20one%20tool%2C%20with%20a%20description%20you%20wrote%20and%20a%20schema%20that%20matches%20your%20function%0A%20%20%20%20-%20runs%20the%20loop%20until%20%60stop_reason%60%20is%20%60end_turn%60%2C%20or%20until%20a%20step%20limit%20you%20set%0A%20%20%20%20-%20prints%20the%20total%20input%20and%20output%20tokens%20for%20the%20whole%20run%2C%20and%20the%20cost%0A%20%20%20%20-%20answers%20a%20question%20that%20cannot%20be%20answered%20without%20the%20tool%0A%0A%20%20%20%20**Four%20decisions%20are%20yours%2C%20and%20the%20notebook%20cannot%20make%20them.**%0A%0A%20%20%20%201.%20**What%20is%20the%20step%20limit%2C%20and%20what%20happens%20when%20you%20hit%20it%3F**%20Stopping%20silently%20and%0A%20%20%20%20%20%20%20stopping%20with%20a%20message%20are%20different%20programs.%0A%20%20%20%202.%20**What%20does%20your%20tool%20return%20when%20the%20lookup%20fails%3F**%20A%20%60None%60%2C%20a%20string%20saying%20so%2C%0A%20%20%20%20%20%20%20or%20a%20%60tool_result%60%20with%20%60is_error%60%20set.%20Each%20one%20makes%20the%20model%20behave%20differently.%0A%20%20%20%203.%20**What%20goes%20in%20the%20system%20prompt%3F**%20Write%20it%2C%20then%20delete%20it%20and%20run%20again%2C%20and%20describe%0A%20%20%20%20%20%20%20what%20changed.%0A%20%20%20%204.%20**How%20do%20you%20know%20the%20answer%20is%20right%3F**%20The%20model%20will%20produce%20a%20number.%20Name%20the%0A%20%20%20%20%20%20%20check%20that%20would%20catch%20it%20being%20wrong.%0A%0A%20%20%20%20Hand%20in%20one%20file%20plus%20a%20markdown%20cell%20naming%20the%20four%20decisions%20and%20what%20the%0A%20%20%20%20alternative%20would%20have%20been.%0A%0A%20%20%20%20**Going%20further.**%20Give%20it%20a%20second%20tool%20and%20a%20question%20that%20needs%20both%2C%20then%20read%20the%0A%20%20%20%20conversation%20to%20see%20what%20order%20it%20chose.%20Then%20add%20a%20tool%20that%20can%20fail%20and%20make%20it%20fail%0A%20%20%20%20on%20purpose.%20Then%20run%20the%20same%20question%20five%20times%20and%20count%20how%20many%20times%20the%20sequence%0A%20%20%20%20of%20tool%20calls%20was%20identical.%20Then%20send%20the%20whole%20thing%20to%20somebody%20outside%20this%20course%2C%0A%20%20%20%20watch%20them%20use%20it%2C%20and%20write%20down%20what%20they%20asked%20for%20that%20it%20could%20not%20do.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%20%F0%9F%A4%96%20Ask%20your%20agent%0A%0A%20%20%20%20Two%20different%20uses%2C%20and%20the%20second%20one%20is%20the%20one%20this%20course%20grades.%0A%0A%20%20%20%20%23%23%23%20Ask%20it%20about%20what%20this%20chapter%20left%20out%0A%0A%20%20%20%20Every%20one%20of%20these%20is%20real%20and%20had%20no%20room%20above.%20Run%20whatever%20comes%20back.%0A%0A%20%20%20%20-%20Ask%20what%20**streaming**%20is%2C%20why%20a%20long%20answer%20arrives%20in%20pieces%2C%20and%20what%0A%20%20%20%20%20%20%60client.messages.stream(...)%60%20gives%20you%20that%20%60create%60%20does%20not.%0A%20%20%20%20-%20Ask%20what%20**prompt%20caching**%20is%2C%20and%20why%20resending%20the%20same%205%2C000-token%20schema%20on%0A%20%20%20%20%20%20every%20turn%20can%20cost%20a%20tenth%20as%20much%20when%20you%20mark%20it.%20Section%2034's%20arithmetic%20is%20the%0A%20%20%20%20%20%20reason%20to%20care.%0A%20%20%20%20-%20Ask%20what%20**structured%20outputs**%20are%2C%20and%20how%20you%20get%20JSON%20back%20that%20matches%20a%20schema%0A%20%20%20%20%20%20you%20specified%20instead%20of%20JSON%20you%20have%20to%20hope%20about.%0A%20%20%20%20-%20Ask%20what%20a%20**429**%20is%2C%20what%20the%20library%20already%20does%20about%20it%20on%20your%20behalf%2C%20and%20how%0A%20%20%20%20%20%20many%20times%20it%20retries%20before%20giving%20up.%0A%20%20%20%20-%20Ask%20what%20%60stop_reason%60%20of%20%60refusal%60%20means%2C%20what%20is%20in%20%60stop_details%60%2C%20and%20why%0A%20%20%20%20%20%20retrying%20the%20identical%20request%20is%20the%20wrong%20response%20to%20it.%0A%0A%20%20%20%20%23%23%23%20Watch%20what%20it%20decides%20without%20telling%20you%0A%0A%20%20%20%20Give%20it%20this%2C%20exactly%3A%0A%0A%20%20%20%20%3E%20*%22Write%20Python%20that%20calls%20a%20model%20and%20summarizes%20a%20customer%20complaint.%22*%0A%0A%20%20%20%20Then%20read%20what%20came%20back%20and%20answer%20four%20questions.%0A%0A%20%20%20%201.%20**Which%20model%20id%20did%20it%20use%2C%20and%20where%20did%20that%20come%20from%3F**%20You%20did%20not%20name%20one.%0A%20%20%20%202.%20**What%20is%20%60max_tokens%60%2C%20and%20how%20did%20it%20pick%20that%20number%3F**%0A%20%20%20%203.%20**Where%20did%20it%20put%20the%20key%3F**%20Look%20for%20a%20literal%20string%2C%20an%20%60os.getenv%60%2C%20or%20nothing%0A%20%20%20%20%20%20%20at%20all.%0A%20%20%20%204.%20**What%20does%20it%20do%20when%20the%20reply%20is%20not%20a%20single%20text%20block%2C%20or%20when%20the%20request%0A%20%20%20%20%20%20%20fails%3F**%20Read%20the%20code%2C%20and%20ignore%20any%20comment%20claiming%20it%20handles%20errors.%0A%0A%20%20%20%20You%20asked%20for%20one%20thing%20and%20got%20four%20or%20five%20decisions.%20That%20gap%20is%20the%20subject%20of%20this%0A%20%20%20%20course%2C%20and%20it%20does%20not%20go%20away%20as%20the%20models%20get%20better.%20It%20gets%20quieter.%0A%0A%20%20%20%20%F0%9F%91%89%20**Try%20it%3A**%20send%20the%20same%20sentence%20twice%2C%20in%20two%20separate%20conversations.%20Compare%20the%0A%20%20%20%20two%20%60max_tokens%60%20values.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20---%0A%20%20%20%20%23%23%20Chapter%20D%20checklist%0A%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20send%20an%20HTTP%20request%20and%20read%20the%20status%2C%20the%20headers%20and%20the%20body%0A%20%20%20%20-%20%5B%20%5D%20I%20check%20the%20shape%20of%20a%20response%20before%20I%20index%20into%20it%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20tell%20a%20%60KeyError%60%20from%20a%20parsing%20failure%2C%20and%20explain%20what%20each%20one%20means%0A%20%20%20%20-%20%5B%20%5D%20I%20check%20the%20type%20of%20each%20value%20in%20a%20response%2C%20because%20a%20price%20can%20arrive%20as%20text%0A%20%20%20%20-%20%5B%20%5D%20When%20a%20library%20makes%20the%20request%20for%20me%2C%20I%20check%20for%20an%20empty%20result%20before%20I%20index%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20set%20up%20%60.env%60%2C%20%60.gitignore%60%20and%20%60.env.example%60%2C%20and%20explain%20what%20each%20is%20for%0A%20%20%20%20-%20%5B%20%5D%20I%20read%20a%20key%20with%20%60os.getenv%60%20and%20fail%20loudly%20when%20it%20is%20missing%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20name%20four%20places%20a%20key%20must%20never%20go%2C%20and%20what%20to%20do%20when%20one%20leaks%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20name%20the%20four%20parts%20of%20a%20request%20to%20a%20model%0A%20%20%20%20-%20%5B%20%5D%20I%20know%20%60system%60%20is%20a%20top-level%20parameter%2C%20and%20what%20a%20naive%20port%20does%20to%20it%0A%20%20%20%20-%20%5B%20%5D%20I%20read%20a%20reply%20by%20looping%20over%20its%20blocks%20and%20checking%20the%20type%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20explain%20why%20the%20model%20has%20no%20memory%20and%20where%20the%20memory%20actually%20is%0A%20%20%20%20-%20%5B%20%5D%20I%20know%20which%20line%20makes%20a%20chat%20loop%20remember%2C%20and%20what%20breaks%20when%20it%20is%20missing%0A%20%20%20%20-%20%5B%20%5D%20I%20run%20a%20prompt%20more%20than%20once%20before%20I%20believe%20a%20number%20that%20came%20out%20of%20it%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20write%20a%20tool%20definition%20whose%20schema%20matches%20my%20function%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20run%20the%20tool%20the%20model%20asked%20for%20and%20send%20the%20result%20back%20correctly%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20write%20the%20agent%20loop%20and%20state%20what%20its%20stop%20condition%20is%0A%20%20%20%20-%20%5B%20%5D%20I%20put%20a%20step%20limit%20on%20any%20loop%20that%20calls%20a%20model%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20read%20%60usage%60%20off%20a%20reply%20and%20turn%20it%20into%20a%20cost%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20explain%20why%20turn%2040%20of%20a%20conversation%20costs%20more%20than%20turn%201%0A%20%20%20%20-%20%5B%20%5D%20I%20can%20explain%20what%20an%20embedding%20is%20for%20without%20doing%20any%20of%20the%20math%0A%0A%20%20%20%20This%20is%20the%20last%20chapter%20of%20the%20Python%20handbook.%20The%20SQL%20handbook%20covers%20the%20database%0A%20%20%20%20side%2C%20and%20the%20two%20meet%20whenever%20a%20question%20needs%20both.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
fe0e3f84e91ed127c00e0d1d4bd3e6bf