{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# Functions as Values\n",
    "\n",
    "**Agentic AI** · playground\n",
    "\n",
    "*A function in Python is an object: it can be stored in a variable or a dictionary, passed to another function and returned from one. The notebook covers `lambda`, closures and their late binding, `*args` / `**kwargs`, and the tool registry `dict[str, Callable]` on which every hand-written agent loop of the course rests.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1",
   "metadata": {},
   "source": [
    "## First-Class Functions\n",
    "\n",
    "**A first-class function is a function that the language treats as an ordinary value:** it can be bound to a name, stored in a data structure, passed as an argument and returned as a result. Python functions have this property. The statement `def f(): ...` creates a function object and binds the name `f` to it; the call `f()` is a separate operation on that object.\n",
    "\n",
    "The property solves a problem that every agent has. A language model does not call a function. It emits **text** that names a tool and its arguments, for example `{\"name\": \"add\", \"arguments\": {\"a\": 2, \"b\": 3}}`. The program around the model has to convert the name `\"add\"` into the function to execute. With first-class functions, this conversion is one dictionary lookup."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [],
   "source": [
    "def add(a: float, b: float) -> float:\n",
    "    \"\"\"Return the sum of a and b.\"\"\"\n",
    "    return a + b\n",
    "\n",
    "\n",
    "f = add                 # no parentheses: the function object itself, not a call\n",
    "print(f)                # a function object\n",
    "print(f(2, 3))          # the same function, called through another name\n",
    "print(f is add)         # both names refer to one object"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3",
   "metadata": {},
   "source": [
    "A function object carries data about itself. The attributes `__name__` and `__doc__` hold the name and the docstring (the string literal directly below the `def` line). A tool-calling agent sends exactly these two items to the model as the description of a tool."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f.__name__)\n",
    "print(f.__doc__)\n",
    "print(f.__annotations__)      # parameter and return annotations, as a dictionary"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5",
   "metadata": {},
   "source": [
    "## Functions as Arguments\n",
    "\n",
    "**A higher-order function is a function that takes a function as an argument or returns one.** The built-in functions `sorted`, `max` and `min` are higher-order: their parameter `key` receives a function that maps an element to the value by which the elements are compared.\n",
    "\n",
    "The example ranks retrieved documents by a similarity score. This is the last step of every retriever in the course."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "hits = [(\"doc-a\", 0.31), (\"doc-b\", 0.87), (\"doc-c\", 0.55)]\n",
    "\n",
    "\n",
    "def score(hit):\n",
    "    return hit[1]\n",
    "\n",
    "\n",
    "print(sorted(hits, key=score, reverse=True))\n",
    "print(max(hits, key=score))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7",
   "metadata": {},
   "source": [
    "## Lambda Expressions\n",
    "\n",
    "**A lambda expression creates a function without a name from a single expression:** `lambda hit: hit[1]` is the same function as `score` above. A lambda expression is used where a function is needed once and a `def` statement would only add a name. The body is restricted to one expression; a function with statements needs `def`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(sorted(hits, key=lambda hit: hit[1], reverse=True))\n",
    "\n",
    "square = lambda x: x * x          # legal, but `def square(x): return x * x` is preferred\n",
    "print(square(4), square.__name__)  # a lambda has no name of its own"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9",
   "metadata": {},
   "source": [
    "## Closures\n",
    "\n",
    "**A closure is a function together with the variables of the enclosing scope that the function uses.** When a function is defined inside another function and refers to a local variable of the outer function, the inner function keeps access to that variable after the outer function has returned.\n",
    "\n",
    "A closure solves the problem of giving a function data that the caller must not supply. In an agent, a tool often needs a resource — a database connection, a working directory, an API key — that the model must not choose. The closure fixes the resource when the tool is created, and the model supplies only the remaining arguments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_reader(directory: str):\n",
    "    \"\"\"Return a tool that reads files, restricted to one directory.\"\"\"\n",
    "    def read_file(name: str) -> str:\n",
    "        return f\"(content of {directory}/{name})\"      # `directory` comes from the enclosing scope\n",
    "    return read_file\n",
    "\n",
    "\n",
    "read_notes = make_reader(\"/srv/notes\")\n",
    "print(read_notes(\"todo.md\"))\n",
    "print(read_notes.__closure__[0].cell_contents)          # the captured variable is stored with the function"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11",
   "metadata": {},
   "source": [
    "### Late Binding\n",
    "\n",
    "**A closure stores the variable, not the value the variable had at definition time.** The value is read when the inner function is called. The documentation states the rule for a lambda defined in a loop: the variable *\"is accessed when the lambda is called — not when it is defined\"* ([Python Software Foundation, 2023, *Programming FAQ*](https://docs.python.org/3.12/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result)).\n",
    "\n",
    "The rule is visible without a loop: the variable is reassigned after the function is defined, and the call reads the new value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12",
   "metadata": {},
   "outputs": [],
   "source": [
    "city = \"Berlin\"\n",
    "report = lambda: f\"weather report for {city}\"\n",
    "city = \"Paris\"\n",
    "print(report())          # Paris: `city` is read at the time of the call"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12a",
   "metadata": {},
   "source": [
    "The defect appears when tools are created in a loop. The key of each entry is evaluated when the entry is created, the body of the lambda only when the tool is called. All three functions share the one variable `city`, and after the loop its value is `\"Rome\"`.\n",
    "\n",
    "At module level, `city` is a global variable, so these lambdas have no closure at all (`__closure__` is `None`): each call looks up the global name `city`. The cause is the same as for a closure — one shared variable that changes after the function is defined."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12b",
   "metadata": {},
   "outputs": [],
   "source": [
    "weather_tools = {}\n",
    "for city in [\"Berlin\", \"Paris\", \"Rome\"]:\n",
    "    weather_tools[city] = lambda: f\"weather report for {city}\"\n",
    "\n",
    "for name, tool in weather_tools.items():\n",
    "    print(f\"{name:6} -> {tool()}\")      # every key, the same report: Rome\n",
    "print(weather_tools[\"Berlin\"].__closure__)   # None: the lambda reads the global `city`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13",
   "metadata": {},
   "source": [
    "#### Factory Function\n",
    "\n",
    "**A factory function is a function that creates and returns a new object; it is called for the object it builds, not for a computed result.** `make_weather_tool` below is a factory function: each call returns a new tool function.\n",
    "\n",
    "A factory function repairs the defect by giving each tool a variable of its own. A call of `make_weather_tool` creates a new local scope, and in it a new parameter `place`, bound to the argument when the call begins and never reassigned afterwards. The lambda created in that call captures this `place`. Three calls create three separate variables with the values `\"Berlin\"`, `\"Paris\"` and `\"Rome\"`. The closure still reads its variable at the time of the call — late binding remains — but no other code changes that variable.\n",
    "\n",
    "The loop variable `city` and the parameter `place` are different variables; the lambda inside the factory refers only to `place`. The cell shows the three captured cells: three distinct objects with three values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13a",
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_weather_tool(place):\n",
    "    return lambda: f\"weather report for {place}\"\n",
    "\n",
    "fixed_factory = {city: make_weather_tool(city) for city in [\"Berlin\", \"Paris\", \"Rome\"]} \n",
    "\n",
    "for name, tool in fixed_factory.items():\n",
    "    cell = tool.__closure__[0]\n",
    "    print(f\"{name:6} -> {tool()}   cell id {id(cell)}, contents {cell.cell_contents!r}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13b",
   "metadata": {},
   "source": [
    "#### Default Argument\n",
    "\n",
    "**A default argument also repairs the defect:** a default value is evaluated once, when the function is defined. In `lambda city=city: ...` the name `city` occurs twice and denotes two variables — on the left of `=` the lambda's own parameter, on the right the loop variable of the comprehension in the cell below, read once, when the lambda is created.\n",
    "\n",
    "The parameter is, however, part of the signature: `inspect.signature` returns `()` for the factory-made tool and `(city='Berlin')` for the default-argument tool. A registry that builds the tool description from the signature describes `city` to the model as an argument, and the model may then pass a city of its own choice. For tools, the factory function is the correct repair."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "14",
   "metadata": {},
   "outputs": [],
   "source": [
    "import inspect\n",
    "\n",
    "fixed_default = {city: lambda city=city: f\"weather report for {city}\" for city in [\"Berlin\", \"Paris\", \"Rome\"]}\n",
    "\n",
    "print(fixed_factory[\"Berlin\"](), inspect.signature(fixed_factory[\"Berlin\"]))\n",
    "print(fixed_default[\"Berlin\"](), inspect.signature(fixed_default[\"Berlin\"]))\n",
    "print(fixed_default[\"Berlin\"](city=\"Tokyo\"))     # the caller can override the captured value"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15",
   "metadata": {},
   "source": [
    "## Argument Packing and Unpacking\n",
    "\n",
    "**The operators `*` and `**` convert between a collection and the arguments of a call.** In a call, `f(*seq)` passes the elements of a sequence as positional arguments, and `f(**d)` passes the items of a dictionary as keyword arguments. In a definition, `*args` collects the surplus positional arguments into a tuple and `**kwargs` collects the surplus keyword arguments into a dictionary.\n",
    "\n",
    "`**` is the step that connects a model's tool call to a Python function: the model emits the arguments as a JSON object, `json.loads` converts the object to a dictionary, and `tool(**arguments)` calls the function with them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16",
   "metadata": {},
   "outputs": [],
   "source": [
    "arguments = {\"a\": 2, \"b\": 3}\n",
    "print(add(**arguments))               # the same call as add(a=2, b=3)\n",
    "\n",
    "\n",
    "def describe(*args, **kwargs):\n",
    "    return f\"positional: {args}, keyword: {kwargs}\"\n",
    "\n",
    "\n",
    "print(describe(1, 2, unit=\"km\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17",
   "metadata": {},
   "source": [
    "A call with a wrong or missing argument name raises `TypeError`. An agent loop converts this exception to an **observation** — the text that the model receives as the result of its action — so that the model can correct the call."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    add(**{\"a\": 2, \"c\": 3})\n",
    "except TypeError as e:\n",
    "    print(\"TypeError:\", e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19",
   "metadata": {},
   "source": [
    "## Tool Registry\n",
    "\n",
    "**A tool registry is a dictionary that maps the name of a tool to the function that implements it.** The agent loop receives a name and arguments from the model, finds the function in the registry and calls it. This procedure is called **dispatch**: the selection of the code to execute from a value known only at run time.\n",
    "\n",
    "<div class=\"mermaid-figure\" style=\"max-width:100%;overflow-x:auto\">\n",
    "<svg style=\"width:100%;max-width:995.3046875px;height:auto\" id=\"mmd-0\" xmlns=\"http://www.w3.org/2000/svg\" class=\"flowchart\" viewBox=\"0 0 995.3046875 186.1179656982422\" role=\"graphics-document document\" aria-roledescription=\"flowchart-v2\"><style>#mmd-0{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mmd-0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mmd-0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mmd-0 .error-icon{fill:#552222;}#mmd-0 .error-text{fill:#552222;stroke:#552222;}#mmd-0 .edge-thickness-normal{stroke-width:1px;}#mmd-0 .edge-thickness-thick{stroke-width:3.5px;}#mmd-0 .edge-pattern-solid{stroke-dasharray:0;}#mmd-0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mmd-0 .edge-pattern-dashed{stroke-dasharray:3;}#mmd-0 .edge-pattern-dotted{stroke-dasharray:2;}#mmd-0 .marker{fill:#333333;stroke:#333333;}#mmd-0 .marker.cross{stroke:#333333;}#mmd-0 svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mmd-0 p{margin:0;}#mmd-0 .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mmd-0 .cluster-label text{fill:#333;}#mmd-0 .cluster-label span{color:#333;}#mmd-0 .cluster-label span p{background-color:transparent;}#mmd-0 .label text,#mmd-0 span{fill:#333;color:#333;}#mmd-0 .node rect,#mmd-0 .node circle,#mmd-0 .node ellipse,#mmd-0 .node polygon,#mmd-0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mmd-0 .rough-node .label text,#mmd-0 .node .label text,#mmd-0 .image-shape .label,#mmd-0 .icon-shape .label{text-anchor:middle;}#mmd-0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mmd-0 .rough-node .label,#mmd-0 .node .label,#mmd-0 .image-shape .label,#mmd-0 .icon-shape .label{text-align:center;}#mmd-0 .node.clickable{cursor:pointer;}#mmd-0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mmd-0 .arrowheadPath{fill:#333333;}#mmd-0 .edgePath .path{stroke:#333333;stroke-width:1px;}#mmd-0 .flowchart-link{stroke:#333333;fill:none;}#mmd-0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mmd-0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mmd-0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mmd-0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mmd-0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mmd-0 .cluster text{fill:#333;}#mmd-0 .cluster span{color:#333;}#mmd-0 .node .collapsed-indicator{fill:#aaaa33;stroke:none;opacity:0.6;}#mmd-0 .node .collapsed-separator{stroke:#aaaa33;stroke-width:0.75px;}#mmd-0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mmd-0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mmd-0 rect.text{fill:none;stroke-width:0;}#mmd-0 .icon-shape,#mmd-0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mmd-0 .icon-shape p,#mmd-0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mmd-0 .icon-shape .label rect,#mmd-0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mmd-0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mmd-0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mmd-0 .node .neo-node{stroke:#9370DB;}#mmd-0 [data-look=\"neo\"].node rect,#mmd-0 [data-look=\"neo\"].cluster rect,#mmd-0 [data-look=\"neo\"].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-0 [data-look=\"neo\"].swimlane.cluster rect{filter:none;}#mmd-0 [data-look=\"neo\"].node path{stroke:#9370DB;stroke-width:1px;}#mmd-0 [data-look=\"neo\"].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-0 [data-look=\"neo\"].node .neo-line path{stroke:#9370DB;filter:none;}#mmd-0 [data-look=\"neo\"].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-0 [data-look=\"neo\"].node circle .state-start{fill:#000000;}#mmd-0 [data-look=\"neo\"].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-0 [data-look=\"neo\"].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-0 :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}</style><g><marker id=\"mmd-0_flowchart-v2-pointEnd\" class=\"marker flowchart-v2\" viewBox=\"0 0 10 10\" refX=\"5\" refY=\"5\" markerUnits=\"userSpaceOnUse\" markerWidth=\"8\" markerHeight=\"8\" orient=\"auto\"><path d=\"M 0 0 L 10 5 L 0 10 z\" class=\"arrowMarkerPath\" style=\"stroke-width: 1; stroke-dasharray: 1, 0;\"></path></marker><marker id=\"mmd-0_flowchart-v2-pointStart\" class=\"marker flowchart-v2\" viewBox=\"0 0 10 10\" refX=\"4.5\" refY=\"5\" markerUnits=\"userSpaceOnUse\" markerWidth=\"8\" markerHeight=\"8\" orient=\"auto\"><path d=\"M 0 5 L 10 10 L 10 0 z\" class=\"arrowMarkerPath\" style=\"stroke-width: 1; stroke-dasharray: 1, 0;\"></path></marker><marker id=\"mmd-0_flowchart-v2-pointEnd-margin\" class=\"marker flowchart-v2\" viewBox=\"0 0 11.5 14\" refX=\"11.5\" refY=\"7\" markerUnits=\"userSpaceOnUse\" markerWidth=\"10.5\" markerHeight=\"14\" orient=\"auto\"><path d=\"M 0 0 L 11.5 7 L 0 14 z\" class=\"arrowMarkerPath\" style=\"stroke-width: 0; stroke-dasharray: 1, 0;\"></path></marker><marker id=\"mmd-0_flowchart-v2-pointStart-margin\" class=\"marker flowchart-v2\" viewBox=\"0 0 11.5 14\" refX=\"1\" refY=\"7\" markerUnits=\"userSpaceOnUse\" markerWidth=\"11.5\" markerHeight=\"14\" orient=\"auto\"><polygon points=\"0,7 11.5,14 11.5,0\" class=\"arrowMarkerPath\" style=\"stroke-width: 0; stroke-dasharray: 1, 0;\"></polygon></marker><marker id=\"mmd-0_flowchart-v2-circleEnd\" class=\"marker flowchart-v2\" viewBox=\"0 0 10 10\" refX=\"11\" refY=\"5\" markerUnits=\"userSpaceOnUse\" markerWidth=\"11\" markerHeight=\"11\" orient=\"auto\"><circle cx=\"5\" cy=\"5\" r=\"5\" class=\"arrowMarkerPath\" style=\"stroke-width: 1; stroke-dasharray: 1, 0;\"></circle></marker><marker id=\"mmd-0_flowchart-v2-circleStart\" class=\"marker flowchart-v2\" viewBox=\"0 0 10 10\" refX=\"-1\" refY=\"5\" markerUnits=\"userSpaceOnUse\" markerWidth=\"11\" markerHeight=\"11\" orient=\"auto\"><circle cx=\"5\" cy=\"5\" r=\"5\" class=\"arrowMarkerPath\" style=\"stroke-width: 1; stroke-dasharray: 1, 0;\"></circle></marker><marker id=\"mmd-0_flowchart-v2-circleEnd-margin\" class=\"marker flowchart-v2\" viewBox=\"0 0 10 10\" refY=\"5\" refX=\"12.25\" markerUnits=\"userSpaceOnUse\" markerWidth=\"14\" markerHeight=\"14\" orient=\"auto\"><circle cx=\"5\" cy=\"5\" r=\"5\" class=\"arrowMarkerPath\" style=\"stroke-width: 0; stroke-dasharray: 1, 0;\"></circle></marker><marker id=\"mmd-0_flowchart-v2-circleStart-margin\" class=\"marker flowchart-v2\" viewBox=\"0 0 10 10\" refX=\"-2\" refY=\"5\" markerUnits=\"userSpaceOnUse\" markerWidth=\"14\" markerHeight=\"14\" orient=\"auto\"><circle cx=\"5\" cy=\"5\" r=\"5\" class=\"arrowMarkerPath\" style=\"stroke-width: 0; stroke-dasharray: 1, 0;\"></circle></marker><marker id=\"mmd-0_flowchart-v2-crossEnd\" class=\"marker cross flowchart-v2\" viewBox=\"0 0 11 11\" refX=\"12\" refY=\"5.2\" markerUnits=\"userSpaceOnUse\" markerWidth=\"11\" markerHeight=\"11\" orient=\"auto\"><path d=\"M 1,1 l 9,9 M 10,1 l -9,9\" class=\"arrowMarkerPath\" style=\"stroke-width: 2; stroke-dasharray: 1, 0;\"></path></marker><marker id=\"mmd-0_flowchart-v2-crossStart\" class=\"marker cross flowchart-v2\" viewBox=\"0 0 11 11\" refX=\"-1\" refY=\"5.2\" markerUnits=\"userSpaceOnUse\" markerWidth=\"11\" markerHeight=\"11\" orient=\"auto\"><path d=\"M 1,1 l 9,9 M 10,1 l -9,9\" class=\"arrowMarkerPath\" style=\"stroke-width: 2; stroke-dasharray: 1, 0;\"></path></marker><marker id=\"mmd-0_flowchart-v2-crossEnd-margin\" class=\"marker cross flowchart-v2\" viewBox=\"0 0 15 15\" refX=\"17.7\" refY=\"7.5\" markerUnits=\"userSpaceOnUse\" markerWidth=\"12\" markerHeight=\"12\" orient=\"auto\"><path d=\"M 1,1 L 14,14 M 1,14 L 14,1\" class=\"arrowMarkerPath\" style=\"stroke-width: 2.5;\"></path></marker><marker id=\"mmd-0_flowchart-v2-crossStart-margin\" class=\"marker cross flowchart-v2\" viewBox=\"0 0 15 15\" refX=\"-3.5\" refY=\"7.5\" markerUnits=\"userSpaceOnUse\" markerWidth=\"12\" markerHeight=\"12\" orient=\"auto\"><path d=\"M 1,1 L 14,14 M 1,14 L 14,1\" class=\"arrowMarkerPath\" style=\"stroke-width: 2.5; stroke-dasharray: 1, 0;\"></path></marker><g class=\"root\"><g class=\"clusters\"></g><g class=\"edgePaths\"><path d=\"M192.516,93.2L196.682,93.2C200.849,93.2,209.182,93.2,216.849,93.2C224.516,93.2,231.516,93.2,235.016,93.2L238.516,93.2\" id=\"mmd-0-L_M_L_0\" class=\" edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link\" style=\";\" data-edge=\"true\" data-et=\"edge\" data-id=\"L_M_L_0\" data-points=\"W3sieCI6MTkyLjUxNTYyNSwieSI6OTMuMTk5OTk4ODU1NTkwODJ9LHsieCI6MjE3LjUxNTYyNSwieSI6OTMuMTk5OTk4ODU1NTkwODJ9LHsieCI6MjQyLjUxNTYyNSwieSI6OTMuMTk5OTk4ODU1NTkwODJ9XQ==\" data-look=\"classic\" marker-end=\"url(#mmd-0_flowchart-v2-pointEnd)\"></path><path d=\"M387.011,67.859L397.81,63.266C408.609,58.673,430.207,49.486,446.915,44.893C463.622,40.3,475.44,40.3,481.349,40.3L487.258,40.3\" id=\"mmd-0-L_L_C_0\" class=\" edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link\" style=\";\" data-edge=\"true\" data-et=\"edge\" data-id=\"L_L_C_0\" data-points=\"W3sieCI6Mzg3LjAxMDkzNjI5MjMwNDgsInkiOjY3Ljg1OTM3MjY0Nzg5NTZ9LHsieCI6NDUxLjgwNDY4NzUsInkiOjQwLjI5OTk5OTIzNzA2MDU1fSx7IngiOjQ5MS4yNTc4MTI1LCJ5Ijo0MC4yOTk5OTkyMzcwNjA1NX1d\" data-look=\"classic\" marker-end=\"url(#mmd-0_flowchart-v2-pointEnd)\"></path><path d=\"M387.011,118.541L397.81,123.134C408.609,127.727,430.207,136.914,447.65,141.507C465.094,146.1,478.383,146.1,485.027,146.1L491.672,146.1\" id=\"mmd-0-L_L_E_0\" class=\" edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link\" style=\";\" data-edge=\"true\" data-et=\"edge\" data-id=\"L_L_E_0\" data-points=\"W3sieCI6Mzg3LjAxMDkzNjI5MjMwNDgsInkiOjExOC41NDA2MjUwNjMyODYwNn0seyJ4Ijo0NTEuODA0Njg3NSwieSI6MTQ2LjA5OTk5ODQ3NDEyMTF9LHsieCI6NDk1LjY3MTg3NSwieSI6MTQ2LjA5OTk5ODQ3NDEyMTF9XQ==\" data-look=\"classic\" marker-end=\"url(#mmd-0_flowchart-v2-pointEnd)\"></path><path d=\"M746.008,40.3L750.174,40.3C754.341,40.3,762.674,40.3,770.341,40.3C778.008,40.3,785.008,40.3,788.508,40.3L792.008,40.3\" id=\"mmd-0-L_C_O_0\" class=\" edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link\" style=\";\" data-edge=\"true\" data-et=\"edge\" data-id=\"L_C_O_0\" data-points=\"W3sieCI6NzQ2LjAwNzgxMjUsInkiOjQwLjI5OTk5OTIzNzA2MDU1fSx7IngiOjc3MS4wMDc4MTI1LCJ5Ijo0MC4yOTk5OTkyMzcwNjA1NX0seyJ4Ijo3OTYuMDA3ODEyNSwieSI6NDAuMjk5OTk5MjM3MDYwNTV9XQ==\" data-look=\"classic\" marker-end=\"url(#mmd-0_flowchart-v2-pointEnd)\"></path></g><g class=\"edgeLabels\"><g class=\"edgeLabel\"><g class=\"label\" data-id=\"L_M_L_0\" transform=\"translate(0, 0)\"><text y=\"-10.1\" text-anchor=\"middle\"><tspan class=\"text-outer-tspan row\" x=\"0\" y=\"-0.1em\" dy=\"1.1em\" text-anchor=\"middle\"></tspan></text></g></g><g><rect class=\"background\" style=\"stroke: none\"></rect></g><g class=\"edgeLabel\" transform=\"translate(451.8046875, 40.29999923706055)\"><g class=\"label\" data-id=\"L_L_C_0\" transform=\"translate(0, -10.5)\"><g><rect class=\"background\" style=\"\" x=\"-14.453125\" y=\"0\" width=\"28.90625\" height=\"21\"></rect><text y=\"-10.1\" text-anchor=\"middle\" style=\"\"><tspan class=\"text-outer-tspan row\" x=\"0\" y=\"-0.1em\" dy=\"1.1em\" text-anchor=\"middle\"><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\">yes</tspan></tspan></text></g></g></g><g class=\"edgeLabel\" transform=\"translate(451.8046875, 146.0999984741211)\"><g class=\"label\" data-id=\"L_L_E_0\" transform=\"translate(-0.05078125, -10.5)\"><g><rect class=\"background\" style=\"\" x=\"-10.8984375\" y=\"0\" width=\"21.8984375\" height=\"21\"></rect><text y=\"-10.1\" text-anchor=\"middle\" style=\"\"><tspan class=\"text-outer-tspan row\" x=\"0\" y=\"-0.1em\" dy=\"1.1em\" text-anchor=\"middle\"><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\">no</tspan></tspan></text></g></g></g><g class=\"edgeLabel\"><g class=\"label\" data-id=\"L_C_O_0\" transform=\"translate(0, 0)\"><text y=\"-10.1\" text-anchor=\"middle\"><tspan class=\"text-outer-tspan row\" x=\"0\" y=\"-0.1em\" dy=\"1.1em\" text-anchor=\"middle\"></tspan></text></g></g><g><rect class=\"background\" style=\"stroke: none\"></rect></g></g><g class=\"nodes\"><g class=\"node default  \" id=\"mmd-0-flowchart-M-0\" data-look=\"classic\" transform=\"translate(100.2578125, 93.19999885559082)\"><rect class=\"basic label-container\" style=\"\" x=\"-92.2578125\" y=\"-32.29999923706055\" width=\"184.515625\" height=\"64.5999984741211\"></rect><g class=\"label\" style=\"\" transform=\"translate(0, -17.299999237060547)\"><rect></rect><g><rect class=\"background\" style=\"stroke: none\"></rect><text y=\"-10.1\" style=\"\"><tspan class=\"text-outer-tspan row\" x=\"0\" y=\"-0.1em\" dy=\"1.1em\"><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\">model</tspan><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\"> output</tspan></tspan><tspan class=\"text-outer-tspan row\" x=\"0\" y=\"1em\" dy=\"1.1em\"><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\">name,</tspan><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\"> arguments</tspan></tspan></text></g></g></g><g class=\"node default  \" id=\"mmd-0-flowchart-L-1\" data-look=\"classic\" transform=\"translate(327.43359375, 93.19999885559082)\"><polygon points=\"84.91796875,0 169.8359375,-84.91796875 84.91796875,-169.8359375 0,-84.91796875\" class=\"label-container\" transform=\"translate(-84.41796875, 84.91796875)\"></polygon><g class=\"label\" style=\"\" transform=\"translate(0, -8.5)\"><rect></rect><g><rect class=\"background\" style=\"stroke: none\"></rect><text y=\"-10.1\" style=\"\"><tspan class=\"text-outer-tspan row\" x=\"0\" y=\"-0.1em\" dy=\"1.1em\"><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\">name</tspan><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\"> in</tspan><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\"> registry?</tspan></tspan></text></g></g></g><g class=\"node default  \" id=\"mmd-0-flowchart-C-3\" data-look=\"classic\" transform=\"translate(618.6328125, 40.29999923706055)\"><rect class=\"basic label-container\" style=\"\" x=\"-127.375\" y=\"-32.29999923706055\" width=\"254.75\" height=\"64.5999984741211\"></rect><g class=\"label\" style=\"\" transform=\"translate(0, -17.299999237060547)\"><rect></rect><g><rect class=\"background\" style=\"stroke: none\"></rect><text y=\"-10.1\" style=\"\"><tspan class=\"text-outer-tspan row\" x=\"0\" y=\"-0.1em\" dy=\"1.1em\"><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\">registry[name](**arguments</tspan></tspan><tspan class=\"text-outer-tspan row\" x=\"0\" y=\"1em\" dy=\"1.1em\"><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\">)</tspan></tspan></text></g></g></g><g class=\"node default  \" id=\"mmd-0-flowchart-E-5\" data-look=\"classic\" transform=\"translate(618.6328125, 146.0999984741211)\"><rect class=\"basic label-container\" style=\"\" x=\"-122.9609375\" y=\"-23.5\" width=\"245.921875\" height=\"47\"></rect><g class=\"label\" style=\"\" transform=\"translate(0, -8.5)\"><rect></rect><g><rect class=\"background\" style=\"stroke: none\"></rect><text y=\"-10.1\" style=\"\"><tspan class=\"text-outer-tspan row\" x=\"0\" y=\"-0.1em\" dy=\"1.1em\"><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\">observation:</tspan><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\"> unknown</tspan><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\"> tool</tspan></tspan></text></g></g></g><g class=\"node default  \" id=\"mmd-0-flowchart-O-7\" data-look=\"classic\" transform=\"translate(891.65625, 40.29999923706055)\"><rect class=\"basic label-container\" style=\"\" x=\"-95.6484375\" y=\"-23.5\" width=\"191.296875\" height=\"47\"></rect><g class=\"label\" style=\"\" transform=\"translate(0, -8.5)\"><rect></rect><g><rect class=\"background\" style=\"stroke: none\"></rect><text y=\"-10.1\" style=\"\"><tspan class=\"text-outer-tspan row\" x=\"0\" y=\"-0.1em\" dy=\"1.1em\"><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\">observation:</tspan><tspan font-style=\"normal\" class=\"text-inner-tspan\" font-weight=\"normal\"> result</tspan></tspan></text></g></g></g></g></g></g><defs><filter id=\"mmd-0-drop-shadow\" height=\"130%\" width=\"130%\"><feDropShadow dx=\"4\" dy=\"4\" stdDeviation=\"0\" flood-opacity=\"0.06\" flood-color=\"#000000\"></feDropShadow></filter></defs><defs><filter id=\"mmd-0-drop-shadow-small\" height=\"150%\" width=\"150%\"><feDropShadow dx=\"2\" dy=\"2\" stdDeviation=\"0\" flood-opacity=\"0.06\" flood-color=\"#000000\"></feDropShadow></filter></defs></svg>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Callable\n",
    "\n",
    "\n",
    "def multiply(a: float, b: float) -> float:\n",
    "    \"\"\"Return the product of a and b.\"\"\"\n",
    "    return a * b\n",
    "\n",
    "\n",
    "TOOLS: dict[str, Callable] = {\"add\": add, \"multiply\": multiply}\n",
    "\n",
    "\n",
    "def dispatch(name: str, arguments: dict) -> str:\n",
    "    \"\"\"Execute one tool call and return the observation as text.\"\"\"\n",
    "    if name not in TOOLS:\n",
    "        return f\"error: unknown tool {name!r}; available: {sorted(TOOLS)}\"\n",
    "    try:\n",
    "        return str(TOOLS[name](**arguments))\n",
    "    except TypeError as e:\n",
    "        return f\"error: {e}\"\n",
    "\n",
    "\n",
    "print(dispatch(\"multiply\", {\"a\": 4, \"b\": 2.5}))\n",
    "print(dispatch(\"divide\", {\"a\": 4, \"b\": 2}))\n",
    "print(dispatch(\"add\", {\"a\": 4}))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21",
   "metadata": {},
   "source": [
    "The description of the tools that the model receives is computed from the same registry, from `__name__` and `__doc__`. Registry and description therefore cannot diverge: a tool that is registered is described, and a tool that is described can be called.\n",
    "\n",
    "The handler in `dispatch` has one weakness: `except TypeError` also handles a `TypeError` raised **inside** the tool and reports it as a wrong call. `07-json-and-schemas.ipynb` separates the two cases by checking the arguments against the signature before the call."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22",
   "metadata": {},
   "outputs": [],
   "source": [
    "def tool_descriptions(tools: dict[str, Callable]) -> str:\n",
    "    return \"\\n\".join(f\"- {fn.__name__}: {fn.__doc__}\" for fn in tools.values())\n",
    "\n",
    "\n",
    "print(tool_descriptions(TOOLS))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "| Construct | Meaning |\n",
    "|---|---|\n",
    "| `f = add` | the function object, bound to a second name; `f(2, 3)` calls it |\n",
    "| `fn.__name__`, `fn.__doc__` | name and docstring, the description of a tool |\n",
    "| `key=lambda hit: hit[1]` | a function without a name, passed as an argument |\n",
    "| closure | an inner function with the variables of the enclosing scope; the variable is read at the time of the call |\n",
    "| `f(**arguments)` | a dictionary passed as keyword arguments |\n",
    "| `TOOLS[name](**arguments)` | dispatch: the function is selected by a value known only at run time |\n",
    "\n",
    "## Use in the Course\n",
    "\n",
    "| Construct | Notebooks |\n",
    "|---|---|\n",
    "| tool registry and dispatch | `course/lecture-ReAct/lecture-ReAct.ipynb` and its exercises; `course/lecture-Gateway/lecture-Gateway-exercises.ipynb`; `playground/SWE-Agent/build-a-swe-agent.ipynb`; `playground/memory/06-write-paths.ipynb` |\n",
    "| `lambda` as `key=` or as a small callback | the exercises of `lecture-Decomp`, `lecture-ToT`, `lecture-RAG`, `lecture-Eval`; `playground/langgraph/01-langgraph-basics.ipynb` (routing functions) |\n",
    "| a function passed as a parameter (`ask=input`, `ask=lambda prompt: \"y\"`) | `playground/pydantic-ai/01-pydantic-ai-basics.ipynb`, `playground/pydantic-ai/02-swe-agent-as-a-graph.ipynb` |\n",
    "\n",
    "The next notebook, `02-decorators.ipynb`, fills the registry with the `@tool` syntax instead of a dictionary literal."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24",
   "metadata": {},
   "source": [
    "## Tasks\n",
    "\n",
    "**Task 1.** Sort the list `hits` by the length of the document name, the longest name first, with a lambda expression.\n",
    "\n",
    "**Task 2.** Write `make_counter()`, which returns a function without parameters. Each call of the returned function returns the number of calls so far: 1, 2, 3, … Two counters created by two calls of `make_counter` count independently. (The statement `nonlocal n` permits an inner function to assign to a variable of the enclosing function.)\n",
    "\n",
    "**Task 3.** Extend `dispatch` so that it limits the number of calls: `make_dispatch(tools, max_calls)` returns a dispatch function that returns `\"error: call limit reached\"` after `max_calls` calls."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25",
   "metadata": {},
   "source": [
    "### Solutions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Task 1\n",
    "print(sorted(hits + [(\"document-d\", 0.1)], key=lambda hit: len(hit[0]), reverse=True))\n",
    "\n",
    "\n",
    "# Task 2\n",
    "def make_counter():\n",
    "    n = 0\n",
    "    def count():\n",
    "        nonlocal n\n",
    "        n += 1\n",
    "        return n\n",
    "    return count\n",
    "\n",
    "\n",
    "c1, c2 = make_counter(), make_counter()\n",
    "print(c1(), c1(), c1(), c2())\n",
    "\n",
    "\n",
    "# Task 3\n",
    "def make_dispatch(tools, max_calls):\n",
    "    calls = 0\n",
    "    def limited(name, arguments):\n",
    "        nonlocal calls\n",
    "        if calls >= max_calls:\n",
    "            return \"error: call limit reached\"\n",
    "        calls += 1\n",
    "        return str(tools[name](**arguments))\n",
    "    return limited\n",
    "\n",
    "\n",
    "d = make_dispatch(TOOLS, max_calls=2)\n",
    "print([d(\"add\", {\"a\": 1, \"b\": i}) for i in range(3)])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27",
   "metadata": {},
   "source": [
    "## References\n",
    "\n",
    "- Python Software Foundation. \"Programming FAQ.\" Python 3.12 documentation. 2023. <https://docs.python.org/3.12/faq/programming.html>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}