{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "dcb093c5",
   "metadata": {},
   "source": [
    "# LLM function calling workflows (Part 4, Universal specs)\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 document (notebook) shows how to 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",
    "\"LLM::Functions\" supports *high level* function calling via `llm-synthesize` and `llm-synthesize-with-tools`. (The latter provides more options for the tool invocation process like max-iterations or overriding tool specs.)\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 set with the names \"ChatGPT\" or \"Gemini\", then the function calling style used is that of ChatGPT. (Many LLM providers -- other than OpenAI and Gemini -- tend to adhere to OpenAI's API.)\n",
    "\n",
    "**Remark:** LLM \"function calling\" is also known as LLM \"tools\" or \"LLM tool invocation.\"\n",
    "\n",
    "In this document, non-trivial Stoichiometry computations are done with the Raku package [\"Chemistry::Stoichiometry\"](https://raku.land/zef:antononcube/Chemistry::Stoichiometry), [AAp4]. Related plots are done with the Raku package [\"JavaScript::D3\"](https://raku.land/zef:antononcube/JavaScript::D3), [AAp6]. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b198e2a3",
   "metadata": {},
   "source": [
    "### Big picture\n",
    "\n",
    "The document is the fourth of the [LLM function calling series](https://rakuforprediction.wordpress.com/tag/llm-function-calling/), [AA1 ÷ AA4]. The other three show lower-level LLM function calling workflows.\n",
    "\n",
    "Here are all related blog posts:\n",
    "\n",
    "1. [\"LLM function calling workflows (Part 1, OpenAI)\"](https://rakuforprediction.wordpress.com/2025/06/01/llm-function-calling-workflows-part-1-openai/)\n",
    "2. [\"LLM function calling workflows (Part 2, Google’s Gemini)\"](https://rakuforprediction.wordpress.com/2025/06/07/llm-function-calling-workflows-part-2-googles-gemini/)\n",
    "3. [\"LLM function calling workflows (Part 3, Facilitation)\"](https://rakuforprediction.wordpress.com/2025/06/08/llm-function-calling-workflows-part-3-facilitation/)\n",
    "4. [\"LLM function calling workflows (Part 4, Universal specs)\"](https://rakuforprediction.wordpress.com/2025/09/28/llm-function-calling-workflows-part-4-universal-specs/)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7af81bdc",
   "metadata": {},
   "source": [
    "### Overall comments and observations\n",
    "\n",
    "- Raku's LLM package constellation was behind with the LLM tool;s"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76407dbd",
   "metadata": {},
   "source": [
    "### Outline\n",
    "\n",
    "Here is an outline of the exposition below:\n",
    "\n",
    "- **Setup**   \n",
    "  Computation environment setup\n",
    "- **Chemistry computations examples**   \n",
    "  Stoichiometry computations demonstrations\n",
    "- **Define package functions as tools**     \n",
    "  Show how to define LLM-tools\n",
    "- **Stoichiometry by LLM**   \n",
    "  Invoking LLM requests with LLM tools\n",
    "- **\"Thoughtful\" response**    \n",
    "  Elaborated LLM answer based in LLM tools results\n",
    "- **Adding plot tools**     \n",
    "  Enhancing the LLM answers with [D3.js](https://d3js.org) plots"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a874186",
   "metadata": {},
   "source": [
    "-----\n",
    "\n",
    "## Setup"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "097abe6f",
   "metadata": {},
   "source": [
    "Load packages:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "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": 2,
   "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": 3,
   "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": 3,
     "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": 4,
   "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,\"right\":40,\"bottom\":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\", \"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\":2.753620038494854e0,\"x\":1},{\"y\":7.495167877978188e0,\"x\":2},{\"x\":3,\"y\":4.744064173635913e0},{\"y\":8.54523018713201e0,\"x\":4},{\"x\":5,\"y\":7.65653769035911e0},{\"x\":6,\"y\":0.9868004176976142e0},{\"x\":7,\"y\":3.9756470084945783e0},{\"x\":8,\"y\":3.9977802559746536e0},{\"y\":0.8653064167208435e0,\"x\":9},{\"x\":10,\"y\":2.310157481613615e0},{\"y\":5.925875392635298e0,\"x\":11},{\"x\":12,\"y\":3.949645229484563e0},{\"x\":13,\"y\":3.6351900265088997e0},{\"y\":2.8442515078814647e0,\"x\":14},{\"y\":2.2945836010652165e0,\"x\":15},{\"x\":16,\"y\":0.6526373549159736e0},{\"y\":6.687818862149603e0,\"x\":17},{\"x\":18,\"y\":4.068936791919052e0},{\"x\":19,\"y\":6.346543534384258e0},{\"x\":20,\"y\":8.37773943920096e0},{\"y\":3.7754861559555577e0,\"x\":21},{\"y\":8.923827697067164e0,\"x\":22},{\"y\":9.124826165861379e0,\"x\":23},{\"x\":24,\"y\":0.8517874777966827e0},{\"y\":9.204743836057826e0,\"x\":25},{\"x\":26,\"y\":6.406850642172142e0},{\"y\":5.0422815981771665e0,\"x\":27},{\"y\":3.836028794706847e0,\"x\":28},{\"y\":3.2740546125429115e0,\"x\":29},{\"y\":7.688087741804809e0,\"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": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#%js\n",
    "js-d3-list-line-plot(10.rand xx 30, background => 'none')"
   ]
  },
  {
   "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": 5,
   "id": "b4ed17f7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "64.058"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "molecular-mass(\"SO2\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12663452",
   "metadata": {},
   "source": [
    "Balance chemical equation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "d2925844",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[4*Al + 3*O2 -> 2*Al2O3]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'Al + O2 -> Al2O3'\n",
    "==> balance-chemical-equation"
   ]
  },
  {
   "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": 7,
   "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 @tools =\n",
    "        LLM::Tool.new(&molecular-mass),\n",
    "        LLM::Tool.new(&balance-chemical-equation)\n",
    "        ;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "793f0e8e",
   "metadata": {},
   "source": [
    "Make an LLM configuration with LLM-tools:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "60f96f70",
   "metadata": {},
   "outputs": [],
   "source": [
    "sink my $conf = llm-configuration($conf41-mini, :@tools);"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d890307e",
   "metadata": {},
   "source": [
    "-----\n",
    "\n",
    "## Stoichiometry by LLM"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c73a889a",
   "metadata": {},
   "source": [
    "Here is a prompt request to compute molecular masses and balances a certain chemical equation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "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": 10,
   "id": "b8519304",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{balanced_equation => 1*C2H5OH + 3*O2 -> 2*CO2 + 3*H2O, masses => {C2H5OH => 46.069, O3 => 47.997, SO2 => 64.058}}"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "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": "b882b061",
   "metadata": {},
   "source": [
    "--- \n",
    "\n",
    "## \"Thoughtful\" response"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3db735f6",
   "metadata": {},
   "source": [
    "Here is a very informative, \"thoughtful\" response for a quantitative Chemistry question:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "1da4d8a4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "To find the number of molecules in a kilogram of water, we can follow these steps:\n",
       "\n",
       "1. Calculate the molar mass of water (H₂O).\n",
       "2. Determine the number of moles in 1 kilogram of water.\n",
       "3. Use Avogadro's number to find the number of molecules.\n",
       "\n",
       "Step 1: Molar mass of water (H₂O)\n",
       "- Hydrogen (H) has an atomic mass of approximately 1 g/mol.\n",
       "- Oxygen (O) has an atomic mass of approximately 16 g/mol.\n",
       "So, the molar mass of water is:\n",
       "$$ M_{\\text{H}_2\\text{O}} = 2 \\times 1 + 16 = 18 \\, \\text{g/mol} $$\n",
       "\n",
       "Step 2: Number of moles in 1 kilogram (1000 grams) of water:\n",
       "$$ n = \\frac{\\text{mass}}{\\text{molar mass}} = \\frac{1000 \\, \\text{g}}{18 \\, \\text{g/mol}} \\approx 55.56 \\, \\text{mol} $$\n",
       "\n",
       "Step 3: Number of molecules using Avogadro's number $ N_A = 6.022 \\times 10^{23} \\, \\text{molecules/mol} $:\n",
       "$$ N = n \\times N_A = 55.56 \\times 6.022 \\times 10^{23} \\approx 3.34 \\times 10^{25} \\, \\text{molecules} $$\n",
       "\n",
       "So, 1 kilogram of water contains approximately $ 3.34 \\times 10^{25} $ molecules."
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "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": "c5ff9332",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## Adding plot tools"
   ]
  },
  {
   "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), [AAp5], or \n",
    "`js-d3-list-plot` of [\"JavaScript::D3\"](https://raku.land/zef:antononcube/JavaScript::D3), [AAp6].\n",
    "For the both automatically derived tool specs -- via the sub `llm-tool-definition` used by `LLM::Tool` -- are somewhat incomplete. Here is the auto-result for `js-d3-list-plot`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "f4d5932a",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Undefined type of parameter ⎡$data⎦; continue assuming it is a string.\n",
      "Undefined type of parameter ⎡⎦; continue assuming it is a string.\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{\n",
       "  \"function\": {\n",
       "    \"name\": \"js-d3-list-plot\",\n",
       "    \"description\": \"Makes a list plot (scatter plot) for a list of numbers or a list of x-y coordinates.\",\n",
       "    \"parameters\": {\n",
       "      \"additionalProperties\": false,\n",
       "      \"properties\": {\n",
       "        \"$data\": {\n",
       "          \"description\": \"\",\n",
       "          \"type\": \"string\"\n",
       "        },\n",
       "        \"\": {\n",
       "          \"description\": \"\",\n",
       "          \"type\": \"string\"\n",
       "        }\n",
       "      },\n",
       "      \"required\": [\n",
       "        \"$data\",\n",
       "        \"\"\n",
       "      ],\n",
       "      \"type\": \"object\"\n",
       "    },\n",
       "    \"type\": \"function\",\n",
       "    \"strict\": true\n",
       "  },\n",
       "  \"type\": \"function\"\n",
       "}"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#llm-tool-definition(&text-list-plot)\n",
    "llm-tool-definition(&js-d3-list-plot)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b5104a60",
   "metadata": {},
   "source": [
    "We could overwrite the tool-spec for `js-d3-list-plot` with this spec:\n",
    "\n",
    "```raku\n",
    "my $spec = q:to/END/;\n",
    "{\n",
    "  \"type\": \"function\",\n",
    "  \"function\": {\n",
    "    \"name\": \"jd-d3-list-plot\",\n",
    "    \"description\": \"Creates D3.js code for 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 `js-d3-list-plot` -- or other plotting function -- and does some additional input processing that anticipates LLM derived argument values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "022e1a02",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "&data-plot"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#| Make a string that represents a list-plot of the given arguments.\n",
    "my sub data-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",
    "    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": "markdown",
   "id": "6de86a4c",
   "metadata": {},
   "source": [
    "Here we add the new tool to the tool list above:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "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",
    "        LLM::Tool.new(&data-plot);"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3c63317",
   "metadata": {},
   "source": [
    "Here we make an LLM request for chemical molecules masses calculation and corresponding plotting -- note that require to obtain a dictionary of the masses and plot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "c21106ab",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Struct([masses, plot], [Hash, Str])"
      ]
     },
     "execution_count": 15,
     "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",
    "# LLM configuration with tools\n",
    "my $conf = llm-configuration($conf41-mini, tools => @tool-objects);\n",
    "\n",
    "# LLM invocation\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",
    "# Type/structure of the result\n",
    "deduce-type($res)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aad3bd04",
   "metadata": {},
   "source": [
    "Here are the molecule masses:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "a3a6c8f7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{C2H5OH => 46.069, Mg2 => 48.61, O3 => 47.997, SO2 => 64.058}"
      ]
     },
     "execution_count": 16,
     "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": 17,
   "id": "e8cf0f80",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": "<svg width=\"600\" height=\"400\" style=\"background:#1F1F1F\"><g transform=\"translate(40,40)\"><text x=\"280\" y=\"20\" text-anchor=\"middle\" font-size=\"16\" fill=\"Gray\">Molecular Masses of Given Molecules</text><text x=\"280\" y=\"360\" text-anchor=\"middle\" font-size=\"12\" fill=\"Gray\">Molecules</text><text transform=\"rotate(-90)\" x=\"-180\" y=\"12\" text-anchor=\"middle\" font-size=\"12\" fill=\"Gray\">Mass (g/mol)</text><g transform=\"translate(0,320)\"><g class=\"x axis\" style=\"font-size:10px\" transform=\"translate(0,0)\"><text transform=\"translate(-10,0)rotate(-45)\" style=\"text-anchor:end\">SO2</text><text transform=\"translate(50,0)rotate(-45)\" style=\"text-anchor:end\">O3</text><text transform=\"translate(110,0)rotate(-45)\" style=\"text-anchor:end\">Mg2</text><text transform=\"translate(170,0)rotate(-45)\" style=\"text-anchor:end\">C2H5OH</text></g></g><g class=\"y axis\" style=\"font-size:10px\"><text y=\"300\" x=\"0\" dy=\".71em\" style=\"text-anchor:end\">0</text><text y=\"200\" x=\"0\" dy=\".71em\" style=\"text-anchor:end\">15</text><text y=\"100\" x=\"0\" dy=\".71em\" style=\"text-anchor:end\">30</text><text y=\"0\" x=\"0\" dy=\".71em\" style=\"text-anchor:end\">45</text><text y=\"-100\" x=\"0\" dy=\".71em\" style=\"text-anchor:end\">60</text><text y=\"-200\" x=\"0\" dy=\".71em\" style=\"text-anchor:end\">75</text></g><rect x=\"0\" y=\"100\" width=\"40\" height=\"220\" fill=\"steelblue\"></rect><rect x=\"60\" y=\"190\" width=\"40\" height=\"130\" fill=\"steelblue\"></rect><rect x=\"120\" y=\"180\" width=\"40\" height=\"140\" fill=\"steelblue\"></rect><rect x=\"180\" y=\"200\" width=\"40\" height=\"120\" fill=\"steelblue\"></rect></g></svg>"
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#%js\n",
    "$res<plot>"
   ]
  },
  {
   "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",
    "[AA4] Anton Antonov,\n",
    "[\"LLM function calling workflows (Part 4, Universal specs)\"](https://rakuforprediction.wordpress.com/2025/09/28/llm-function-calling-workflows-part-4-universal-specs/),\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",
    "[OAI1] Open AI,\n",
    "[\"Function calling guide\"](https://platform.openai.com/docs/guides/function-calling).\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).\n",
    "\n",
    "[AAp5] Anton Antonov,\n",
    "[Text::Plot, Raku package](https://github.com/antononcube/Raku-Text-Plot),\n",
    "(2022-2025),\n",
    "[GitHub/antononcube](https://github.com/antononcube).\n",
    "\n",
    "[AAp6] Anton Antonov,\n",
    "[JavaScript::D3, Raku package](https://github.com/antononcube/Raku-JavaScript-D3),\n",
    "(2022-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
}
