{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "dcb093c5",
   "metadata": {},
   "source": [
    "# LLM tooling for Chemistry\n",
    "\n",
    "<p style=\"font-style: italic; font-size: 18px; color: dimgray;\">ChatGPT and Gemini</p>\n",
    "\n",
    "Anton Antonov   \n",
    "[RakuForPrediction at WordPress](https://rakuforprediction.wordpress.com)   \n",
    "September 2025"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc255d8f",
   "metadata": {},
   "source": [
    "-----\n",
    "\n",
    "## Introduction"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "04e8bf87",
   "metadata": {},
   "source": [
    "This notebook shows how to do utilize Large Language Model (LLM) [Function Calling](https://ai.google.dev/gemini-api/docs/function-calling) with the Raku package [\"LLM::Functions\"](https://github.com/antononcube/Raku-LLM-Functions), [AAp1].\n",
    "\n",
    "Non-trivial Stoichiometry examples are demonstrated with tools of the Raku package [\"Chemistry::Stoichiometry\"](https://raku.land/zef:antononcube/Chemistry::Stoichiometry), [AAp4].\n",
    "\n",
    "At this point \"LLM::Functions\" supports function calling in the styles of OpenAI's ChatGPT and Google's Gemini. If the LLM configuration is not using ChatGPT or Gemini, the function calling style used is that of ChatGPT. (Many LLM providers -- other than OpenAI and Gemini -- tend to adhere to OpenAI's API.)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a874186",
   "metadata": {},
   "source": [
    "-----\n",
    "\n",
    "## Setup"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "097abe6f",
   "metadata": {},
   "source": [
    "Load packages:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "95d84f7a",
   "metadata": {},
   "outputs": [],
   "source": [
    "use JSON::Fast;\n",
    "use LLM::Functions;\n",
    "use LLM::Tooling;\n",
    "use Chemistry::Stoichiometry;\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e0e10660",
   "metadata": {},
   "source": [
    "Define LLM access configurations:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "8d96238d",
   "metadata": {},
   "outputs": [],
   "source": [
    "sink my $conf5-mini = llm-configuration('ChatGPT', model => 'gpt-5-mini');\n",
    "sink my $conf41-mini = llm-configuration('ChatGPT', model => 'gpt-4.1-mini', :8192max-tokens, temperature => 0.4);\n",
    "sink my $conf-gemini-flash = llm-configuration('Gemini', model => 'gemini-2.0-flash', :8192max-tokens, temperature => 0.4);"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6efae8bb",
   "metadata": {},
   "source": [
    "### JavaScript::D3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "78f89d5c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": "require.config({\n     paths: {\n     d3: 'https://d3js.org/d3.v7.min'\n}});\n\nrequire(['d3'], function(d3) {\n     console.log(d3);\n});"
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#%javascript\n",
    "require.config({\n",
    "     paths: {\n",
    "     d3: 'https://d3js.org/d3.v7.min'\n",
    "}});\n",
    "\n",
    "require(['d3'], function(d3) {\n",
    "     console.log(d3);\n",
    "});"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "131a8ead",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": "(function(element) { require(['d3'], function(d3) {\n\n// set the dimensions and margins of the graph\nvar margin = {\"left\":40,\"top\":40,\"bottom\":40,\"right\":40},\n    width = 600 - margin.left - margin.right,\n    height = 400 - margin.top - margin.bottom;\n\n// append the svg object to the body of the page\nvar svg = d3\n   .select(element.get(0))\n  .append(\"svg\")\n    .attr(\"width\", width + margin.left + margin.right)\n    .attr(\"height\", height + margin.top + margin.bottom)\n    .style(\"background\", \"none\")\n  .append(\"g\")\n    .attr(\"transform\",\n          \"translate(\" + margin.left + \",\" + margin.top + \")\")\n\n// Obtain title\nvar title = \"\"\nvar titleFontSize = 16\n\nif ( title.length > 0 ) {\n    svg.append(\"text\")\n        .attr(\"x\", (width / 2))\n        .attr(\"y\", 0 - (margin.top / 2))\n        .attr(\"text-anchor\", \"middle\")\n        .style(\"font-size\", titleFontSize.toString() + \"px\")\n        .style(\"fill\", \"Black\")\n        .text(title);\n}\n\n// Obtain x-axis label\nvar xAxisLabel = \"\"\nvar xAxisLabelFontSize = 12\n\nif ( xAxisLabel.length > 0 ) {\n    svg.append(\"text\")\n        .attr(\"x\", (width / 2))\n        .attr(\"y\", height + margin.bottom - xAxisLabelFontSize/2)\n        .attr(\"text-anchor\", \"middle\")\n        .style(\"font-size\", xAxisLabelFontSize.toString() + \"px\")\n        .style(\"fill\", \"Black\")\n        .text(xAxisLabel);\n}\n\n// Obtain y-axis label\nvar yAxisLabel = \"\"\nvar yAxisLabelFontSize = 12\n\nif ( yAxisLabel.length > 0 ) {\n    svg.append(\"text\")\n        .attr(\"transform\", \"rotate(-90)\")\n        .attr(\"x\", - (height / 2))\n        .attr(\"y\", 0 - margin.left + yAxisLabelFontSize)\n        .attr(\"text-anchor\", \"middle\")\n        .style(\"font-size\", yAxisLabelFontSize.toString() + \"px\")\n        .style(\"fill\", \"Black\")\n        .text(yAxisLabel);\n}\n\n\n// Obtain data\nvar data = [{\"y\":6.307515127960545e0,\"x\":1},{\"x\":2,\"y\":3.457115937315034e0},{\"x\":3,\"y\":7.3757021640406055e0},{\"x\":4,\"y\":4.753051284994561e0},{\"x\":5,\"y\":8.872934805143363e0},{\"x\":6,\"y\":0.7485617808289591e0},{\"y\":6.872340045926402e0,\"x\":7},{\"y\":6.130877352164021e0,\"x\":8},{\"x\":9,\"y\":9.943383742797453e0},{\"x\":10,\"y\":3.0827424090594957e0},{\"x\":11,\"y\":5.3540159669059415e0},{\"y\":3.775533306084852e0,\"x\":12},{\"x\":13,\"y\":4.144604657820409e0},{\"y\":0.07544297569339276e0,\"x\":14},{\"x\":15,\"y\":6.9687244259681735e0},{\"y\":2.6451376193487786e0,\"x\":16},{\"y\":8.537825373534499e0,\"x\":17},{\"y\":8.855313220970858e0,\"x\":18},{\"y\":9.702987478618137e0,\"x\":19},{\"x\":20,\"y\":0.850406860633115e0},{\"y\":1.6138350032979742e0,\"x\":21},{\"x\":22,\"y\":4.8768869890374305e0},{\"y\":1.6139138939253106e0,\"x\":23},{\"y\":6.150695322114432e0,\"x\":24},{\"x\":25,\"y\":6.718073698873562e0},{\"x\":26,\"y\":8.631828483584604e0},{\"y\":6.617110414418754e0,\"x\":27},{\"y\":2.51787070044481e0,\"x\":28},{\"y\":4.702275543695472e0,\"x\":29},{\"y\":2.7187895259741888e0,\"x\":30}]\n\nvar xMin = Math.min.apply(Math, data.map(function(o) { return o.x; }))\nvar xMax = Math.max.apply(Math, data.map(function(o) { return o.x; }))\n\nvar yMin = Math.min.apply(Math, data.map(function(o) { return o.y; }))\nvar yMax = Math.max.apply(Math, data.map(function(o) { return o.y; }))\n\n// X scale and Axis\nvar x = d3.scaleLinear()\n    .domain([xMin, xMax])\n    .range([0, width]);\n\n// Y scale and Axis\nvar y = d3.scaleLinear()\n    .domain([yMin, yMax])\n    .range([height, 0]);\n\nsvg\n  .append('g')\n  .attr(\"transform\", \"translate(0,\" + height + \")\")\n  .call(d3.axisBottom(x))\n\nsvg\n  .append('g')\n  .call(d3.axisLeft(y));\n\n// prepare a helper function\nvar lineFunc = d3.line()\n  .x(function(d) { return x(d.x) })\n  .y(function(d) { return y(d.y) })\n\n// Add the path using this helper function\nsvg.append('path')\n  .attr('d', lineFunc(data))\n  .attr(\"stroke-width\", 1.5)\n  .attr('stroke', \"SteelBlue\")\n  .attr('fill', 'none');\n\n}) })(element);\n"
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#%js\n",
    "js-d3-list-line-plot(10.rand xx 30, background => 'none')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd022748",
   "metadata": {},
   "source": [
    "----\n",
    "\n",
    "## Diagram"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fbfa151f",
   "metadata": {},
   "source": [
    "Here is a [Mermaid-JS](https://mermaid.js.org) diagram that shows _single-pass_ LLM-and-tool interaction:\n",
    "\n",
    "\n",
    "```mermaid\n",
    "sequenceDiagram\n",
    "    participant Developer\n",
    "    participant Model as LLM Service (Gemini)\n",
    "    participant Tool as External Function\n",
    "\n",
    "    Note over Developer, Model: 1. Tool Definitions + Messages\n",
    "    Developer->>Model: molecular-mass(molecule) <br> \"What's the mass of SO2?\"\n",
    "\n",
    "    Note over Model: 2. Tool Calls\n",
    "    Model->>Tool: molecular-mass(\"SO2\")\n",
    "\n",
    "    Note over Tool: 3. Execute Function Code\n",
    "    Tool->>Model: {\"mass\": 64.058}\n",
    "\n",
    "    Note over Model: 4. Results\n",
    "    Model->>Developer: {\"mass\": 64.058}\n",
    "\n",
    "    Note over Developer: 5. Final Response\n",
    "    Developer->>Model: The molecular mass of SO2 is 64.058.\n",
    "```\n",
    "\n",
    "\n",
    "**Remark:** Instead of one invocation of a local tool -- as shown in the diagram -- often enough multiple LLM-&-local-computer interactions are used for LLM-tool computation.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb69d7f9",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## Chemistry computations examples"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa70b59d",
   "metadata": {},
   "source": [
    "The package [\"Chemistry::Stoichiometry\"](https://raku.land/zef:antononcube/Chemistry::Stoichiometry), [AAp4], provides element data, a grammar (or parser) for chemical formulas, and subs for computing molecular masses and balancing equations. Here is an example of calling `molecular-mass`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "b4ed17f7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "64.058"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "molecular-mass(\"SO2\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12663452",
   "metadata": {},
   "source": [
    "Balance chemical equation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "d2925844",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[4*Al + 3*O2 -> 2*Al2O3]"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'Al + O2 -> Al2O3'\n",
    "==> balance-chemical-equation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "id": "3ae02758",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "+---+----------+-----------+----------+-----------+--------+       \n",
       "|                                                          |       \n",
       "|   *    *                                                 |       \n",
       "+                                                          +  80.00\n",
       "|                                                          |       \n",
       "|              *                                           |       \n",
       "|                                                          |       \n",
       "+                                                          +  60.00\n",
       "|                          *                               |       \n",
       "|                               *                      *   |       \n",
       "+                                                          +  40.00\n",
       "|                                           *              |       \n",
       "|                    *                *           *        |       \n",
       "|                                                          |       \n",
       "+---+----------+-----------+----------+-----------+--------+       \n",
       "    0.00       2.00        4.00       6.00        8.00             "
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "text-list-plot(random-real(100,10))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "08330d81",
   "metadata": {},
   "source": [
    "----\n",
    "\n",
    "## Define package functions as tools"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ea8d1593",
   "metadata": {},
   "source": [
    "Define a few subs -- _tools_ -- with sub- and argument descriptions (i.e. attached Pod values, or [declarator blocks](https://docs.raku.org/language/pod#Declarator_blocks)):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "2a2299a6",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Undefined type of parameter ⎡$spec⎦; continue assuming it is a string.\n"
     ]
    }
   ],
   "source": [
    "sink my @tool-objects =\n",
    "        LLM::Tool.new(&molecular-mass),\n",
    "        LLM::Tool.new(&balance-chemical-equation)\n",
    "        ;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d890307e",
   "metadata": {},
   "source": [
    "-----\n",
    "\n",
    "## LLM-tool invocation example **1**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c73a889a",
   "metadata": {},
   "source": [
    "A single prompt or an array of prompts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "id": "b4866292",
   "metadata": {},
   "outputs": [],
   "source": [
    "sink my $input = \"What are the masses of SO2, O3, and C2H5OH? Also balance: C2H5OH + O2 = H2O + CO2.\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3453645",
   "metadata": {},
   "source": [
    "LLM result:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b8519304",
   "metadata": {},
   "outputs": [],
   "source": [
    "my $conf = llm-configuration($conf41-mini, tools => @tool-objects);\n",
    "\n",
    "llm-synthesize(\n",
    "        [$input, llm-prompt('NothingElse')('JSON')],\n",
    "        e => $conf, \n",
    "        form => sub-parser('JSON'):drop)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fbe4f87d",
   "metadata": {},
   "source": [
    "**Remark:** It order to see the LLM-tool interaction use the Boolean option (adverb) `:echo` of `llm-synthesize`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c5ff9332",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## LLM-tool invocation example 2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7826485c",
   "metadata": {},
   "source": [
    "Let us add a plotting tool. We can use `text-list-plot` of [\"Text::Plot\"](https://raku.land/zef:antononcube/Text::Plot), but automatically derived tool spec -- via the sub `llm-tool-definition` used by `LLM::Tool` -- is somewhat incomplete. Here is auto-result:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f4d5932a",
   "metadata": {},
   "outputs": [],
   "source": [
    "llm-tool-definition(&text-list-plot)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b5104a60",
   "metadata": {},
   "source": [
    "We could overwrite the tool-spec for `text-list-plot` with this spec:\n",
    "\n",
    "```raku\n",
    "my $spec = q:to/END/;\n",
    "{\n",
    "  \"type\": \"function\",\n",
    "  \"function\": {\n",
    "    \"name\": \"text-list-plot\",\n",
    "    \"description\": \"Make a string that represents a list-plot of the given arguments.\",\n",
    "    \"parameters\": {\n",
    "      \"type\": \"object\",\n",
    "      \"properties\": {\n",
    "        \"$x\": {\n",
    "          \"type\": \"array\",\n",
    "          \"description\": \"A list of a list of x-coordinates or x-labels\",\n",
    "          \"items\": {\n",
    "            \"anyOf\": [\n",
    "              { \"type\": \"string\" },\n",
    "              { \"type\": \"number\" }\n",
    "            ]\n",
    "          }\n",
    "        }\n",
    "        \"$y\": {\n",
    "          \"type\": \"array\",\n",
    "          \"description\": \"A list of y-coordinates\",\n",
    "          \"items\": {\n",
    "            \"type\": \"number\"\n",
    "          }\n",
    "        }\n",
    "      },\n",
    "      \"required\": [\"$x\", \"$y\"]\n",
    "    }\n",
    "  }\n",
    "}\n",
    "END\n",
    "\n",
    "my $t = LLM::Tool.new(&text-list-plot);\n",
    "$t.json-spec = $spec;\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2441266e",
   "metadata": {},
   "source": [
    "Though, it is easier and more robust to define a new function that delegates to `text-list-plot`, and does some additional input processing that anticipates LLM derived argument values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "71bf2bc6",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"[64.058, 47.997, 48.61, 46.069]\".split(/<[\\[\\],]>/, :skip-empty)».trim.raku"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "93ce62ed",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "&list-plot"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#| Make a string that represents a list-plot of the given arguments.\n",
    "my sub list-plot(\n",
    "    Str:D $x,             #= A list of comma separated x-coordinates or x-labels\n",
    "    Str:D $y,             #= A list of comma separated y-coordinates\n",
    "    Str:D :$x-label = '', #= Label of the x-axis\n",
    "    Str:D :$y-label = '', #= Label of the y-axis\n",
    "    ) {\n",
    "    my @x = $x.split(/<[\\[\\],\"]>/, :skip-empty)».trim.grep(*.chars);\n",
    "    my @y = $y.split(/<[\\[\\],\"]>/, :skip-empty)».trim».Num;\n",
    "    \n",
    "    # text-list-plot($x, $y, :$x-label, :$y-label, title => (@x.kv.map(-> $k, $v { \"{$k+1} : $v\" }).join(' | ') ))\n",
    "    \n",
    "    my @points = (1 ... @x.elems)».Num Z @y;\n",
    "\n",
    "    text-list-plot(@points, :$x-label, :$y-label, point-char => @x )\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 132,
   "id": "022e1a02",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "&list-plot"
      ]
     },
     "execution_count": 132,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#| Make a string that represents a list-plot of the given arguments.\n",
    "my sub list-plot(\n",
    "    Str:D $x,             #= A list of comma separated x-coordinates or x-labels\n",
    "    Str:D $y,             #= A list of comma separated y-coordinates\n",
    "    Str:D :$x-label = '', #= Label of the x-axis\n",
    "    Str:D :$y-label = '', #= Label of the y-axis\n",
    "    Str:D :$title = '',   #= Plot title\n",
    "    ) {\n",
    "  \n",
    "    my @x = $x.split(/<[\\[\\],\"]>/, :skip-empty)».trim.grep(*.chars);\n",
    "    my @y = $y.split(/<[\\[\\],\"]>/, :skip-empty)».trim».Num;\n",
    "  \n",
    "    # text-list-plot($x, $y, :$x-label, :$y-label, title => (@x.kv.map(-> $k, $v { \"{$k+1} : $v\" }).join(' | ') ))\n",
    "    \n",
    "    #my @points = (^@x.elems)».Num Z @y;\n",
    "    #@points .= map({ %( x => $_.head, y => $_.tail, group => @x[$_.head] ) });\n",
    "    #js-d3-list-plot(@points, :$x-label, :$y-label, :tooltip, title-color => 'Gray', background => '#1F1F1F', :legends, :grid-lines, :13point-size)\n",
    "    my @points = (@x Z @y).map({ %( variable => $_.head, value => $_.tail ) });\n",
    "    js-d3-bar-chart(@points, :$x-label, :$y-label, title-color => 'Gray', background => '#1F1F1F', :grid-lines)\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 133,
   "id": "4062ef16",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": "(function(element) { require(['d3'], function(d3) {\n\n// set the dimensions and margins of the graph\nvar margin = {\"right\":40,\"bottom\":40,\"left\":40,\"top\":40},\n    width = 600 - margin.left - margin.right,\n    height = 400 - margin.top - margin.bottom;\n\n// append the svg object to the body of the page\nvar svg = d3\n   .select(element.get(0))\n  .append(\"svg\")\n    .attr(\"width\", width + margin.left + margin.right)\n    .attr(\"height\", height + margin.top + margin.bottom)\n    .style(\"background\", \"#1F1F1F\")\n  .append(\"g\")\n    .attr(\"transform\",\n          \"translate(\" + margin.left + \",\" + margin.top + \")\")\n\n// Obtain title\nvar title = \"\"\nvar titleFontSize = 16\n\nif ( title.length > 0 ) {\n    svg.append(\"text\")\n        .attr(\"x\", (width / 2))\n        .attr(\"y\", 0 - (margin.top / 2))\n        .attr(\"text-anchor\", \"middle\")\n        .style(\"font-size\", titleFontSize.toString() + \"px\")\n        .style(\"fill\", \"Gray\")\n        .text(title);\n}\n\n// Obtain x-axis label\nvar xAxisLabel = \"\"\nvar xAxisLabelFontSize = 12\n\nif ( xAxisLabel.length > 0 ) {\n    svg.append(\"text\")\n        .attr(\"x\", (width / 2))\n        .attr(\"y\", height + margin.bottom - xAxisLabelFontSize/2)\n        .attr(\"text-anchor\", \"middle\")\n        .style(\"font-size\", xAxisLabelFontSize.toString() + \"px\")\n        .style(\"fill\", \"Gray\")\n        .text(xAxisLabel);\n}\n\n// Obtain y-axis label\nvar yAxisLabel = \"mass\"\nvar yAxisLabelFontSize = 12\n\nif ( yAxisLabel.length > 0 ) {\n    svg.append(\"text\")\n        .attr(\"transform\", \"rotate(-90)\")\n        .attr(\"x\", - (height / 2))\n        .attr(\"y\", 0 - margin.left + yAxisLabelFontSize)\n        .attr(\"text-anchor\", \"middle\")\n        .style(\"font-size\", yAxisLabelFontSize.toString() + \"px\")\n        .style(\"fill\", \"Gray\")\n        .text(yAxisLabel);\n}\n\n// Obtain data\nvar data = [{\"y\":64.058e0,\"x\":\"SO2\"},{\"x\":\"O3\",\"y\":47.997e0},{\"y\":48.61e0,\"x\":\"Mg2\"},{\"x\":\"C2H5OH\",\"y\":46.069e0}]\n\nvar valueMin = Math.min.apply(Math, data.map(function(o) { return o.y; }))\nvar valueMax = Math.max.apply(Math, data.map(function(o) { return o.y; }))\n\n// X axis\nvar x = d3.scaleBand()\n  .range([ 0, width ])\n  .domain(data.map(function(d) { return d.x; }))\n  .padding(0.2);\nsvg.append(\"g\")\n  .attr(\"transform\", \"translate(0,\" + height + \")\")\n  .call(d3.axisBottom(x).ticks(5).tickSizeInner(-height))\n  .selectAll(\"text\")\n    .attr(\"transform\", \"translate(-10,0)rotate(-45)\")\n    .style(\"text-anchor\", \"end\");\n\n// Add Y axis\nvar y = d3.scaleLinear()\n  .domain([0, valueMax])\n  .range([ height, 0]);\nsvg.append(\"g\")\n  .call(d3.axisLeft(y).ticks(5).tickSizeInner(-width));\n\n// Bars\nsvg.selectAll(\"mybar\")\n  .data(data)\n  .enter()\n  .append(\"rect\")\n    .attr(\"x\", function(d) { return x(d.x); })\n    .attr(\"y\", function(d) { return y(d.y); })\n    .attr(\"width\", x.bandwidth())\n    .attr(\"height\", function(d) { return height - y(d.y); })\n    .attr(\"fill\", \"steelblue\")\n\n}) })(element);\n"
     },
     "execution_count": 133,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#%js\n",
    "list-plot(\"[\\\"SO2\\\", \\\"O3\\\", \\\"Mg2\\\", \\\"C2H5OH\\\"]\",\"64.058, 47.997, 48.61, 46.069\", y-label=>'mass')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6de86a4c",
   "metadata": {},
   "source": [
    "Here we add the new tool to the tool list above:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 134,
   "id": "33b64c6f",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Undefined type of parameter ⎡$spec⎦; continue assuming it is a string.\n"
     ]
    }
   ],
   "source": [
    "sink my @tool-objects =\n",
    "        LLM::Tool.new(&molecular-mass),\n",
    "        LLM::Tool.new(&balance-chemical-equation)\n",
    "        ;\n",
    "        \n",
    "sink @tool-objects.push(LLM::Tool.new(&list-plot))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 142,
   "id": "c21106ab",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Struct([masses, plot], [Hash, Str])"
      ]
     },
     "execution_count": 142,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my $input = q:to/END/;\n",
    "What are the masses of SO2, O3, Mg2, and C2H5OH? \n",
    "Make a plot the obtained quantities: x-axes for the molecules, y-axis for the masses.\n",
    "The plot has to have appropriate title and axes labels.\n",
    "Return a JSON dictionary with keys \"masses\" and \"plot\".\n",
    "END\n",
    "\n",
    "my $conf = llm-configuration($conf41-mini, tools => @tool-objects);\n",
    "\n",
    "my $res = llm-synthesize([\n",
    "        $input, \n",
    "        llm-prompt('NothingElse')('JSON')\n",
    "    ], \n",
    "    e => $conf,\n",
    "    form => sub-parser('JSON'):drop\n",
    ");\n",
    "\n",
    "deduce-type($res)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aad3bd04",
   "metadata": {},
   "source": [
    "Here are the molecule masses:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 143,
   "id": "a3a6c8f7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{C2H5OH => 46.069, Mg2 => 48.61, O3 => 47.997, SO2 => 64.058}"
      ]
     },
     "execution_count": 143,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "$res<masses>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6ec1c2f",
   "metadata": {},
   "source": [
    "Here is the corresponding plot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 144,
   "id": "e8cf0f80",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": "(function(element) { require(['d3'], function(d3) {\n\n// set the dimensions and margins of the graph\nvar margin = {\"top\":40,\"right\":40,\"left\":40,\"bottom\":40},\n    width = 600 - margin.left - margin.right,\n    height = 400 - margin.top - margin.bottom;\n\n// append the svg object to the body of the page\nvar svg = d3\n   .select(element.get(0))\n  .append(\"svg\")\n    .attr(\"width\", width + margin.left + margin.right)\n    .attr(\"height\", height + margin.top + margin.bottom)\n    .style(\"background\", \"#1F1F1F\")\n  .append(\"g\")\n    .attr(\"transform\",\n          \"translate(\" + margin.left + \",\" + margin.top + \")\")\n\n// Obtain title\nvar title = \"\"\nvar titleFontSize = 16\n\nif ( title.length > 0 ) {\n    svg.append(\"text\")\n        .attr(\"x\", (width / 2))\n        .attr(\"y\", 0 - (margin.top / 2))\n        .attr(\"text-anchor\", \"middle\")\n        .style(\"font-size\", titleFontSize.toString() + \"px\")\n        .style(\"fill\", \"Gray\")\n        .text(title);\n}\n\n// Obtain x-axis label\nvar xAxisLabel = \"Molecules\"\nvar xAxisLabelFontSize = 12\n\nif ( xAxisLabel.length > 0 ) {\n    svg.append(\"text\")\n        .attr(\"x\", (width / 2))\n        .attr(\"y\", height + margin.bottom - xAxisLabelFontSize/2)\n        .attr(\"text-anchor\", \"middle\")\n        .style(\"font-size\", xAxisLabelFontSize.toString() + \"px\")\n        .style(\"fill\", \"Gray\")\n        .text(xAxisLabel);\n}\n\n// Obtain y-axis label\nvar yAxisLabel = \"Molecular Mass (g/mol)\"\nvar yAxisLabelFontSize = 12\n\nif ( yAxisLabel.length > 0 ) {\n    svg.append(\"text\")\n        .attr(\"transform\", \"rotate(-90)\")\n        .attr(\"x\", - (height / 2))\n        .attr(\"y\", 0 - margin.left + yAxisLabelFontSize)\n        .attr(\"text-anchor\", \"middle\")\n        .style(\"font-size\", yAxisLabelFontSize.toString() + \"px\")\n        .style(\"fill\", \"Gray\")\n        .text(yAxisLabel);\n}\n\n// Obtain data\nvar data = [{\"y\":64.058e0,\"x\":\"SO2\"},{\"x\":\"O3\",\"y\":47.997e0},{\"x\":\"Mg2\",\"y\":48.61e0},{\"x\":\"C2H5OH\",\"y\":46.069e0}]\n\nvar valueMin = Math.min.apply(Math, data.map(function(o) { return o.y; }))\nvar valueMax = Math.max.apply(Math, data.map(function(o) { return o.y; }))\n\n// X axis\nvar x = d3.scaleBand()\n  .range([ 0, width ])\n  .domain(data.map(function(d) { return d.x; }))\n  .padding(0.2);\nsvg.append(\"g\")\n  .attr(\"transform\", \"translate(0,\" + height + \")\")\n  .call(d3.axisBottom(x).ticks(5).tickSizeInner(-height))\n  .selectAll(\"text\")\n    .attr(\"transform\", \"translate(-10,0)rotate(-45)\")\n    .style(\"text-anchor\", \"end\");\n\n// Add Y axis\nvar y = d3.scaleLinear()\n  .domain([0, valueMax])\n  .range([ height, 0]);\nsvg.append(\"g\")\n  .call(d3.axisLeft(y).ticks(5).tickSizeInner(-width));\n\n// Bars\nsvg.selectAll(\"mybar\")\n  .data(data)\n  .enter()\n  .append(\"rect\")\n    .attr(\"x\", function(d) { return x(d.x); })\n    .attr(\"y\", function(d) { return y(d.y); })\n    .attr(\"width\", x.bandwidth())\n    .attr(\"height\", function(d) { return height - y(d.y); })\n    .attr(\"fill\", \"steelblue\")\n\n}) })(element);"
     },
     "execution_count": 144,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#%js\n",
    "$res<plot>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e73f751",
   "metadata": {},
   "source": [
    "--- \n",
    "\n",
    "## \"Thoughtful\" response"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9d0f612",
   "metadata": {},
   "source": [
    "Here is a very informative, \"thoughtful\" response for a quantitative Chemistry question:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "92342f9d",
   "metadata": {},
   "outputs": [],
   "source": [
    "#% markdown\n",
    "my $input = \"How many molecules a kilogram of water has? Use LaTeX for the formulas. (If any.)\";\n",
    "\n",
    "llm-synthesize($input, e => $conf)\n",
    "==> { .subst(/'\\[' | '\\]'/, '$$', :g).subst(/'\\(' | '\\)'/, '$', :g) }() # Make sure LaTeX code has proper fences"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e42571e",
   "metadata": {},
   "source": [
    "-----\n",
    "\n",
    "## References"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e7d12e8",
   "metadata": {},
   "source": [
    "### Articles, blog posts\n",
    "\n",
    "[AA1] Anton Antonov,\n",
    "[\"LLM function calling workflows (Part 1, OpenAI)\"](https://rakuforprediction.wordpress.com/2025/06/01/llm-function-calling-workflows-part-1-openai/),\n",
    "(2025),\n",
    "[RakuForPrediction at WordPress](https://rakuforprediction.wordpress.com).\n",
    "\n",
    "[AA2] Anton Antonov,\n",
    "[\"LLM function calling workflows (Part 2, Google's Gemini)\"](https://rakuforprediction.wordpress.com/2025/06/01/llm-function-calling-workflows-part-2-google-gemini/),\n",
    "(2025),\n",
    "[RakuForPrediction at WordPress](https://rakuforprediction.wordpress.com).\n",
    "\n",
    "[AA3] Anton Antonov,\n",
    "[\"LLM function calling workflows (Part 3, Facilitation)\"](https://rakuforprediction.wordpress.com/2025/06/08/llm-function-calling-workflows-part-3-facilitation/),\n",
    "(2025),\n",
    "[RakuForPrediction at WordPress](https://rakuforprediction.wordpress.com).\n",
    "\n",
    "[Gem1] Google Gemini,\n",
    "[\"Gemini Developer API\"](https://ai.google.dev/gemini-api/docs).\n",
    "\n",
    "[WRI1] Wolfram Research, Inc.\n",
    "[\"LLM-Related Functionality\" guide](https://reference.wolfram.com/language/guide/LLMFunctions.html).\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37641916",
   "metadata": {},
   "source": [
    "### Packages \n",
    "\n",
    "[AAp1] Anton Antonov,\n",
    "[LLM::Functions, Raku package](https://github.com/antononcube/Raku-LLM-Functions),\n",
    "(2023-2025),\n",
    "[GitHub/antononcube](https://github.com/antononcube).\n",
    "\n",
    "[AAp2] Anton Antonov,\n",
    "[WWW::OpenAI, Raku package](https://github.com/antononcube/Raku-WWW-OpenAI),\n",
    "(2023-2025),\n",
    "[GitHub/antononcube](https://github.com/antononcube).\n",
    "\n",
    "[AAp3] Anton Antonov,\n",
    "[WWW::Gemini, Raku package](https://github.com/antononcube/Raku-WWW-Gemini),\n",
    "(2023-2025),\n",
    "[GitHub/antononcube](https://github.com/antononcube).\n",
    "\n",
    "[AAp4] Anton Antonov,\n",
    "[Chemistry::Stoichiometry, Raku package](https://github.com/antononcube/Raku-Chemistry-Stoichiometry),\n",
    "(2021-2025),\n",
    "[GitHub/antononcube](https://github.com/antononcube)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "RakuChatbook",
   "language": "raku",
   "name": "raku"
  },
  "language_info": {
   "file_extension": ".raku",
   "mimetype": "text/x-raku",
   "name": "raku",
   "version": "6.d"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
