<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Interactive Apps on Posit Open Source</title>
    <link>https://posit-open-source.netlify.app/categories/interactive-apps/</link>
    <description>Recent content in Interactive Apps on Posit Open Source</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Thu, 22 Jan 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://posit-open-source.netlify.app/categories/interactive-apps/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Where Questions Become Queries: Meet querychat</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/</link>
      <pubDate>Thu, 22 Jan 2026 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/</guid>
      <dc:creator>Veerle Eeftink - Van Leemput</dc:creator><description><![CDATA[<p>You love data. And you love building dashboards with it, especially with your favourite tool Shiny. But even with a bullet-proof design, dozens of user stories, feedback loops, and adjustments, you also know that there are always questions that your dashboard leaves unanswered.</p>
<p>Let&rsquo;s say you developed a dashboard to display women&rsquo;s international soccer matches. You are proud of what you have built and you eagerly show it to a colleague:</p>
<p><strong>Colleague</strong>: &ldquo;Amazing! Can you show me the soccer matches for the FIFA World Cup only?&rdquo;</p>
<p><strong>You</strong>: &ldquo;Of course, let me filter it down for you and select the FIFA World Cup tournaments&rdquo;</p>
<p><strong>Colleague</strong>: &ldquo;Interesting, can you show me all the matches in which The Netherlands have played?&rdquo;</p>
<p><strong>You</strong>: &ldquo;Eh&hellip; Well, I could, but I just have to include a country filter in my dashboard then!&rdquo;</p>
<p>Right. How are we supposed to filter down to a specific country if there is no input for it? And what about getting summary statistics for countries or players? By now it becomes painfully clear that our soccer dashboard has its limits.</p>
<p>This is exactly the moment when <a href="https://posit-dev.github.io/querychat" target="_blank" rel="noopener"><code>querychat</code></a>
 becomes interesting. It is a multilingual package that allows you to chat with your data using natural language queries. No more clicking, no more limited filters, just you and your questions. And in this article, you&rsquo;re going to learn everything about it!</p>
<p>To bring <code>querychat</code> to life, we will keep returning to two examples:</p>
<ul>
<li>The classic diamonds dataset. After all, diamonds are a girl&rsquo;s best friend, and a data scientist&rsquo;s too! The familiar dataset offers a mix of variables such as cut, colour, clarity and price, which makes it ideal for all sorts of natural language questions. You might wonder about average prices for particular cuts, or you want to compare colours, look at how clarity affects value, or explore simple patterns in the data. In other words, it is a perfect playground for testing how well natural language queries behave on structured data.</li>
<li>SheScores, the soccer dashboard that you were so proud of earlier. This app originates from the shiny::conf(2024) workshop <a href="https://github.com/hypebright/shinyconf2024-shiny101" target="_blank" rel="noopener">&ldquo;Shiny 101: The Modular App Blueprint&rdquo;</a>
, although it has been tweaked to make it a bit more interesting and updated with matches through to November 2025.</li>
</ul>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-1" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-1-1">Python</a></li>
<li><a href="#tabset-1-2">R</a></li>
</ul>
<div id="tabset-1-1">
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/shescores-original-py.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
</div>
<div id="tabset-1-2">
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/shescores-original-r.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
</div>
</div>
<p>Both datasets set the stage nicely, so let&rsquo;s roll the ball and see how <code>querychat</code> plays. We&rsquo;re talking about soccer after all!</p>
<blockquote>
<p><strong>Full code available on GitHub</strong></p>
<p>Instead of copy-pasting the content of this blog into your favourite IDE, you can also <a href="https://github.com/hypebright/shescores-dashboard" target="_blank" rel="noopener">pull the project from GitHub</a>
 and follow along. All the code is available in both Python and R.</p>
</blockquote>
<blockquote>
<p><strong>Short on time?</strong></p>
<p>Jump straight to the <a href="#adding-querychat-to-your-existing-shiny-app">SheScores app with querychat</a>
 or visit the <a href="https://posit-dev.github.io/querychat" target="_blank" rel="noopener">querychat</a>
 website</p>
</blockquote>
<h1 id="hello-querychat">Hello, querychat
</h1>
<p>In short, <code>querychat</code> makes it easy to query data using natural language. It offers a drop-in component for Shiny, a console interface, and other programmatic building-blocks. You ask questions, <code>querychat</code> translates it to a SQL query, executes it, and returns the results. The results are available as a reactive data frame, which makes it easy to display or further process the data.</p>
<p><code>querychat</code> would solve the problem we encountered earlier. We can ask it any question we can imagine without constantly adding filters or other analysis. No country filter? No problem. And yes, that sounds as cool as it is!</p>
<p>So, what do we need?</p>
<p><code>querychat</code> is powered by a Large Language Model (LLM), so you need access to a model. You first need to register at an LLM provider that provides those models. You can choose any model you like, with two little &ldquo;restrictions&rdquo;: <a href="https://posit-dev.github.io/chatlas/" target="_blank" rel="noopener"><code>chatlas</code></a>
 (Python) or <a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener"><code>ellmer</code></a>
 (R) supports it (which shouldn&rsquo;t be hard, because all the major models are) and the model has the ability to do tool calls.</p>
<blockquote>
<p><strong>Recommended models</strong></p>
<p>In this blog we&rsquo;ll use Claude Sonnet 4.5 from Anthropic. Other good choices would be GPT-4.1 (the current default for <code>querychat</code>) and Google Gemini 3.0 (as of November 2025).</p>
</blockquote>
<p>Once you&rsquo;ve made your choice and registered, you can get an API key. You need this key to authenticate with the LLM provider. One important note: never, ever hardcode the key directly into your script. You&rsquo;ll be amazed how many keys are publicly available on GitHub repos. Don&rsquo;t be that developer. As always with secrets, store it as an environment variable. Just note that the exact name of the key depends on the provider. For example, Anthropic expects <code>ANTHROPIC_API_KEY=yourkey</code>, while OpenAI uses <code>OPENAI_API_KEY=yourkey</code>.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-2" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-2-1">Python</a></li>
<li><a href="#tabset-2-2">R</a></li>
</ul>
<div id="tabset-2-1">
<p>In Python, the recommended approach is to create a <code>.env</code> file in your project folder:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ANTHROPIC_API_KEY</span><span class="o">=</span><span class="n">yourkey</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>It&rsquo;s recommended to use the <code>dotenv</code> package to load the <code>.env</code> file into your environment:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dotenv</span> <span class="kn">import</span> <span class="n">load_dotenv</span>
</span></span><span class="line"><span class="cl"><span class="n">load_dotenv</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>To keep the demo code concise we&rsquo;ll omit these lines from subsequent code examples.</p>
</div>
<div id="tabset-2-2">
<p>In R, environment variables are typically stored in a <code>.Renviron</code> file. You can create this file in your project root or in your home directory (<code>~/.Renviron</code>). Or, if you want to make it yourself really easy: you can also open/edit the relevant file with <code>usethis::edit_r_environ()</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">ANTHROPIC_API_KEY</span><span class="o">=</span><span class="n">yourkey</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>Of course we can&rsquo;t use <code>querychat</code> without installing it, so that&rsquo;s the next step:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-3" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-3-1">Python</a></li>
<li><a href="#tabset-3-2">R</a></li>
</ul>
<div id="tabset-3-1">
<p>For Python, <code>querychat</code> is available on PyPI, so you can install it easily with <code>pip</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install querychat
</span></span></code></pre></td></tr></table>
</div>
</div><p>Or, if you&rsquo;re using <code>uv</code>, add it like so:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">uv add querychat
</span></span></code></pre></td></tr></table>
</div>
</div><p>Once installed, import it like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">querychat</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-3-2">
<p>You can get <code>querychat</code> from CRAN using:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;querychat&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Alternatively, if you want the latest development version, you can install <code>querychat</code> from GitHub using:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">pak</span><span class="o">::</span><span class="nf">pak</span><span class="p">(</span><span class="s">&#34;posit-dev/querychat/pkg-r&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Once installed, load the package as usual:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">querychat</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>Wouldn&rsquo;t it be great if you can use <code>querychat</code> straight away without much code? Just to see what it&rsquo;s all about? Luckily you can with the &ldquo;quick launch&rdquo; Shiny app! You can simply call <code>app()</code> which spins up an app with <code>querychat</code> chat interface. Let&rsquo;s try it out for our diamonds dataset:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-4" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-4-1">Python</a></li>
<li><a href="#tabset-4-2">R</a></li>
</ul>
<div id="tabset-4-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">seaborn</span> <span class="kn">import</span> <span class="n">load_dataset</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">querychat</span> <span class="kn">import</span> <span class="n">QueryChat</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">diamonds</span> <span class="o">=</span> <span class="n">load_dataset</span><span class="p">(</span><span class="s2">&#34;diamonds&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span><span class="n">diamonds</span><span class="p">,</span> <span class="s2">&#34;diamonds&#34;</span><span class="p">,</span> <span class="n">client</span><span class="o">=</span><span class="s2">&#34;anthropic/claude-sonnet-4-5&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">qc</span><span class="o">.</span><span class="n">app</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>qc = QueryChat(...)</code> creates an instance of the <code>QueryChat</code> class. You pass in the dataset, give it a name and specify the model client (powered by <a href="https://posit-dev.github.io/chatlas/" target="_blank" rel="noopener"><code>chatlas</code></a>
). <code>qc.app()</code> returns the web app that lets you explore the diamonds data using natural language questions.</p>
<p>Want to try this with a different provider and/or model? No problem, just change the <code>client</code> argument accordingly. For example, to use GPT-4.1 from OpenAI, you would write: <code>client=&quot;openai/gpt-4.1&quot;</code>. You can learn more about the different options in the <a href="https://posit-dev.github.io/querychat/py/models.html" target="_blank" rel="noopener"><code>querychat</code> documentation</a>
.</p>
<p>To run this app, you need to save the code above in a file (and call it <a href="http://diamonds-app.py" target="_blank" rel="noopener"><code>diamonds-app.py</code></a>
 for example) and run it like so:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">shiny</span> <span class="n">run</span> <span class="o">--</span><span class="n">reload</span> <span class="n">diamonds</span><span class="o">-</span><span class="n">app</span><span class="o">.</span><span class="n">py</span> 
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-4-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ggplot2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">querychat_app</span><span class="p">(</span><span class="n">diamonds</span><span class="p">,</span> <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;claude/claude-sonnet-4-5&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Or, alternatively, you could write:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ggplot2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">diamonds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;diamonds&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;claude/claude-sonnet-4-5&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span><span class="o">$</span><span class="nf">app</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Both result in the same outcome, the first one is just a simplified version.</p>
<p><code>QueryChat$new()</code> creates the R6 object, taking the dataset, a table name and the model client (which will be passed to <code>ellmer::chat()</code>). Calling <code>qc$app()</code> then launches the Shiny app so you can query the diamonds dataset in plain English.</p>
<p>Want to change the provider and/or model? No problem, just change the <code>client</code> argument accordingly. For example, to use GPT-4.1 from OpenAI, you would write: <code>client = &quot;openai/gpt-4.1&quot;</code>. You can learn more about the different options in the <a href="https://posit-dev.github.io/querychat/r/index.html#use-a-different-llm-provider" target="_blank" rel="noopener"><code>querychat</code> documentation</a>
.</p>
</div>
</div>
<p>The result: a Shiny app that allows users to interact with a data source using natural language queries.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-5" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-5-1">Python</a></li>
<li><a href="#tabset-5-2">R</a></li>
</ul>
<div id="tabset-5-1">
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/diamonds-py.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
</div>
<div id="tabset-5-2">
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/diamonds-r.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
<blockquote>
<p><strong>Custom branding</strong></p>
<p>Do you notice the nice green touches and custom font in this demo app? That&rsquo;s because the project we&rsquo;ll be using in this article uses <a href="https://posit-dev.github.io/brand-yml/" target="_blank" rel="noopener">brand.yml</a>
: a simple, portable YAML file that codifies brand guidelines into a format that can be used by Quarto, Python and R. And in this case, it works beautifully for Shiny. Curious to see what such a <code>_brand.yml</code> file looks like? You can check it out <a href="https://github.com/hypebright/shescores-dashboard/blob/0cd4e3f3ae52bcf4a39f7d63fb26e555de9a6b5e/_brand.yml" target="_blank" rel="noopener">here</a>
.</p>
</blockquote>
</div>
</div>
<p>You can ask the diamonds dataset some surprisingly rich questions, and <code>querychat</code> handles them with ease. A simple place to begin is something like &ldquo;show the 10 most expensive diamonds&rdquo;. It produces straightforward SQL and updates the table in the app instantly.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">diamonds</span><span class="w"> </span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">price</span><span class="w"> </span><span class="k">DESC</span><span class="w"> </span><span class="k">LIMIT</span><span class="w"> </span><span class="mi">10</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>The question may be simple, but it already highlights the convenience of <code>querychat</code>. Without it, users would need to sort the table manually or rely on a picker, slider or some other input that filters the data for this very specific request.</p>
<p>Things get more interesting when we introduce a calculation. Asking &ldquo;can you show the 20 biggest diamonds, based on volume?&rdquo; still results in simple SQL, but the output now includes an extra column, volume, which appears neatly in the app.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- Calculate volume (x * y * z) and sort by largest volume
</span></span></span><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="p">,</span><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">x</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">z</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">volume</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="n">diamonds</span><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">volume</span><span class="w"> </span><span class="k">DESC</span><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="k">LIMIT</span><span class="w"> </span><span class="mi">20</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>From there, we can try some grouping and window functions. &ldquo;Within each cut, what is the most expensive diamond?&rdquo; works perfectly, showing the grouped results along with the necessary window function behind the scenes.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- Find the most expensive diamond for each cut type
</span></span></span><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">cut</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">MAX</span><span class="p">(</span><span class="n">price</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">max_price</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">-- Get the details of the diamond with the max price for each cut
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">ARG_MAX</span><span class="p">(</span><span class="n">carat</span><span class="p">,</span><span class="w"> </span><span class="n">price</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">carat</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">ARG_MAX</span><span class="p">(</span><span class="n">color</span><span class="p">,</span><span class="w"> </span><span class="n">price</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">color</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">ARG_MAX</span><span class="p">(</span><span class="n">clarity</span><span class="p">,</span><span class="w"> </span><span class="n">price</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">clarity</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">ARG_MAX</span><span class="p">(</span><span class="n">depth</span><span class="p">,</span><span class="w"> </span><span class="n">price</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">depth</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">ARG_MAX</span><span class="p">(</span><span class="s2">&#34;table&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">price</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">table_pct</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="n">diamonds</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">GROUP</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">cut</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">max_price</span><span class="w"> </span><span class="k">DESC</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">        cut max_price carat color clarity depth table_pct
</span></span><span class="line"><span class="cl">1   Premium     18823  2.29     I     VS2  60.8        60
</span></span><span class="line"><span class="cl">2 Very Good     18818  2.00     G     SI1  63.5        56
</span></span><span class="line"><span class="cl">3     Ideal     18806  1.51     G      IF  61.7        55
</span></span><span class="line"><span class="cl">4      Good     18788  2.80     G     SI2  63.8        58
</span></span><span class="line"><span class="cl">5      Fair     18574  2.01     G     SI1  70.6        64
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now, for a grand finale, we can throw in a more analytical question: &ldquo;are larger physical dimensions always associated with higher price?&rdquo; This one generates a slightly more complex SQL query, but it also comes with a clear and helpful explanation.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- Calculate correlation between physical dimensions and price
</span></span></span><span class="line"><span class="cl"><span class="c1">-- Also look for outliers where small diamonds cost more than large ones
</span></span></span><span class="line"><span class="cl"><span class="k">WITH</span><span class="w"> </span><span class="n">dimension_stats</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">SELECT</span><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">carat</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="p">,</span><span class="w"> </span><span class="n">z</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">-- Calculate volume as a proxy for overall size
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">x</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">z</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">volume</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">price</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">-- Rank by volume and price separately
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">RANK</span><span class="p">()</span><span class="w"> </span><span class="n">OVER</span><span class="w"> </span><span class="p">(</span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">z</span><span class="w"> </span><span class="k">DESC</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">volume_rank</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">RANK</span><span class="p">()</span><span class="w"> </span><span class="n">OVER</span><span class="w"> </span><span class="p">(</span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">price</span><span class="w"> </span><span class="k">DESC</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">price_rank</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">FROM</span><span class="w"> </span><span class="n">diamonds</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">WHERE</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="n">z</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w">  </span><span class="c1">-- Exclude malformed records
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">CORR</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">price</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">x_price_correlation</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">CORR</span><span class="p">(</span><span class="n">y</span><span class="p">,</span><span class="w"> </span><span class="n">price</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">y_price_correlation</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">CORR</span><span class="p">(</span><span class="n">z</span><span class="p">,</span><span class="w"> </span><span class="n">price</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">z_price_correlation</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">CORR</span><span class="p">(</span><span class="n">volume</span><span class="p">,</span><span class="w"> </span><span class="n">price</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">volume_price_correlation</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c1">-- Count cases where rank differences are significant (outliers)
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">COUNT</span><span class="p">(</span><span class="k">CASE</span><span class="w"> </span><span class="k">WHEN</span><span class="w"> </span><span class="n">volume_rank</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">price_rank</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">1000</span><span class="w"> </span><span class="k">THEN</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="k">END</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">large_cheap_diamonds</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">COUNT</span><span class="p">(</span><span class="k">CASE</span><span class="w"> </span><span class="k">WHEN</span><span class="w"> </span><span class="n">price_rank</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">volume_rank</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">1000</span><span class="w"> </span><span class="k">THEN</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="k">END</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">small_expensive_diamonds</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="n">dimension_stats</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">x_price_correlation        : 0.8872314
</span></span><span class="line"><span class="cl">y_price_correlation        : 0.8678642
</span></span><span class="line"><span class="cl">z_price_correlation        : 0.8682064
</span></span><span class="line"><span class="cl">volume_price_correlation   : 0.9042547
</span></span><span class="line"><span class="cl">large_cheap_diamonds       : 21082
</span></span><span class="line"><span class="cl">small_expensive_diamonds   : 21065
</span></span></code></pre></td></tr></table>
</div>
</div><p>The conclusion?</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">Answer: No, larger physical dimensions are NOT always associated with higher price
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">While there&#39;s a strong positive correlation (~0.90 for overall volume), quality factors can override size.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Correlation Analysis
</span></span><span class="line"><span class="cl">Volume ↔ Price: 0.90 correlation
</span></span><span class="line"><span class="cl">X dimension ↔ Price: 0.89 correlation
</span></span><span class="line"><span class="cl">Y dimension ↔ Price: 0.87 correlation
</span></span><span class="line"><span class="cl">Z dimension ↔ Price: 0.87 correlation
</span></span><span class="line"><span class="cl">This means physical size explains about 80-90% of price variation, but not all of it.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">The Key Finding
</span></span><span class="line"><span class="cl">I found over 21,000 cases where small diamonds are more expensive than much larger ones, and vice versa. This happens because quality factors (cut, color, clarity) matter significantly.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Concrete Example
</span></span><span class="line"><span class="cl">Here&#39;s a smaller diamond that costs more per unit volume than typical large diamonds:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Small, premium diamond: 74mm³ volume, 1.41 carat, $9,752 (Ideal cut, H color, VS1 clarity)
</span></span><span class="line"><span class="cl">This costs $131 per mm³ of volume
</span></span><span class="line"><span class="cl">Compare this to larger diamonds with lower quality that sell for much less per unit volume due to poor cut, color, or clarity grades.
</span></span></code></pre></td></tr></table>
</div>
</div><p>To encourage further exploration, <code>querychat</code> presents suggestions such as comparing how cut quality affects the price to carat ratio, finding diamonds where clarity has the biggest impact on price, or checking the price difference between the best and worst colour grades for similar sized stones. Yeah, that&rsquo;s right, you don&rsquo;t even have to come up with questions yourself.</p>
<p>Some questions result in a filtered table, others result in an explanation with results in the chat window. <code>querychat</code> figures out, based on your question, whether you want an answer straight away, or want to inspect the filtered data yourself. Pretty cool that this only took a few lines of code.</p>
<blockquote>
<p><strong>LLMs can make mistakes</strong></p>
<p>Note that it&rsquo;s still an LLM that generates these queries. LLMs can make mistakes. The nice thing about <code>querychat</code> though, is that you can inspect the SQL query yourself.</p>
<p>Most issues fall into two categories:</p>
<ul>
<li>Query errors: the SQL may fail to run or may not fully reflect what you intended. When it fails, the model will often try again. In this case, giving more context about the data can help.</li>
<li>Result errors: even when the query is correct, the model may misunderstand or oversimplify the results, especially if that result is large or complex. The result might be that key insights are missed or misinterpreted.</li>
</ul>
</blockquote>
<h1 id="why-this-matters-reliability-transparency-reproducibility">Why this matters: reliability, transparency, reproducibility
</h1>
<p>What makes the &ldquo;quick launch&rdquo; app so powerful is that it is far more than a chat window sitting on top of a dataset. Think back to the questions we explored earlier. We filtered, sorted, computed new columns, grouped data and used window functions. We also looked at analytical relationships without writing a single line of code. And that is only the beginning. If you want to go further, you can hunt for anomalies, create categories, build benchmarks or explore almost any analysis you can imagine. The key is that you never have to think about <em>how</em> to do it. You just ask.</p>
<p>And yes, you could ask all those questions in a typical LLM chat tool. But <code>querychat</code> is different. You are not relying on the model to <em>invent</em> answers or reason about the data internally. Instead, every single question is translated into SQL, executed on the actual dataset and returned exactly as the data dictates. And crucially, the SQL is always shown, so you can see precisely what is being run.</p>
<p>This brings four important benefits:</p>
<ul>
<li><strong>Reliability:</strong> the LLM does not analyse or transform the raw data itself. It only generates SQL text. <code>querychat</code> handles the execution of that SQL via tool calling so all results come from the real data engine, not from the model&rsquo;s internal guesswork.</li>
<li><strong>Transparency:</strong> every query reveals the full SQL statement. Nothing is hidden, nothing is adjusted, and you always know how the answer was produced.</li>
<li><strong>Reproducibility:</strong> since every SQL query is visible, analyses can be reused, shared, and audited.</li>
<li><strong>Safety</strong>: <code>querychat</code>&rsquo;s tools are designed with read-only actions in mind, meaning the LLM is essentially unable to perform destructive actions. However, to fully guarantee no destructive actions on your production database, make sure <code>querychat</code>&rsquo;s database permissions are read-only!</li>
</ul>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-6" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-6-1">Python</a></li>
<li><a href="#tabset-6-2">R</a></li>
</ul>
<div id="tabset-6-1">
<img src="https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/diamonds-drop-py.png" style="width:50.0%" data-fig-align="center" />
</div>
<div id="tabset-6-2">
<img src="https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/diamonds-drop-r.png" style="width:50.0%" data-fig-align="center" />
</div>
</div>
<h1 id="how-it-works-tool-calling">How it works: tool calling
</h1>
<p>If you read <a href="https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/../../../blog/shiny/shiny-side-of-llms-part-2/#when-llms-guess-tools-know">The Shiny Side of LLMs</a>
 blog series, you already know a bit about tool calling. In that series we explored how LLMs can call external tools instead of trying to do everything themselves, and <code>querychat</code> is a very practical example of this idea in action.</p>
<p>Tool calling is essentially a bridge between an LLM and your Python or R session. The model does not execute code. Instead, it requests your Python or R session execute a certain function with certain inputs (e.g., a SQL statement). Once Python or R performs the execution, the result is then passed back to the model for interpretation.</p>
<p>So how does tool calling help us here? Well, LLMs have their strengths and weaknesses. They are not great at counting things, creating data summaries or doing basic calculations. But they <em>are</em> excellent at taking natural language and turning it into structured code. SQL that is. This SQL is then executed through a tool call: a function that executes the (read only) SQL. In both Python and R this means the LLM can express your question as a request to call a function with precise arguments, and the host language performs the real work. This makes it all reliable, reproducible, and safe (read only SQL).</p>
<p>Given that, generally speaking, LLMs are very good at writing SQL, it makes perfect sense to ask one to translate your natural language questions into SQL queries. In order to generate SQL that can be executed, the LLM does need to know something about your data: which columns are there, what do they mean, and what type are they? This <strong>schema information</strong> is shared with the model, but not the raw data. With this information, it produces an SQL query as a tool call. Now, to run SQL you need a database engine. <code>querychat</code>&rsquo;s weapon of choice: <a href="https://duckdb.org" target="_blank" rel="noopener">DuckDB</a>
. Basically, our diamonds dataset gets turned into a DuckDB database, and generated SQL queries are executed on this database. Then the results are passed back to the LLM so it can say some interesting things about it.</p>
<p>To summarise:</p>
<p>prompt → SQL query → tool call → execute SQL query → return results<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Tool calling is worth emphasising because it gives us a controlled and predictable interface between LLMs and real code execution. Instead of writing and maintaining your own custom tools, you can turn to <code>querychat</code>. It already provides the functions needed to turn natural language into reliable SQL that Python or R can execute with confidence.</p>
<h1 id="customising-querychat-from-chat-to-toolkit">Customising <code>querychat</code>: from chat to toolkit
</h1>
<p>Alright, enough talking. You now know what <code>querychat</code> can do, and how it does it (high-level). You might even have brilliant ideas for your next app&hellip; In that case it would be nice to know how to build your own app with <code>querychat</code>. The Diamonds &ldquo;quick launch&rdquo; app from earlier, that you run with <code>qc.app()</code> (Python) or <code>qc$app()</code> (R), consists of a handful of methods that you can find in <code>querychat</code>, and we&rsquo;re going to use them directly.</p>
<p>The main component is the <code>QueryChat</code> object, which has different arguments and methods.</p>
<h2 id="querychat-object">QueryChat object
</h2>
<p>You call <code>QueryChat</code> to initialise a <code>QueryChat</code> object (often called <code>qc</code>), like so:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-7" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-7-1">Python</a></li>
<li><a href="#tabset-7-2">R</a></li>
</ul>
<div id="tabset-7-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-7-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span><span class="kc">...</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>You can pass <code>QueryChat</code> several arguments:</p>
<ul>
<li>
<p><code>data_source</code> and <code>table_name</code></p>
<p>These are the two most important arguments: they specify your data source and the name of your table that can be used for the SQL queries. The data source can be your data frame, a tibble, a table or any other Python or R data object, and the table name is usually the variable name of your data frame. In our example our <code>data_source</code> was <code>diamonds</code>, which we also stored in a variable called <code>diamonds</code>.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-8" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-8-1">Python</a></li>
<li><a href="#tabset-8-2">R</a></li>
</ul>
<div id="tabset-8-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span><span class="n">diamonds</span><span class="p">,</span> <span class="s2">&#34;diamonds&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-8-2">
<p>Generally, in R, the table name isn&rsquo;t required as it can be inferred from the variable name. However, it is required when you use a database connection, which we&rsquo;ll use later.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">diamonds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;diamonds&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>You&rsquo;re not limited to data objects: you can also pass a database connection to <code>data_source</code>. We&rsquo;ll come back to that later.</p>
</li>
<li>
<p><code>client</code></p>
<p>We used the <code>client</code> argument before: we use it to tell <code>querychat</code> that we want to use Claude Sonnet 4.5 (or any other model). This gets us back at the starting point of our Diamonds &ldquo;quick launch&rdquo; app.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-9" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-9-1">Python</a></li>
<li><a href="#tabset-9-2">R</a></li>
</ul>
<div id="tabset-9-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span><span class="n">diamonds</span><span class="p">,</span> <span class="s2">&#34;diamonds&#34;</span><span class="p">,</span> <span class="n">client</span><span class="o">=</span><span class="s2">&#34;anthropic/claude-sonnet-4-5&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Alternatively, you can set the client in options the <code>QUERYCHAT_CLIENT</code> environment variable.</p>
</div>
<div id="tabset-9-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">diamonds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;diamonds&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;claude/claude-sonnet-4-5&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Alternatively, you can set the client in options with <code>options(querychat.client = &quot;claude/claude-sonnet-4-5&quot;)</code>.</p>
</div>
</div>
</li>
<li>
<p><code>id</code></p>
<p>This is an optional argument, and if it&rsquo;s not given it&rsquo;s derived from the <code>table_name</code>. When to use it? If you want to work with <a href="https://posit-dev.github.io/querychat/py/build.html#multiple-datasets" target="_blank" rel="noopener">multiple QueryChat instances</a>
, for example.</p>
</li>
<li>
<p><code>greeting</code></p>
<p>A nice greeting message to display to your users. It&rsquo;s the first thing your users see, so you better make it good! If not provided, one is generated at the start. While this one looks fine on first sight, it&rsquo;s rather slow and wasteful (it costs extra tokens because it&rsquo;s generated every single time). Also, because it&rsquo;s generated on the fly, it&rsquo;s far from consistent. Earlier, when we ran the &ldquo;quick launch&rdquo; app, you already might have noticed that it generated a warning message:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-10" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-10-1">Python</a></li>
<li><a href="#tabset-10-2">R</a></li>
</ul>
<div id="tabset-10-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">Warning: No greeting provided; the LLM will be invoked at conversation start to generate one. For faster startup, lower cost, and determinism, please save a greeting and pass it to init(). You can also use `querychat.greeting()` to help generate a greeting.
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-10-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">Warning message:
</span></span><span class="line"><span class="cl">No greeting provided; the LLM will be invoked at conversation start to generate one.
</span></span><span class="line"><span class="cl">• For faster startup, lower cost, and determinism, please save a greeting and pass it to QueryChat$new().
</span></span><span class="line"><span class="cl">ℹ You can generate a greeting with $generate_greeting(). 
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>So yes, we need a greeting! You can add your own greeting by providing a string in Markdown format.</p>
<p>Some inspiration on what you can put in there: basic instructions, suggestions for filtering, sorting or analysing the data, addressing data privacy concerns, or letting people know where they can get support if something goes wrong.</p>
<p>And if you don&rsquo;t feel like writing your own greeting, or if you feel uninspired, you can let <code>querychat</code> handle it! Simply use <code>generate_greeting()</code>:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-11" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-11-1">Python</a></li>
<li><a href="#tabset-11-2">R</a></li>
</ul>
<div id="tabset-11-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span><span class="n">diamonds</span><span class="p">,</span> <span class="s2">&#34;diamonds&#34;</span><span class="p">,</span> <span class="n">client</span><span class="o">=</span><span class="s2">&#34;anthropic/claude-sonnet-4-5&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Generate a greeting with help from the LLM</span>
</span></span><span class="line"><span class="cl"><span class="n">greeting_text</span> <span class="o">=</span> <span class="n">qc</span><span class="o">.</span><span class="n">generate_greeting</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Save it</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;diamonds_greeting.md&#34;</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">greeting_text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Then use the saved greeting in your app</span>
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span><span class="n">diamonds</span><span class="p">,</span> <span class="s2">&#34;diamonds&#34;</span><span class="p">,</span> <span class="n">client</span><span class="o">=</span><span class="s2">&#34;anthropic/claude-sonnet-4-5&#34;</span><span class="p">,</span> <span class="n">greeting</span><span class="o">=</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;diamonds_greeting.md&#34;</span><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Which give us this nice greeting:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gh"># Welcome! 👋
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">I&#39;m here to help you explore and understand your diamonds dataset. 
</span></span><span class="line"><span class="cl">I can filter and sort the data, answer questions with SQL queries, 
</span></span><span class="line"><span class="cl">and help you discover insights.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Here are some ideas to get started:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Explore the data
</span></span></span><span class="line"><span class="cl"><span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Show me the most expensive diamonds&lt;/span&gt;
</span></span><span class="line"><span class="cl"><span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;What&#39;s the average price of diamonds by cut quality?&lt;/span&gt;
</span></span><span class="line"><span class="cl"><span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;How many diamonds are in each clarity category?&lt;/span&gt;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Filter and analyze
</span></span></span><span class="line"><span class="cl"><span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Show only ideal cut diamonds over 2 carats&lt;/span&gt;
</span></span><span class="line"><span class="cl"><span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Filter to diamonds with the best color grades (D, E, F)&lt;/span&gt;
</span></span><span class="line"><span class="cl"><span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Which cut has the highest average price per carat?&lt;/span&gt;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">What would you like to explore first?
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-11-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">diamonds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;diamonds&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;claude/claude-sonnet-4-5&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Generate a greeting with help from the LLM</span>
</span></span><span class="line"><span class="cl"><span class="n">greeting_text</span> <span class="o">&lt;-</span> <span class="n">qc</span><span class="o">$</span><span class="nf">generate_greeting</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Save it</span>
</span></span><span class="line"><span class="cl"><span class="nf">writeLines</span><span class="p">(</span><span class="n">greeting_text</span><span class="p">,</span> <span class="s">&#34;diamonds_greeting.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Then use the saved greeting in your app</span>
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">diamonds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;diamonds&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;claude/claude-sonnet-4-5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">greeting</span> <span class="o">=</span> <span class="s">&#34;diamonds_greeting.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Which give us this nice greeting:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gh"># Welcome to the Diamond Dashboard! 💎
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">I&#39;m here to help you explore and analyze this dataset of diamond characteristics and prices. 
</span></span><span class="line"><span class="cl">I can filter and sort the data, answer questions, and help you discover interesting patterns.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Here are some ideas to get started:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">**Explore the Data**</span>
</span></span><span class="line"><span class="cl">  <span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;What&#39;s the average price of diamonds in this dataset?&lt;/span&gt;
</span></span><span class="line"><span class="cl">  <span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;How many diamonds are there in each clarity category?&lt;/span&gt;
</span></span><span class="line"><span class="cl">  <span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Which diamond has the highest price?&lt;/span&gt;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">**Filter and Sort**</span>
</span></span><span class="line"><span class="cl">  <span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Show me only Ideal cut diamonds&lt;/span&gt;
</span></span><span class="line"><span class="cl">  <span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Filter to diamonds over 2 carats and sort by price&lt;/span&gt;
</span></span><span class="line"><span class="cl">  <span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Show me the most expensive diamonds with VS1 clarity&lt;/span&gt;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">What would you like to explore?
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>You can see that the generated greeting contains a span HTML tag: <code>&lt;span class=&quot;suggestion&quot;&gt;…&lt;/span&gt;</code>. If you make your own greeting, you can use this tag to automatically populate the chatbox when it&rsquo;s being clicked.</p>
</li>
<li>
<p><code>data_description</code></p>
<p><code>querychat</code> automatically helps the LLM by providing things like the column names and datatypes of your data (the <strong>schema information</strong>), but results can be even more accurate when you provide additional context in the data description. There&rsquo;s no specific format needed, and you can add whatever information you like. To give some inspiration, this is what we could say about the diamonds dataset:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gh"># Diamonds Dataset Description
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">A structured dataset describing physical and quality attributes of individual diamonds,
</span></span><span class="line"><span class="cl">commonly used to model or predict price.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Fields
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> carat (float) — Diamond weight  
</span></span><span class="line"><span class="cl"><span class="k">-</span> cut (category) — Cut quality: Fair, Good, Very Good, Premium, Ideal  
</span></span><span class="line"><span class="cl"><span class="k">-</span> color (category) — Color grade from D (best) to J (worst)  
</span></span><span class="line"><span class="cl"><span class="k">-</span> clarity (category) — Clarity grades: I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF  
</span></span><span class="line"><span class="cl"><span class="k">-</span> depth (float) — Total depth percentage  
</span></span><span class="line"><span class="cl"><span class="k">-</span> table (float) — Table width percentage  
</span></span><span class="line"><span class="cl"><span class="k">-</span> price (int) — Price in USD  
</span></span><span class="line"><span class="cl"><span class="k">-</span> x (float) — Length in mm  
</span></span><span class="line"><span class="cl"><span class="k">-</span> y (float) — Width in mm  
</span></span><span class="line"><span class="cl"><span class="k">-</span> z (float) — Depth in mm
</span></span></code></pre></td></tr></table>
</div>
</div><p>We can save this in a Markdown file and pass it on to <code>querychat</code>:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-12" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-12-1">Python</a></li>
<li><a href="#tabset-12-2">R</a></li>
</ul>
<div id="tabset-12-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">diamonds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;diamonds&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="s2">&#34;anthropic/claude-sonnet-4-5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">greeting</span><span class="o">=</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;diamonds_greeting.md&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">data_description</span><span class="o">=</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;diamonds_data_description.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-12-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">diamonds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;diamonds&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;claude/claude-sonnet-4-5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">greeting</span> <span class="o">=</span> <span class="s">&#34;diamonds_greeting.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">data_description</span> <span class="o">=</span> <span class="s">&#34;diamonds_data_description.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
</li>
<li>
<p><code>extra_instructions</code></p>
<p>For further tweaking the LLMs behaviour you can use <code>extra_instructions</code>. You can go nuts here: make it talk like a pirate, use an emoji in every sentence, or use an annoying amount of diamond-related phrases. You can also use this section for more practical guidance like notes on preferred spelling, tone, or handling of sensitive terms. For example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="k">-</span> Assume the user doesn&#39;t know much about diamonds: 
</span></span><span class="line"><span class="cl">  keep explanations simple and accessible.
</span></span><span class="line"><span class="cl"><span class="k">-</span> When describing diamond attributes, default to plain English. 
</span></span><span class="line"><span class="cl">  If a term is highly technical, include a short clarification.
</span></span><span class="line"><span class="cl"><span class="k">-</span> Maintain consistent spelling in British English.
</span></span></code></pre></td></tr></table>
</div>
</div><div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-13" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-13-1">Python</a></li>
<li><a href="#tabset-13-2">R</a></li>
</ul>
<div id="tabset-13-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">diamonds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;diamonds&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="s2">&#34;anthropic/claude-sonnet-4-5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">greeting</span><span class="o">=</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;diamonds_greeting.md&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">data_description</span><span class="o">=</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;diamonds_data_description.md&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">extra_instructions</span><span class="o">=</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;diamonds_extra_instructions.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-13-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">diamonds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;diamonds&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;claude/claude-sonnet-4-5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">greeting</span> <span class="o">=</span> <span class="s">&#34;diamonds_greeting.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">data_description</span> <span class="o">=</span> <span class="s">&#34;diamonds_data_description.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">extra_instructions</span> <span class="o">=</span> <span class="s">&#34;diamonds_extra_instructions.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
</li>
<li>
<p><code>categorical_threshold</code></p>
<p>This threshold applies to text columns, and sets the maximum number of unique values to consider it as a categorical variable. The default is 20.</p>
</li>
<li>
<p><code>prompt_template</code></p>
<p>The <code>prompt_template</code> is a more advanced parameter to provide a custom prompt template. If you don&rsquo;t provide it, <code>querychat</code> will use the built-in prompt, which we&rsquo;ll inspect a little bit closer later.</p>
</li>
</ul>
<p>Besides arguments, you can also call methods on the <code>QueryChat</code> object. One of them is <code>cleanup()</code>, which releases any resources (e.g. database connections) associated with the data source. You should call this when you are done using the <code>QueryChat</code> object to avoid resource leaks:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-14" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-14-1">Python</a></li>
<li><a href="#tabset-14-2">R</a></li>
</ul>
<div id="tabset-14-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">qc</span><span class="o">.</span><span class="n">cleanup</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-14-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">qc</span><span class="o">$</span><span class="nf">cleanup</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>That&rsquo;s&hellip; A lot! And all you need to chat safely with your data. As you&rsquo;ve seen in our earlier examples, you don&rsquo;t need a lot to get started (<code>data_source</code> and <code>table_name</code> are enough, and in R you can even omit the <code>table_name</code>). But knowing the possibilities makes it easier to customise <code>querychat</code> to your liking.</p>
<h1 id="beyond-chat-bespoke-interfaces">Beyond chat: bespoke interfaces
</h1>
<p>Now you know everything there is to know about the <code>QueryChat</code> object. You know how to add a greeting, additional context, and your favourite LLM. However, it&rsquo;s time to dream bigger and time to get building! Because chatting with your data safely is one thing, but if you truly want to amaze your users you can build an entire dashboard around it. Plots, maps, tables, and value boxes that all update based on the user&rsquo;s questions. Your own bespoke interface. Before we dive into that, let&rsquo;s first take a step back and see if we can reconstruct the &ldquo;quick launch&rdquo; app.</p>
<p>You need two things if you want to build a Shiny app with <code>querychat</code>:</p>
<ul>
<li>The UI component (the chat window)</li>
<li>A server method that deals with the results</li>
</ul>
<p>For the UI component, there are two choices: <code>sidebar()</code> or <code>ui()</code>. The difference? <code>ui</code> creates a basic chat interface, while <code>sidebar</code> wraps the chat interface in a (<code>bslib</code>, for the R lovers) sidebar component designed to be used as the <code>sidebar</code> argument to <code>page_sidebar</code>.</p>
<p>If we want to do something with the results that get returned by <code>querychat</code>, we need to make use of the <code>server()</code> method. The server method returns:</p>
<ul>
<li><code>sql</code>: a reactive that returns the current SQL query. And, if you want to run your own queries, you can also call the <code>$sql()</code> method on the <code>QueryChat</code> object to run queries.</li>
<li><code>title</code>: a reactive that returns the current title.</li>
<li><code>df</code>: a reactive that returns the data frame, filtered and sorted by the current SQL query.</li>
</ul>
<p>Let&rsquo;s take a look at a minimal example that rebuilds the &ldquo;quick launch&rdquo; app:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-15" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-15-1">Python</a></li>
<li><a href="#tabset-15-2">R</a></li>
</ul>
<div id="tabset-15-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">App</span><span class="p">,</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">seaborn</span> <span class="kn">import</span> <span class="n">load_dataset</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">querychat</span> <span class="kn">import</span> <span class="n">QueryChat</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Setup</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 1. Initialize QueryChat with custom files</span>
</span></span><span class="line"><span class="cl"><span class="n">diamonds</span> <span class="o">=</span> <span class="n">load_dataset</span><span class="p">(</span><span class="s2">&#34;diamonds&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">diamonds_greeting</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;diamonds_greeting.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">diamonds_data_description</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;diamonds_data_description.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">diamonds_extra_instructions</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;diamonds_extra_instructions.md&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">diamonds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;diamonds&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="s2">&#34;anthropic/claude-sonnet-4-5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">greeting</span><span class="o">=</span><span class="n">diamonds_greeting</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">data_description</span><span class="o">=</span><span class="n">diamonds_data_description</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">extra_instructions</span><span class="o">=</span><span class="n">diamonds_extra_instructions</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># UI</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 2. QueryChat sidebar UI component</span>
</span></span><span class="line"><span class="cl">    <span class="n">qc</span><span class="o">.</span><span class="n">sidebar</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="s2">&#34;SQL Query&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">output_text_verbatim</span><span class="p">(</span><span class="s2">&#34;sql_output&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">fill</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">output_text</span><span class="p">(</span><span class="s2">&#34;title&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">output_data_frame</span><span class="p">(</span><span class="s2">&#34;data_table&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">fill</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">fillable</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">theme</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">Theme</span><span class="o">.</span><span class="n">from_brand</span><span class="p">(</span><span class="vm">__file__</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Server</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 3. QueryChat server component</span>
</span></span><span class="line"><span class="cl">    <span class="n">vals</span> <span class="o">=</span> <span class="n">qc</span><span class="o">.</span><span class="n">server</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># 4. Use the filtered/sorted data frame reactively</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@render.data_frame</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">data_table</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">vals</span><span class="o">.</span><span class="n">df</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">title</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">vals</span><span class="o">.</span><span class="n">title</span><span class="p">()</span> <span class="ow">or</span> <span class="s2">&#34;Diamonds&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># 5. Display the generated SQL query</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">sql_output</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">vals</span><span class="o">.</span><span class="n">sql</span><span class="p">()</span> <span class="ow">or</span> <span class="s2">&#34;SELECT * FROM diamonds;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>To keep things simple, we opted for a simple verbatim text output, but we also could&rsquo;ve chosen for this combination, which is from the <a href="https://posit-dev.github.io/shinychat/py/" target="_blank" rel="noopener"><code>shinychat</code></a>
 package:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">output_ui</span><span class="p">(</span><span class="s2">&#34;sql_output&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@render.ui</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">sql_output</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">sql_value</span> <span class="o">=</span> <span class="n">vals</span><span class="o">.</span><span class="n">sql</span><span class="p">()</span> <span class="ow">or</span> <span class="sa">f</span><span class="s2">&#34;SELECT * FROM </span><span class="si">{</span><span class="n">table_name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">sql_code</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;```sql</span><span class="se">\n</span><span class="si">{</span><span class="n">sql_value</span><span class="si">}</span><span class="se">\n</span><span class="s2">```&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">output_markdown_stream</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;sql_code&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">content</span><span class="o">=</span><span class="n">sql_code</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">auto_scroll</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">width</span><span class="o">=</span><span class="s2">&#34;100%&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This actually happens in the source code for the quick launch app. It would give us the nice &ldquo;copy to clipboard&rdquo; feature and nice formatting. Another alternative would be the native <a href="https://shiny.posit.co/py/api/core/ui.output_markdown_stream.html" target="_blank" rel="noopener">markdown stream component in Shiny</a>
.</p>
<blockquote>
<p><strong>brand.yml</strong></p>
<p>If you want to make use of brand.yml, you need to add a theme argument: <code>theme=ui.Theme.from_brand(**file**)</code>. Make sure you have installed the latest version of shiny with the <code>theme</code> extra! You can simply add it with: <code>uv add &quot;shiny[theme]&quot;</code> (if using <code>uv</code>), or <code>pip install &quot;shiny[theme]&quot;</code></p>
</blockquote>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/diamonds-bespoke-py.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
</div>
<div id="tabset-15-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">DT</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">querychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ggplot2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Setup</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 1. Initialize QueryChat with custom files</span>
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">diamonds</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;diamonds&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;claude/claude-sonnet-4-5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">greeting</span> <span class="o">=</span> <span class="s">&#34;diamonds_greeting.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">data_description</span> <span class="o">=</span> <span class="s">&#34;diamonds_data_description.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">extra_instructions</span> <span class="o">=</span> <span class="s">&#34;diamonds_extra_instructions.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># UI</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;Diamonds Explorer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># 2. QueryChat sidebar UI component</span>
</span></span><span class="line"><span class="cl">  <span class="n">sidebar</span> <span class="o">=</span> <span class="n">qc</span><span class="o">$</span><span class="nf">sidebar</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_header</span><span class="p">(</span><span class="s">&#34;SQL Query&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">verbatimTextOutput</span><span class="p">(</span><span class="s">&#34;sql_query&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_header</span><span class="p">(</span><span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;title&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="n">DT</span><span class="o">::</span><span class="nf">DTOutput</span><span class="p">(</span><span class="s">&#34;data_table&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Server</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># 3. QueryChat server component</span>
</span></span><span class="line"><span class="cl">  <span class="n">vals</span> <span class="o">&lt;-</span> <span class="n">qc</span><span class="o">$</span><span class="nf">server</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># 3. Display generated SQL query</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">sql_query</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="nf">is.null</span><span class="p">(</span><span class="n">vals</span><span class="o">$</span><span class="nf">sql</span><span class="p">()))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">return</span><span class="p">(</span><span class="s">&#34;SELECT * FROM diamonds;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">vals</span><span class="o">$</span><span class="nf">sql</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># 4. Display data table based on user query</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">data_table</span> <span class="o">&lt;-</span> <span class="n">DT</span><span class="o">::</span><span class="nf">renderDT</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">vals</span><span class="o">$</span><span class="nf">df</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># 5. Dynamic title based on user query</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">title</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="nf">is.null</span><span class="p">(</span><span class="n">vals</span><span class="o">$</span><span class="nf">title</span><span class="p">()))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">return</span><span class="p">(</span><span class="s">&#34;Diamonds Data&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">vals</span><span class="o">$</span><span class="nf">title</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/diamonds-bespoke-r.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
</div>
</div>
<p>Looks pretty similar to the quick launch app, right?! So that&rsquo;s how it was build. Note that there a few aesthetic differences though. The quick launch app has a few extra sparks here and there, and our app makes use of custom theming with <code>brand.yml</code>.</p>
<p>So far in our diamonds adventure we have only looked at a simple table, but we can extent this idea much further and build an entire dashboard around it: value boxes, graphs, tables, maps, you name it! This is also what <a href="https://shiny.posit.co/py/templates/sidebot/" target="_blank" rel="noopener">sidebot</a>
 does, and this template is available to get you started quickly. A nice touch is the inclusion of the ✨ icon, which sends a screenshot of the visuals to the LLM for an explanation. How cool is that!</p>
<h1 id="adding-querychat-to-your-existing-shiny-app">Adding querychat to your existing Shiny app
</h1>
<p>The idea of <a href="https://shiny.posit.co/py/templates/sidebot/" target="_blank" rel="noopener">sidebot</a>
 is certainly interesting: why build a dashboard with all kind of filters when you can just add a chat window with access to a smart LLM. You ask it questions, <code>querychat</code> returns some SQL and reactive filtered data, and you make sure you update the entire dashboard. Unlimited filter possibilities. And it doesn&rsquo;t have to be complicated to achieve that.</p>
<p>To demonstrate how easy it is, we are going to use an existing dashboard (SheScores), that currently has a number of filters in it: a slider for the year(s), a dropdown for the continent where the matches took place, the tournaments that took place on those continents, and a switch that filters the data to include only data with known scorers, or not.</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/shescores-original-py.gif" alt="Python version of SheScores" />
<figcaption aria-hidden="true">Python version of SheScores</figcaption>
</figure>
<p>So what does SheScores look like behind the scenes? We&rsquo;re not going into the nitty gritty details of the SheScores dashboard, and we don&rsquo;t have to if we want to add <code>querychat</code> to it. The most important bit of logic is stored in a reactive that contains the filtered data. It reacts to changes in any of the inputs (year, continent, tournament, scorer only or not).</p>
<p>The reactive, <code>filtered_data()</code>, forms the basis for all the elements in the dashboard: the value boxes, the map, the graph, and the table.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-16" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-16-1">Python</a></li>
<li><a href="#tabset-16-2">R</a></li>
</ul>
<div id="tabset-16-1">
<blockquote>
<p><strong>Tip</strong></p>
<p>See <a href="https://github.com/hypebright/shescores-dashboard/blob/68f34785f3217d005497f4719b1f5c64af00ac4d/Python/shescores-app.py" target="_blank" rel="noopener">GitHub</a>
 for the full source code.</p>
</blockquote>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span><span class="lnt">83
</span><span class="lnt">84
</span><span class="lnt">85
</span><span class="lnt">86
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Setup</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="n">results_with_scorers</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;data/results_with_scorers.csv&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">results_with_scorers</span><span class="p">[</span><span class="s2">&#34;date&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">to_datetime</span><span class="p">(</span><span class="n">results_with_scorers</span><span class="p">[</span><span class="s2">&#34;date&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">results_with_scorers</span> <span class="o">=</span> <span class="n">results_with_scorers</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="n">results_with_scorers</span><span class="p">[</span><span class="s2">&#34;tournament&#34;</span><span class="p">]</span> <span class="o">!=</span> <span class="s2">&#34;Friendly&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">&amp;</span> <span class="p">(</span><span class="n">results_with_scorers</span><span class="p">[</span><span class="s2">&#34;date&#34;</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="s2">&#34;2000-01-01&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># UI</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">h4</span><span class="p">(</span><span class="s2">&#34;Filters&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">input_slider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;year_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Select year range:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nb">min</span><span class="o">=</span><span class="nb">int</span><span class="p">(</span><span class="n">results_with_scorers</span><span class="p">[</span><span class="s2">&#34;date&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">dt</span><span class="o">.</span><span class="n">year</span><span class="o">.</span><span class="n">min</span><span class="p">()),</span>
</span></span><span class="line"><span class="cl">            <span class="nb">max</span><span class="o">=</span><span class="nb">int</span><span class="p">(</span><span class="n">results_with_scorers</span><span class="p">[</span><span class="s2">&#34;date&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">dt</span><span class="o">.</span><span class="n">year</span><span class="o">.</span><span class="n">max</span><span class="p">()),</span>
</span></span><span class="line"><span class="cl">            <span class="n">value</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="nb">int</span><span class="p">(</span><span class="n">results_with_scorers</span><span class="p">[</span><span class="s2">&#34;date&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">dt</span><span class="o">.</span><span class="n">year</span><span class="o">.</span><span class="n">min</span><span class="p">()),</span>
</span></span><span class="line"><span class="cl">                <span class="nb">int</span><span class="p">(</span><span class="n">results_with_scorers</span><span class="p">[</span><span class="s2">&#34;date&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">dt</span><span class="o">.</span><span class="n">year</span><span class="o">.</span><span class="n">max</span><span class="p">()),</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="n">sep</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">input_selectize</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;continent_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Select continents:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">choices</span><span class="o">=</span><span class="nb">sorted</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">results_with_scorers</span><span class="p">[</span><span class="s2">&#34;continent&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">dropna</span><span class="p">()</span><span class="o">.</span><span class="n">unique</span><span class="p">()</span><span class="o">.</span><span class="n">tolist</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">selected</span><span class="o">=</span><span class="s2">&#34;Europe&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">multiple</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">input_select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;tournament_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Select tournaments:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">choices</span><span class="o">=</span><span class="p">[],</span>
</span></span><span class="line"><span class="cl">            <span class="n">selected</span><span class="o">=</span><span class="p">[],</span>
</span></span><span class="line"><span class="cl">            <span class="n">multiple</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">input_switch</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;scorer_only&#34;</span><span class="p">,</span> <span class="s2">&#34;Show matches with scorer data only&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="kc">False</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">width</span><span class="o">=</span><span class="s2">&#34;30%&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Other UI content</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">    <span class="n">title</span><span class="o">=</span><span class="s2">&#34;She Scores ⚽️: Women&#39;s International Soccer Matches&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">fillable</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">theme</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">Theme</span><span class="o">.</span><span class="n">from_brand</span><span class="p">(</span><span class="vm">__file__</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Server</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Reactive filtered data based on inputs</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">filtered_data</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">req</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">continent_filter</span><span class="p">())</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">req</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">tournament_filter</span><span class="p">())</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">results_with_scorers</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;date&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">dt</span><span class="o">.</span><span class="n">year</span> <span class="o">&gt;=</span> <span class="nb">input</span><span class="o">.</span><span class="n">year_filter</span><span class="p">()[</span><span class="mi">0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">            <span class="o">&amp;</span> <span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;date&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">dt</span><span class="o">.</span><span class="n">year</span> <span class="o">&lt;=</span> <span class="nb">input</span><span class="o">.</span><span class="n">year_filter</span><span class="p">()[</span><span class="mi">1</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">            <span class="o">&amp;</span> <span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;continent&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">isin</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">continent_filter</span><span class="p">()))</span>
</span></span><span class="line"><span class="cl">            <span class="o">&amp;</span> <span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tournament&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">isin</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">tournament_filter</span><span class="p">()))</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">input</span><span class="o">.</span><span class="n">scorer_only</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;scorer&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">notna</span><span class="p">()]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># Other server logic</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-16-2">
<blockquote>
<p><strong>Tip</strong></p>
<p>Check out the full source code on <a href="https://github.com/hypebright/shescores-dashboard/blob/9c8b20d64adfb67566272c587e158dbf2a5052d8/R/shescores-app.R" target="_blank" rel="noopener">GitHub</a>
.</p>
</blockquote>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span><span class="lnt">83
</span><span class="lnt">84
</span><span class="lnt">85
</span><span class="lnt">86
</span><span class="lnt">87
</span><span class="lnt">88
</span><span class="lnt">89
</span><span class="lnt">90
</span><span class="lnt">91
</span><span class="lnt">92
</span><span class="lnt">93
</span><span class="lnt">94
</span><span class="lnt">95
</span><span class="lnt">96
</span><span class="lnt">97
</span><span class="lnt">98
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Setup</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="n">results_with_scorers</span> <span class="o">&lt;-</span> <span class="nf">read.csv</span><span class="p">(</span><span class="s">&#34;../data/results_with_scorers.csv&#34;</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">filter</span><span class="p">(</span><span class="n">tournament</span> <span class="o">!=</span> <span class="s">&#34;Friendly&#34;</span><span class="p">,</span> <span class="n">date</span> <span class="o">&gt;=</span> <span class="s">&#34;2000-01-01&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Other setup</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># UI</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">fillable</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;She Scores ⚽️: Women&#39;s International Soccer Matches&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">sidebar</span> <span class="o">=</span> <span class="nf">sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;Filters&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">width</span> <span class="o">=</span> <span class="s">&#34;30%&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Year filter</span>
</span></span><span class="line"><span class="cl">    <span class="nf">sliderInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">inputId</span> <span class="o">=</span> <span class="s">&#34;year_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Select year range:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">min</span> <span class="o">=</span> <span class="nf">year</span><span class="p">(</span><span class="nf">min</span><span class="p">(</span><span class="nf">as.Date</span><span class="p">(</span><span class="n">results_with_scorers</span><span class="o">$</span><span class="n">date</span><span class="p">))),</span>
</span></span><span class="line"><span class="cl">      <span class="n">max</span> <span class="o">=</span> <span class="nf">year</span><span class="p">(</span><span class="nf">max</span><span class="p">(</span><span class="nf">as.Date</span><span class="p">(</span><span class="n">results_with_scorers</span><span class="o">$</span><span class="n">date</span><span class="p">))),</span>
</span></span><span class="line"><span class="cl">      <span class="n">value</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">year</span><span class="p">(</span><span class="nf">min</span><span class="p">(</span><span class="nf">as.Date</span><span class="p">(</span><span class="n">results_with_scorers</span><span class="o">$</span><span class="n">date</span><span class="p">))),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">year</span><span class="p">(</span><span class="nf">max</span><span class="p">(</span><span class="nf">as.Date</span><span class="p">(</span><span class="n">results_with_scorers</span><span class="o">$</span><span class="n">date</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">sep</span> <span class="o">=</span> <span class="s">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Continent filter (dropdown)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">pickerInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">inputId</span> <span class="o">=</span> <span class="s">&#34;continent_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Select continents:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">choices</span> <span class="o">=</span> <span class="nf">sort</span><span class="p">(</span><span class="nf">unique</span><span class="p">(</span><span class="n">results_with_scorers</span><span class="o">$</span><span class="n">continent</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">      <span class="n">selected</span> <span class="o">=</span> <span class="nf">sort</span><span class="p">(</span><span class="nf">unique</span><span class="p">(</span><span class="n">results_with_scorers</span><span class="o">$</span><span class="n">continent</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">      <span class="n">options</span> <span class="o">=</span> <span class="nf">pickerOptions</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">actionsBox</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">selectedTextFormat</span> <span class="o">=</span> <span class="s">&#34;count &gt; 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">countSelectedText</span> <span class="o">=</span> <span class="s">&#34;{0} continents selected&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">multiple</span> <span class="o">=</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Tournament filter (dropdown)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">pickerInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">inputId</span> <span class="o">=</span> <span class="s">&#34;tournament_filter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Select tournaments:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">choices</span> <span class="o">=</span> <span class="kc">NULL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">selected</span> <span class="o">=</span> <span class="kc">NULL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">options</span> <span class="o">=</span> <span class="nf">pickerOptions</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">actionsBox</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">liveSearch</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">liveSearchPlaceholder</span> <span class="o">=</span> <span class="s">&#34;Search for a tournament&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">selectedTextFormat</span> <span class="o">=</span> <span class="s">&#34;count &gt; 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">countSelectedText</span> <span class="o">=</span> <span class="s">&#34;{0} tournaments selected&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">multiple</span> <span class="o">=</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Switch to show data with scorers only</span>
</span></span><span class="line"><span class="cl">    <span class="nf">input_switch</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;scorer_only&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Show matches with scorer data only&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">value</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Other UI content</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Server</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Reactive filtered data based on inputs</span>
</span></span><span class="line"><span class="cl">  <span class="n">filtered_data</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="nf">length</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">continent_filter</span><span class="p">)</span> <span class="o">&gt;</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="nf">length</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">tournament_filter</span><span class="p">)</span> <span class="o">&gt;</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">results_with_scorers</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">mutate</span><span class="p">(</span><span class="n">date</span> <span class="o">=</span> <span class="nf">as.Date</span><span class="p">(</span><span class="n">date</span><span class="p">))</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">filter</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">year</span><span class="p">(</span><span class="n">date</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">input</span><span class="o">$</span><span class="n">year_filter[1]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nf">year</span><span class="p">(</span><span class="n">date</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="n">input</span><span class="o">$</span><span class="n">year_filter[2]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">continent</span> <span class="o">%in%</span> <span class="n">input</span><span class="o">$</span><span class="n">continent_filter</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">tournament</span> <span class="o">%in%</span> <span class="n">input</span><span class="o">$</span><span class="n">tournament_filter</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kr">if</span> <span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">scorer_only</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="o">!</span><span class="nf">is.na</span><span class="p">(</span><span class="n">scorer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="kr">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Other server logic</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>Now we want to get rid of all those filters. We want a chat window instead. What do we need to change in order to use <code>querychat</code>? Spoiler alert: not much.</p>
<p>Of course we need to initialise our <code>QueryChat</code> object. And since we&rsquo;re not talking about diamonds, we need to make sure to provide a proper soccer-themed greeting, a data description, and extra instructions:</p>
<p><code>shescores_greeting.md</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gh"># Welcome to SheScores! ⚽
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">I&#39;m here to help you explore international women&#39;s soccer match data. 
</span></span><span class="line"><span class="cl">You can ask me to filter and sort the dashboard, answer questions about the data, 
</span></span><span class="line"><span class="cl">or provide insights about teams, players, tournaments, and more.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Here are some ideas to get started:
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">**Explore match data:**</span>
</span></span><span class="line"><span class="cl">  <span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Show me the highest-scoring matches in World Cup tournaments&lt;/span&gt;
</span></span><span class="line"><span class="cl">  <span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Which teams have played the most matches against each other?&lt;/span&gt;
</span></span><span class="line"><span class="cl">  <span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Filter to matches from the 2025 UEFA Euro&lt;/span&gt;
</span></span><span class="line"><span class="cl">  <span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Which team has the best win rate in the Canada vs United States rivalry?&lt;/span&gt;
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="gs">**Analyze player performance:**</span>
</span></span><span class="line"><span class="cl">  <span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Who are the top scorers in World Cup history?&lt;/span&gt;
</span></span><span class="line"><span class="cl">  <span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Which players have scored the most penalty goals?&lt;/span&gt;
</span></span><span class="line"><span class="cl">  <span class="k">*</span> &lt;span class=&#34;suggestion&#34;&gt;Which matches had the most own goals?&lt;/span&gt;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">What would you like to explore?
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>shescores_data_description.md</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gh"># Dataset description
</span></span></span><span class="line"><span class="cl">This dataset contains international women’s football match results. 
</span></span><span class="line"><span class="cl">It includes match metadata (date, location, teams), outcomes (scores), 
</span></span><span class="line"><span class="cl">plus optional event-level information such as individual scorers. 
</span></span><span class="line"><span class="cl">Not all friendly matches are represented; 
</span></span><span class="line"><span class="cl">major tournaments are mostly complete.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Column descriptions
</span></span></span><span class="line"><span class="cl"><span class="k">-</span> date (string, YYYY-MM-DD): The calendar date on which the match was played.
</span></span><span class="line"><span class="cl"><span class="k">-</span> home_team (string): Name of the home team.
</span></span><span class="line"><span class="cl"><span class="k">-</span> date (string, YYYY-MM-DD): The calendar date on which the match was played.
</span></span><span class="line"><span class="cl"><span class="k">-</span> home_team (string): Name of the home team.
</span></span><span class="line"><span class="cl"><span class="k">-</span> away_team (string): Name of the away team.
</span></span><span class="line"><span class="cl"><span class="k">-</span> home_score (integer): Goals scored by the home team at full time (extra time included, - penalty shoot-outs excluded).
</span></span><span class="line"><span class="cl"><span class="k">-</span> away_score (integer): Goals scored by the away team at full time (extra time included, - penalty shoot-outs excluded).
</span></span><span class="line"><span class="cl"><span class="k">-</span> tournament (string): Name of the competition or event.
</span></span><span class="line"><span class="cl"><span class="k">-</span> city (string): City or administrative area where the match was played.
</span></span><span class="line"><span class="cl"><span class="k">-</span> country (string): Country where the match was played.
</span></span><span class="line"><span class="cl"><span class="k">-</span> neutral (boolean): Indicates whether the match took place at a neutral venue.
</span></span><span class="line"><span class="cl"><span class="k">-</span> team (string, optional): Team associated with a recorded scoring event.
</span></span><span class="line"><span class="cl"><span class="k">-</span> scorer (string, optional): Player who scored the goal.
</span></span><span class="line"><span class="cl"><span class="k">-</span> minute (integer, optional): Match minute in which the goal occurred.
</span></span><span class="line"><span class="cl"><span class="k">-</span> own_goal (boolean, optional): Indicates whether the goal was an own goal.
</span></span><span class="line"><span class="cl"><span class="k">-</span> penalty (boolean, optional): Indicates whether the goal was scored from a penalty kick.
</span></span><span class="line"><span class="cl"><span class="k">-</span> country_flag_home (string): Emoji or symbol representing the home country.
</span></span><span class="line"><span class="cl"><span class="k">-</span> country_flag_away (string): Emoji or symbol representing the away country.
</span></span><span class="line"><span class="cl"><span class="k">-</span> continent (string): Continent associated with the home country.
</span></span><span class="line"><span class="cl"><span class="k">-</span> country_code (string): Country code associated with the home team (e.g., ISO-like).
</span></span><span class="line"><span class="cl"><span class="k">-</span> latitude (float): Latitude of the match location.
</span></span><span class="line"><span class="cl"><span class="k">-</span> longitude (float): Longitude of the match location.
</span></span><span class="line"><span class="cl"><span class="k">-</span> match_id (string): Unique identifier for the match, typically based on date and team names.
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>shescores_extra_instrucions.md</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="k">-</span> Maintain consistent spelling in British English.
</span></span><span class="line"><span class="cl"><span class="k">-</span> Don&#39;t add any extra columns to the dataset. You may use them internally 
</span></span><span class="line"><span class="cl">  for calculations, but the final output should only include the original 
</span></span><span class="line"><span class="cl">  columns with the original column names.
</span></span><span class="line"><span class="cl"><span class="k">-</span> Soccer terminology should be used throughout the analysis 
</span></span><span class="line"><span class="cl">  (e.g., &#34;goal&#34; instead of &#34;point&#34;).
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now, adding <code>querychat</code> into the mix is as simple as replacing our inputs in the sidebar with the <code>querychat</code> sidebar component (<code>sidebar()</code>), and our reactive with the results of <code>server()</code>.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-17" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-17-1">Python</a></li>
<li><a href="#tabset-17-2">R</a></li>
</ul>
<div id="tabset-17-1">
<blockquote>
<p><strong>Tip</strong></p>
<p>See <a href="https://github.com/hypebright/shescores-dashboard/blob/963d2b72c600ee9f30ce04da170b05a01c1dc31c/Python/shescores-querychat-app.py" target="_blank" rel="noopener">GitHub</a>
 for the full source code</p>
</blockquote>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Setup</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="n">results_with_scorers</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;data/results_with_scorers.csv&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">results_with_scorers</span><span class="p">[</span><span class="s2">&#34;date&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">to_datetime</span><span class="p">(</span><span class="n">results_with_scorers</span><span class="p">[</span><span class="s2">&#34;date&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">results_with_scorers</span> <span class="o">=</span> <span class="n">results_with_scorers</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="n">results_with_scorers</span><span class="p">[</span><span class="s2">&#34;tournament&#34;</span><span class="p">]</span> <span class="o">!=</span> <span class="s2">&#34;Friendly&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">&amp;</span> <span class="p">(</span><span class="n">results_with_scorers</span><span class="p">[</span><span class="s2">&#34;date&#34;</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="s2">&#34;2000-01-01&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 1. Initialize QueryChat with custom files</span>
</span></span><span class="line"><span class="cl"><span class="n">shescores_greeting</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;shescores_greeting.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">shescores_data_description</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;shescores_data_description.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">shescores_extra_instructions</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;shescores_extra_instructions.md&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">results_with_scorers</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;results_with_scorers&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="s2">&#34;anthropic/claude-sonnet-4-5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">greeting</span><span class="o">=</span><span class="n">shescores_greeting</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">data_description</span><span class="o">=</span><span class="n">shescores_data_description</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">extra_instructions</span><span class="o">=</span><span class="n">shescores_extra_instructions</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Other setup</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># UI</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">qc</span><span class="o">.</span><span class="n">sidebar</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Other UI components</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">    <span class="n">title</span><span class="o">=</span><span class="s2">&#34;She Scores ⚽️: Women&#39;s International Soccer Matches&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">fillable</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">theme</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">Theme</span><span class="o">.</span><span class="n">from_brand</span><span class="p">(</span><span class="vm">__file__</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Server</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Reactive filtered data based on query</span>
</span></span><span class="line"><span class="cl">    <span class="n">filtered_data</span> <span class="o">=</span> <span class="n">qc</span><span class="o">.</span><span class="n">server</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># Other server logic</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-17-2">
<blockquote>
<p><strong>Tip</strong></p>
<p>Check out the full code on <a href="https://github.com/hypebright/shescores-dashboard/blob/9c8b20d64adfb67566272c587e158dbf2a5052d8/R/shescores-querychat-app.R" target="_blank" rel="noopener">GitHub</a>
.</p>
</blockquote>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Setup</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="n">results_with_scorers</span> <span class="o">&lt;-</span> <span class="nf">read.csv</span><span class="p">(</span><span class="s">&#34;../data/results_with_scorers.csv&#34;</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">filter</span><span class="p">(</span><span class="n">tournament</span> <span class="o">!=</span> <span class="s">&#34;Friendly&#34;</span><span class="p">,</span> <span class="n">date</span> <span class="o">&gt;=</span> <span class="s">&#34;2000-01-01&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 1. Initialize QueryChat with custom files</span>
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">results_with_scorers</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;results_with_scorers&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;claude/claude-sonnet-4-5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">greeting</span> <span class="o">=</span> <span class="s">&#34;shescores_greeting.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">data_description</span> <span class="o">=</span> <span class="s">&#34;shescores_data_description.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">extra_instructions</span> <span class="o">=</span> <span class="s">&#34;shescores_extra_instructions.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Other setup</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># UI</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">fillable</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;She Scores ⚽️: Women&#39;s International Soccer Matches&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">sidebar</span> <span class="o">=</span> <span class="n">qc</span><span class="o">$</span><span class="nf">sidebar</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Other UI components</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Server</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ===============================</span>
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Reactive filtered data based on query</span>
</span></span><span class="line"><span class="cl">  <span class="n">filtered_data</span> <span class="o">&lt;-</span> <span class="n">qc</span><span class="o">$</span><span class="nf">server</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Other server logic</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>It results in a lot less code and logic too. Win-win. Thanks <code>querychat</code> !</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/shescores-querychat-py.gif" alt="Python version of SheScores with querychat" />
<figcaption aria-hidden="true">Python version of SheScores with querychat</figcaption>
</figure>
<blockquote>
<p><strong>Note</strong></p>
<p>While we don&rsquo;t have a reset button in the app, <code>querychat</code> knows very well what to do when you ask it to reset the dashboard. In this case, it will display the unfiltered data, just like we started when we launched the app.</p>
</blockquote>
<h1 id="database-options">Database options
</h1>
<p>So far we&rsquo;ve only worked with simple datasets: the <code>diamonds</code> dataset that ships with a package, and our soccer data loaded from a <code>.csv</code>. But here&rsquo;s how it works under the hood: even in those examples, you weren&rsquo;t really querying a data frame directly. <code>querychat</code> hands everything off to DuckDB, which becomes the engine that executes all generated SQL. And DuckDB does so quickly and efficiently. Your data frame or <code>.csv</code> is effectively registered inside DuckDB, and every answer comes from real SQL running on that engine.</p>
<p>But what if you don&rsquo;t want to work with in-memory tables at all? What if you already have a database you want to query directly? Maybe a DuckDB file, a SQLite database, Postgres, or even BigQuery? That&rsquo;s exactly what the <code>data_source</code> argument is for. Earlier we used it with plain data frames, but it also accepts database connections. In Python, that means any <a href="https://www.sqlalchemy.org" target="_blank" rel="noopener">SQLAlchemy-supported database</a>
; in R, anything that <a href="https://dbi.r-dbi.org" target="_blank" rel="noopener"><code>DBI</code></a>
 can handle. <code>querychat</code> will inspect the schema of whatever you connect, and from that moment on the workflow is identical as before, only now you&rsquo;re interacting with your own database.</p>
<p>Let&rsquo;s take a look at how to set up <code>querychat</code> with another backend (SQLite) using the <code>data_source</code> argument.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-18" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-18-1">Python</a></li>
<li><a href="#tabset-18-2">R</a></li>
</ul>
<div id="tabset-18-1">
<p>For demonstration purposes, we&rsquo;ll create a SQLite database from the SheScores data (<code>results_with_scorers.csv</code>).</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy</span> <span class="kn">import</span> <span class="n">create_engine</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># From results_with_scorers.csv, create a SQLite database named shescores.db</span>
</span></span><span class="line"><span class="cl"><span class="n">df_path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;data/results_with_scorers.csv&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="n">df_path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create the SQLite database and store the DataFrame in it</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Save database in top-level /data directory</span>
</span></span><span class="line"><span class="cl"><span class="n">df</span><span class="o">.</span><span class="n">to_sql</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;results_with_scorers&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">con</span><span class="o">=</span><span class="n">create_engine</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;sqlite:///&#34;</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;data/shescores.db&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">if_exists</span><span class="o">=</span><span class="s2">&#34;replace&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">index</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>We can then use this database in our <code>QueryChat</code> instance like so:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy</span> <span class="kn">import</span> <span class="n">create_engine</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">querychat</span> <span class="kn">import</span> <span class="n">QueryChat</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dotenv</span> <span class="kn">import</span> <span class="n">load_dotenv</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">load_dotenv</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Custom files for SheScores</span>
</span></span><span class="line"><span class="cl"><span class="n">shescores_greeting</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;shescores_greeting.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">shescores_data_description</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;shescores_data_description.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">shescores_extra_instructions</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;shescores_extra_instructions.md&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Now create a QueryChat instance to interact with the database</span>
</span></span><span class="line"><span class="cl"><span class="n">db_path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;data/shescores.db&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">engine</span> <span class="o">=</span> <span class="n">create_engine</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;sqlite:///</span><span class="si">{</span><span class="n">db_path</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">=</span> <span class="n">QueryChat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">engine</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;results_with_scorers&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="o">=</span><span class="s2">&#34;anthropic/claude-sonnet-4-5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">greeting</span><span class="o">=</span><span class="n">shescores_greeting</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">data_description</span><span class="o">=</span><span class="n">shescores_data_description</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">extra_instructions</span><span class="o">=</span><span class="n">shescores_extra_instructions</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">qc</span><span class="o">.</span><span class="n">app</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You can also create a DuckDB database from a CSV file or a pandas DataFrame, which is definitely nice for larger datasets. For more examples you can check out the package documentation on <a href="https://posit-dev.github.io/querychat/py/data-sources.html" target="_blank" rel="noopener">data sources</a>
.</p>
<p>Even if you have a database that isn&rsquo;t supported by SQLAlchemy or isn&rsquo;t suited for DuckDB, you can still let <code>querychat</code> access it. In that case, you need to implement the <a href="https://posit-dev.github.io/querychat/py/reference/types.DataSource.html" target="_blank" rel="noopener">DataSource</a>
 interface/protocol.</p>
</div>
<div id="tabset-18-2">
<p>For demonstration purposes, we&rsquo;ll create a SQLite database from the SheScores data (<code>results_with_scorers.csv</code>). To create a new SQLite database, you simply supply the filename to <a href="https://dbi.r-dbi.org/reference/dbConnect.html" target="_blank" rel="noopener"><code>dbConnect()</code></a>
. And with <code>dbWriteTable(</code>), you can easily copy an R dataframe into that newly generated SQLite database:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">DBI</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># From results_with_scorers.csv, create a SQLite database named shescores.db</span>
</span></span><span class="line"><span class="cl"><span class="n">results_with_scorers</span> <span class="o">&lt;-</span> <span class="nf">read.csv</span><span class="p">(</span><span class="s">&#34;data/results_with_scorers.csv&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create a connection to a new SQLite database</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Save database in top-level /data directory</span>
</span></span><span class="line"><span class="cl"><span class="n">conn</span> <span class="o">&lt;-</span> <span class="nf">dbConnect</span><span class="p">(</span><span class="n">RSQLite</span><span class="o">::</span><span class="nf">SQLite</span><span class="p">(),</span> <span class="s">&#34;data/shescores.db&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Write the data frame to a table named results_with_scorers</span>
</span></span><span class="line"><span class="cl"><span class="nf">dbWriteTable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">conn</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;results_with_scorers&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">results_with_scorers</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">overwrite</span> <span class="o">=</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">dbDisconnect</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If you have a SQLite database, connecting to it works in the same manner:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">querychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">DBI</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create a connection to a SQLite database</span>
</span></span><span class="line"><span class="cl"><span class="n">conn</span> <span class="o">&lt;-</span> <span class="nf">dbConnect</span><span class="p">(</span><span class="n">RSQLite</span><span class="o">::</span><span class="nf">SQLite</span><span class="p">(),</span> <span class="s">&#34;data/shescores.db&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Write the data frame to a table named results_with_scorers</span>
</span></span><span class="line"><span class="cl"><span class="nf">dbWriteTable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">conn</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;results_with_scorers&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">results_with_scorers</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">overwrite</span> <span class="o">=</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Now create a QueryChat instance to interact with the database</span>
</span></span><span class="line"><span class="cl"><span class="n">qc</span> <span class="o">&lt;-</span> <span class="n">QueryChat</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">conn</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;results_with_scorers&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">=</span> <span class="s">&#34;claude/claude-sonnet-4-5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">greeting</span> <span class="o">=</span> <span class="s">&#34;shescores_greeting.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">data_description</span> <span class="o">=</span> <span class="s">&#34;shescores_data_description.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">extra_instructions</span> <span class="o">=</span> <span class="s">&#34;shescores_extra_instructions.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qc</span><span class="o">$</span><span class="nf">app</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Looking for more examples? Check out these <a href="https://github.com/posit-dev/querychat/tree/main/pkg-r/inst/examples-shiny/sqlite" target="_blank" rel="noopener">database setup examples for querychat</a>
.</p>
</div>
</div>
<p><code>querychat</code> knows how to deal with databases, and it has some convenient features for it too, especially when things go wrong: it validates whether tables actually exist and handles any issues gracefully (without cryptic error messages).</p>
<p>One thing to keep in mind when you move from in-memory data to real databases, especially inside Shiny apps, is proper connection management. Whenever your app opens a database connection, it also needs to close it. In Python that usually means calling <code>engine.dispose()</code> when the app shuts down. In R you would use <code>dbDisconnect(conn)</code>, or rely on a connection pool. SQLAlchemy already provides pooling on the Python side, but in R you&rsquo;ll want the <code>pool</code> package to handle this in a nice manner.</p>
<h1 id="for-the-curious-how-does-querychat-know-what-to-do">For the curious: how does querychat know what to do?
</h1>
<p>You&rsquo;ve seen what <code>querychat</code> can do, and you know a bit how it works conceptually. But behind all those concepts is of course some real code. So, for the curious amongst us, here&rsquo;s a little peek into the <code>querychat</code> code!</p>
<p>To talk with an LLM you need a good prompt: prompt design is crucial for a good outcome. A prompt contains context and instructions that an LLM will use to come up with its answer. <code>querychat</code> has a set of instructions for the LLM too, the system prompt, which is stored in a Markdown file (<code>prompt.md</code>).</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-19" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-19-1">Python</a></li>
<li><a href="#tabset-19-2">R</a></li>
</ul>
<div id="tabset-19-1">
<p>You can check out the <code>prompt.md</code> file <a href="https://github.com/posit-dev/querychat/blob/fea52e4e2b56a2cc0a042140dbe5ce194aca8ac6/pkg-py/src/querychat/prompts/prompt.md" target="_blank" rel="noopener">here</a>
, or you can simply print it out:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">qc</span><span class="o">.</span><span class="n">system_prompt</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-19-2">
<p>You can check out the <code>prompt.md</code> file <a href="https://github.com/posit-dev/querychat/blob/main/pkg-r/inst/prompts/prompt.md" target="_blank" rel="noopener">here</a>
, or you can simply print it out:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">print</span><span class="p">(</span><span class="n">qc</span><span class="o">$</span><span class="nf">system_prompt</span><span class="p">())</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>So, what&rsquo;s in this prompt? Let&rsquo;s highlight a few bits:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">You have access to a {{db_type}} SQL database with the following schema:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">&lt;database_schema&gt;
</span></span><span class="line"><span class="cl">{{schema}}
</span></span><span class="line"><span class="cl">&lt;/database_schema&gt;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">{{<span class="ni">#data_description</span>}}
</span></span><span class="line"><span class="cl">Here is additional information about the data:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">&lt;data_description&gt;
</span></span><span class="line"><span class="cl">{{data_description}}
</span></span><span class="line"><span class="cl">&lt;/data_description&gt;
</span></span><span class="line"><span class="cl">{{/data_description}}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">For security reasons, you may only query this specific table.
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">{{<span class="ni">#extra_instructions</span>}}
</span></span><span class="line"><span class="cl"><span class="gu">## Additional Instructions
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">{{extra_instructions}}
</span></span><span class="line"><span class="cl">{{/extra_instructions}}
</span></span></code></pre></td></tr></table>
</div>
</div><p>The prompt is a <a href="https://mustache.github.io" target="_blank" rel="noopener">Mustache</a>
 template. It&rsquo;s a fill-in-the-blanks template: the <code>{name}</code> parts get replaced with real values, and the <code>{#something} ... {{/something}}</code> blocks only appear if that &ldquo;something&rdquo; actually exists. When you call QueryChat with corresponding arguments, everything gets filled in.</p>
<p>We talked about tool calling earlier, and there was a little note that said that there&rsquo;s not just one tool. There are multiple, for different tasks. You can see that back clearly in the prompt, where we instruct the LLM to call a certain tool (e.g. <code>querychat_update_dashboard</code>) when it receives a request:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">You can handle three types of requests:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### 1. Filtering and Sorting Data
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> Call <span class="sb">`querychat_update_dashboard`</span> with the query and a descriptive title
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">The user may ask to &#34;reset&#34; or &#34;start over&#34;; that means clearing the filter and title. Do this by calling querychat_reset_dashboard().
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### 2. Answering Questions About Data
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> Use the <span class="sb">`querychat_query`</span> tool to run SQL queries
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### 3. Providing Suggestions for Next Steps
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></td></tr></table>
</div>
</div><p>There are three tools in <code>querychat</code>:</p>
<ul>
<li><code>querychat_query</code>: used whenever the user asks a question that requires data analysis, aggregation, or calculations.</li>
<li><code>querychat_update_dashboard</code>: used whenever the user requests filtering, sorting, or data manipulation on the dashboard with questions like &ldquo;Show me&hellip;&rdquo; or &ldquo;Which records have&hellip;&rdquo;. Basically any request that involves showing a subset of the data or reordering it.</li>
<li><code>querychat_reset_dashboard</code>: if the user asks to reset the dashboard</li>
</ul>
<p>All the tools are written as <code>chatlas</code> or <code>ellmer</code> tools. As a user, you don&rsquo;t have to worry about this though. The LLM makes sure to use the rights tools, which will make sure the SQL gets executed and the data gets filtered accordingly. But hey, this section was for the curious amongst us!</p>
<h1 id="safety-control-and-confidence">Safety, control, and confidence
</h1>
<p>At some point, everyone asks the same question: is this safe? And it&rsquo;s a fair one. Luckily, <code>querychat</code> is designed entirely around control. The LLM never executes anything itself, never touches your data(base) and never sees raw data. Its only job is to propose <em>read-only</em> SQL.</p>
<p>Remember the moment we asked it to drop a table? It refused. Not because it&rsquo;s polite, but because it&rsquo;s instructed to do so. Combine that with an underlying database (the built-in DuckDB temporary database or your own) that only provides read-only access, and your data will always be left untouched. </p>
<p>It&rsquo;s not a black box either: every generated query can be logged, inspected or audited at any time. In Shiny v1.12.0 this becomes even easier thanks to built in OpenTelemetry support via <code>otel</code>. If you&rsquo;re curious about what that looks like in practice, you can read more in this <a href="https://shiny.posit.co/r/articles/improve/opentelemetry/" target="_blank" rel="noopener">article</a>
.</p>
<p>The safety, control, and (hopefully) the confidence you&rsquo;ve gained by now, make it also suitable for enterprise and regulated environments. If you need to use private or managed LLMs, you&rsquo;re covered: Azure, AWS Bedrock and Google Vertex AI all provide versions of popular models that support tool calling and can work with <code>querychat</code>.</p>
<h1 id="other-querychat-apps-in-the-wild">Other querychat apps in the wild
</h1>
<p>It&rsquo;s always nice to see what others have done with <code>querychat</code>. So here are few sources of inspiration:</p>
<ul>
<li>Do you like trail running? This <a href="https://posit.co/blog/race-stats-dashboard-querychat/" target="_blank" rel="noopener">Race Stats dashboard</a>
 is for you!</li>
<li>Is the American football league more your thing? This <a href="https://www.infoworld.com/article/4040535/chat-with-your-data-the-easy-way-in-r-or-python.html" target="_blank" rel="noopener">Shiny for Python app</a>
 shows you a lot of stats.</li>
<li>Joe Cheng and Garrick Aden-Buie hosted a workshop at posit::conf(2025) called &ldquo;Programming with LLMs&rdquo; that also contains some <a href="https://github.com/posit-conf-2025/llm" target="_blank" rel="noopener">examples</a>
.</li>
<li>And one that we mentioned before: <a href="https://www.infoworld.com/article/4040535/chat-with-your-data-the-easy-way-in-r-or-python.html" target="_blank" rel="noopener">sidebot</a>
, a dashboard analysing restaurant tipping, which is a template you can use very easily.</li>
</ul>
<p>Whether you&rsquo;re playing with a small example dataset or building something much bigger, <code>querychat</code> can be the companion in your app that you&rsquo;re users will love. Build a whole dashboard around the chatbot, or add a touch of LLM magic for those extra side questions. With all this knowledge under your belt, you can build it all!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>While it seems like there is only one tool call, there&rsquo;s not. In <code>querychat</code> there are different tools for, surprise, different tasks. For the curious there&rsquo;s a deep dive into <code>querychat</code>&rsquo;s source code later in this article.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/querychat-python-r/querychat-python-r-header.png" length="871482" type="image/png" />
    </item>
    <item>
      <title>plumber2 0.2.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/plumber2-0-2-0/</link>
      <pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/plumber2-0-2-0/</guid>
      <dc:creator>Thomas Lin Pedersen</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [ ] Add intro sentence, e.g. the standard tagline for the package
* [ ] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<p>We&rsquo;re stoked to announce the release of <a href="https://plumber2.posit.co" target="_blank" rel="noopener">plumber2</a>
 0.2.0. plumber2 is a package for creating webservers in R based on either an annotation-based or programmatic workflow. It is the successor to the <a href="https://www.rplumber.io/" target="_blank" rel="noopener">plumber</a>
 package who has empowered the R community for 10 years and allowed them to share their R based functionalities with their organizations and the world.</p>
<p>You can install it from CRAN with:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>pak</span><span class='nf'>::</span><span class='nf'><a href='https://pak.r-lib.org/reference/pak.html'>pak</a></span><span class='o'>(</span><span class='s'>"plumber2"</span><span class='o'>)</span></span></code></pre>
</div>
<p>This release covers both a bunch of new features as well as some tangible improvements to performance. The headlining features are OpenTelemetry (OTEL) support and support for authentication which we will dive into below. In the end we will also provide a grab-bag of miscellaneous improvements for your enjoyment.</p>
<p>You can see a full list of changes in the <a href="https://plumber2.posit.co/news/index.html" target="_blank" rel="noopener">release notes</a>
</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://plumber2.posit.co/'>plumber2</a></span><span class='o'>)</span></span></code></pre>
</div>
<h2 id="otel-support">OTEL support
</h2>
<p>We have been hard at work at adding support for <a href="https://opentelemetry.io/" target="_blank" rel="noopener">OpenTelemetry (OTEL)</a>
 for our tools to allow easy instrumentation across our offerings, see e.g. the <a href="https://shiny.posit.co/blog/posts/shiny-r-1.12/" target="_blank" rel="noopener">shiny blog post</a>
 announcing support for it there. If you do not know what OTEL is, here is a short introduction to the subject:</p>
<p>OTEL describes itself as &ldquo;high-quality, ubiquitous, and portable telemetry to enable effective observability&rdquo;. In simpler terms, OpenTelemetry is a set of tools, APIs, and SDKs that help you collect and export telemetry data (like traces, logs, and metrics) from your applications. This data provides insights into how your applications are performing and behaving in real-world scenarios.</p>
<p>It captures three key types of data:</p>
<ol>
<li><strong>Traces:</strong> These show the path of a request through your application.</li>
<li><strong>Logs:</strong> These are detailed event records that capture what happened at specific moments.</li>
<li><strong>Metrics:</strong> These are numerical measurements over time, like how many users are connected or how long outputs take to render.</li>
</ol>
<p>These data types were standardized under the OTEL project, <a href="https://opentelemetry.io/community/marketing-guidelines/#i-opentelemetry-is-a-joint-effort" target="_blank" rel="noopener">which is supported by a large community and many companies</a>
. The goal is to provide a consistent way to collect and export observability data, making it easier to monitor and troubleshoot applications.</p>
<p>OTEL is vendor-neutral, meaning you can send your telemetry data to various local backends like <a href="https://www.jaegertracing.io/" target="_blank" rel="noopener">Jaeger</a>
, <a href="https://zipkin.io/" target="_blank" rel="noopener">Zipkin</a>
, <a href="https://prometheus.io/" target="_blank" rel="noopener">Prometheus</a>
, or cloud-based services like <a href="https://grafana.com/products/cloud/" target="_blank" rel="noopener">Grafana Cloud</a>
, <a href="https://pydantic.dev/logfire" target="_blank" rel="noopener">Logfire</a>
, and <a href="https://langfuse.com/" target="_blank" rel="noopener">Langfuse</a>
. This flexibility means you&rsquo;re not locked into any particular monitoring solution.</p>
<p>While that may be somewhat of a mouthful the <em>tldr;</em> is that with OTEL you can capture what goes on in your application and use a variety of services to explore this data. This is great especially for code that is meant to be deployed and thus not readily available for introspection.</p>
<p>A great thing about OTEL is that traces are linked across applications. If you have multiple linked microservices based on plumber2, then you can follow a request trace as it travels between the different APIs. The same goes for a shiny app that calls into a plumber2 api or the other way around. As we build out support across our tools this benefit will only get more profound.</p>
<h3 id="otel-in-plumber2">OTEL in plumber2
</h3>
<p>While OTEL is integrated into plumber2 it is not activated by default. To set it up you need the otel and otelsdk installed and configured:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>pak</span><span class='nf'>::</span><span class='nf'><a href='https://pak.r-lib.org/reference/pak.html'>pak</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='s'>"otel"</span>, <span class='s'>"otelsdk"</span><span class='o'>)</span><span class='o'>)</span></span></code></pre>
</div>
<p>Configuration is completely code free and based on environment variables. You can e.g. add the lines below to your <code>.Renviron</code> file to setup OTEL with Logfire</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Enable OpenTelemetry by setting Collector environment variables</span>
</span></span><span class="line"><span class="cl"><span class="nv">OTEL_TRACES_EXPORTER</span><span class="o">=</span>http
</span></span><span class="line"><span class="cl"><span class="nv">OTEL_LOGS_EXPORTER</span><span class="o">=</span>http
</span></span><span class="line"><span class="cl"><span class="nv">OTEL_LOG_LEVEL</span><span class="o">=</span>debug
</span></span><span class="line"><span class="cl"><span class="nv">OTEL_METRICS_EXPORTER</span><span class="o">=</span>http
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">OTEL_EXPORTER_OTLP_ENDPOINT</span><span class="o">=</span><span class="s2">&#34;https://logfire-us.pydantic.dev&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">OTEL_EXPORTER_OTLP_HEADERS</span><span class="o">=</span><span class="s2">&#34;Authorization=&lt;your-write-token&gt;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You can verify that everything is set up by calling <a href="https://otel.r-lib.org/reference/is_tracing_enabled.html" target="_blank" rel="noopener"><code>otel::is_tracing_enabled()</code></a>
 which should return <code>TRUE</code> in that case.</p>
<p>OTEL has an extensive list of semantic conventions for telemetry of various domains so that information is captured in a standardised way. plumber2 adheres to the HTTP server conventions and supports all the required and most of the recommended <a href="https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-server-span" target="_blank" rel="noopener">trace attributes</a>
 and <a href="https://opentelemetry.io/docs/specs/semconv/http/http-metrics/" target="_blank" rel="noopener">metrics</a>
.</p>
<p>Within a plumber2 API, a trace span is started the moment a request is received. The span is populated with the following information:</p>
<ul>
<li><code>http.request.method</code>: The method of the request (e.g. <code>GET</code>, <code>POST</code>, etc)</li>
<li><code>url.path</code>: The exact path requested</li>
<li><code>url.scheme</code>: The protocol used for the request</li>
<li><code>http.route</code>: The route pattern of the last of the route handlers the request went through</li>
<li><code>network.protocol.name</code>: The internal protocol used. Always <code>http</code></li>
<li><code>network.protocol.version</code>: The version of the protocol. Always <code>1.1</code></li>
<li><code>server.port</code>: The port the server is listening on. Can be used to distinguish multiple concurrent servers</li>
<li><code>url.query</code>: The querystring of the request</li>
<li><code>client.address</code>: The IP address the request comes from</li>
<li><code>server.address</code>: The address the request was send to</li>
<li><code>user_agent.original</code>: The user agent of the client sending the request</li>
<li><code>http.request.header.&lt;header-name&gt;</code>: The value of <code>header-name</code> in the request. E.g. <code>http.request.header.date</code> will contain the value of the <code>Date</code> header</li>
</ul>
<p>Once the request has been handled it will further append the following information:</p>
<ul>
<li><code>http.response.status_code</code>: The status code of the response</li>
<li><code>http.response.header.&lt;header-name&gt;</code>: The value of <code>header-name</code> in the response. E.g. <code>http.response.header.content-type</code> will contain the value of the <code>Content-Type</code> header</li>
</ul>
<p>In addition to the trace attributes above, a number of OTEL metrics are also recorded:</p>
<ul>
<li><code>http.server.request.duration</code>: The duration of the request handling from it is received to it is send back</li>
<li><code>http.server.active_requests</code>: The number of active requests being handled at the given time</li>
<li><code>http.server.request.body.size</code>: The size of the request body</li>
<li><code>http.server.response.body.size</code>: The size of the response body</li>
</ul>
<p>As a child of this parent span each handler in your API will also initiate a span with the following attributes:</p>
<ul>
<li><code>routr.route</code>: The path pattern of the handler. This will be recorded in the routr representation which uses <code>:param</code> instead of <code>{param}</code> format (e.g. <code>users/:username</code> instead of <code>users/{username}</code>)</li>
<li><code>routr.path.param.&lt;param-name&gt;</code>: The value of the <code>param-name</code> path parameter. E.g. a request for <code>users/thomas</code> will get a <code>routr.path.param.username</code> attribute with the value <code>thomas</code> for the route <code>users/{username}</code>.</li>
</ul>
<p>Any span you initiate inside a handler will become a child of the handler span and through that be linked to the parent request span.</p>
<p>As you can see, the integration provides extensive information for you to use when figuring out what is going on in your application. On top of that, you can also use OTEL as your logging solution by setting <code>logger_otel</code> as your logging solution:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://plumber2.posit.co/reference/api.html'>api</a></span><span class='o'>(</span><span class='o'>)</span> <span class='o'>|&gt;</span> </span>
<span>  <span class='nf'><a href='https://plumber2.posit.co/reference/api_logger.html'>api_logger</a></span><span class='o'>(</span><span class='nv'>logger_otel</span><span class='o'>)</span></span></code></pre>
</div>
<p>This ensures that all the logs from errors, warnings, etc all end up in the same place as your other recordings and further gets linked to the exact request that gave rise to the log.</p>
<p>We truly believe extensive OTEL support across the ecosystem will be a game changer for deployed R code and we can&rsquo;t wait for our users to take advantage of it!</p>
<h2 id="auth-support">Auth support
</h2>
<p>The second headliner is support for various authentication schemes out of the box. This comes courtesy of of the <a href="https://fireproof.data-imaginist.com" target="_blank" rel="noopener">fireproof</a>
 package which provides an auth plugin for fiery.</p>
<p>Setting up authentication is twofold: creating guards and attaching guards to routes.</p>
<p>First, you need to define one or more guards to use. A guard is an adaption of a specific authentication scheme such as e.g. OAuth. Currently, fireproof supports the Basic and Bearer HTTP authorization schemes, a custom key based scheme, as well as OAuth 2.0 and OpenID Connect. Setting up a guard can be done both programmatically and with annotations:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'># Programmatic</span></span>
<span><span class='nv'>api</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://plumber2.posit.co/reference/api.html'>api</a></span><span class='o'>(</span><span class='o'>)</span> <span class='o'>|&gt;</span> </span>
<span>  <span class='nf'><a href='https://plumber2.posit.co/reference/api_auth_guard.html'>api_auth_guard</a></span><span class='o'>(</span></span>
<span>    guard <span class='o'>=</span> <span class='nf'>fireproof</span><span class='nf'>::</span><span class='nf'><a href='https://fireproof.data-imaginist.com/reference/guard_key.html'>guard_key</a></span><span class='o'>(</span></span>
<span>      key_name <span class='o'>=</span> <span class='s'>"X-API-KEY"</span>,</span>
<span>      validate <span class='o'>=</span> <span class='s'>"MY_VERY_SECRET_KEY"</span></span>
<span>    <span class='o'>)</span>,</span>
<span>    name <span class='o'>=</span> <span class='s'>"key_guard"</span></span>
<span>  <span class='o'>)</span></span></code></pre>
</div>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'># Annotation</span></span>
<span></span>
<span><span class='c'>#* @authGuard key_guard</span></span>
<span><span class='nf'>fireproof</span><span class='nf'>::</span><span class='nf'><a href='https://fireproof.data-imaginist.com/reference/guard_key.html'>guard_key</a></span><span class='o'>(</span></span>
<span>  key_name <span class='o'>=</span> <span class='s'>"X-API-KEY"</span>,</span>
<span>  validate <span class='o'>=</span> <span class='s'>"MY_VERY_SECRET_KEY"</span></span>
<span><span class='o'>)</span></span></code></pre>
</div>
<p>Both of these pieces of code yields the same result. You API now has a guard registered under the name <code>key_guard</code> which will (if called upon) check a request for the existence of a cookie named <code>X-API-KEY</code> with the value <code>MY_VERY_SECRET_KEY</code>.</p>
<p>Secondly, your handlers can now integrate the guards to protect access to the requested path. Again, this can be done both programmatically and in annotation and will generally be handled when the request handler is created:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'># Programmatic</span></span>
<span><span class='nv'>api</span> <span class='o'>|&gt;</span> </span>
<span>  <span class='nf'><a href='https://plumber2.posit.co/reference/api_request_handlers.html'>api_get</a></span><span class='o'>(</span></span>
<span>    path <span class='o'>=</span> <span class='s'>"/admin"</span>,</span>
<span>    <span class='kr'>function</span><span class='o'>(</span><span class='nv'>...</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>      <span class='c'># whatever you wish to protect</span></span>
<span>    <span class='o'>&#125;</span>,</span>
<span>    auth_flow <span class='o'>=</span> <span class='nv'>key_guard</span></span>
<span>  <span class='o'>)</span></span>
<span></span></code></pre>
</div>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'># Annotation</span></span>
<span></span>
<span><span class='c'>#* An example endpoint with auth</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @get /admin</span></span>
<span><span class='c'>#* @auth key_guard</span></span>
<span><span class='kr'>function</span><span class='o'>(</span><span class='nv'>...</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='c'># whatever you wish to protect</span></span>
<span><span class='o'>&#125;</span></span></code></pre>
</div>
<p>Again, both code chunks achieve the same thing. They set up the endpoint to require the <code>key_guard</code> to be passed before further handling takes place.</p>
<h3 id="multiple-guards-and-requirements">Multiple guards and requirements
</h3>
<p>The previous section demonstrates the most basic authentication setup as it only uses the key guard&mdash;the simplest guard to configure. We can imagine a situation where we both want to allow users to log in with a username and password <em>or</em> authorize with a key and a google login. This requires defining multiple guards which can be done in sequence:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* @authGuard key</span></span>
<span><span class='nf'>fireproof</span><span class='nf'>::</span><span class='nf'><a href='https://fireproof.data-imaginist.com/reference/guard_key.html'>guard_key</a></span><span class='o'>(</span></span>
<span>  key_name <span class='o'>=</span> <span class='s'>"X-API-KEY"</span>,</span>
<span>  validate <span class='o'>=</span> <span class='s'>"MY_VERY_SECRET_KEY"</span></span>
<span><span class='o'>)</span></span>
<span><span class='c'>#* @authGuard basic</span></span>
<span><span class='nf'>fireproof</span><span class='nf'>::</span><span class='nf'><a href='https://fireproof.data-imaginist.com/reference/guard_basic.html'>guard_basic</a></span><span class='o'>(</span></span>
<span>  validate <span class='o'>=</span> <span class='kr'>function</span><span class='o'>(</span><span class='nv'>username</span>, <span class='nv'>password</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>    <span class='nv'>username</span> <span class='o'>==</span> <span class='s'>"thomas"</span> <span class='o'>&amp;&amp;</span> <span class='nv'>password</span> <span class='o'>==</span> <span class='s'>"xrCy45rWrgwq"</span></span>
<span>  <span class='o'>&#125;</span></span>
<span><span class='o'>)</span></span>
<span><span class='c'>#* @authGuard google</span></span>
<span><span class='nf'>fireproof</span><span class='nf'>::</span><span class='nf'><a href='https://fireproof.data-imaginist.com/reference/guard_google.html'>guard_google</a></span><span class='o'>(</span></span>
<span>  redirect_url <span class='o'>=</span> <span class='s'>"https://example.com/auth"</span>,</span>
<span>  client_id <span class='o'>=</span> <span class='s'>"MY_APP_ID"</span>,</span>
<span>  client_secret <span class='o'>=</span> <span class='s'>"SUCHASECRET"</span></span>
<span><span class='o'>)</span></span></code></pre>
</div>
<p>We now have 3 guards (of dubious quality) that we can attach to our handler. How do we capture the relationship of requiring either the basic to pass or the key and google to pass? Simple, with a logical expression:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* An example endpoint with auth</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @get /admin</span></span>
<span><span class='c'>#* @auth basic || (key &amp;&amp; google)</span></span>
<span><span class='kr'>function</span><span class='o'>(</span><span class='nv'>...</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='c'># whatever you wish to protect</span></span>
<span><span class='o'>&#125;</span></span></code></pre>
</div>
<p>The names of the guards act as booleans and can be composed with the basic boolean operators (<code>||</code>, <code>&amp;&amp;</code>, and <code>(</code>/<code>)</code>). The combinations are endless!</p>
<h3 id="scopes">Scopes
</h3>
<p>Sometimes you need more granularity in your authentication. Some users may only read while others may read and write to resources. This could be solved with multiple guards but it quickly becomes unwieldy. Instead you can set scope requirements on an endpoint. Guards can then grant scopes to a user in their <code>validate</code> function by returning a character vector instead of a boolean, like this:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* @authGuard basic</span></span>
<span><span class='nf'>fireproof</span><span class='nf'>::</span><span class='nf'><a href='https://fireproof.data-imaginist.com/reference/guard_basic.html'>guard_basic</a></span><span class='o'>(</span></span>
<span>  validate <span class='o'>=</span> <span class='kr'>function</span><span class='o'>(</span><span class='nv'>username</span>, <span class='nv'>password</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>    <span class='kr'>if</span> <span class='o'>(</span><span class='nv'>username</span> <span class='o'>==</span> <span class='s'>"guest"</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>      <span class='kr'><a href='https://rdrr.io/r/base/function.html'>return</a></span><span class='o'>(</span><span class='s'>"read"</span><span class='o'>)</span></span>
<span>    <span class='o'>&#125;</span></span>
<span>    <span class='kr'>if</span> <span class='o'>(</span><span class='nv'>username</span> <span class='o'>==</span> <span class='s'>"thomas"</span> <span class='o'>&amp;&amp;</span> <span class='nv'>password</span> <span class='o'>==</span> <span class='s'>"xrCy45rWrgwq"</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>      <span class='kr'><a href='https://rdrr.io/r/base/function.html'>return</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='s'>"read"</span>, <span class='s'>"write"</span><span class='o'>)</span><span class='o'>)</span></span>
<span>    <span class='o'>&#125;</span></span>
<span>    <span class='kc'>FALSE</span></span>
<span>  <span class='o'>&#125;</span></span>
<span><span class='o'>)</span></span>
<span></span>
<span><span class='c'>#* Read the calendar entries</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @get /calendar</span></span>
<span><span class='c'>#* @auth basic</span></span>
<span><span class='c'>#* @authScope read</span></span>
<span><span class='c'>#* </span></span>
<span><span class='kr'>function</span><span class='o'>(</span><span class='nv'>...</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='c'># return calendar entries</span></span>
<span><span class='o'>&#125;</span></span>
<span></span>
<span><span class='c'>#* Add a new calendar entry</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @post /calendar</span></span>
<span><span class='c'>#* @auth basic</span></span>
<span><span class='c'>#* @authScope write</span></span>
<span><span class='c'>#* </span></span>
<span><span class='kr'>function</span><span class='o'>(</span><span class='nv'>...</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='c'># update the calendar</span></span>
<span><span class='o'>&#125;</span></span></code></pre>
</div>
<p>The authentication that can be integrated is very flexible and will only grow as more guards are added to fireproof.</p>
<h2 id="other-news">Other news
</h2>
<h3 id="annotation-for-datastores">Annotation for datastores
</h3>
<p>While datastores through the <a href="https://github.com/thomasp85/firesale" target="_blank" rel="noopener">firesale</a>
 package was supported upon release, they could only be set up programmatically. This has now been corrected with the addition of the <code>@datastore</code> tag. It works like this:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* @datastore my_store</span></span>
<span><span class='nf'>storr</span><span class='nf'>::</span><span class='nf'><a href='https://richfitz.github.io/storr/reference/storr_environment.html'>driver_environment</a></span><span class='o'>(</span><span class='o'>)</span></span></code></pre>
</div>
<p>The <code>my_store</code> proceeding the key is optional and gives the name of the datastore (defaults to <code>datastore</code>). Below the block you provide a <a href="https://richfitz.github.io/storr/" target="_blank" rel="noopener">storr</a>
 driver and then you are good to go.</p>
<p>Authentication requires a datastore in order to work as it facilitates persistent session login. Below, you can see an annotation implementation of a single guard that leverages a storr datastore.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* @datastore ds</span></span>
<span><span class='nf'>storr</span><span class='nf'>::</span><span class='nf'><a href='https://richfitz.github.io/storr/reference/storr_environment.html'>driver_environment</a></span><span class='o'>(</span><span class='o'>)</span></span>
<span></span>
<span><span class='c'>#* @authGuard github</span></span>
<span><span class='nf'>fireproof</span><span class='nf'>::</span><span class='nf'><a href='https://fireproof.data-imaginist.com/reference/guard_github.html'>guard_github</a></span><span class='o'>(</span></span>
<span>  redirect_url <span class='o'>=</span> <span class='s'>"https://example.com/auth"</span>,</span>
<span>  client_id <span class='o'>=</span> <span class='s'>"MY_APP_ID"</span>,</span>
<span>  client_secret <span class='o'>=</span> <span class='s'>"SUCHASECRET"</span></span>
<span><span class='o'>)</span></span>
<span></span>
<span><span class='c'>#* Get a summary of your github commit history</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @auth github</span></span>
<span><span class='kr'>function</span><span class='o'>(</span><span class='nv'>ds</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='nv'>github_token</span> <span class='o'>&lt;-</span> <span class='nv'>ds</span><span class='o'>$</span><span class='nv'>session</span><span class='o'>$</span><span class='nv'>github</span><span class='o'>$</span><span class='nv'>token</span><span class='o'>$</span><span class='nv'>access_token</span></span>
<span>  <span class='c'># Use the access token to fetch commit history and do some fun things</span></span>
<span><span class='o'>&#125;</span></span></code></pre>
</div>
<h3 id="more-powerful-report-support">More powerful report support
</h3>
<p>The report endpoint has gotten even more powerful in this release in a number of ways:</p>
<ul>
<li>Report endpoints can now be added programmatically as well using <a href="https://plumber2.posit.co/reference/api_report.html" target="_blank" rel="noopener"><code>api_report()</code></a>
</li>
<li>There is now support for quarto documents using the jupyter engine</li>
<li>OpenAPI documentation is now generated automatically for the report and incorporates the standard annotation known from request handler blocks.</li>
<li>Parameterised reports now has their parameters type checked and casted based on the type of the default values or on explicit type specification in the <code>@param</code> tags.</li>
<li>You can now request specific named output formats through the <code>/{output_format}</code> subpath. This is in addition to the content negotiation already available. E.g. <code>/report/revealjs</code> will request the revealjs format of the report served at <code>/report</code>.</li>
<li>Caches can now be user specific if the rendering includes information specific to the user requesting it</li>
<li>Caches can now be cleared using a <code>DELETE</code> request</li>
</ul>
<h2 id="thank-you">Thank you
</h2>
<p>I want to say thanks to everyone who has given plumber2 a spin. It takes some time to reach maturity when replacing a decade old package and every test spin brings more insight. With the addition of OTEL integration and auth support plumber2 has now reached the feature set I was planning for during the initial development and the next phase will be about refinement, performance, and bug fixes. Your input and experiences will be critical there.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/plumber2-0-2-0/thumbnail-wd.jpg" length="482139" type="image/jpeg" />
    </item>
    <item>
      <title>OpenTelemetry &#43; Shiny for R v1.12</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/shiny-r-1.12/</link>
      <pubDate>Wed, 10 Dec 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/shiny-r-1.12/</guid>
      <dc:creator>Barret Schloerke</dc:creator><description><![CDATA[<style>
img { border-radius: 8px; }
</style>
<p>We&rsquo;re thrilled to announce the release of Shiny v1.12! This release brings a powerful new feature that we&rsquo;ve been working on for months: <strong>built-in OpenTelemetry support</strong>. Whether you&rsquo;re building small apps or deploying production applications at scale, this release will help you lift the veil on understanding your app&rsquo;s execution in production.</p>
<details class="callout callout-note" role="note" aria-label="Note">
<summary class="callout-header">
<span class="callout-title">What is Shiny?</span>
</summary>
<div class="callout-body">
<p>If you&rsquo;re new to Shiny, welcome! <a href="https://shiny.posit.co/r/" target="_blank" rel="noopener">Shiny</a>
 is an R package that makes it easy to build interactive web applications directly from R. You don&rsquo;t need to be a web developer&mdash;if you can write R code, you can create beautiful, interactive dashboards, data explorers, and analytical tools. Shiny handles all the web programming complexity behind the scenes, letting you focus on what you do best: working with data and building analyses.</p>
<p>Since its launch in 2012, Shiny has become the go-to framework for creating data-driven web applications in R, powering everything from internal company dashboards to public-facing data visualization tools. With this latest release, we&rsquo;re making it easier than ever to understand what&rsquo;s happening inside your Shiny apps, especially when they&rsquo;re deployed in production environments.</p>
</div>
</details>
<details class="callout callout-note" role="note" aria-label="Note">
<summary class="callout-header">
<span class="callout-title">What about Shiny for Python?</span>
</summary>
<div class="callout-body">
<p>OpenTelemetry support is coming to Shiny for Python! The Shiny team is actively working on bringing the same automatic instrumentation capabilities to Python. This will enable Python developers to gain the same level of observability into their Shiny applications.</p>
<p>Stay tuned for future announcements about OpenTelemetry integration in Shiny for Python. In the meantime, you can follow the development on the <a href="https://github.com/posit-dev/py-shiny" target="_blank" rel="noopener">Shiny for Python GitHub repository</a>
.</p>
</div>
</details>
<h2 id="understanding-opentelemetry">Understanding OpenTelemetry
</h2>
<p>Before we dive into what&rsquo;s new in Shiny, let&rsquo;s talk about OpenTelemetry&mdash;a topic that might sound intimidating but is actually quite straightforward once we cover the basics.</p>
<h3 id="what-is-opentelemetry">What is OpenTelemetry?
</h3>
<p><a href="https://opentelemetry.io/" target="_blank" rel="noopener"><strong>OpenTelemetry</strong></a>
 (aka OTel) describes itself as &ldquo;high-quality, ubiquitous, and portable telemetry to enable effective observability&rdquo;. In simpler terms, OpenTelemetry is a set of tools, APIs, and SDKs that help you collect and export telemetry data (like traces, logs, and metrics) from your applications. This data provides insights into how your applications are performing and behaving in real-world scenarios.</p>
<p>It captures three key types of data:</p>
<ol>
<li>
<p><strong>Traces</strong>: These show the path of a request through your application. In a Shiny app, a trace might show how a user&rsquo;s input triggered a series of reactive calculations, leading to updated outputs.</p>
</li>
<li>
<p><strong>Logs</strong>: These are detailed event records that capture what happened at specific moments.</p>
</li>
<li>
<p><strong>Metrics</strong>: These are numerical measurements over time, like how many users are connected or how long outputs take to render.</p>
</li>
</ol>
<p>These data types were standardized under the OpenTelemetry project, <a href="https://opentelemetry.io/community/marketing-guidelines/#i-opentelemetry-is-a-joint-effort" target="_blank" rel="noopener">which is supported by a large community and many companies</a>
. The goal is to provide a consistent way to collect and export observability data, making it easier to monitor and troubleshoot applications.</p>
<h3 id="the-opentelemetry-ecosystem">The OpenTelemetry ecosystem
</h3>
<p>OpenTelemetry is vendor-neutral, meaning you can send your telemetry data to various local backends like <a href="https://www.jaegertracing.io/" target="_blank" rel="noopener">Jaeger</a>
, <a href="https://zipkin.io/" target="_blank" rel="noopener">Zipkin</a>
, <a href="https://prometheus.io/" target="_blank" rel="noopener">Prometheus</a>
, or cloud-based services like <a href="https://grafana.com/products/cloud/" target="_blank" rel="noopener">Grafana Cloud</a>
, <a href="https://pydantic.dev/logfire" target="_blank" rel="noopener">Logfire</a>
, and <a href="https://langfuse.com/" target="_blank" rel="noopener">Langfuse</a>
. This flexibility means <a href="https://opentelemetry.io/community/marketing-guidelines/#iii-promote-awareness-of-otel-interoperability-and-modularization" target="_blank" rel="noopener">you&rsquo;re not locked into any particular monitoring solution</a>
.</p>
<p>We&rsquo;ve been using <a href="https://pydantic.dev/logfire" target="_blank" rel="noopener">Logfire</a>
 internally at Posit to help develop OTel integration in many R packages and other applications. Throughout this post, you&rsquo;ll see examples of OTel traces visualized in Logfire.</p>
<p>The image below shows an example trace in Logfire (left) from a Shiny app (right) that uses Generative AI to provide weather forecasts. The trace captures the entire user session, including reactive updates, model calls, and a tool invocation. We will explore this example in more detail later in the post.</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/shiny-r-1.12/demo-app-and-logfire.png" data-fig-alt="OpenTelemetry trace of chat app with weather tool" alt="OpenTelemetry trace of chat app with weather tool" />
<figcaption aria-hidden="true">OpenTelemetry trace of chat app with weather tool</figcaption>
</figure>
<h2 id="opentelemetry-in-shiny">OpenTelemetry in Shiny
</h2>
<p>If you&rsquo;ve ever wondered&hellip;</p>
<ul>
<li>&ldquo;Why is my app slow for some users when hosted? Which reactive expressions are causing the slowdown?&rdquo;</li>
<li>&ldquo;How long does it take for my plot to render? Is it the data or the plotting code that is taking longer to calculate?&rdquo;</li>
<li>&ldquo;What&rsquo;s the sequence of events leading up to when a user clicks <em>that</em> button?&rdquo;</li>
<li>&ldquo;How often are errors occurring in my app, and under what conditions?&rdquo;</li>
</ul>
<p>Normally, we can attempt to answer these questions using <a href="https://rstudio.github.io/reactlog/" target="_blank" rel="noopener"><code>{reactlog}</code></a>
 (a package to replay the recording of a reactive graph) or <a href="https://profvis.r-lib.org/" target="_blank" rel="noopener"><code>{profvis}</code></a>
 (a package to profile R code in the main R process). However, both of these packages are not built for production use. These debugging tools are only to be used locally as they would be considered a memory leak in production.</p>
<p>OpenTelemetry allows us to record information <strong>at scale</strong> with minimal overhead, helping you answer previously impossible questions about your production environment. OpenTelemetry provides visibility into your app&rsquo;s performance and behavior, helping you identify bottlenecks, debug issues, and optimize user experience&mdash;especially crucial when your app is deployed in production with real-world usage.</p>
<h3 id="adding-opentelemetry-integration">Adding OpenTelemetry integration
</h3>
<p>OTel support is automatically enabled in Shiny once <a href="https://otel.r-lib.org/" target="_blank" rel="noopener"><code>{otel}</code></a>
 is able to record traces and logs.</p>
<p>To do this, let&rsquo;s get started by installing the latest version of Shiny, <code>{otel}</code>, and <a href="https://otelsdk.r-lib.org" target="_blank" rel="noopener"><code>{otelsdk}</code></a>
:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">pak</span><span class="o">::</span><span class="nf">pak</span><span class="p">(</span><span class="nf">c</span><span class="p">(</span><span class="s">&#34;shiny&#34;</span><span class="p">,</span> <span class="s">&#34;otel&#34;</span><span class="p">,</span> <span class="s">&#34;otelsdk&#34;</span><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>To enable OpenTelemetry tracing, you need set a few <a href="https://otelsdk.r-lib.org/reference/collecting.html" target="_blank" rel="noopener">specific system environment variables</a>
 to describe where your recordings are being sent. In the example below, we set them in an <code>.Renviron</code> file to point to Logfire.</p>
<p><strong>.Renviron</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Enable OpenTelemetry by setting Collector environment variables</span>
</span></span><span class="line"><span class="cl"><span class="nv">OTEL_TRACES_EXPORTER</span><span class="o">=</span>http
</span></span><span class="line"><span class="cl"><span class="nv">OTEL_LOGS_EXPORTER</span><span class="o">=</span>http
</span></span><span class="line"><span class="cl"><span class="nv">OTEL_LOG_LEVEL</span><span class="o">=</span>debug
</span></span><span class="line"><span class="cl"><span class="nv">OTEL_METRICS_EXPORTER</span><span class="o">=</span>http
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">OTEL_EXPORTER_OTLP_ENDPOINT</span><span class="o">=</span><span class="s2">&#34;https://logfire-us.pydantic.dev&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">OTEL_EXPORTER_OTLP_HEADERS</span><span class="o">=</span><span class="s2">&#34;Authorization=&lt;your-write-token&gt;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Editing <code>.Renviron</code></span>
</div>
<div class="callout-body">
<p>You can edit your app-specific environment variables by calling <code>usethis::edit_r_environ(scope=&quot;project&quot;)</code> from within your Shiny app project directory.</p>
</div>
</div>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Verifying OTel setup</span>
</div>
<div class="callout-body">
<p>You&rsquo;ll know your setup is enabled if <a href="https://otel.r-lib.org/reference/is_tracing_enabled.html" target="_blank" rel="noopener"><code>otel::is_tracing_enabled()</code></a>
 returns <code>TRUE</code>.</p>
</div>
</div>
<h3 id="opentelemetry-in-action">OpenTelemetry in action
</h3>
<p>Below is an example <code>{shinychat}</code> app with an <code>{ellmer}</code> tool to fetch realtime weather forecasts (via <code>{weathR}</code>, which uses <code>{httr2}</code>) for a given latitude and longitude. This simple (yet non-trivial) app helps us showcase what sort of information <code>{shiny}</code>, <code>{ellmer}</code>, and <code>{httr2}</code> can surface via OTel.</p>
<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">OTel + GenAI</span>
</div>
<div class="callout-body">
<p>Gaining timing insights into applications that leverage <a href="https://aws.amazon.com/what-is/generative-ai/" target="_blank" rel="noopener">Generative AI</a>
 (GenAI) is critical to improving user experience. Without OpenTelemetry, if a user stated an app was slow, we would not be able to accurately determine if the slowness was due to the AI model request time, AI model streaming time, tool execution time, or even followup reactive calculations in Shiny.</p>
</div>
</div>
<p><strong>app.R</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create tool that grabs the weather forecast (free) for a given lat/lon</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Inspired from: https://posit-dev.github.io/shinychat/r/articles/tool-ui.html</span>
</span></span><span class="line"><span class="cl"><span class="n">get_weather_forecast_basic</span> <span class="o">&lt;-</span> <span class="n">ellmer</span><span class="o">::</span><span class="nf">tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="kr">function</span><span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">weathR</span><span class="o">::</span><span class="nf">point_tomorrow</span><span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">,</span> <span class="n">short</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="n">name</span> <span class="o">=</span> <span class="s">&#34;get_weather_forecast&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Get the weather forecast for a location.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">arguments</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">lat</span> <span class="o">=</span> <span class="n">ellmer</span><span class="o">::</span><span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Latitude&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">lon</span> <span class="o">=</span> <span class="n">ellmer</span><span class="o">::</span><span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Longitude&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="n">bslib</span><span class="o">::</span><span class="nf">page_fillable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">shinychat</span><span class="o">::</span><span class="nf">chat_mod_ui</span><span class="p">(</span><span class="s">&#34;chat&#34;</span><span class="p">,</span> <span class="n">height</span> <span class="o">=</span> <span class="s">&#34;100%&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Set up client within `server` to not _share_ the client for all sessions</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">&lt;-</span> <span class="n">ellmer</span><span class="o">::</span><span class="nf">chat_claude</span><span class="p">(</span><span class="s">&#34;Be terse.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span><span class="o">$</span><span class="nf">register_tool</span><span class="p">(</span><span class="n">get_weather_forecast_basic</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">chat_server</span> <span class="o">&lt;-</span> <span class="n">shinychat</span><span class="o">::</span><span class="nf">chat_mod_server</span><span class="p">(</span><span class="s">&#34;chat&#34;</span><span class="p">,</span> <span class="n">client</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Set chat placeholder on app init</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observe</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat_server</span><span class="o">$</span><span class="nf">update_user_input</span><span class="p">(</span><span class="s">&#34;What is the weather in Atlanta, GA?&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span> <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;set-default-input&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You&rsquo;ll notice that the <code>app.R</code> has no OpenTelemetry specific code.</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/shiny-r-1.12/demo-app.png" data-fig-alt="Chat app with weather tool" height="500" alt="Chat app with weather tool" />
<figcaption aria-hidden="true">Chat app with weather tool</figcaption>
</figure>
<p>Here&rsquo;s an example trace from Logfire showing a user session interacting with the chat app and the weather tool:</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/shiny-r-1.12/demo-logfire.png" data-fig-alt="OpenTelemetry trace of chat app with weather tool" alt="OpenTelemetry trace of chat app with weather tool" />
<figcaption aria-hidden="true">OpenTelemetry trace of chat app with weather tool</figcaption>
</figure>
<p>The traces above recorded a single user session where the user asked for the weather in Atlanta, GA and then closed the app. The trace shows:</p>
<ul>
<li>The Shiny session lifecycle, including <code>session_start</code> and <code>session_end</code></li>
<li>Many <code>{shinychat}</code> <code>chat</code> module spans for handling user input and messages</li>
<li>Reactive updates triggered by changes in the <code>session</code>&rsquo;s input</li>
<li>An <code>ExtendedTask</code> span for the computation of the AI agent response</li>
<li>2x <code>chat claude</code> spans representing calls to the AI agent model</li>
<li>A single <code>get_weather_forecast</code> tool call being executed, including the HTTP requests made by <code>{httr2}</code> to fetch the weather data</li>
</ul>
<p>The gap between this span&rsquo;s length and its parent&rsquo;s length is how long the results took to stream back to the user or make a decision. For the overall user experience, the total time taken from input to output is represented by the <code>ExtendedTask</code> span, 9.5 seconds in this case. Only a half of a second was spent in the tool call (something <em>we as app authors</em> could possibly optimize). The remaining 9 seconds was spent in the model response generation and streaming.</p>
<p>Notice how the spans are nested, showing the relationship between user actions, required reactive calculations, and external API calls. This level of detail helps you understand exactly how your app is performing in production and where any bottlenecks or issues may arise.</p>
<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Packages used in demo</span>
</div>
<div class="callout-body">
<p>The Shiny app above currently requires the development version of <code>{ellmer}</code> to record OpenTelemetry traces. More R packages (mentioned later in this post) will contain native OpenTelemetry support in their upcoming releases.</p>
</div>
</div>
<h3 id="what-can-shiny-record">What can Shiny record?
</h3>
<p>Shiny automatically creates OpenTelemetry spans for:</p>
<ul>
<li><strong>Session lifecycle</strong>: When sessions start and end, including HTTP request details</li>
<li><strong>Reactive updates</strong>: The entire cascade of reactive calculations triggered by an input change or a new output to be rendered</li>
<li><strong>Reactive expressions</strong>: Individual <code>reactive()</code>, <code>observe()</code>, and <code>output</code> calculations; <code>debounce()</code> and <code>throttle()</code> value updates; <code>reactiveFileReader()</code> and <code>reactivePoll()</code> updates</li>
<li><strong>Extended tasks</strong>: Long-running background computations</li>
</ul>
<p>Additionally, Shiny logs events for:</p>
<ul>
<li>Fatal or unhandled errors (with optional error message sanitization)</li>
<li>When a <code>reactiveVal()</code> is set and</li>
<li>When a <code>reactiveValues()</code> value is set</li>
</ul>
<p>Every span and log entry provided by Shiny includes the session ID, making it easy to filter and analyze data for specific user sessions.</p>
<p>At the time of this post, no metrics (numerical measurements over time) are automatically recorded for Shiny. However, <a href="https://plumber2.posit.co/" target="_blank" rel="noopener"><code>{plumber2}</code></a>
 (via <a href="https://reqres.data-imaginist.com/" target="_blank" rel="noopener"><code>{reqres}</code></a>
) has added OTel metrics in their latest release: a counter of the number of active requests and histograms for request durations and request/response body sizes.</p>
<p>For more detailed information on configuring and using OpenTelemetry within R, check out the <a href="https://otel.r-lib.org/" target="_blank" rel="noopener"><code>{otel}</code> package documentation</a>
 and how to set up record collection with <a href="https://otelsdk.r-lib.org/reference/collecting.html" target="_blank" rel="noopener"><code>{otelsdk}</code></a>
.</p>
<h3 id="fine-grained-control">Fine-grained control
</h3>
<p>Automatic tracing is perfect to get started, but you may want more control over what gets traced. Shiny v1.12 gives you that flexibility through the <code>shiny.otel.collect</code> option (or <code>SHINY_OTEL_COLLECT</code> environment variable). You can set this to control the level of tracing detail:</p>
<ul>
<li><code>&quot;none&quot;</code> - No Shiny OpenTelemetry tracing</li>
<li><code>&quot;session&quot;</code> - Track session start and end</li>
<li><code>&quot;reactive_update&quot;</code> - Track reactive updates (includes <code>&quot;session&quot;</code> tracing)</li>
<li><code>&quot;reactivity&quot;</code> - Trace all reactive expressions (includes <code>&quot;reactive_update&quot;</code> tracing)</li>
<li><code>&quot;all&quot;</code> - Everything (currently equivalent to &ldquo;reactivity&rdquo;)</li>
</ul>
<p>For example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># Only trace session lifecycle, not every reactive calculation</span>
</span></span><span class="line"><span class="cl"><span class="nf">options</span><span class="p">(</span><span class="n">shiny.otel.collect</span> <span class="o">=</span> <span class="s">&#34;session&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This is especially useful in production environments where you want observability without overwhelming your telemetry backend with data.</p>
<p>You can also temporarily override the <code>shiny.otel.collect</code> option within a specific block of code using the <code>withOtelCollect()</code> or <code>localOtelCollect()</code> functions. This is helpful when you want to exclude certain parts of your app from being traced. For example, to avoid tracing Shiny reactive expressions within a block:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># Do not trace _Shiny_ reactive expressions within this block</span>
</span></span><span class="line"><span class="cl"><span class="c1"># All other otel spans/logs will still be recorded</span>
</span></span><span class="line"><span class="cl"><span class="nf">withOtelCollect</span><span class="p">(</span><span class="s">&#34;none&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">expensive_calculation</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Start a custom span for this calculation</span>
</span></span><span class="line"><span class="cl">    <span class="n">otel</span><span class="o">::</span><span class="nf">start_local_active_span</span><span class="p">(</span><span class="s">&#34;my custom span&#34;</span><span class="p">,</span> <span class="n">tracer</span> <span class="o">=</span> <span class="s">&#34;my_tracer&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">Sys.sleep</span><span class="p">(</span><span class="m">2</span><span class="p">)</span>  <span class="c1"># Simulate a long calculation</span>
</span></span><span class="line"><span class="cl">    <span class="nf">rnorm</span><span class="p">(</span><span class="m">1e6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">plot</span> <span class="o">&lt;-</span> <span class="nf">renderPlot</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">hist</span><span class="p">(</span><span class="nf">expensive_calculation</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="in-summary">In summary
</h2>
<p>Whether you&rsquo;re developing apps locally or managing production deployments, OpenTelemetry support in Shiny now gives you visibility into your applications:</p>
<ul>
<li><strong>Developers</strong> can debug complex reactive flows</li>
<li><strong>Performance engineers</strong> can identify and eliminate bottlenecks</li>
<li><strong>Data scientists</strong> can understand how users interact with their applications</li>
</ul>
<p>We&rsquo;re excited to see how the community uses this powerful new capability. Try it out, and let us know what you think!</p>
<p>While OpenTelemetry support is the star of this release, Shiny v1.12 includes several other nice improvements. For a complete list of changes, check out the NEWS entries for <a href="https://shiny.posit.co/r/reference/shiny/1.12.0/upgrade.html" target="_blank" rel="noopener">Shiny v1.12.0</a>
 and <a href="https://shiny.posit.co/r/reference/shiny/1.12.1/upgrade.html" target="_blank" rel="noopener">Shiny v1.12.1</a>
.</p>
<h2 id="learn-more">Learn more
</h2>
<p>For more details on OpenTelemetry in Shiny, check out our <a href="https://shiny.posit.co/r/articles/improve/opentelemetry/" target="_blank" rel="noopener">dedicated article on OpenTelemetry + Shiny</a>
 article.</p>
<h2 id="whats-next">What&rsquo;s next?
</h2>
<p>We have big plans for OpenTelemetry in Shiny and the broader R ecosystem. Existing and upcoming enhancements include:</p>
<ul>
<li><a href="https://mirai.r-lib.org/" target="_blank" rel="noopener"><code>{mirai}</code></a>
 v2.5.0</li>
<li><a href="https://rstudio.github.io/promises/" target="_blank" rel="noopener"><code>{promises}</code></a>
 v1.5.0</li>
<li><a href="https://plumber2.posit.co/" target="_blank" rel="noopener"><code>{plumber2}</code></a>
 (via <a href="https://fiery.data-imaginist.com/" target="_blank" rel="noopener"><code>{fiery}</code></a>
 v1.4.0, <a href="https://reqres.data-imaginist.com/" target="_blank" rel="noopener"><code>{reqres}</code></a>
 v1.1.0, and <a href="https://routr.data-imaginist.com/" target="_blank" rel="noopener"><code>{routr}</code></a>
 v1.1.0)</li>
<li><a href="https://httr2.r-lib.org/" target="_blank" rel="noopener"><code>{httr2}</code></a>
 v1.2.2</li>
<li><a href="https://yihui.org/knitr/" target="_blank" rel="noopener"><code>{knitr}</code></a>
 (<code>yihui/knitr</code>)</li>
<li><a href="https://ellmer.tidyverse.org/" target="_blank" rel="noopener"><code>{ellmer}</code></a>
 (<a href="https://github.com/tidyverse/ellmer/pull/526" target="_blank" rel="noopener"><code>tidyverse/ellmer#526</code></a>
)</li>
<li><a href="https://dbi.r-dbi.org/" target="_blank" rel="noopener"><code>{DBI}</code></a>
 (<a href="https://github.com/r-dbi/DBI/pull/551" target="_blank" rel="noopener"><code>r-dbi/dbi#551</code></a>
)</li>
<li><a href="https://testthat.r-lib.org/" target="_blank" rel="noopener"><code>{testthat}</code></a>
 (<a href="https://github.com/r-lib/testthat/pull/2282" target="_blank" rel="noopener"><code>r-lib/testthat#2282</code></a>
)</li>
</ul>
<p>In addition, the Shiny Team&rsquo;s focus will be shifting to add OpenTelemetry integration to Shiny for Python. Be sure to be on the lookout for the future announcement!</p>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thank you to all [the folks/everyone] who [helped make this release happen/contributed to this release]:</p>
<p><a href="https://github.com/andriygm" target="_blank" rel="noopener">@andriygm</a>
, <a href="https://github.com/BajczA475" target="_blank" rel="noopener">@BajczA475</a>
, <a href="https://github.com/bedantaguru" target="_blank" rel="noopener">@bedantaguru</a>
, <a href="https://github.com/billdenney" target="_blank" rel="noopener">@billdenney</a>
, <a href="https://github.com/chendaniely" target="_blank" rel="noopener">@chendaniely</a>
, <a href="https://github.com/Copilot" target="_blank" rel="noopener">@Copilot</a>
, <a href="https://github.com/cpsievert" target="_blank" rel="noopener">@cpsievert</a>
, <a href="https://github.com/csgillespie" target="_blank" rel="noopener">@csgillespie</a>
, <a href="https://github.com/deadbytesus" target="_blank" rel="noopener">@deadbytesus</a>
, <a href="https://github.com/dempsey-CMAR" target="_blank" rel="noopener">@dempsey-CMAR</a>
, <a href="https://github.com/dleopold" target="_blank" rel="noopener">@dleopold</a>
, <a href="https://github.com/federiva" target="_blank" rel="noopener">@federiva</a>
, <a href="https://github.com/federivaFirebird" target="_blank" rel="noopener">@federivaFirebird</a>
, <a href="https://github.com/fzenoni" target="_blank" rel="noopener">@fzenoni</a>
, <a href="https://github.com/gadenbuie" target="_blank" rel="noopener">@gadenbuie</a>
, <a href="https://github.com/gsmolinski" target="_blank" rel="noopener">@gsmolinski</a>
, <a href="https://github.com/ismirsehregal" target="_blank" rel="noopener">@ismirsehregal</a>
, <a href="https://github.com/janxkoci" target="_blank" rel="noopener">@janxkoci</a>
, <a href="https://github.com/JohnADawson" target="_blank" rel="noopener">@JohnADawson</a>
, <a href="https://github.com/karangattu" target="_blank" rel="noopener">@karangattu</a>
, <a href="https://github.com/marcozanotti" target="_blank" rel="noopener">@marcozanotti</a>
, <a href="https://github.com/Mkranj" target="_blank" rel="noopener">@Mkranj</a>
, <a href="https://github.com/mm225022" target="_blank" rel="noopener">@mm225022</a>
, <a href="https://github.com/mmuurr" target="_blank" rel="noopener">@mmuurr</a>
, <a href="https://github.com/nicholasdavies" target="_blank" rel="noopener">@nicholasdavies</a>
, <a href="https://github.com/r2evans" target="_blank" rel="noopener">@r2evans</a>
, <a href="https://github.com/schloerke" target="_blank" rel="noopener">@schloerke</a>
, <a href="https://github.com/shikokuchuo" target="_blank" rel="noopener">@shikokuchuo</a>
, <a href="https://github.com/simon-smart88" target="_blank" rel="noopener">@simon-smart88</a>
, <a href="https://github.com/vedhav" target="_blank" rel="noopener">@vedhav</a>
, and <a href="https://github.com/wch" target="_blank" rel="noopener">@wch</a>
.</p>
<hr>
<p><em>Have questions or feedback? Join the conversation on the <a href="https://community.rstudio.com/c/shiny" target="_blank" rel="noopener">Shiny Community forum</a>
 or <a href="https://github.com/rstudio/shiny" target="_blank" rel="noopener">GitHub</a>
.</em></p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/shiny-r-1.12/feature.png" length="75668" type="image/png" />
    </item>
    <item>
      <title>Shiny Talks from posit::conf(2025)</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/conf-2025-shinytalks/</link>
      <pubDate>Tue, 02 Dec 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/conf-2025-shinytalks/</guid><description><![CDATA[<p>Videos from posit::conf(2025) are now available. To make it easier to explore how people are building with Shiny across R and Python, we&rsquo;ve put together a curated playlist of the Shiny-focused talks from the conference. These sessions highlight practical apps, new tooling, design patterns, AI workflows, and real-world deployments built with Shiny.</p>
<h2 id="shiny-talks-playlist">Shiny Talks Playlist:
</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Kh8xDym8sTg?si=trOED7T3iArnY7Kj" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen>
</iframe>
<p>Talks included in the playlist, broken up into a few categories for easier browsing, are as follows:</p>
<h2 id="shiny--ai--intelligent-automation">Shiny + AI &amp; Intelligent Automation
</h2>
<table>
  <thead>
      <tr>
          <th style="text-align: right">Speakers</th>
          <th style="text-align: center">Title</th>
          <th style="text-align: left">Link</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: right"><strong>Winston Chang (Shiny Team)</strong></td>
          <td style="text-align: center">Web applications with Shiny and React (and AI)</td>
          <td style="text-align: left"><a href="https://youtu.be/Kh8xDym8sTg" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://img.youtube.com/vi/Kh8xDym8sTg/maxresdefault.jpg"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
      <tr>
          <td style="text-align: right"><strong>Karan Gathani (Shiny Team)</strong></td>
          <td style="text-align: center">Old Apps, New Tricks: How AI can write Automated Tests for Shiny</td>
          <td style="text-align: left"><a href="https://youtu.be/gxcAadq0Bmk" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://img.youtube.com/vi/gxcAadq0Bmk/maxresdefault.jpg"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
      <tr>
          <td style="text-align: right"><strong>Regis A. James</strong></td>
          <td style="text-align: center">AskRADS: An AI Recommendation Agent for Maximizing Shiny Development</td>
          <td style="text-align: left"><a href="https://youtu.be/v3CCoq7j9Tk" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://img.youtube.com/vi/v3CCoq7j9Tk/maxresdefault.jpg"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
  </tbody>
</table>
<h2 id="shiny-architecture-performance--data">Shiny Architecture, Performance &amp; Data
</h2>
<table>
  <thead>
      <tr>
          <th style="text-align: right">Speakers</th>
          <th style="text-align: center">Title</th>
          <th style="text-align: left">Link</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: right"><strong>Alex Chisholm</strong></td>
          <td style="text-align: center">Keeping Data Alive: Persistent Storage Options for Shiny</td>
          <td style="text-align: left"><a href="https://youtu.be/Pe-XTCKUzV8" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://img.youtube.com/vi/Pe-XTCKUzV8/maxresdefault.jpg"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
      <tr>
          <td style="text-align: right"><strong>Melissa Albino Hegeman</strong></td>
          <td style="text-align: center">Get your ducks in a row&hellip; faster Shiny apps with DuckDB</td>
          <td style="text-align: left"><a href="https://www.youtube.com/watch?v=2sLJSosz1OY" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://i.ytimg.com/vi/2sLJSosz1OY/hqdefault.jpg?sqp=-oaymwEjCNACELwBSFryq4qpAxUIARUAAAAAGAElAADIQj0AgKJDeAE=&amp;rs=AOn4CLBZKR5FG40MaA-0o4SMJg3cKepLdQ"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
      <tr>
          <td style="text-align: right"><strong>Barret Schloerke (Shiny Team)</strong></td>
          <td style="text-align: center">Observability at scale: Monitoring Shiny Applications</td>
          <td style="text-align: left"><a href="https://youtu.be/zwxhFKRlQLs" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://img.youtube.com/vi/zwxhFKRlQLs/maxresdefault.jpg"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
  </tbody>
</table>
<h2 id="shiny-ux-ui-design--accessibility">Shiny UX, UI Design &amp; Accessibility
</h2>
<table>
  <thead>
      <tr>
          <th style="text-align: right">Speakers</th>
          <th style="text-align: center">Title</th>
          <th style="text-align: left">Link</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: right"><strong>Cameron Race; Sarah Wong-Brown</strong></td>
          <td style="text-align: center">shinyGovStyle &ndash; accessible government design in Shiny</td>
          <td style="text-align: left"><a href="https://youtu.be/33doZkPSUqY" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://img.youtube.com/vi/33doZkPSUqY/maxresdefault.jpg"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
      <tr>
          <td style="text-align: right"><strong>Casey Aguilar-Gervase; Maya Gans</strong></td>
          <td style="text-align: center">Design of Everyday Shiny Apps</td>
          <td style="text-align: left"><a href="https://youtu.be/RQod46DSkiA" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://img.youtube.com/vi/RQod46DSkiA/maxresdefault.jpg"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
      <tr>
          <td style="text-align: right"><strong>Jeremy Winget, PhD</strong></td>
          <td style="text-align: center">Death by Dropdown? Engineer Insightful Shiny Apps</td>
          <td style="text-align: left"><a href="https://youtu.be/QvMnUZLQ-oo" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://img.youtube.com/vi/QvMnUZLQ-oo/maxresdefault.jpg"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
      <tr>
          <td style="text-align: right"><strong>Kim Schouten</strong></td>
          <td style="text-align: center">Modular, layout-as-code approach for customizable Shiny dashboards</td>
          <td style="text-align: left"><a href="https://youtu.be/HJs2HVltpcs" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://img.youtube.com/vi/HJs2HVltpcs/maxresdefault.jpg"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
  </tbody>
</table>
<h2 id="shiny-extensions-packages--developer-tooling">Shiny Extensions, Packages &amp; Developer Tooling
</h2>
<table>
  <thead>
      <tr>
          <th style="text-align: right">Speakers</th>
          <th style="text-align: center">Title</th>
          <th style="text-align: left">Link</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: right"><strong>Eric Nantz</strong></td>
          <td style="text-align: center">shinystate: Launching collaboration and session state</td>
          <td style="text-align: left"><a href="https://youtu.be/69bnkXD3e6w" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://img.youtube.com/vi/69bnkXD3e6w/maxresdefault.jpg"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
      <tr>
          <td style="text-align: right"><strong>James Wade</strong></td>
          <td style="text-align: center">shinyEventLogger &mdash; logging events in Shiny apps</td>
          <td style="text-align: left"><a href="https://youtu.be/smnrmTtoiOM" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://img.youtube.com/vi/smnrmTtoiOM/maxresdefault.jpg"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
  </tbody>
</table>
<h2 id="applied-shiny-case-studies">Applied Shiny Case Studies
</h2>
<table>
  <thead>
      <tr>
          <th style="text-align: right">Speakers</th>
          <th style="text-align: center">Title</th>
          <th style="text-align: left">Link</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: right"><strong>Marcus Beck</strong></td>
          <td style="text-align: center">Shiny for ecological data workflows</td>
          <td style="text-align: left"><a href="https://youtu.be/Cnk3770AzuA" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://img.youtube.com/vi/Cnk3770AzuA/maxresdefault.jpg"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
      <tr>
          <td style="text-align: right"><strong>Hugo Fitipaldi</strong></td>
          <td style="text-align: center">Building a Real-Time COVID-19 Surveillance System with Shiny</td>
          <td style="text-align: left"><a href="https://youtu.be/3-UsetFXFlk" target="_blank" rel="noopener"><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://img.youtube.com/vi/3-UsetFXFlk/maxresdefault.jpg"
      alt="thumb" 
      loading="lazy"
    >
  </figure></div>
</a>
</td>
      </tr>
  </tbody>
</table>
<h2 id="shiny-workshops-from-positconf2025">Shiny Workshops from posit::conf(2025)
</h2>
<p>Full workshop materials are now available, including slides, exercises, and code for all Shiny workshops.</p>
<h3 class="me-2">
LLMs + Shiny Workshop
</h3>
/
<p class="ms-2">
Joe Cheng & Garrick Aden-Buie
</p>
<p><a href="https://posit-conf-2025.github.io/llm/" class="me-3">LLM Course Materials</a><a href="https://github.com/posit-conf-2025/llm"><img src="https://posit-open-source.netlify.app/images/github.svg" style="width:16px;height:auto;display:inline;"></a></p>
<h3 class="me-2">
Shiny for Python Workshop
</h3>
/
<p class="ms-2">
Daniel Chen
</p>
<p><a href="https://posit-conf-2025.github.io/shiny-py/" class="me-3">Shiny for R Materials</a><a href="https://github.com/posit-conf-2025/shiny-py"><img src="https://posit-open-source.netlify.app/images/github.svg" style="width:16px;height:auto;display:inline;"></a></p>
<h3 class="me-2">
Shiny for R Workshop
</h3>
/
<p class="ms-2">
Colin Rundel
</p>
<p><a href="https://posit-conf-2025.github.io/shiny-r/" class="me-3">Shiny for Python Materials</a><a href="https://github.com/posit-conf-2025/shiny-r/"><img src="https://posit-open-source.netlify.app/images/github.svg" style="width:16px;height:auto;display:inline;"></a></p>
<h2 id="closing">Closing
</h2>
<p>We hope you enjoy this roundup of Shiny talks from posit::conf(2025). The Shiny community continues to explore new patterns&mdash;AI-assisted development, real-time analytics, performance tooling, design systems, and more.
We hope to see you next year at posit::conf&mdash;and maybe even see <em>your</em> Shiny work up on stage.</p>
<style>
  td, th {text-align: left !important;}
  /*tr:last-of-type td {border-bottom: 0px !important;}
  table {border-bottom: 1px solid transparent !important;}*/
</style>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/conf-2025-shinytalks/conf-recordings-banner.png" length="243314" type="image/png" />
    </item>
    <item>
      <title>Tool Calling UI in shinychat</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/shinychat-tool-ui/</link>
      <pubDate>Thu, 20 Nov 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/shinychat-tool-ui/</guid>
      <dc:creator>Garrick Aden-Buie</dc:creator>
      <dc:creator>Carson Sievert</dc:creator>
      <dc:creator>Barret Schloerke</dc:creator><description><![CDATA[<link href="index_files/libs/shinychat-0.3.0/chat/chat.css" rel="stylesheet" />
<link href="index_files/libs/shinychat-0.3.0/markdown-stream/markdown-stream.css" rel="stylesheet" />
<script src="https://posit-open-source.netlify.app/blog/shiny/shinychat-tool-ui/index_files/libs/shinychat-0.3.0/chat/chat.js" type="module"></script>
<script src="https://posit-open-source.netlify.app/blog/shiny/shinychat-tool-ui/index_files/libs/shinychat-0.3.0/markdown-stream/markdown-stream.js" type="module"></script>
<style>
.highlight-line {
  font-weight: bold;
}
body:not(.modal-open) div.sourceCode pre code.has-line-highlights> span:not(.highlight-line) {
  opacity: 0.6;
}
body:not(.modal-open) div.sourceCode:hover pre code.has-line-highlights> span:not(.highlight-line) {
  opacity: 0.8;
}
.card-header {
  --bs-card-cap-bg: transparent;
}
.shiny-tool-card {
  margin-bottom: 1rem !important;
}
.code-copy-button> .bi::after {
  display: none;
}
</style>
<p>We&rsquo;re jazzed to announce that <a href="https://posit-dev.github.io/shinychat" target="_blank" rel="noopener">shinychat</a>
 now includes rich UI for tool calls!
shinychat makes it easy to build LLM-powered chat interfaces in Shiny apps, and with tool calling UI, your users can see which tools are being executed and their outcomes.
This feature is available in <a href="https://posit-dev.github.io/shinychat/r" target="_blank" rel="noopener">shinychat for R</a>
 (v0.3.0) and <a href="https://posit-dev.github.io/shinychat/py/" target="_blank" rel="noopener">shinychat for Python</a>
 (v0.2.0 or later).</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-1" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-1-1">R</a></li>
<li><a href="#tabset-1-2">Python</a></li>
</ul>
<div id="tabset-1-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;shinychat&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-1-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install shinychat
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>This release brings tool call displays that work with <a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener">ellmer</a>
 (R) and <a href="https://github.com/posit-dev/chatlas" target="_blank" rel="noopener">chatlas</a>
 (Python).
When the LLM calls a tool, shinychat automatically displays the request and result in a collapsible card interface.</p>
<p>In this post we&rsquo;ll cover the new <a href="#tool-calling-ui">Tool calling UI</a>
 features, how to set them up in your apps, and ways to customize the display.
We&rsquo;ll also highlight some <a href="#bookmarking-support">chat bookmarking support</a>
 and <a href="#other-improvements-in-r-v0.3.0">other improvements in shinychat for R v0.3.0</a>
.
As always, you can find the full list of changes in the <a href="https://posit-dev.github.io/shinychat/r/news/index.html#shinychat-030" target="_blank" rel="noopener">R release notes</a>
 and <a href="https://github.com/posit-dev/shinychat/blob/main/pkg-py/CHANGELOG.md" target="_blank" rel="noopener">Python release notes</a>
.</p>
<h2 id="tool-calling-ui">Tool calling UI
</h2>
<p>Tool calling lets you extend an LLM&rsquo;s capabilities by giving it access to functions you define.
When you provide a tool to the LLM, you&rsquo;re telling it &ldquo;here&rsquo;s a function you can call if you need it.&rdquo;
The key thing to understand is that the tool runs on <em>your machine</em> (or wherever your Shiny app is running) &mdash; the LLM doesn&rsquo;t directly run the tool itself.
Instead, it asks <em>you</em> to run the function and return the result.</p>
<p>Both ellmer and chatlas make it easy to define tools and register them with your chat client<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, and they also handle the back-and-forth of tool calls by receiving requests from the LLM, executing the tool, and sending the results back.
This means you can focus on what you do best: writing code to solve problems.</p>
<p>Any problem you can solve with a function can become a tool for an LLM!
You can give the LLM access to live data, APIs, databases, or any other resources your app can reach.</p>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">btw: A complete toolkit for R</span>
</div>
<div class="callout-body">
<p>If you&rsquo;re working in R, <a href="https://posit-dev.github.io/btw" target="_blank" rel="noopener">btw</a>
 is a complete toolkit to help LLMs work better with R.
Whether you&rsquo;re copy-pasting to ChatGPT, chatting with an AI assistant in your IDE, or building LLM-powered apps with shinychat, btw makes it easy to give LLMs the context they need.</p>
<p>And, most importantly, btw provides a full suite of tools for gathering context from R sessions, including tools to: read help pages and vignettes, describe data frames, search for packages on CRAN, read web pages, and more.</p>
<p>Learn more at <a href="https://posit-dev.github.io/btw" target="_blank" rel="noopener">posit-dev.github.io/btw</a>
!</p>
</div>
</div>
<p>When the LLM decides to call a tool, shinychat displays the request and result in the chat interface.
Users can see which tools are being invoked, what arguments are being passed, and what data is being returned.
The tool display is designed to be customizable, so shinychat developers can customize the appearance and display of tool calls to best serve their users.</p>
<h3 id="basic-tool-display">Basic tool display
</h3>
<p>Let&rsquo;s start by creating a simple weather forecasting tool that fetches a weather data (in the United States) for a given latitude and longitude.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-2" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-2-1">R</a></li>
<li><a href="#tabset-2-2">Python</a></li>
</ul>
<div id="tabset-2-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shinychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">weathR</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">get_weather_forecast</span> <span class="o">&lt;-</span> <span class="nf">tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="kr">function</span><span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">point_tomorrow</span><span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">,</span> <span class="n">short</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="n">name</span> <span class="o">=</span> <span class="s">&#34;get_weather_forecast&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Get the weather forecast for a location.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">arguments</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">lat</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Latitude&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">lon</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Longitude&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Register the tool with your chat client</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="n">ellmer</span><span class="o">::</span><span class="nf">chat</span><span class="p">(</span><span class="s">&#34;openai/gpt-4.1-nano&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">register_tool</span><span class="p">(</span><span class="n">get_weather_forecast</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-2-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatOpenAI</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_weather_forecast</span><span class="p">(</span><span class="n">lat</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get the weather forecast for a location.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">lat_lng</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;latitude=</span><span class="si">{</span><span class="n">lat</span><span class="si">}</span><span class="s2">&amp;longitude=</span><span class="si">{</span><span class="n">lon</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;https://api.open-meteo.com/v1/forecast?</span><span class="si">{</span><span class="n">lat_lng</span><span class="si">}</span><span class="s2">&amp;current=temperature_2m,wind_speed_10m&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()[</span><span class="s2">&#34;current&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Register the tool with your chat client</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatOpenAI</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="s2">&#34;gpt-4.1-nano&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">register_tool</span><span class="p">(</span><span class="n">get_weather_forecast</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>With this tool registered, when you ask a weather-related question, the LLM might decide to call the <code>get_weather_forecast()</code> tool to get the latest weather.</p>
<p>In a chat conversation in your R console with ellmer, this might look like the following.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat</span><span class="p">(</span><span class="s">&#34;Will I need an umbrella for my walk to the T?&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ◯ [tool call] get_weather_forecast(lat = 42.3515, lon = -71.0552)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ● #&gt; [{&#34;time&#34;:&#34;2025-11-20 16:00:00 EST&#34;,&#34;temp&#34;:42,&#34;dewpoint&#34;:0,&#34;humidity&#34;:67,&#34;p_rain&#34;:1,&#34;wi…</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Based on the weather forecast, there is a chance of rain around 4 to 5 PM,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; with mostly cloudy to partly sunny skies. It seems there might be some rain</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; during this time, so carrying an umbrella could be a good idea if you plan</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; to go out around that time. Otherwise, the weather looks relatively clear</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; in the evening.</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Notice that I didn&rsquo;t provide many context clues, but the model correctly guessed that I&rsquo;m walking to the MBTA in Boston, MA and picked <a href="https://www.openstreetmap.org/?mlat=42.35150&amp;mlon=-71.05520#map=16/42.35150/-71.05520&amp;layers=P" target="_blank" rel="noopener">the latitude and longitude for Boston&rsquo;s South Station</a>
.</p>
<p>In shinychat, when the LLM calls the tool, shinychat automatically displays the tool request in a collapsed card:</p>
<pre><code>OpenTelemetry error: there is no package called 'otelsdk'
</code></pre>
<p><shiny-tool-request request-id="tool_call_001" tool-name="get_weather_forecast" arguments="{&quot;lat&quot;:42.3515,&quot;lon&quot;:-71.0552}"></shiny-tool-request></p>
<p>Expanding the card shows the arguments passed to the tool.
When the tool completes, shinychat replaces the request with a card containing the result:</p>
<p><shiny-tool-result request-id="tool_call_002" tool-name="get_weather_forecast" request-call="get_weather_forecast(lat = 42.3515, lon = -71.0552)" status="success" show-request value="[&#10;  {&#10;    &quot;time&quot;: &quot;2026-04-02 18:00:00 EDT&quot;,&#10;    &quot;temp&quot;: 38,&#10;    &quot;dewpoint&quot;: 1.1111,&#10;    &quot;humidity&quot;: 85,&#10;    &quot;p_rain&quot;: 13,&#10;    &quot;wind_speed&quot;: 12,&#10;    &quot;wind_dir&quot;: &quot;NE&quot;,&#10;    &quot;skies&quot;: &quot;Patchy Fog&quot;,&#10;    &quot;geometry&quot;: {&#10;      &quot;type&quot;: &quot;Point&quot;,&#10;      &quot;coordinates&quot;: [-71.0589, 42.3601]&#10;    }&#10;  },&#10;  {&#10;    &quot;time&quot;: &quot;2026-04-02 19:00:00 EDT&quot;,&#10;    &quot;temp&quot;: 38,&#10;    &quot;dewpoint&quot;: 1.1111,&#10;    &quot;humidity&quot;: 85,&#10;    &quot;p_rain&quot;: 12,&#10;    &quot;wind_speed&quot;: 12,&#10;    &quot;wind_dir&quot;: &quot;NE&quot;,&#10;    &quot;skies&quot;: &quot;Patchy Fog&quot;,&#10;    &quot;geometry&quot;: {&#10;      &quot;type&quot;: &quot;Point&quot;,&#10;      &quot;coordinates&quot;: [-71.0589, 42.3601]&#10;    }&#10;  },&#10;  {&#10;    &quot;time&quot;: &quot;2026-04-02 20:00:00 EDT&quot;,&#10;    &quot;temp&quot;: 38,&#10;    &quot;dewpoint&quot;: 1.1111,&#10;    &quot;humidity&quot;: 85,&#10;    &quot;p_rain&quot;: 10,&#10;    &quot;wind_speed&quot;: 10,&#10;    &quot;wind_dir&quot;: &quot;NE&quot;,&#10;    &quot;skies&quot;: &quot;Patchy Fog&quot;,&#10;    &quot;geometry&quot;: {&#10;      &quot;type&quot;: &quot;Point&quot;,&#10;      &quot;coordinates&quot;: [-71.0589, 42.3601]&#10;    }&#10;  },&#10;  {&#10;    &quot;time&quot;: &quot;2026-04-02 21:00:00 EDT&quot;,&#10;    &quot;temp&quot;: 38,&#10;    &quot;dewpoint&quot;: 1.1111,&#10;    &quot;humidity&quot;: 85,&#10;    &quot;p_rain&quot;: 10,&#10;    &quot;wind_speed&quot;: 9,&#10;    &quot;wind_dir&quot;: &quot;NE&quot;,&#10;    &quot;skies&quot;: &quot;Patchy Fog&quot;,&#10;    &quot;geometry&quot;: {&#10;      &quot;type&quot;: &quot;Point&quot;,&#10;      &quot;coordinates&quot;: [-71.0589, 42.3601]&#10;    }&#10;  },&#10;  {&#10;    &quot;time&quot;: &quot;2026-04-02 22:00:00 EDT&quot;,&#10;    &quot;temp&quot;: 38,&#10;    &quot;dewpoint&quot;: 0.5556,&#10;    &quot;humidity&quot;: 82,&#10;    &quot;p_rain&quot;: 11,&#10;    &quot;wind_speed&quot;: 8,&#10;    &quot;wind_dir&quot;: &quot;E&quot;,&#10;    &quot;skies&quot;: &quot;Cloudy&quot;,&#10;    &quot;geometry&quot;: {&#10;      &quot;type&quot;: &quot;Point&quot;,&#10;      &quot;coordinates&quot;: [-71.0589, 42.3601]&#10;    }&#10;  },&#10;  {&#10;    &quot;time&quot;: &quot;2026-04-02 23:00:00 EDT&quot;,&#10;    &quot;temp&quot;: 38,&#10;    &quot;dewpoint&quot;: 0.5556,&#10;    &quot;humidity&quot;: 82,&#10;    &quot;p_rain&quot;: 11,&#10;    &quot;wind_speed&quot;: 7,&#10;    &quot;wind_dir&quot;: &quot;E&quot;,&#10;    &quot;skies&quot;: &quot;Cloudy&quot;,&#10;    &quot;geometry&quot;: {&#10;      &quot;type&quot;: &quot;Point&quot;,&#10;      &quot;coordinates&quot;: [-71.0589, 42.3601]&#10;    }&#10;  }&#10;]" value-type="code"></shiny-tool-result></p>
<p>If the tool throws an error, the error is captured and the error message is shown to the LLM.
Typically this happens when the model makes a mistake in calling the tool and often the error message is instructive.</p>
<p>shinychat updates the card to show the error message:</p>
<p><shiny-tool-result request-id="tool_call_001c" tool-name="get_weather_forecast" request-call="get_weather_forecast(lat = 42.3515, lon = -71.0552)" status="error" show-request value="object of type &#39;closure&#39; is not subsettable" value-type="code"></shiny-tool-result></p>
<h3 id="setting-up-streaming">Setting up streaming
</h3>
<p>To enable tool UI in your apps, you need to ensure that tool requests and results are streamed to shinychat:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-3" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-3-1">R</a></li>
<li><a href="#tabset-3-2">Python</a></li>
</ul>
<div id="tabset-3-1">
<p>You don&rsquo;t need to do anything if you&rsquo;re using <code>chat_app()</code> or the chat module via <code>chat_mod_ui()</code> and <code>chat_mod_server()</code>; tool UI is enabled automatically.</p>
<p>If you&rsquo;re using <code>chat_ui()</code> with <code>chat_append()</code>, set <code>stream = &quot;content&quot;</code> when calling <code>$stream_async()</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span> <span class="o">&lt;-</span> <span class="n">ellmer</span><span class="o">::</span><span class="nf">chat</span><span class="p">(</span><span class="s">&#34;openai/gpt-4.1-nano&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">client</span><span class="o">$</span><span class="nf">register_tool</span><span class="p">(</span><span class="n">get_weather_forecast</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">chat_user_input</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">stream</span> <span class="o">&lt;-</span> <span class="n">client</span><span class="o">$</span><span class="nf">stream_async</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">chat_user_input</span><span class="p">,</span> <span class="n">stream</span> <span class="o">=</span> <span class="s">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">chat_append</span><span class="p">(</span><span class="s">&#34;chat&#34;</span><span class="p">,</span> <span class="n">stream</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-3-2">
<p>In Python with Shiny Express, use <code>content=&quot;all&quot;</code> when calling <code>stream_async()</code>:</p>
<p><strong>app.py</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatOpenAI</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shinychat.express</span> <span class="kn">import</span> <span class="n">Chat</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">client</span> <span class="o">=</span> <span class="n">ChatOpenAI</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="s2">&#34;gpt-4.1-nano&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">client</span><span class="o">.</span><span class="n">register_tool</span><span class="p">(</span><span class="n">get_weather_forecast</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">Chat</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="s2">&#34;chat&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">ui</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@chat.on_user_submit</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">handle_user_input</span><span class="p">(</span><span class="n">user_input</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">stream_async</span><span class="p">(</span><span class="n">user_input</span><span class="p">,</span> <span class="n">content</span><span class="o">=</span><span class="s2">&#34;all&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">chat</span><span class="o">.</span><span class="n">append_message_stream</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>For Shiny Core mode:</p>
<p><strong>app.py</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatOpenAI</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">App</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shinychat</span> <span class="kn">import</span> <span class="n">Chat</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">client</span> <span class="o">=</span> <span class="n">ChatOpenAI</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="s2">&#34;gpt-4.1-nano&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">client</span><span class="o">.</span><span class="n">register_tool</span><span class="p">(</span><span class="n">get_weather_forecast</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">Chat</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="s2">&#34;chat&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">ui</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat</span> <span class="o">=</span> <span class="n">Chat</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="s2">&#34;chat&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@chat.on_user_submit</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">handle_user_input</span><span class="p">(</span><span class="n">user_input</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">stream_async</span><span class="p">(</span><span class="n">user_input</span><span class="p">,</span> <span class="n">content</span><span class="o">=</span><span class="s2">&#34;all&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="n">chat</span><span class="o">.</span><span class="n">append_message_stream</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<h3 id="customizing-tool-title-and-icon">Customizing tool title and icon
</h3>
<p>You can enhance the visual presentation of tool requests and results by adding custom titles and icons to your tools.
This helps users quickly identify which tools are being called.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-4" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-4-1">R</a></li>
<li><a href="#tabset-4-2">Python</a></li>
</ul>
<div id="tabset-4-1">
<p>Use <code>tool_annotations()</code> to add a title and icon:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">get_weather_forecast</span> <span class="o">&lt;-</span> <span class="nf">tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="kr">function</span><span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">point_tomorrow</span><span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">,</span> <span class="n">short</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="n">name</span> <span class="o">=</span> <span class="s">&#34;get_weather_forecast&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Get the weather forecast for a location.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">arguments</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">lat</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Latitude&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">lon</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Longitude&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">annotations</span> <span class="o">=</span> <span class="nf">tool_annotations</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;Weather Forecast&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">icon</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;cloud-sun&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-4-2">
<p>With chatlas, you can customize the tool display in two ways:</p>
<ol>
<li>
<p>Use the <code>._display</code> attribute to customize the tool display:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">faicons</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_weather_forecast</span><span class="p">(</span><span class="n">lat</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get the weather forecast for a location.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ... implementation ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">get_weather_forecast</span><span class="o">.</span><span class="n">_display</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Weather Forecast&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;icon&#34;</span><span class="p">:</span> <span class="n">faicons</span><span class="o">.</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;cloud-sun&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This approach sets the title and icon for all calls to this tool, so it&rsquo;s ideal for predefined tools or tools that are bundled in a Python module or package.</p>
</li>
<li>
<p>Set the tool annotations at registration time:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">register_tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">get_weather_forecast</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">annotations</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Weather Forecast&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;icon&#34;</span><span class="p">:</span> <span class="n">faicons</span><span class="o">.</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;cloud-sun&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This approach allows you to customize the display for a specific chat client or application without modifying the tool function itself.</p>
</li>
</ol>
</div>
</div>
<p>Now the tool card shows your custom title and icon:</p>
<p><shiny-tool-result request-id="tool_call_004" tool-name="get_weather_forecast" request-call="get_weather_forecast(lat = 42.3515, lon = -71.0552)" status="success" tool-title="Weather Forecast" icon="&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 16 16&quot; class=&quot;bi bi-cloud-sun &quot; style=&quot;height:1em;width:1em;fill:currentColor;vertical-align:-0.125em;&quot; aria-hidden=&quot;true&quot; role=&quot;img&quot; &gt;&lt;path d=&quot;M7 8a3.5 3.5 0 0 1 3.5 3.555.5.5 0 0 0 .624.492A1.503 1.503 0 0 1 13 13.5a1.5 1.5 0 0 1-1.5 1.5H3a2 2 0 1 1 .1-3.998.5.5 0 0 0 .51-.375A3.502 3.502 0 0 1 7 8zm4.473 3a4.5 4.5 0 0 0-8.72-.99A3 3 0 0 0 3 16h8.5a2.5 2.5 0 0 0 0-5h-.027z&quot;&gt;&lt;/path&gt;&#10;&lt;path d=&quot;M10.5 1.5a.5.5 0 0 0-1 0v1a.5.5 0 0 0 1 0v-1zm3.743 1.964a.5.5 0 1 0-.707-.707l-.708.707a.5.5 0 0 0 .708.708l.707-.708zm-7.779-.707a.5.5 0 0 0-.707.707l.707.708a.5.5 0 1 0 .708-.708l-.708-.707zm1.734 3.374a2 2 0 1 1 3.296 2.198c.199.281.372.582.516.898a3 3 0 1 0-4.84-3.225c.352.011.696.055 1.028.129zm4.484 4.074c.6.215 1.125.59 1.522 1.072a.5.5 0 0 0 .039-.742l-.707-.707a.5.5 0 0 0-.854.377zM14.5 6.5a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1h-1z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;" show-request value="[&#10;  {&#10;    &quot;time&quot;: &quot;2026-04-02 18:00:00 EDT&quot;,&#10;    &quot;temp&quot;: 38,&#10;    &quot;dewpoint&quot;: 1.1111,&#10;    &quot;humidity&quot;: 85,&#10;    &quot;p_rain&quot;: 13,&#10;    &quot;wind_speed&quot;: 12,&#10;    &quot;wind_dir&quot;: &quot;NE&quot;,&#10;    &quot;skies&quot;: &quot;Patchy Fog&quot;,&#10;    &quot;geometry&quot;: {&#10;      &quot;type&quot;: &quot;Point&quot;,&#10;      &quot;coordinates&quot;: [-71.0589, 42.3601]&#10;    }&#10;  },&#10;  {&#10;    &quot;time&quot;: &quot;2026-04-02 19:00:00 EDT&quot;,&#10;    &quot;temp&quot;: 38,&#10;    &quot;dewpoint&quot;: 1.1111,&#10;    &quot;humidity&quot;: 85,&#10;    &quot;p_rain&quot;: 12,&#10;    &quot;wind_speed&quot;: 12,&#10;    &quot;wind_dir&quot;: &quot;NE&quot;,&#10;    &quot;skies&quot;: &quot;Patchy Fog&quot;,&#10;    &quot;geometry&quot;: {&#10;      &quot;type&quot;: &quot;Point&quot;,&#10;      &quot;coordinates&quot;: [-71.0589, 42.3601]&#10;    }&#10;  },&#10;  {&#10;    &quot;time&quot;: &quot;2026-04-02 20:00:00 EDT&quot;,&#10;    &quot;temp&quot;: 38,&#10;    &quot;dewpoint&quot;: 1.1111,&#10;    &quot;humidity&quot;: 85,&#10;    &quot;p_rain&quot;: 10,&#10;    &quot;wind_speed&quot;: 10,&#10;    &quot;wind_dir&quot;: &quot;NE&quot;,&#10;    &quot;skies&quot;: &quot;Patchy Fog&quot;,&#10;    &quot;geometry&quot;: {&#10;      &quot;type&quot;: &quot;Point&quot;,&#10;      &quot;coordinates&quot;: [-71.0589, 42.3601]&#10;    }&#10;  },&#10;  {&#10;    &quot;time&quot;: &quot;2026-04-02 21:00:00 EDT&quot;,&#10;    &quot;temp&quot;: 38,&#10;    &quot;dewpoint&quot;: 1.1111,&#10;    &quot;humidity&quot;: 85,&#10;    &quot;p_rain&quot;: 10,&#10;    &quot;wind_speed&quot;: 9,&#10;    &quot;wind_dir&quot;: &quot;NE&quot;,&#10;    &quot;skies&quot;: &quot;Patchy Fog&quot;,&#10;    &quot;geometry&quot;: {&#10;      &quot;type&quot;: &quot;Point&quot;,&#10;      &quot;coordinates&quot;: [-71.0589, 42.3601]&#10;    }&#10;  },&#10;  {&#10;    &quot;time&quot;: &quot;2026-04-02 22:00:00 EDT&quot;,&#10;    &quot;temp&quot;: 38,&#10;    &quot;dewpoint&quot;: 0.5556,&#10;    &quot;humidity&quot;: 82,&#10;    &quot;p_rain&quot;: 11,&#10;    &quot;wind_speed&quot;: 8,&#10;    &quot;wind_dir&quot;: &quot;E&quot;,&#10;    &quot;skies&quot;: &quot;Cloudy&quot;,&#10;    &quot;geometry&quot;: {&#10;      &quot;type&quot;: &quot;Point&quot;,&#10;      &quot;coordinates&quot;: [-71.0589, 42.3601]&#10;    }&#10;  },&#10;  {&#10;    &quot;time&quot;: &quot;2026-04-02 23:00:00 EDT&quot;,&#10;    &quot;temp&quot;: 38,&#10;    &quot;dewpoint&quot;: 0.5556,&#10;    &quot;humidity&quot;: 82,&#10;    &quot;p_rain&quot;: 11,&#10;    &quot;wind_speed&quot;: 7,&#10;    &quot;wind_dir&quot;: &quot;E&quot;,&#10;    &quot;skies&quot;: &quot;Cloudy&quot;,&#10;    &quot;geometry&quot;: {&#10;      &quot;type&quot;: &quot;Point&quot;,&#10;      &quot;coordinates&quot;: [-71.0589, 42.3601]&#10;    }&#10;  }&#10;]" value-type="code"></shiny-tool-result></p>
<h3 id="custom-display-content">Custom display content
</h3>
<p>By default, shinychat shows the raw tool result value as a code block.
But often you&rsquo;ll want to present data to users in a more polished format&mdash;like a formatted table or a summary.</p>
<p>You can customize the display by returning alternative content:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-5" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-5-1">R</a></li>
<li><a href="#tabset-5-2">Python</a></li>
</ul>
<div id="tabset-5-1">
<p>Return a <code>ContentToolResult</code> with <code>extra$display</code> containing alternative content:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">get_weather_forecast</span> <span class="o">&lt;-</span> <span class="nf">tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="kr">function</span><span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">,</span> <span class="n">location_name</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">forecast_data</span> <span class="o">&lt;-</span> <span class="nf">point_tomorrow</span><span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">,</span> <span class="n">short</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">forecast_table</span> <span class="o">&lt;-</span> <span class="n">gt</span><span class="o">::</span><span class="nf">as_raw_html</span><span class="p">(</span><span class="n">gt</span><span class="o">::</span><span class="nf">gt</span><span class="p">(</span><span class="n">forecast_data</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">ContentToolResult</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">forecast_data</span><span class="p">,</span>  <span class="c1"># This is what the LLM sees</span>
</span></span><span class="line"><span class="cl">      <span class="n">extra</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">display</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">html</span> <span class="o">=</span> <span class="n">forecast_table</span><span class="p">,</span>  <span class="c1"># This is what users see</span>
</span></span><span class="line"><span class="cl">          <span class="n">title</span> <span class="o">=</span> <span class="nf">paste</span><span class="p">(</span><span class="s">&#34;Weather Forecast for&#34;</span><span class="p">,</span> <span class="n">location_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="n">name</span> <span class="o">=</span> <span class="s">&#34;get_weather_forecast&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Get the weather forecast for a location.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">arguments</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">lat</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Latitude&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">lon</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span><span class="s">&#34;Longitude&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">location_name</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span><span class="s">&#34;Name of the location&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">annotations</span> <span class="o">=</span> <span class="nf">tool_annotations</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;Weather Forecast&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">icon</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;cloud-sun&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-5-2">
<p>Return a <code>ToolResult</code> with display options:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ToolResult</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_weather_forecast</span><span class="p">(</span><span class="n">lat</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="n">location_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get the weather forecast for a location.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Get forecast data</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">fetch_weather_data</span><span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Create a DataFrame for the LLM</span>
</span></span><span class="line"><span class="cl">    <span class="n">forecast_df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Create HTML table for users</span>
</span></span><span class="line"><span class="cl">    <span class="n">forecast_table</span> <span class="o">=</span> <span class="n">forecast_df</span><span class="o">.</span><span class="n">to_html</span><span class="p">(</span><span class="n">index</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">ToolResult</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">value</span><span class="o">=</span><span class="n">forecast_df</span><span class="o">.</span><span class="n">to_dict</span><span class="p">(),</span>  <span class="c1"># LLM sees this</span>
</span></span><span class="line"><span class="cl">        <span class="n">display</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;html&#34;</span><span class="p">:</span> <span class="n">forecast_table</span><span class="p">,</span>  <span class="c1"># Users see this</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;title&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;Weather Forecast for </span><span class="si">{</span><span class="n">location_name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>The <code>display</code> options support three content types (in order of preference):</p>
<ol>
<li><strong><code>html</code></strong>: HTML content from packages like <code>{gt}</code>, <code>{reactable}</code>, or <code>{htmlwidgets}</code> (R), or Pandas/HTML strings (Python)</li>
<li><strong><code>markdown</code></strong>: Markdown text that&rsquo;s automatically rendered</li>
<li><strong><code>text</code></strong>: Plain text without code formatting</li>
</ol>
<p>Here&rsquo;s what a formatted table looks like in the tool result:</p>
<p><shiny-tool-result request-id="tool_call_007" tool-name="get_weather_forecast" request-call="get_weather_forecast(lat = 42.3515, lon = -71.0552, location_name = &quot;South Station in Boston, MA&quot;)" status="success" tool-title="Weather Forecast for South Station in Boston, MA" icon="&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 16 16&quot; class=&quot;bi bi-cloud-sun &quot; style=&quot;height:1em;width:1em;fill:currentColor;vertical-align:-0.125em;&quot; aria-hidden=&quot;true&quot; role=&quot;img&quot; &gt;&lt;path d=&quot;M7 8a3.5 3.5 0 0 1 3.5 3.555.5.5 0 0 0 .624.492A1.503 1.503 0 0 1 13 13.5a1.5 1.5 0 0 1-1.5 1.5H3a2 2 0 1 1 .1-3.998.5.5 0 0 0 .51-.375A3.502 3.502 0 0 1 7 8zm4.473 3a4.5 4.5 0 0 0-8.72-.99A3 3 0 0 0 3 16h8.5a2.5 2.5 0 0 0 0-5h-.027z&quot;&gt;&lt;/path&gt;&#10;&lt;path d=&quot;M10.5 1.5a.5.5 0 0 0-1 0v1a.5.5 0 0 0 1 0v-1zm3.743 1.964a.5.5 0 1 0-.707-.707l-.708.707a.5.5 0 0 0 .708.708l.707-.708zm-7.779-.707a.5.5 0 0 0-.707.707l.707.708a.5.5 0 1 0 .708-.708l-.708-.707zm1.734 3.374a2 2 0 1 1 3.296 2.198c.199.281.372.582.516.898a3 3 0 1 0-4.84-3.225c.352.011.696.055 1.028.129zm4.484 4.074c.6.215 1.125.59 1.522 1.072a.5.5 0 0 0 .039-.742l-.707-.707a.5.5 0 0 0-.854.377zM14.5 6.5a.5.5 0 0 0 0 1h1a.5.5 0 0 0 0-1h-1z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;" show-request value="&lt;div id=&quot;sxmpupkcrl&quot; style=&quot;padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;&quot;&gt;&#10;  &#10;  &lt;table class=&quot;gt_table&quot; data-quarto-disable-processing=&quot;false&quot; data-quarto-bootstrap=&quot;false&quot; style=&quot;-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-family: system-ui, &#39;Segoe UI&#39;, Roboto, Helvetica, Arial, sans-serif, &#39;Apple Color Emoji&#39;, &#39;Segoe UI Emoji&#39;, &#39;Segoe UI Symbol&#39;, &#39;Noto Color Emoji&#39;; display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3;&quot; bgcolor=&quot;#FFFFFF&quot;&gt;&#10;  &lt;thead style=&quot;border-style: none;&quot;&gt;&#10;    &lt;tr class=&quot;gt_col_headings&quot; style=&quot;border-style: none; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3;&quot;&gt;&#10;      &lt;th class=&quot;gt_col_heading gt_columns_bottom_border gt_left&quot; rowspan=&quot;1&quot; colspan=&quot;1&quot; scope=&quot;col&quot; id=&quot;time&quot; style=&quot;border-style: none; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; text-align: left;&quot; bgcolor=&quot;#FFFFFF&quot; valign=&quot;bottom&quot; align=&quot;left&quot;&gt;time&lt;/th&gt;&#10;      &lt;th class=&quot;gt_col_heading gt_columns_bottom_border gt_right&quot; rowspan=&quot;1&quot; colspan=&quot;1&quot; scope=&quot;col&quot; id=&quot;temp&quot; style=&quot;border-style: none; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; bgcolor=&quot;#FFFFFF&quot; valign=&quot;bottom&quot; align=&quot;right&quot;&gt;temp&lt;/th&gt;&#10;      &lt;th class=&quot;gt_col_heading gt_columns_bottom_border gt_right&quot; rowspan=&quot;1&quot; colspan=&quot;1&quot; scope=&quot;col&quot; id=&quot;dewpoint&quot; style=&quot;border-style: none; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; bgcolor=&quot;#FFFFFF&quot; valign=&quot;bottom&quot; align=&quot;right&quot;&gt;dewpoint&lt;/th&gt;&#10;      &lt;th class=&quot;gt_col_heading gt_columns_bottom_border gt_right&quot; rowspan=&quot;1&quot; colspan=&quot;1&quot; scope=&quot;col&quot; id=&quot;humidity&quot; style=&quot;border-style: none; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; bgcolor=&quot;#FFFFFF&quot; valign=&quot;bottom&quot; align=&quot;right&quot;&gt;humidity&lt;/th&gt;&#10;      &lt;th class=&quot;gt_col_heading gt_columns_bottom_border gt_right&quot; rowspan=&quot;1&quot; colspan=&quot;1&quot; scope=&quot;col&quot; id=&quot;p_rain&quot; style=&quot;border-style: none; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; bgcolor=&quot;#FFFFFF&quot; valign=&quot;bottom&quot; align=&quot;right&quot;&gt;p_rain&lt;/th&gt;&#10;      &lt;th class=&quot;gt_col_heading gt_columns_bottom_border gt_right&quot; rowspan=&quot;1&quot; colspan=&quot;1&quot; scope=&quot;col&quot; id=&quot;wind_speed&quot; style=&quot;border-style: none; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; bgcolor=&quot;#FFFFFF&quot; valign=&quot;bottom&quot; align=&quot;right&quot;&gt;wind_speed&lt;/th&gt;&#10;      &lt;th class=&quot;gt_col_heading gt_columns_bottom_border gt_left&quot; rowspan=&quot;1&quot; colspan=&quot;1&quot; scope=&quot;col&quot; id=&quot;wind_dir&quot; style=&quot;border-style: none; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; text-align: left;&quot; bgcolor=&quot;#FFFFFF&quot; valign=&quot;bottom&quot; align=&quot;left&quot;&gt;wind_dir&lt;/th&gt;&#10;      &lt;th class=&quot;gt_col_heading gt_columns_bottom_border gt_left&quot; rowspan=&quot;1&quot; colspan=&quot;1&quot; scope=&quot;col&quot; id=&quot;skies&quot; style=&quot;border-style: none; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; text-align: left;&quot; bgcolor=&quot;#FFFFFF&quot; valign=&quot;bottom&quot; align=&quot;left&quot;&gt;skies&lt;/th&gt;&#10;      &lt;th class=&quot;gt_col_heading gt_columns_bottom_border gt_center&quot; rowspan=&quot;1&quot; colspan=&quot;1&quot; scope=&quot;col&quot; id=&quot;geometry&quot; style=&quot;border-style: none; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; text-align: center;&quot; bgcolor=&quot;#FFFFFF&quot; valign=&quot;bottom&quot; align=&quot;center&quot;&gt;geometry&lt;/th&gt;&#10;    &lt;/tr&gt;&#10;  &lt;/thead&gt;&#10;  &lt;tbody class=&quot;gt_table_body&quot; style=&quot;border-style: none; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3;&quot;&gt;&#10;    &lt;tr style=&quot;border-style: none;&quot;&gt;&lt;td headers=&quot;time&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;2026-04-02 18:00:00 EDT&lt;/td&gt;&#10;&lt;td headers=&quot;temp&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;38&lt;/td&gt;&#10;&lt;td headers=&quot;dewpoint&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;1.666667&lt;/td&gt;&#10;&lt;td headers=&quot;humidity&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;89&lt;/td&gt;&#10;&lt;td headers=&quot;p_rain&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;13&lt;/td&gt;&#10;&lt;td headers=&quot;wind_speed&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;14&lt;/td&gt;&#10;&lt;td headers=&quot;wind_dir&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;NE&lt;/td&gt;&#10;&lt;td headers=&quot;skies&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;Patchy Fog&lt;/td&gt;&#10;&lt;td headers=&quot;geometry&quot; class=&quot;gt_row gt_center&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: center;&quot; valign=&quot;middle&quot; align=&quot;center&quot;&gt;c(-71.0552, 42.3515)&lt;/td&gt;&lt;/tr&gt;&#10;    &lt;tr style=&quot;border-style: none;&quot;&gt;&lt;td headers=&quot;time&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;2026-04-02 19:00:00 EDT&lt;/td&gt;&#10;&lt;td headers=&quot;temp&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;38&lt;/td&gt;&#10;&lt;td headers=&quot;dewpoint&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;1.666667&lt;/td&gt;&#10;&lt;td headers=&quot;humidity&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;89&lt;/td&gt;&#10;&lt;td headers=&quot;p_rain&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;12&lt;/td&gt;&#10;&lt;td headers=&quot;wind_speed&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;13&lt;/td&gt;&#10;&lt;td headers=&quot;wind_dir&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;NE&lt;/td&gt;&#10;&lt;td headers=&quot;skies&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;Patchy Fog&lt;/td&gt;&#10;&lt;td headers=&quot;geometry&quot; class=&quot;gt_row gt_center&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: center;&quot; valign=&quot;middle&quot; align=&quot;center&quot;&gt;c(-71.0552, 42.3515)&lt;/td&gt;&lt;/tr&gt;&#10;    &lt;tr style=&quot;border-style: none;&quot;&gt;&lt;td headers=&quot;time&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;2026-04-02 20:00:00 EDT&lt;/td&gt;&#10;&lt;td headers=&quot;temp&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;38&lt;/td&gt;&#10;&lt;td headers=&quot;dewpoint&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;1.666667&lt;/td&gt;&#10;&lt;td headers=&quot;humidity&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;89&lt;/td&gt;&#10;&lt;td headers=&quot;p_rain&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;10&lt;/td&gt;&#10;&lt;td headers=&quot;wind_speed&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;13&lt;/td&gt;&#10;&lt;td headers=&quot;wind_dir&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;NE&lt;/td&gt;&#10;&lt;td headers=&quot;skies&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;Patchy Fog&lt;/td&gt;&#10;&lt;td headers=&quot;geometry&quot; class=&quot;gt_row gt_center&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: center;&quot; valign=&quot;middle&quot; align=&quot;center&quot;&gt;c(-71.0552, 42.3515)&lt;/td&gt;&lt;/tr&gt;&#10;    &lt;tr style=&quot;border-style: none;&quot;&gt;&lt;td headers=&quot;time&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;2026-04-02 21:00:00 EDT&lt;/td&gt;&#10;&lt;td headers=&quot;temp&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;38&lt;/td&gt;&#10;&lt;td headers=&quot;dewpoint&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;1.666667&lt;/td&gt;&#10;&lt;td headers=&quot;humidity&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;89&lt;/td&gt;&#10;&lt;td headers=&quot;p_rain&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;10&lt;/td&gt;&#10;&lt;td headers=&quot;wind_speed&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;13&lt;/td&gt;&#10;&lt;td headers=&quot;wind_dir&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;E&lt;/td&gt;&#10;&lt;td headers=&quot;skies&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;Patchy Fog&lt;/td&gt;&#10;&lt;td headers=&quot;geometry&quot; class=&quot;gt_row gt_center&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: center;&quot; valign=&quot;middle&quot; align=&quot;center&quot;&gt;c(-71.0552, 42.3515)&lt;/td&gt;&lt;/tr&gt;&#10;    &lt;tr style=&quot;border-style: none;&quot;&gt;&lt;td headers=&quot;time&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;2026-04-02 22:00:00 EDT&lt;/td&gt;&#10;&lt;td headers=&quot;temp&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;38&lt;/td&gt;&#10;&lt;td headers=&quot;dewpoint&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;1.111111&lt;/td&gt;&#10;&lt;td headers=&quot;humidity&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;85&lt;/td&gt;&#10;&lt;td headers=&quot;p_rain&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;11&lt;/td&gt;&#10;&lt;td headers=&quot;wind_speed&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;12&lt;/td&gt;&#10;&lt;td headers=&quot;wind_dir&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;E&lt;/td&gt;&#10;&lt;td headers=&quot;skies&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;Patchy Fog&lt;/td&gt;&#10;&lt;td headers=&quot;geometry&quot; class=&quot;gt_row gt_center&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: center;&quot; valign=&quot;middle&quot; align=&quot;center&quot;&gt;c(-71.0552, 42.3515)&lt;/td&gt;&lt;/tr&gt;&#10;    &lt;tr style=&quot;border-style: none;&quot;&gt;&lt;td headers=&quot;time&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;2026-04-02 23:00:00 EDT&lt;/td&gt;&#10;&lt;td headers=&quot;temp&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;38&lt;/td&gt;&#10;&lt;td headers=&quot;dewpoint&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;1.111111&lt;/td&gt;&#10;&lt;td headers=&quot;humidity&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;85&lt;/td&gt;&#10;&lt;td headers=&quot;p_rain&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;11&lt;/td&gt;&#10;&lt;td headers=&quot;wind_speed&quot; class=&quot;gt_row gt_right&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: right; font-variant-numeric: tabular-nums;&quot; valign=&quot;middle&quot; align=&quot;right&quot;&gt;10&lt;/td&gt;&#10;&lt;td headers=&quot;wind_dir&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;E&lt;/td&gt;&#10;&lt;td headers=&quot;skies&quot; class=&quot;gt_row gt_left&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: left;&quot; valign=&quot;middle&quot; align=&quot;left&quot;&gt;Patchy Fog&lt;/td&gt;&#10;&lt;td headers=&quot;geometry&quot; class=&quot;gt_row gt_center&quot; style=&quot;border-style: none; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; text-align: center;&quot; valign=&quot;middle&quot; align=&quot;center&quot;&gt;c(-71.0552, 42.3515)&lt;/td&gt;&lt;/tr&gt;&#10;  &lt;/tbody&gt;&#10;  &#10;&lt;/table&gt;&#10;&lt;/div&gt;" value-type="html"></shiny-tool-result></p>
<h3 id="additional-display-options">Additional display options
</h3>
<p>You can control how tool results are presented using additional display options:</p>
<ul>
<li><code>show_request = FALSE</code>: Hide the tool call details when they&rsquo;re obvious from the display</li>
<li><code>open = TRUE</code>: Expand the result panel by default (useful for rich content like maps or charts)</li>
<li><code>title</code> and <code>icon</code>: Override the tool&rsquo;s default title and icon for this specific result</li>
</ul>
<p>Another helpful feature is to include an <code>_intent</code> argument in your tool definition.
When present in the tool arguments, shinychat shows the <code>_intent</code> value in the tool card header, helping users understand why the LLM is calling the tool.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-6" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-6-1">R</a></li>
<li><a href="#tabset-6-2">Python</a></li>
</ul>
<div id="tabset-6-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">tool_with_intent</span> <span class="o">&lt;-</span> <span class="nf">tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="kr">function</span><span class="p">(</span><span class="n">`_intent`</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">runif</span><span class="p">(</span><span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="n">name</span> <span class="o">=</span> <span class="s">&#34;random_number&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Generate a random number.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">arguments</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">`_intent`</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;Explain why you&#39;re generating this number&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-6-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">random_number</span><span class="p">(</span><span class="n">_intent</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Generate a random number.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="cl"><span class="s2">        _intent: Explain why you&#39;re generating this number
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">random</span><span class="o">.</span><span class="n">random</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>Notice that the tool function itself doesn&rsquo;t actually use the <code>_intent</code> argument, but its presence allows shinychat to give the user additional context about the tool call.</p>
<h2 id="bookmarking-support">Bookmarking support
</h2>
<p>When a Shiny app reloads, the app returns to its initial state, unless the URL includes <a href="https://shiny.posit.co/r/articles/build/bookmarking-state/" target="_blank" rel="noopener">bookmarked state</a>
.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>
Automatically updating the URL to include a bookmark of the chat state is a great way to help users return to their work if they accidentally refresh the page or unexpectedly lose their connection.</p>
<p>Both shinychat for R and Python provide helper functions that make it easy to restore conversations with bookmarks.
This means users can refresh the page or share a URL and pick up right where they left off.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-7" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-7-1">R</a></li>
<li><a href="#tabset-7-2">Python</a></li>
</ul>
<div id="tabset-7-1">
<p>In R, the <code>chat_restore()</code> function restores the message history from the bookmark when the app starts up <em>and</em> ensures that the chat client state is automatically bookmarked on user input and assistant responses.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shinychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">request</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nf">page_fillable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">chat_ui</span><span class="p">(</span><span class="s">&#34;chat&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">chat_client</span> <span class="o">&lt;-</span> <span class="n">ellmer</span><span class="o">::</span><span class="nf">chat_openai</span><span class="p">(</span><span class="n">model</span> <span class="o">=</span> <span class="s">&#34;gpt-4o-mini&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Automatically save chat state on user input and responses</span>
</span></span><span class="line"><span class="cl">  <span class="nf">chat_restore</span><span class="p">(</span><span class="s">&#34;chat&#34;</span><span class="p">,</span> <span class="n">chat_client</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">chat_user_input</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">stream</span> <span class="o">&lt;-</span> <span class="n">chat_client</span><span class="o">$</span><span class="nf">stream_async</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">chat_user_input</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">chat_append</span><span class="p">(</span><span class="s">&#34;chat&#34;</span><span class="p">,</span> <span class="n">stream</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Enable URL-based bookmarking</span>
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">,</span> <span class="n">enableBookmarking</span> <span class="o">=</span> <span class="s">&#34;url&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>enableBookmarking = &quot;url&quot;</code> stores the chat state in encoded data in the query string of the app&rsquo;s URL.
Because browsers have native limitations on the size of a URL, you should use <code>enableBookmarking = &quot;server&quot;</code> to store state server-side without URL size limitations for chatbots expected to have large conversation histories.</p>
<p>And if you&rsquo;re using <code>chat_app()</code> for quick prototypes, bookmarking is already enabled automatically.</p>
</div>
<div id="tabset-7-2">
<p>In Python, the <code>.enable_bookmarking()</code> method handles the where, when, and how of bookmarking chat state.</p>
<h3 id="express-mode">Express mode
</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatOllama</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat_client</span> <span class="o">=</span> <span class="n">ChatOllama</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="s2">&#34;llama3.2&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">Chat</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="s2">&#34;chat&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">ui</span><span class="p">(</span><span class="n">messages</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;Welcome!&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">enable_bookmarking</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat_client</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">bookmark_store</span><span class="o">=</span><span class="s2">&#34;url&#34;</span><span class="p">,</span> <span class="c1"># or &#34;server&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">bookmark_on</span><span class="o">=</span><span class="s2">&#34;response&#34;</span><span class="p">,</span> <span class="c1"># or None</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="core-mode">Core mode
</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatOllama</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">ui</span><span class="p">,</span> <span class="n">App</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fixed</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">chat_ui</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="s2">&#34;chat&#34;</span><span class="p">,</span> <span class="n">messages</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;Welcome!&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat_client</span> <span class="o">=</span> <span class="n">ChatOllama</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="s2">&#34;llama3.2&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">Chat</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="s2">&#34;chat&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">chat</span><span class="o">.</span><span class="n">enable_bookmarking</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat_client</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">bookmark_on</span><span class="o">=</span><span class="s2">&#34;response&#34;</span><span class="p">,</span> <span class="c1"># or None</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">,</span> <span class="n">bookmark_store</span><span class="o">=</span><span class="s2">&#34;url&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="configuration-options">Configuration options
</h3>
<p>The <code>.enable_bookmarking()</code> method handles three aspects of bookmarking:</p>
<ol>
<li><strong>Where</strong> (<code>bookmark_store</code>)
<ul>
<li><code>&quot;url&quot;</code>: Store the state in the URL.</li>
<li><code>&quot;server&quot;</code>: Store the state on the server. Consider this over <code>&quot;url&quot;</code> if you want to support a large amount of state, or have other bookmark state that can&rsquo;t be serialized to JSON.</li>
</ul>
</li>
<li><strong>When</strong> (<code>bookmark_on</code>)
<ul>
<li><code>&quot;response&quot;</code>: Triggers a bookmark when an <code>&quot;assistant&quot;</code> response is appended.</li>
<li><code>None</code>: Don&rsquo;t trigger a bookmark automatically. This assumes you&rsquo;ll be triggering bookmarks through other means (e.g., a button).</li>
</ul>
</li>
<li><strong>How</strong> is handled automatically by registering the relevant <code>on_bookmark</code> and <code>on_restore</code> callbacks.</li>
</ol>
<p>When <code>.enable_bookmarking()</code> triggers a bookmark for you, it&rsquo;ll also update the URL query string to include the bookmark state.
This way, when the user unexpectedly loses connection, they can load the current URL to restore the chat state, or go back to the original URL to start over.</p>
</div>
</div>
<h2 id="other-improvements-in-shinychat-for-r">Other improvements in shinychat for R
</h2>
<p>Beyond tool calling UI and bookmarking support, shinychat for R v0.3.0 includes several other enhancements.</p>
<h3 id="better-programmatic-control">Better programmatic control
</h3>
<p><code>chat_mod_server()</code> now returns a set of reactive values and functions for controlling the chat interface:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_mod_server</span><span class="p">(</span><span class="s">&#34;chat&#34;</span><span class="p">,</span> <span class="n">ellmer</span><span class="o">::</span><span class="nf">chat_openai</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># React to user input</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observe</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">chat</span><span class="o">$</span><span class="nf">last_input</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="nf">print</span><span class="p">(</span><span class="nf">paste</span><span class="p">(</span><span class="s">&#34;User said:&#34;</span><span class="p">,</span> <span class="n">chat</span><span class="o">$</span><span class="nf">last_input</span><span class="p">()))</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># React to assistant responses</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observe</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">chat</span><span class="o">$</span><span class="nf">last_turn</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="nf">print</span><span class="p">(</span><span class="s">&#34;Assistant completed response&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Programmatically control the chat</span>
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">suggest_question</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat</span><span class="o">$</span><span class="nf">update_user_input</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">value</span> <span class="o">=</span> <span class="s">&#34;What&#39;s the weather like today?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">submit</span> <span class="o">=</span> <span class="kc">TRUE</span>  <span class="c1"># Automatically submit</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">reset</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat</span><span class="o">$</span><span class="nf">clear</span><span class="p">()</span>  <span class="c1"># Clear history and UI</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The returned list includes:</p>
<ul>
<li><strong><code>last_input</code></strong> and <strong><code>last_turn</code></strong> reactives for monitoring chat state</li>
<li><strong><code>update_user_input()</code></strong> for programmatically setting or submitting user input&mdash;great for suggested prompts or guided conversations</li>
<li><strong><code>append()</code></strong> for adding messages to the chat UI</li>
<li><strong><code>clear()</code></strong> for resetting the chat, with options to control how the client history is handled</li>
<li><strong><code>client</code></strong> for direct access to the ellmer chat client</li>
</ul>
<p>There&rsquo;s also a standalone <code>update_chat_user_input()</code> function if you&rsquo;re using <code>chat_ui()</code> directly, which supports updating the placeholder text and moving focus to the input.</p>
<h3 id="custom-assistant-icons">Custom assistant icons
</h3>
<p>You can now customize the icon shown next to assistant messages to better match your application&rsquo;s branding or to distinguish between different assistants:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bsicons</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Set a custom icon for a specific response</span>
</span></span><span class="line"><span class="cl"><span class="nf">chat_append</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;chat&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Here&#39;s some helpful information!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">icon</span> <span class="o">=</span> <span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;lightbulb&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Or set a default icon for all assistant messages</span>
</span></span><span class="line"><span class="cl"><span class="nf">chat_ui</span><span class="p">(</span><span class="s">&#34;chat&#34;</span><span class="p">,</span> <span class="n">icon_assistant</span> <span class="o">=</span> <span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;robot&#34;</span><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This is especially useful when building multi-agent applications where different assistants might have different personalities or roles.</p>
<h3 id="safer-external-links">Safer external links
</h3>
<p>External links in chat messages now open in a new tab with a confirmation dialog.
This prevents users from accidentally navigating away from the chat session and losing their conversation.
This is particularly helpful when LLMs include links in their responses, for example when shinychat is used in combination with Retrieval Augmented Generation via <a href="https://ragnar.tidyverse.org" target="_blank" rel="noopener">ragnar</a>
.</p>
<h2 id="learn-more">Learn more
</h2>
<p>The tool calling UI opens up exciting possibilities for building transparent, user-friendly AI applications.
Whether you&rsquo;re fetching data, running calculations, or integrating with external services, users can now see exactly what&rsquo;s happening.</p>
<p>To dive deeper:</p>
<ul>
<li>Read the <a href="https://posit-dev.github.io/shinychat/r/articles/tool-ui.html" target="_blank" rel="noopener">tool calling UI article</a>
 for comprehensive examples in R</li>
<li>Explore tool calling with <a href="https://ellmer.tidyverse.org/articles/tool-calling.html" target="_blank" rel="noopener">ellmer</a>
 (R) or <a href="https://posit-dev.github.io/chatlas/tool-calling/displays.html" target="_blank" rel="noopener">chatlas</a>
 (Python)</li>
</ul>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A huge thank you to everyone who contributed to this release with bug reports, feature requests, and code contributions:</p>
<p><a href="https://github.com/bianchenhao" target="_blank" rel="noopener">@bianchenhao</a>
, <a href="https://github.com/cboettig" target="_blank" rel="noopener">@cboettig</a>
, <a href="https://github.com/chendaniely" target="_blank" rel="noopener">@chendaniely</a>
, <a href="https://github.com/cpsievert" target="_blank" rel="noopener">@cpsievert</a>
, <a href="https://github.com/DavZim" target="_blank" rel="noopener">@DavZim</a>
, <a href="https://github.com/DeepanshKhurana" target="_blank" rel="noopener">@DeepanshKhurana</a>
, <a href="https://github.com/DivadNojnarg" target="_blank" rel="noopener">@DivadNojnarg</a>
, <a href="https://github.com/gadenbuie" target="_blank" rel="noopener">@gadenbuie</a>
, <a href="https://github.com/iainwallacebms" target="_blank" rel="noopener">@iainwallacebms</a>
, <a href="https://github.com/janlimbeck" target="_blank" rel="noopener">@janlimbeck</a>
, <a href="https://github.com/jcheng5" target="_blank" rel="noopener">@jcheng5</a>
, <a href="https://github.com/jimrothstein" target="_blank" rel="noopener">@jimrothstein</a>
, <a href="https://github.com/karangattu" target="_blank" rel="noopener">@karangattu</a>
, <a href="https://github.com/ManuelSpinola" target="_blank" rel="noopener">@ManuelSpinola</a>
, <a href="https://github.com/MohoWu" target="_blank" rel="noopener">@MohoWu</a>
, <a href="https://github.com/nissinbo" target="_blank" rel="noopener">@nissinbo</a>
, <a href="https://github.com/noamanemobidata" target="_blank" rel="noopener">@noamanemobidata</a>
, <a href="https://github.com/parmsam" target="_blank" rel="noopener">@parmsam</a>
, <a href="https://github.com/PaulC91" target="_blank" rel="noopener">@PaulC91</a>
, <a href="https://github.com/rkennedy01" target="_blank" rel="noopener">@rkennedy01</a>
, <a href="https://github.com/schloerke" target="_blank" rel="noopener">@schloerke</a>
, <a href="https://github.com/selesnow" target="_blank" rel="noopener">@selesnow</a>
, <a href="https://github.com/simonpcouch" target="_blank" rel="noopener">@simonpcouch</a>
, <a href="https://github.com/skaltman" target="_blank" rel="noopener">@skaltman</a>
, <a href="https://github.com/stefanlinner" target="_blank" rel="noopener">@stefanlinner</a>
, <a href="https://github.com/t-kalinowski" target="_blank" rel="noopener">@t-kalinowski</a>
, <a href="https://github.com/thendrix-trlm" target="_blank" rel="noopener">@thendrix-trlm</a>
, <a href="https://github.com/wch" target="_blank" rel="noopener">@wch</a>
, <a href="https://github.com/wlandau" target="_blank" rel="noopener">@wlandau</a>
, and <a href="https://github.com/Yousuf28" target="_blank" rel="noopener">@Yousuf28</a>
.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>See the <a href="https://ellmer.tidyverse.org/articles/tool-calling.html" target="_blank" rel="noopener">ellmer tool calling documentation</a>
 for R and the <a href="https://posit-dev.github.io/chatlas/tool-calling/how-it-works.html" target="_blank" rel="noopener">chatlas tool calling documentation</a>
 for Python for more details on defining and registering tools.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>This can be especially frustrating behavior since hosted apps, by default, will close an idle session after a certain (<a href="https://docs.posit.co/shinyapps.io/guide/applications/#advanced-settings" target="_blank" rel="noopener">configurable</a>
) amount of time.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/shinychat-tool-ui/feature.png" length="147726" type="image/png" />
    </item>
    <item>
      <title>promises v1.5.0</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/promises-1.5.0/</link>
      <pubDate>Fri, 07 Nov 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/promises-1.5.0/</guid>
      <dc:creator>Barret Schloerke</dc:creator><description><![CDATA[<p>We&rsquo;re excited to announce the release of <a href="https://rstudio.github.io/promises/" target="_blank" rel="noopener">promises</a>
 v1.5.0. The <a href="https://rstudio.github.io/promises/" target="_blank" rel="noopener">promises</a>
 package provides fundamental abstractions for asynchronous programming in R using JavaScript-style promises, enabling a single R process to orchestrate multiple concurrent tasks.</p>
<h1 id="getting-started">Getting Started
</h1>
<p><a href="https://rstudio.github.io/promises/" target="_blank" rel="noopener">promises</a>
 provides fundamental abstractions for asynchronous programming in R using JavaScript-style promises. It enables a single R process to orchestrate multiple tasks in the background while remaining responsive to other operations. Key features include:</p>
<ul>
<li><strong>Promise chaining</strong> with <a href="https://rstudio.github.io/promises/reference/then.html" target="_blank" rel="noopener"><code>then()</code></a>
, <a href="https://rstudio.github.io/promises/reference/then.html" target="_blank" rel="noopener"><code>catch()</code></a>
, and <a href="https://rstudio.github.io/promises/reference/then.html" target="_blank" rel="noopener"><code>finally()</code></a>
 for handling asynchronous results</li>
<li><strong>Combinators</strong> like <a href="https://rstudio.github.io/promises/reference/promise_all.html" target="_blank" rel="noopener"><code>promise_all()</code></a>
 and <a href="https://rstudio.github.io/promises/reference/promise_all.html" target="_blank" rel="noopener"><code>promise_race()</code></a>
 for coordinating multiple async operations</li>
<li><strong>Promise domains</strong> for managing execution context across asynchronous boundaries (e.g., graphics devices, reactive contexts)</li>
</ul>
<p>The package uses semantics similar to JavaScript promises but with idiomatic R syntax, making it particularly useful for building responsive Shiny applications, coordinating parallel computations, and managing complex asynchronous workflows.</p>
<p>Install the latest version with:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;promises&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="new-features">New Features
</h2>
<p>This release includes several new features for more flexible asynchronous programming, OpenTelemetry integration for distributed tracing, and important performance improvements.</p>
<h3 id="hybrid-synchronousasynchronous-execution-with-hybrid_then">Hybrid Synchronous/Asynchronous Execution with <code>hybrid_then()</code>
</h3>
<p>The new <code>hybrid_then()</code> function intelligently handles both synchronous values and asynchronous promises, executing callbacks either immediately or asynchronously as appropriate. This is particularly useful when writing functions that need to work seamlessly with mixed inputs:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># Works with both regular values and promises</span>
</span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="o">&lt;-</span> <span class="nf">hybrid_then</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">expr</span> <span class="o">=</span> <span class="nf">possible_promise_or_value</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="n">on_success</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="n">x</span> <span class="o">*</span> <span class="m">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">on_failure</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">err</span><span class="p">)</span> <span class="nf">stop</span><span class="p">(</span><span class="s">&#34;Error occurred&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>When <code>expr</code> evaluates to a regular value, callbacks execute synchronously on the same tick. When it&rsquo;s a promise, execution is delegated to <code>then()</code> for proper asynchronous handling.</p>
<h3 id="side-effect-operations-with-tee">Side-Effect Operations with <code>tee</code>
</h3>
<p>The <code>then()</code> function gains a <code>tee</code> parameter for performing side-effect operations without modifying the promise chain. When <code>tee = TRUE</code>, the callback&rsquo;s return value is discarded and the original value is propagated:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">promise_that_works</span><span class="p">()</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">then</span><span class="p">(</span><span class="kr">function</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Log for debugging without affecting the chain</span>
</span></span><span class="line"><span class="cl">    <span class="nf">cat</span><span class="p">(</span><span class="s">&#34;Current value:&#34;</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="s">&#34;\n&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span> <span class="n">tee</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">then</span><span class="p">(</span><span class="kr">function</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Original value is still available here</span>
</span></span><span class="line"><span class="cl">    <span class="nf">process</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="opentelemetry-integration">OpenTelemetry Integration
</h3>
<p><a href="https://rstudio.github.io/promises/" target="_blank" rel="noopener">promises</a>
 now integrates with the <a href="https://github.com/rstudio/otel" target="_blank" rel="noopener">otel</a>
 package to provide observability and distributed tracing for asynchronous operations. Three new functions help manage OpenTelemetry spans across asynchronous boundaries:</p>
<ul>
<li><strong><code>with_otel_span(name, expr, ..., tracer)</code></strong>: Creates an OpenTelemetry span that automatically handles both synchronous and asynchronous operations. A <code>tracer=</code> parameter must be supplied for proper span creation and maximizing performance for when both tracing is enabled and disabled.</li>
<li><strong><code>with_otel_promise_domain(expr)</code></strong>: Creates a promise domain that preserves the active OpenTelemetry span context across asynchronous operations.</li>
<li><strong><code>local_otel_promise_domain()</code></strong>: A <code>local_*()</code> variant of <code>with_otel_promise_domain()</code> that sets up a local promise domain for the current environment.</li>
</ul>
<p>Here&rsquo;s how to use <code>with_otel_promise_domain()</code> and <code>with_otel_span()</code> in concert:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># Setup a consistent OpenTelemetry tracer</span>
</span></span><span class="line"><span class="cl"><span class="n">my_tracer</span> <span class="o">&lt;-</span> <span class="n">otel</span><span class="o">::</span><span class="nf">get_tracer</span><span class="p">(</span><span class="s">&#34;my-tracer&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># When using OpenTelemetry (otel) spans that must be active across async</span>
</span></span><span class="line"><span class="cl"><span class="c1"># boundaries, wrap the entire promise chain creation within</span>
</span></span><span class="line"><span class="cl"><span class="c1"># `with_otel_promise_domain()`. Once is enough, but it may be nested.</span>
</span></span><span class="line"><span class="cl"><span class="nf">with_otel_promise_domain</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Makes an otel span that persists until the promise chain is resolved.</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># This otel span will be reactivated for each step in the promise chain.</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># This otel span will end when the encapsulated promise chain completes.</span>
</span></span><span class="line"><span class="cl">  <span class="nf">with_otel_span</span><span class="p">(</span><span class="s">&#34;my-promise-chain&#34;</span><span class="p">,</span> <span class="n">tracer</span> <span class="o">=</span> <span class="n">my_tracer</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">promise1</span><span class="p">()</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">then</span><span class="p">(</span><span class="nf">\</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Make an OpenTelemetry span around `promise2()` promise chain</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># whose parent span is `my-promise-chain`</span>
</span></span><span class="line"><span class="cl">        <span class="nf">with_otel_span</span><span class="p">(</span><span class="s">&#34;middle-span&#34;</span><span class="p">,</span> <span class="n">tracer</span> <span class="o">=</span> <span class="n">my_tracer</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nf">promise2</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nf">then</span><span class="p">(</span><span class="n">sideeffect2</span><span class="p">,</span> <span class="n">tee</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">})</span>
</span></span><span class="line"><span class="cl">      <span class="p">})</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">then</span><span class="p">(</span><span class="nf">\</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># `with_otel_span()` is not needed for _purely_ synchronous work</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># (but may be used if desired!)</span>
</span></span><span class="line"><span class="cl">        <span class="n">otel</span><span class="o">::</span><span class="nf">start_local_active_span</span><span class="p">(</span><span class="s">&#34;last-span&#34;</span><span class="p">,</span> <span class="n">tracer</span> <span class="o">=</span> <span class="n">my_tracer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nf">sync3</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="p">})</span> <span class="c1"># `my-promise-chain` span ends here</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>When ever the <code>then()</code> callbacks are invoked, the <u>currently active</u> OpenTelemetry span will be captured and reactivated as the active parent for the followup async operation, ensuring accurate tracing across asynchronous operations.</p>
<p>When used correctly, OpenTelemetry traces will show nested spans that accurately represent the asynchronous execution flow:</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/promises-1.5.0/logfire-nested.png" alt="Nested OpenTelemetry spans" />
<figcaption aria-hidden="true">Nested OpenTelemetry spans</figcaption>
</figure>
<p>When <code>with_otel_promise_domain()</code> does not wrap <code>with_otel_span()</code>, the active span context will be lost across asynchronous boundaries, resulting in disconnected traces with a very fast parent span duration:</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/promises-1.5.0/logfire-disconnected.png" alt="Disconnected OpenTelemetry spans" />
<figcaption aria-hidden="true">Disconnected OpenTelemetry spans</figcaption>
</figure>
<p>OpenTelemetry support will be integrated into the next releases of <a href="https://shiny.posit.co/r/" target="_blank" rel="noopener">Shiny</a>
 and <a href="https://plumber2.posit.co/" target="_blank" rel="noopener">plumber2</a>
 (via <a href="https://routr.data-imaginist.com/" target="_blank" rel="noopener">routr</a>
). Users of those packages will not need to directly call <code>with_otel_promise_domain()</code> or <code>local_otel_promise_domain()</code>.</p>
<h2 id="breaking-changes">Breaking Changes
</h2>
<h3 id="fixed-nested-promise-domain-behavior">Fixed Nested Promise Domain Behavior
</h3>
<p>Nested promise domains now correctly invoke in reverse order. Previously, when promise domains were nested, the outer domain would incorrectly take precedence over the inner domain. The innermost (most recently added) domain now properly wraps callbacks first, ensuring consistent scoping behavior:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># Inner domain now correctly takes precedence</span>
</span></span><span class="line"><span class="cl"><span class="nf">with_promise_domain</span><span class="p">(</span><span class="n">outer_domain</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nf">with_promise_domain</span><span class="p">(</span><span class="n">inner_domain</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">promise</span><span class="p">()</span> <span class="o">|&gt;</span> <span class="nf">then</span><span class="p">(</span><span class="n">callback</span><span class="p">)</span>  <span class="c1"># inner_domain wraps first</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If your code relied on the previous (incorrect) behavior, you will need to adjust the ordering of your promise domains.</p>
<h2 id="other-improvements">Other Improvements
</h2>
<h3 id="performance-enhancements">Performance Enhancements
</h3>
<p>Promise creation is now faster due to removal of the R6 data structures in favor of lightweight environments, resulting in reduced memory overhead and improved speed for creating and resolving promises.</p>
<p>We&rsquo;ve also improved the performance of <code>promise_all()</code> by maintaining an internal completion counter, reducing time complexity from <code>O(n²)</code> to <code>O(n)</code> for determining when all promises complete.
Additionally, we&rsquo;ve fixed <code>promise_all()</code> to handle arguments with the same name, ensuring we get a result for every promise provided.</p>
<p>The promises package no longer requires compilation and is now a pure R package, simplifying and reducing installation time, as well as avoiding any additional build requirements.</p>
<h3 id="api-improvements">API Improvements
</h3>
<p>The package now requires R 4.1 or later, and we&rsquo;re excited to adopt R 4.1+ recommended syntax!
The native pipe (<code>|&gt;</code>) and function shorthand (<code>\(x) fn(x)</code>) are now preferred over promise pipe methods (<code>%...&gt;%</code>, <code>%...!%</code>, <code>%...T&gt;%</code>), which are now superseded.</p>
<p>We&rsquo;ve also tightened up some of the function signatures: for the argument <code>tee</code>, this is now required to be specified as a named argument <code>catch(promise, onRejected, ..., tee = FALSE)</code>.
The <code>tee</code> value must also now be a logical value rather than any truthy value. This named argument is available for <code>then()</code>, <code>catch()</code>, and <code>hybrid_then()</code>.</p>
<h3 id="documentation">Documentation
</h3>
<p><strong>Updated for mirai</strong>: The package documentation and vignettes have been comprehensively updated to feature <a href="https://mirai.r-lib.org/" target="_blank" rel="noopener">mirai</a>
 as the recommended approach for launching asynchronous tasks. A new vignette, &ldquo;Launching tasks with mirai,&rdquo; provides a complete introduction to using mirai with promises. mirai offers a lightweight, modern alternative to the <a href="https://future.futureverse.org/" target="_blank" rel="noopener">future</a>
 package, utilizing background R processes (daemons) without polling overhead. Examples throughout the documentation now demonstrate mirai usage alongside or in place of future examples.</p>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>For a complete list of changes, see the <a href="https://rstudio.github.io/promises/news/index.html#promises-150" target="_blank" rel="noopener">release notes for v1.5.0</a>
 and <a href="https://rstudio.github.io/promises/news/index.html#promises-140" target="_blank" rel="noopener">v1.4.0</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/promises-1.5.0/feature.png" length="313805" type="image/png" />
    </item>
    <item>
      <title>plumber2 0.1.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2025/plumber2-0-1-0/</link>
      <pubDate>Tue, 23 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2025/plumber2-0-1-0/</guid>
      <dc:creator>Thomas Lin Pedersen</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [ ] Add intro sentence, e.g. the standard tagline for the package
* [x] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<p>I&rsquo;m super excited to announce the release of the plumber2 package on CRAN. plumber2 is a package for creating webservers in R based on either an annotation-based or programmatic workflow. It is the successor to the plumber package who has empowered the R community for 10 years and allowed them to share their R based functionalities with their organizations and the world.</p>
<p>You can install it from CRAN with:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>pak</span><span class='nf'>::</span><span class='nf'><a href='https://pak.r-lib.org/reference/pak.html'>pak</a></span><span class='o'>(</span><span class='s'>"plumber2"</span><span class='o'>)</span></span></code></pre>
</div>
<p>This blog post will go over the new release. Why create a new package? What has changed? What has stayed the same? and, What is new?</p>
<p>It&rsquo;s a mouthful, so let&rsquo;s get to it!</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://posit-dev.github.io/plumber2/'>plumber2</a></span><span class='o'>)</span></span></code></pre>
</div>
<h2 id="waving-goodbye-to-plumber">Waving goodbye to plumber
</h2>
<p>The first question that may cross your mind is: Why even create a new package instead of continue to build on, and improve, the old one?</p>
<p>It is always a weighing of pros and cons when such a decision is made, but what largely tipped the scale was that the codebase had accrued so much technical debt that it had become hard to maintain. A lot has happened in 10 years and plumber has had to adapt to it all while maintaining backwards compatibility and in the end it took a toll on the codebase.</p>
<p>If you end up deciding on a rewrite you might as well take the opportunity to learn from past mistakes and shed some decisions that turned out wrong, without worrying about breaking existing code. This both gives you a chance to improve on the API, but also give you the freedom to create the best possible foundation without artificial boundaries based on existing uses.</p>
<p>So, in the end, we wanted a complete rewrite, and we wanted breaking changes. Instead of pulling the rug on users and break their deployments we chose to start afresh, leave the old plumber around, and allow users to gradually migrate to the new package.</p>
<h2 id="familiarity">Familiarity
</h2>
<p>If you fear that the decisions we outlined above means that you need to start from square one despite being a seasoned plumber user then fear not. plumber2 takes the soul of plumber and carries it on. Annotations are still central to how you use plumber2 and most of them work just as before. There are, however, foundational changes in store for you, so you will have to update some of your habits to suit a new (and better) world.</p>
<p>Let&rsquo;s start with the core of annotations, which is the parsing of them. In plumber this was handled by an internal parser which tried to mimic how roxygen2 parsed documentation annotation. This led to almost, but not quite, parity with roxygen2 which tripped up users. plumber2 now uses roxygen2 directly for parsing, so any convention you have become used to from writing package documentation can be transferred. Most important of these are support for multi-line annotation (hello 2025), and the conventions around the first line being the title as well as any text between that and the first tag being a long-form description.</p>
<p>Along with the move to using roxygen2 comes a well-defined way of extending the annotation API, so that other packages more easily can extend plumber2 in a way that feels native without resorting to any unpleasant hacks.</p>
<p>Enough talk. How does it look? Below you see an annotation for a <code>GET</code> endpoint written for plumber2:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* Get a weather forecast</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* This endpoint will provide the client with a forecast for a </span></span>
<span><span class='c'>#* specific city. You can modify the length of the forecast </span></span>
<span><span class='c'>#* through the query parameters</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @get /forecast/&lt;city&gt;</span></span>
<span><span class='c'>#*</span></span>
<span><span class='c'>#*.@param city:string The city to query</span></span>
<span><span class='c'>#* @query length:integer|1, 10|(7) How long a forecast</span></span>
<span><span class='c'>#*</span></span>
<span><span class='c'>#* @response 200:[&#123;day:date, temp:number, rain:boolean&#125;] An array </span></span>
<span><span class='c'>#* of objects, each corresponding to a day</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @serializer json</span></span>
<span><span class='c'>#* @serializer yaml</span></span>
<span><span class='c'>#* </span></span>
<span><span class='kr'>function</span><span class='o'>(</span><span class='nv'>city</span>, <span class='nv'>query</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='nf'>get_forecast</span><span class='o'>(</span><span class='nv'>city</span>, <span class='nv'>query</span><span class='o'>$</span><span class='nv'>length</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span></span></code></pre>
</div>
<p>If you are a plumber user I hope you can agree that it doesn&rsquo;t look totally foreign. Sure, there are new things in there but all of them in the same spirit as what you know. We have already mentioned the support for multi-lines and how the first lines until the first tag are parsed. The first line with a tag, <code>@get</code> is also straight out of the plumber book and so is the following <code>@param</code> line.</p>
<p>The <code>@query</code> line is new however (in multiple ways). In plumber2 function input can come from 3 places: path parameters (<code>city</code> in the above), the query string (<code>length</code> in the above), or the request body (not shown). This was true in plumber as well, but in plumber2 we have made the distinction explicit. Instead of using <code>@param</code> to document them all we now have dedicated tags for input coming from query and body (<code>@query</code> and <code>@body</code> respectively). This is also reflected in the function signature where only path parameters are provided directly as arguments. Query input is provided through the <code>query</code> argument and body input is provided through the <code>body</code> argument. This means that there is no longer the potential for masking out arguments if e.g. the query string contained an element with the same name as a path parameter. It also means that we can avoid parsing both the query string and the request body if it is not used by the handler function.</p>
<p>The <code>@query</code> line holds more surprises in the form of richer type annotation. Numbers and integers can now be bounded (here, between 1 and 10), and all input can be provided with a default value (here, 7). plumber2 does automatic input checking and type conversion based on this, and will return early with an error if an input is of the wrong format. There are also even more types to use. Enums, Dates, Datetimes, Bytes, and Patterns are all now possible, along with rich descriptions of objects (as can be seen in the <code>@response</code> line). All of these will be casted to the correct R type and can potentially be provided as arrays by enclosing them in <code>[...]</code>.</p>
<p>The <code>@response</code> tag is also available in plumber, but in plumber2 you can now annotate the return type as well, using the same type syntax as used for input. Doing so will allow the people using your API to now what kind of return value to expect which is critical to writing robust interfaces.</p>
<p>The last two annotations are both new an old. plumber had a <code>@serializer</code> tag as well, where you could specify a named serializer. This would then be used to convert the return value of your function into a string or binary representation to send back to the client. In plumber2 this is still the case, but what is new is the possibility of specifying multiples. In fact, the default is to use a collection of common serialization formats and let the client chose which they prefer through the <code>Content-Type</code> header (a process known as content negotiation). While not shown here, the same is true for the <code>@parser</code> tag which allows you to specify how the request body should be parsed into an R object. If content negotiation fails (on either side) plumber2 will send a structured error response to the client so they can see what goes wrong.</p>
<p>There are of course more to it (we will touch on a range of new tags below), but the above will probably cover 80% of use cases and hopefully that all feels very familiar.</p>
<h3 id="programmatic-interface">Programmatic interface
</h3>
<p>While plumber2 promotes the use of annotations as a way to describe your webserver logic, it also provides a programmatic API that gives you all of the same abilities. In plumber, this api was prefixed with <code>pr_</code> which was a an acronym for &ldquo;Plumber Route&rdquo;. In plumber2, both to avoid namespace collision and because not all functions are concerned with creating routes, we use the <code>api_</code> prefix. Many functions in plumber have a counterpart in plumber2 but there has been made no attempt to ensure compatibility between arguments etc. Thus, if you have used the programmatic interface in plumber you may experience a bit more friction in moving over to plumber2. Consult the <a href="https://plumber2.posit.co/reference/index.html" target="_blank" rel="noopener">extensive documentation</a>
 if you are in that boat.</p>
<h2 id="new-features">New features
</h2>
<p>The rewrite has allowed us to add many new features which would either have been extremely cumbersome or downright unfeasible to add to plumber. While not exhaustive, the following will give you a taste of what is now possible</p>
<h3 id="multi-file-apis">Multi-file APIs
</h3>
<p>With plumber2 you are no longer limited to a single file for describing your api. Multiple files can be passed into the constructor (<a href="https://plumber2.posit.co/reference/api.html" target="_blank" rel="noopener"><code>api()</code></a>
) and by default they will each constitute a single route. This implies the plumber2 has support for multiple routes which will be tried in turn, which again implies full middleware support. This extended power replaces the filters and <code>@preempt</code> in plumber.</p>
<p>The properties of each plumber2 file can be modified with a few specific annotations that must (if present) appear at the top of the file, e.g.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* @routeName secondary_route</span></span>
<span><span class='c'>#* @routeOrder 10</span></span>
<span><span class='c'>#* @root /sub/path</span></span>
<span><span class='kc'>NULL</span></span></code></pre>
</div>
<p>The above sets the name of the route (defaults to the file name), the position in the chain of routes (defaults to the order they are passed to the constructor), as well as provides a root which will be prepended to all endpoints defined in the file. If multiple files has the same <code>@routeName</code> they will be merged into the same route, so even if you only need a single layer of middleware, this is a great way to organize a web server implementation that has grown large.</p>
<h3 id="websocket-support">Websocket support
</h3>
<p>While plumber was born as a way to create REST apis, the web is broader than that and sometimes your web server need to use additional technologies. WebSocket is a bidirectional communication layer that is initiated by the client and, once established, allows both the server and the client to send messages back and forth at any point in time. WebSockets is the technology that powers Shiny&rsquo; reactive capabilities so it is not new in the world of R, and plumber2 gives you access to both receive and send messages at your leisure.</p>
<p>You can add a websocket listener using the <code>@message</code> tag like so:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* @message</span></span>
<span><span class='kr'>function</span><span class='o'>(</span><span class='nv'>message</span>, <span class='nv'>client_id</span>, <span class='nv'>server</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='nf'>cli</span><span class='nf'>::</span><span class='nf'><a href='https://cli.r-lib.org/reference/cli_abort.html'>cli_inform</a></span><span class='o'>(</span><span class='s'>"WS message from &#123;client_id&#125;: &#123;message&#125;"</span><span class='o'>)</span></span>
<span>  </span>
<span>  <span class='nv'>server</span><span class='o'>$</span><span class='nf'>time</span><span class='o'>(</span></span>
<span>    <span class='nv'>server</span><span class='o'>$</span><span class='nf'>send</span><span class='o'>(</span><span class='s'>"We got your message, alright!"</span>, <span class='nv'>client_id</span><span class='o'>)</span>,</span>
<span>    after <span class='o'>=</span> <span class='m'>5</span></span>
<span>  <span class='o'>)</span></span>
<span><span class='o'>&#125;</span></span></code></pre>
</div>
<p>In the above we set up (a rather nonsensical) WebSocket logic which will log any incoming messages from a client and then, after 5 seconds have passed, send back a message to the client.</p>
<h3 id="async-evaluation">Async evaluation
</h3>
<p>plumber2 expands on the asynchronous evaluation supported in plumber. Like in plumber it is still possible to return a promise from a handler (both for HTTP and websocket handlers), which will then be evaluated asynchronously and, in the case of a HTTP handler, modify the response once done, before sending it back to the client. What is new is that standard handlers can be converted to asynchronous handlers automatically. All it takes is adding the <code>@async</code> tag to the annotation, and plumber2 takes care of the rest. The functionality is build upon <a href="https://mirai.r-lib.org" target="_blank" rel="noopener">mirai</a>
, which is a modern framework for async evaluation with very little overhead. However, it is extendible so if a new better framework comes along it is easy to add, either directly in plumber2 or in an extension package.</p>
<p>To convert the forecast endpoint from our first example into an asynchronous one we just have to add a single line:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* Get a weather forecast</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* This endpoint will provide the client with a forecast for a </span></span>
<span><span class='c'>#* specific city. You can modify the length of the forecast </span></span>
<span><span class='c'>#* through the query parameters</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @get /forecast/&lt;city&gt;</span></span>
<span><span class='c'>#*</span></span>
<span><span class='c'>#*.@param city:string The city to query</span></span>
<span><span class='c'>#* @query length:integer|1, 10|(7) How long a forecast</span></span>
<span><span class='c'>#*</span></span>
<span><span class='c'>#* @response 200:[&#123;day:date, temp:number, rain:boolean&#125;] An array </span></span>
<span><span class='c'>#* of objects, each corresponding to a day</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @serializer json</span></span>
<span><span class='c'>#* @serializer yaml</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @async</span></span>
<span><span class='kr'>function</span><span class='o'>(</span><span class='nv'>city</span>, <span class='nv'>query</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='nf'>get_forecast</span><span class='o'>(</span><span class='nv'>city</span>, <span class='nv'>query</span><span class='o'>$</span><span class='nv'>length</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span></span></code></pre>
</div>
<p>Since async evaluation is happening in a different process they do not have access to the server object, nor the request and response object. All they can do is return a value which will be set to the response body. If you need to work with either of these objects you can chain a function call to the async one which will execute in the main process once the async expression has returned. You can do this by adding a <code>@then</code> block directly after the async one, e.g.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* ...</span></span>
<span><span class='c'>#* of objects, each corresponding to a day</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @serializer json</span></span>
<span><span class='c'>#* @serializer yaml</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @async</span></span>
<span><span class='kr'>function</span><span class='o'>(</span><span class='nv'>city</span>, <span class='nv'>query</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='nf'>get_forecast</span><span class='o'>(</span><span class='nv'>city</span>, <span class='nv'>query</span><span class='o'>$</span><span class='nv'>length</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span></span>
<span><span class='c'>#* @then</span></span>
<span><span class='kr'>function</span><span class='o'>(</span><span class='nv'>result</span>, <span class='nv'>response</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='nv'>response</span><span class='o'>$</span><span class='nv'>body</span> <span class='o'>&lt;-</span> <span class='nv'>result</span></span>
<span>  <span class='nv'>response</span><span class='o'>$</span><span class='nf'>set_header</span><span class='o'>(</span><span class='s'>"cache-control"</span>, <span class='s'>"max-age=86400"</span><span class='o'>)</span></span>
<span>  <span class='nv'>Next</span></span>
<span><span class='o'>&#125;</span></span></code></pre>
</div>
<p>In the above we set the body to the result of the prior async expression (this would have happened automatically), then we add an additional header to the response which would have been otherwise impossible in the async expression and lastly we return the <code>Next</code> sentinel which signal that the request can move on to the next route/middleware.</p>
<h3 id="redirection-and-forwarding">Redirection and forwarding
</h3>
<p>Over the lifetime of a webserver you may end up cleaning up functionality or moving things around. If some functionality ends up at a different path your API will contain dead links unless you do something about it. plumber2 makes it easy to redirect requests to a new location so that users of the API can gracefully migrate to the new location without disruption.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* @redirect !get /old/data/* /new/data/*</span></span>
<span><span class='c'>#* @redirect any /unstable/endpoint /stable/endpoint</span></span>
<span><span class='kc'>NULL</span></span></code></pre>
</div>
<p>The above adds two different redirects. One is a permanent redirect (denoted by the <code>!</code> in front of the method). It redirects all <code>GET</code> requests from <code>/old/data/*</code> to <code>/new/data/*</code> be returning a <code>308</code> response directing the client to try the new location. The other is a temporary redirect which instead returns a <code>307</code> response.</p>
<p>You can use wildcards (as shown above) and path parameters in the redirection paths as long as they match between the old and new path (the new path can drop path parameters from old, but can&rsquo;t make up new ones).</p>
<p>Redirection goes through the client. The server responds with a <code>307</code>/<code>308</code> response that include the new location of the resource and it is up to the client to follow that to the final destination.</p>
<p>There is another kind of redirection, one that is invisible to the client, where the server forwards the request to another service and returns the response to the client once it receives it. This is called a reverse proxy. A reverse proxy can either forward a request to another service running locally, or to a service running on a separate server. Reverse proxying is implemented with the <code>@forward</code> tag:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* @forward /proxy http://127.0.0.1:56789</span></span>
<span><span class='c'>#* @except /local</span></span>
<span><span class='kc'>NULL</span></span></code></pre>
</div>
<p>The above sets up a reverse proxy that forwards requests made to <code>/proxy</code> to the service running locally on <code>http://127.0.0.1:56789</code>. It uses the <code>@except</code> tag to preclude requests to <code>/proxy/local/*</code> from being forwarded.</p>
<h3 id="shiny-support">Shiny support
</h3>
<p>Build on top of the reverse proxy capabilities is support for launching and serving one or more shiny applications. The shiny applications are launched in another process and HTTP and WebSocket communication is forwarded to it. Once the plumber2 server stops the shiny applications are stopped as well. Launching a shiny application is very straightforward:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* @shiny /my_app/</span></span>
<span><span class='nf'>shiny</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/pkg/shiny/man/shinyApp.html'>shinyAppDir</a></span><span class='o'>(</span><span class='s'>"./shiny"</span><span class='o'>)</span></span></code></pre>
</div>
<p>You use the <code>@shiny</code> tag and provide the path from where you want to serve the shiny app, then, below the annotation where the handler would normally be, you provide a shiny app object.</p>
<p>You can also use the <code>@except</code> tag here meaning that it is e.g. possible to serve a shiny app from the root, but e.g. let requests to <code>/api/*</code> fall through and be handled by plumber2:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* @shiny /</span></span>
<span><span class='c'>#* @except /api</span></span>
<span><span class='nf'>shiny</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/pkg/shiny/man/shinyApp.html'>shinyAppDir</a></span><span class='o'>(</span><span class='s'>"./frontend"</span><span class='o'>)</span></span>
<span></span>
<span><span class='c'>#* @get /api</span></span>
<span><span class='c'>#* ...</span></span>
<span><span class='kr'>function</span><span class='o'>(</span><span class='nv'>...</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='nv'>...</span></span>
<span><span class='o'>&#125;</span></span></code></pre>
</div>
<h3 id="serving-quarto-and-rmarkdown-documents">Serving Quarto and Rmarkdown documents
</h3>
<p>In the same vein as serving shiny applications, plumber2 also allows you to easily serve quarto or rmarkdown documents. The syntax for this follows that of the shiny functionality closely</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* @report /quarterly_report</span></span>
<span><span class='s'>"./reports/my_amazing_report.qmd"</span></span></code></pre>
</div>
<p>You provide the path to serve the report from with the <code>@report</code> tag and then points to the quarto (or rmarkdown) file below the block. If you have a parameterized report you can pass in parameters through the query string, and if your report provides multiple output formats the client can choose between them, either through content negotiation or be appending the correct file extension to the url (e.g. requesting <code>/quarterly_report.pdf</code> in the above will render a PDF version and requesting <code>/quarterly_report.html</code> will render to HTML). Reports are cached so they are only rendered when needed.</p>
<h3 id="persistent-data-storage">Persistent data storage
</h3>
<p>Keeping state between requests to a server can be done in multiple ways. Of course a REST api does away completely with state so if you follow that then it is simply not needed. But not everything is RESTful and sometimes state is required. In plumber, this could be done through an encrypted session cookie. This cookie was passed back and forth at every request ensuring that the same data was available at repeat visits. This is still possible in plumber2 through the <code>session</code> field of the request and response object. However, as noted in the plumber documentation as well, this approach comes with certain downsides. First, the need to pass the data back and forth at every request limits how much data can feasibly be stored. Second, the use of a cookie means that e.g. websocket logic will not have access to the data. Lastly, sending it back and forth is a security liability even if encrypted. If someone got hold of your encryption key they could eavesdrop on everything going on between the server and client.</p>
<p>The alternative is to keep all the data on the server, in a persistent cross-session way. To that end plumber2 provides a persistent datastore build on the <a href="https://richfitz.github.io/storr/" target="_blank" rel="noopener">storr</a>
 package. storr provides a unified frontend for a variety of different data stores, such as redis, postgresql, LMDB etc. Neither storr nor plumber2 takes care of setting up the data store so the onus for that is still on the developer. However, once setup plumber2 automatically provides a global and a client-scoped key-value store accessible for handlers.</p>
<p>The data store cannot be set up through annotations but uses the programmatic interface. However, this can be mixed in with annotations in a <code>@plumber</code> block. Below is an example of setting it up as well as using it in a handler</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* @plumber</span></span>
<span><span class='kr'>function</span><span class='o'>(</span><span class='nv'>api</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='nv'>api</span> <span class='o'>|&gt;</span> </span>
<span>    <span class='nf'>api_datastore</span><span class='o'>(</span><span class='nf'>storr</span><span class='nf'>::</span><span class='nf'><a href='https://richfitz.github.io/storr/reference/storr_environment.html'>driver_environment</a></span><span class='o'>(</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span></span>
<span></span>
<span><span class='c'>#* Example of using the datastore</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @get /hello</span></span>
<span><span class='c'>#* </span></span>
<span><span class='kr'>function</span><span class='o'>(</span><span class='nv'>datastore</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='kr'>if</span> <span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/length.html'>length</a></span><span class='o'>(</span><span class='nv'>datastore</span><span class='o'>$</span><span class='nv'>session</span><span class='o'>)</span> <span class='o'>==</span> <span class='m'>0</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>    <span class='nv'>datastore</span><span class='o'>$</span><span class='nv'>global</span><span class='o'>$</span><span class='nv'>count</span> <span class='o'>&lt;-</span> <span class='o'>(</span><span class='nv'>datastore</span><span class='o'>$</span><span class='nv'>global</span><span class='o'>$</span><span class='nv'>count</span> <span class='o'><a href='https://rdrr.io/r/base/Control.html'>%||%</a></span> <span class='m'>0</span><span class='o'>)</span> <span class='o'>+</span> <span class='m'>1</span></span>
<span>    <span class='nv'>datastore</span><span class='o'>$</span><span class='nv'>session</span><span class='o'>$</span><span class='nv'>not_first_visit</span> <span class='o'>&lt;-</span> <span class='kc'>TRUE</span></span>
<span>    <span class='nf'><a href='https://rdrr.io/r/base/paste.html'>paste0</a></span><span class='o'>(</span><span class='s'>"Welcome. You are visitor #"</span>, <span class='nv'>datastore</span><span class='o'>$</span><span class='nv'>global</span><span class='o'>$</span><span class='nv'>count</span><span class='o'>)</span></span>
<span>  <span class='o'>&#125;</span> <span class='kr'>else</span> <span class='o'>&#123;</span></span>
<span>    <span class='s'>"Welcome back"</span></span>
<span>  <span class='o'>&#125;</span></span>
<span><span class='o'>&#125;</span></span></code></pre>
</div>
<p>Above we set up a datastore based on an R environment. This is of course not a scalable solution but is easy to use for trying things out. The <code>api_datastore()</code> most importantly takes a driver argument which is a storr compliant driver, as well as a number of other configurations that all have sensible defaults. After activating the datastore a new argument will be available in the handlers (the name defaults to <code>datastore</code> but can be changed). This argument contains two elements: <code>global</code> and <code>session</code>. Both of these are list-like interfaces to the underlying datastore and allows you to read and write data, either to the global store or one scoped to the current session.</p>
<h3 id="security">Security
</h3>
<p>Whenever you opens up a server to the rest of the world, security should be a concern. This is both true for servers only used internally, but even more so for servers that communicate with the world wide web. plumber2 sets out to be a huge improvement over plumber in this regard. While no amount of tooling can substitute good understanding of the various attack vectors possible on the web, they can make it more ergonomic to have sensible security measures.</p>
<p>The key takeaway from the above is that the following functionality doesn&rsquo;t negate the need for a security professional if your organization exposes a server to the web, but they can make said security professional more happy in their day-to-day work.</p>
<h4 id="security-headers">Security headers
</h4>
<p>A plumber2 API will predominantly use the HTTP protocol to communicate with the client. Over the cause of the internets existence there have been a cat-and-mouse game going on between bad actors that wish to scam or otherwise harm users, and the people developing the internet into being a safe experience. A lot of the improvements to security have been implemented as specific HTTP headers where the server opt into certain behavior that is safer for the client. plumber2 provides an easy way to set these headers and provides good sensible defaults as well.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>pa</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://plumber2.posit.co/reference/api.html'>api</a></span><span class='o'>(</span><span class='o'>)</span> <span class='o'>|&gt;</span> </span>
<span>  <span class='nf'>api_security_headers</span><span class='o'>(</span><span class='o'>)</span></span></code></pre>
</div>
<p>There are a lot of different headers being set by the above code. Some of it is only relevant if you serve HTML, while other is relevant predominantly if you are serving other assets. Some of it may even stop your web server from working properly because it gets too restricted. The golden path to walk is as tight settings as possible without breaking the api, so always start with tight settings and then gradually relax it (or find alternative ways to implement it) until things work.</p>
<h4 id="cors">CORS
</h4>
<p>CORS (Cross Origin Resource Sharing) is a way to allow sharing of content across domains, something that is otherwise restricted for safety. If you host your API on one domain and tries to access it from another domain you need to allow CORS. In plumber2 this is fairly straightforward using the <code>@cors</code> tag:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#* Get a weather forecast</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* This endpoint will provide the client with a forecast for a </span></span>
<span><span class='c'>#* specific city. You can modify the length of the forecast </span></span>
<span><span class='c'>#* through the query parameters</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @get /forecast/&lt;city&gt;</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* @cors https://my-trusted-weather-app.com</span></span>
<span><span class='c'>#* </span></span>
<span><span class='c'>#* ...</span></span></code></pre>
</div>
<p>Continuing our weather forecast app from before, if we wish to allow a secondary app to use the api, we will need to turn on CORS and allow it for the domain (here, <code>https://my-trusted-weather-app.com</code>). This is easily done with the <code>@cors</code> tag as can be seen above.</p>
<h4 id="resource-isolation-policies">Resource Isolation Policies
</h4>
<p>RIP (Resource Isolation Policies) is another way of ensuring that resources from your server is not used by other sites. It is build upon the <code>Sec-Fetch-*</code> suite of request headers and allow you to block a request at the server level if it does not come from a trusted place or is being used for a valid reason. RIP can be configured much like CORS using the <code>@rip</code> tag.</p>
<h2 id="future">Future
</h2>
<p>So, plumber2 is here, it is great, and it contains a bunch of new stuff. What now?</p>
<p>Well, if you are already using plumber I hope you are excited. But, there is no rush. plumber will stay on CRAN in a superseded state and all your servers will continue to work. We hope you&rsquo;ll take part in kicking the tires on plumber2 however, so that it can get some milage under its belt.</p>
<p>If you haven&rsquo;t used plumber but still managed to reach this point of the blog post I think it is fair to assume that you are quite interested in creating web servers. Welcome! plumber2 will hopefully be a joyful experience for you but we can&rsquo;t wait to learn where it could be even easier to use for a newcomer.</p>
<p>If you do maintain packages that build on top of plumber, I hope you&rsquo;ll consider augmenting those to also work with plumber2. plumber2 has been build with extensibility in mind so hopefully you&rsquo;ll feel empowered to make your tools even more amazing. If you do come across things that are hard, or impossible, to extend, let us know so we may look into it.</p>
<p>Happy plumbing!</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2025/plumber2-0-1-0/thumbnail-wd.jpg" length="665686" type="image/jpeg" />
    </item>
    <item>
      <title>Build Your First LLM App with Shiny for Python or R</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/</link>
      <pubDate>Mon, 15 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/</guid>
      <dc:creator>Veerle Eeftink - Van Leemput</dc:creator><description><![CDATA[<p>You&rsquo;ve made it to third part of &ldquo;The Shiny Side of LLMs&rdquo; series, where we turn everything we&rsquo;ve learned into something real and interactive! Our weapon of choice: Shiny, of course.</p>
<p>In the first part, <a href="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/../../../blog/shiny/shiny-side-of-llms-part-1/">What LLMs Actually Do (and What They Don&rsquo;t)</a>
, we explored what LLMs actually do. We covered how they generate text, what they&rsquo;re good (and bad) at, and we covered most of the jargon that gets thrown around. Then in part two, <a href="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/../../../blog/shiny/shiny-side-of-llms-part-2/">Talking to LLMs: From Prompt to Response</a>
, we got practical. You learned how to structure prompts, send them to a model via an API using <a href="https://posit-dev.github.io/chatlas/" target="_blank" rel="noopener"><code>chatlas</code></a>
 or <a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener"><code>ellmer</code></a>
, and handle the responses in your code. Now it&rsquo;s time to wrap that logic in an interface your users can love and can actually interact with!</p>
<p>In this post, we&rsquo;ll cover how to:</p>
<ul>
<li>Structure a basic Shiny app (in both Python and R) with a user interface (UI) and reactive server logic</li>
<li>Connect user input to an LLM call and show the result in the UI</li>
<li>Set up a chat interface</li>
<li>Keep your app responsive while doing lengthy tasks or calculations (yes, a quick guide to async programming!)</li>
<li>Add UI polish like pretty looking value boxes, loading indicators, and error messages</li>
<li>Bonus: deploy your app with a single click to Posit Connect (Cloud) or shinyapps.io</li>
</ul>
<p>By the end of this part, you&rsquo;ll have your first real, working, LLM-powered app. And most importantly: the knowledge to build many more.</p>
<blockquote>
<p><strong>What are we going to do in this last part?</strong></p>
<p>This part of &ldquo;The Shiny Side of LLMs&rdquo; series will build an app called &ldquo;DeckCheck&rdquo;: a genius app that gets rid of lengthy unfocused presentations, like a perfect &ldquo;Presentation Rehearsal Buddy&rdquo;. The goal: let users upload their <a href="https://quarto.org" target="_blank" rel="noopener">Quarto</a>
 presentation and provide them with feedback on how to make it better.</p>
</blockquote>
<blockquote>
<p><strong>No time for the walkthrough?</strong></p>
<p>Want to dive straight into the full app? Head over to <a href="#the-end-result">the end result</a>
.</p>
</blockquote>
<h1 id="why-shiny">Why Shiny?
</h1>
<p>We knew pretty early on that &ldquo;DeckCheck&rdquo; was going to be an app. Why? Because an app is just easier. People can click buttons, type things, and get results without ever touching code (which is great, because &ldquo;copy-paste this into your terminal&rdquo; tends to scare off half the audience).</p>
<p>But ok, why Shiny? You&rsquo;re reading this on the Shiny blog, so yes, it seems pretty obvious we love it. Still, we have solid reasons:</p>
<ul>
<li>You can go from idea to something you can actually click in almost no time. Shiny is great for prototyping. You can quickly experiment, gather feedback, and gradually build an app out to a production-ready state.</li>
<li>Shiny is reactive by design, which is a fancy way of saying: it keeps track of what depends on what, and only updates what actually needs to change. You don&rsquo;t need to worry about the logic, which means you can spend more time building your app instead of dealing with state management.</li>
<li>This reactive engine also makes Shiny efficient: outputs only re-render when the stuff they depend on changes.</li>
<li>If your LLM workflow is in Python, there&rsquo;s Shiny for Python. If it&rsquo;s in R, there&rsquo;s Shiny for R. No language wars or conversations about &ldquo;X is better than Y&rdquo;. Just pick whatever you&rsquo;re comfortable with.</li>
<li>You can even drop in your own HTML, CSS, and JavaScript to really make it your own.</li>
</ul>
<p>Alright, enough with the sales pitch. Shiny it is!</p>
<blockquote>
<p><strong>How Shiny Assistant assists you</strong></p>
<p>Fun fact: this article contains side-by-side examples in both Python and R. To showcase how <a href="https://gallery.shinyapps.io/assistant/" target="_blank" rel="noopener">Shiny Assistant</a>
 can support you in either language, it was used to generate some of the conversions. That&rsquo;s a neat way to highlight how an LLM can help you get started with Shiny! The true &ldquo;Shiny Side of LLMs&rdquo;. Of course result were not always 100% spot on, but luckily there was still a human in the loop.</p>
</blockquote>
<h1 id="optimising-conversations-for-shiny">Optimising conversations for Shiny
</h1>
<blockquote>
<p><strong>Getting your API key</strong></p>
<p>Remember you need to <a href="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/../../../blog/shiny/shiny-side-of-llms-part-2/#what-do-you-need">grab an API key for your chosen LLM provider</a>
. You need this key to authenticate. Store this key as an environment variable. For example, to use Claude from Anthropic, <code>ANTHROPIC_API_KEY=yourkey</code> needs to be in <code>.Renviron</code> or <code>.env</code> file.</p>
</blockquote>
<p>Going from a script-like workflow to an app requires a different way of thinking. We simply have other expectations from a web app compared to just a regular Python or R script. We want things to be interactive, and ideally we want to have the result instantly. If we click on something, we expect something to happen, fast. Ever encountered a web page that stayed blank for just 5 seconds? How long did that feel? Like 10 minutes? Or didn&rsquo;t you even stick out the 5 seconds? Yes, you are impatient! You need to see something is happening, and get some visual feedback.</p>
<p>LLMs take your impatience into account, and that&rsquo;s why most models stream tokens progressively. Instead of making you wait forever for a big wall of text, they start showing you the answer bit by bit, almost like they&rsquo;re thinking out loud. This streaming feels faster, keeps you engaged, and makes the whole experience way less frustrating. <code>chatlas</code> and <code>ellmer</code> use this streaming capability too and they print the result on the console as soon as words come in. But&hellip; a Shiny app doesn&rsquo;t have a console! So what to do?</p>
<p>Where the <code>chat()</code> method does not return any results until the entire response is received and only prints the streaming results to the console, the <code>stream()</code> method can process the response as it arrives. It&rsquo;s perfect for something like a Shiny chat window. You simply replace the <code>chat()</code> method with the <code>stream()</code> method, which returns something called a &ldquo;generator&rdquo;. A generator is a function that can pause (<code>yield</code>) and resume later, remembering where it left off. That&rsquo;s handy because it lets your code:</p>
<ol>
<li>Process text as it arrives (aka, by chunk) instead of waiting for the whole thing.</li>
<li>Pause between chunks without blocking other things.</li>
<li>Keep its place so it can pick up right where it left off when the next chunk arrives.</li>
</ol>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-1" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-1-1">Python</a></li>
<li><a href="#tabset-1-2">R</a></li>
</ul>
<div id="tabset-1-1">
<p>In the <code>chatlas</code> documentation you can read more about <a href="https://posit-dev.github.io/chatlas/get-started/stream.html" target="_blank" rel="noopener">streams</a>
.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatAnthropic</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span><span class="o">=</span><span class="s2">&#34;You are a presentation coach for data scientists. You give constructive, focused, and practical feedback on titles, structure, and storytelling.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Set model parameters (optional)</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">set_model_params</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">temperature</span><span class="o">=</span><span class="mf">0.8</span><span class="p">,</span>  <span class="c1"># default is 1</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">stream</span> <span class="o">=</span> <span class="n">chat</span><span class="o">.</span><span class="n">stream</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;I&#39;m working on a presentation with the title: &#39;The Shiny Side of LLMs&#39;.
</span></span></span><span class="line"><span class="cl"><span class="s2">        Please evaluate the clarity, tone, and relevance of this title for the intended audience.
</span></span></span><span class="line"><span class="cl"><span class="s2">        For context, this is a 10-minute lightning talk at posit::conf(2025).
</span></span></span><span class="line"><span class="cl"><span class="s2">        The audience is Python and R users who are curious about AI and large language models,
</span></span></span><span class="line"><span class="cl"><span class="s2">        but not all of them have a deep technical background.
</span></span></span><span class="line"><span class="cl"><span class="s2">        
</span></span></span><span class="line"><span class="cl"><span class="s2">        The talk uses Shiny as a way to explore and demo LLMs in practice.
</span></span></span><span class="line"><span class="cl"><span class="s2">        
</span></span></span><span class="line"><span class="cl"><span class="s2">        Return your answer as a JSON array of objects, where each object has the following keys:
</span></span></span><span class="line"><span class="cl"><span class="s2">        
</span></span></span><span class="line"><span class="cl"><span class="s2">        - &#39;aspect&#39;: one of &#39;clarity&#39;, &#39;tone&#39;, or &#39;relevance&#39;
</span></span></span><span class="line"><span class="cl"><span class="s2">        - &#39;feedback&#39;: your concise assessment
</span></span></span><span class="line"><span class="cl"><span class="s2">        - &#39;suggestion&#39;: an optional improvement if applicable&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">chunk</span> <span class="ow">in</span> <span class="n">stream</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The object that gets returned by the <a href="https://posit-dev.github.io/chatlas/reference/Chat.html#stream" target="_blank" rel="noopener"><code>stream()</code></a>
 method is a <a href="https://wiki.python.org/moin/Generators" target="_blank" rel="noopener">generator</a>
.</p>
</div>
<div id="tabset-1-2">
<p>In the <code>ellmer</code> documentation you can read more about <a href="https://ellmer.tidyverse.org/articles/streaming-async.html" target="_blank" rel="noopener">streaming</a>
.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">system_prompt</span> <span class="o">=</span> <span class="s">&#34;You are a presentation coach for data scientists.
</span></span></span><span class="line"><span class="cl"><span class="s">  You give constructive, focused, and practical feedback on titles, structure, and storytelling.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">params</span> <span class="o">=</span> <span class="nf">params</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">temperature</span> <span class="o">=</span> <span class="m">0.8</span> <span class="c1"># default is 1</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">stream</span> <span class="o">&lt;-</span> <span class="n">chat</span><span class="o">$</span><span class="nf">stream</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;I&#39;m working on a presentation with the title: &#39;The Shiny Side of LLMs&#39;.
</span></span></span><span class="line"><span class="cl"><span class="s">Please evaluate the clarity, tone, and relevance of this title for the intended audience.
</span></span></span><span class="line"><span class="cl"><span class="s">For context, this is a 10-minute lightning talk at posit::conf(2025).
</span></span></span><span class="line"><span class="cl"><span class="s">The audience is Python and R users who are curious about AI and large language models,
</span></span></span><span class="line"><span class="cl"><span class="s">but not all of them have a deep technical background.
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">The talk uses Shiny as a way to explore and demo LLMs in practice.
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">Return your answer as a JSON array of objects, where each object has the following keys:
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">- &#39;aspect&#39;: one of &#39;clarity&#39;, &#39;tone&#39;, or &#39;relevance&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">- &#39;feedback&#39;: your concise assessment
</span></span></span><span class="line"><span class="cl"><span class="s">- &#39;suggestion&#39;: an optional improvement if applicable&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">coro</span><span class="o">::</span><span class="nf">loop</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="kr">for</span> <span class="p">(</span><span class="n">chunk</span> <span class="kr">in</span> <span class="n">stream</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">cat</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The object that gets returned by the <a href="https://ellmer.tidyverse.org/reference/Chat.html#method-stream-" target="_blank" rel="noopener"><code>stream()</code></a>
 method is a <a href="https://coro.r-lib.org/articles/generator.html#generators" target="_blank" rel="noopener">coro generator</a>
.</p>
</div>
</div>
<p>Streaming is great for things like chatbots, live transcription, or anything where seeing text appear in real time feels natural. It&rsquo;s perfect for conversations, or when the answer is long and you don&rsquo;t want to keep users staring at a blank screen.</p>
<p>In apps like &ldquo;DeckCheck&rdquo;, where we&rsquo;re analysing a Quarto presentation behind the scenes and then showing a finished result, streaming doesn&rsquo;t really add much. Users expect a clear, polished answer all at once. Not a JSON, but ready-to-go value boxes, graphs and tables. So in this case, a smooth loading indicator or progress bar will probably satisfy impatient users and we can stick to our <code>chat()</code> method. Don&rsquo;t forget the <code>stream()</code> method though: we&rsquo;ll use that a little bit later in a small demo chatbot.</p>
<p>Whether we&rsquo;re streaming or not, users simply have to wait for an LLM response and you can keep them entertained while they&rsquo;re doing that. But another important thing related to a long waiting time is when your app is used by multiple users at the same time. They might ask things from the model at the same time too, meaning that you&rsquo;re dealing with concurrent chat sessions. If you would just use <code>chat()</code> or <code>stream()</code>, the Shiny app (or technically speaking: the Python or R session running the Shiny app) will be blocked for other users for the duration of each response. And that&rsquo;s not cool. The more users are concurrently using your app, the longer the queue gets, and the longer users have to wait. This is <strong>synchronous</strong> behaviour. We rather deal <strong>asynchronously</strong> (aka <strong>async</strong>) with a model&rsquo;s responses. It means that we can receive responses at the same time, in parallel. To use async chat, we need to call <code>chat_async()</code> / <code>stream_async()</code> instead of <code>chat()</code> / <code>stream()</code>. The <code>_async</code> variants take the same arguments but returns a <strong>coroutine</strong> object, (aka a placeholder for something that will come) instead of the actual response.</p>
<p>So ideally, asking a question to an LLM would look something like this in our Shiny application:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-2" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-2-1">Python</a></li>
<li><a href="#tabset-2-2">R</a></li>
</ul>
<div id="tabset-2-1">
<blockquote>
<p><strong>Note for Positron/Jupyter users</strong></p>
<p>Positron and Jupyter already run their own event loop, so <code>asyncio.run(main())</code> will fail with a runtime error. Instead of wrapping in <code>asyncio.run</code>, you can just do: <code>await main()</code> at the top level.</p>
</blockquote>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatAnthropic</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span><span class="o">=</span><span class="s2">&#34;You are a presentation coach for data scientists. You give constructive, focused, and practical feedback on titles, structure, and storytelling.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="n">chat</span><span class="o">.</span><span class="n">chat_async</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;I&#39;m working on a presentation with the title: &#39;The Shiny Side of LLMs&#39;.
</span></span></span><span class="line"><span class="cl"><span class="s2">        Please evaluate the clarity, tone, and relevance of this title for the intended audience.
</span></span></span><span class="line"><span class="cl"><span class="s2">        For context, this is a 10-minute lightning talk at posit::conf(2025).
</span></span></span><span class="line"><span class="cl"><span class="s2">        The audience is Python and R users who are curious about AI and large language models,
</span></span></span><span class="line"><span class="cl"><span class="s2">        but not all of them have a deep technical background.
</span></span></span><span class="line"><span class="cl"><span class="s2">        
</span></span></span><span class="line"><span class="cl"><span class="s2">        The talk uses Shiny as a way to explore and demo LLMs in practice.
</span></span></span><span class="line"><span class="cl"><span class="s2">        
</span></span></span><span class="line"><span class="cl"><span class="s2">        Return your answer as a JSON array of objects, where each object has the following keys:
</span></span></span><span class="line"><span class="cl"><span class="s2">        
</span></span></span><span class="line"><span class="cl"><span class="s2">        - &#39;aspect&#39;: one of &#39;clarity&#39;, &#39;tone&#39;, or &#39;relevance&#39;
</span></span></span><span class="line"><span class="cl"><span class="s2">        - &#39;feedback&#39;: your concise assessment
</span></span></span><span class="line"><span class="cl"><span class="s2">        - &#39;suggestion&#39;: an optional improvement if applicable&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2"> [                                                                                                          
</span></span></span><span class="line"><span class="cl"><span class="s2">   {                                                                                                        
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;aspect&#34;: &#34;clarity&#34;,                                                                                   
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;feedback&#34;: &#34;The title is somewhat ambiguous - &#39;shiny side&#39; could refer to positive aspects of LLMs or 
</span></span></span><span class="line"><span class="cl"><span class="s2"> the Shiny framework itself. For an audience not deeply familiar with both topics, this creates confusion   
</span></span></span><span class="line"><span class="cl"><span class="s2"> about the main focus.&#34;,                                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;suggestion&#34;: &#34;Consider titles like &#39;Exploring LLMs with Shiny&#39; or &#39;Building LLM Demos in Shiny&#39; to    
</span></span></span><span class="line"><span class="cl"><span class="s2"> clearly establish that Shiny is your vehicle for demonstrating LLMs.&#34;                                      
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                                                       
</span></span></span><span class="line"><span class="cl"><span class="s2">   {                                                                                                        
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;aspect&#34;: &#34;tone&#34;,                                                                                      
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;feedback&#34;: &#34;The tone is appropriately casual and engaging for a lightning talk, with a clever play on 
</span></span></span><span class="line"><span class="cl"><span class="s2"> words that fits the conference atmosphere. However, it may be too playful for conveying the practical valu 
</span></span></span><span class="line"><span class="cl"><span class="s2"> of the content.&#34;,                                                                                          
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;suggestion&#34;: &#34;Balance the wordplay with clearer value proposition, such as &#39;The Shiny Side of LLMs:   
</span></span></span><span class="line"><span class="cl"><span class="s2"> Interactive AI Exploration&#39; to maintain engagement while signaling practical benefits.&#34;                    
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                                                       
</span></span></span><span class="line"><span class="cl"><span class="s2">   {                                                                                                        
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;aspect&#34;: &#34;relevance&#34;,                                                                                 
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;feedback&#34;: &#34;Highly relevant to the posit::conf audience who knows Shiny well, but the connection      
</span></span></span><span class="line"><span class="cl"><span class="s2"> between Shiny and LLMs isn&#39;t immediately clear from the title alone. Given the 10-minute format, you need  
</span></span></span><span class="line"><span class="cl"><span class="s2"> attendees to quickly understand the value.&#34;,                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;suggestion&#34;: &#34;Make the connection more explicit with alternatives like &#39;Hands-On LLM Exploration with 
</span></span></span><span class="line"><span class="cl"><span class="s2"> Shiny Apps&#39; or &#39;From Prompts to Production: LLMs in Shiny&#39; to immediately communicate the practical        
</span></span></span><span class="line"><span class="cl"><span class="s2"> application.&#34;                                                                                              
</span></span></span><span class="line"><span class="cl"><span class="s2">   }                                                                                                        
</span></span></span><span class="line"><span class="cl"><span class="s2"> ]                                                                                                          
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;chatlas._chat.ChatResponseAsync object at 0x111a5c560&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<p><a href="https://posit-dev.github.io/chatlas/reference/Chat.html#chatlas.Chat.chat_async" target="_blank" rel="noopener"><code>chat_async()</code></a>
 returns a coroutine object, which is basically a special kind of function that runs asynchronously. It doesn&rsquo;t do the work right away when you call it, but it gives you this object that you can later &ldquo;await&rdquo; to actually get the result. If you&rsquo;re running regular (non-asynchronous) code, you use <code>asyncio.run()</code> to start and wait for the task to finish.</p>
</div>
<div id="tabset-2-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">promises</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">system_prompt</span> <span class="o">=</span> <span class="s">&#34;You are a presentation coach for data scientists.
</span></span></span><span class="line"><span class="cl"><span class="s">  You give constructive, focused, and practical feedback on titles, structure, and storytelling.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat_async</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;I&#39;m working on a presentation with the title: &#39;The Shiny Side of LLMs&#39;.
</span></span></span><span class="line"><span class="cl"><span class="s">Please evaluate the clarity, tone, and relevance of this title for the intended audience.
</span></span></span><span class="line"><span class="cl"><span class="s">For context, this is a 10-minute lightning talk at posit::conf(2025).
</span></span></span><span class="line"><span class="cl"><span class="s">The audience is Python and R users who are curious about AI and large language models,
</span></span></span><span class="line"><span class="cl"><span class="s">but not all of them have a deep technical background.
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">The talk uses Shiny as a way to explore and demo LLMs in practice.
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">Return your answer as a JSON array of objects, where each object has the following keys:
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">- &#39;aspect&#39;: one of &#39;clarity&#39;, &#39;tone&#39;, or &#39;relevance&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">- &#39;feedback&#39;: your concise assessment
</span></span></span><span class="line"><span class="cl"><span class="s">- &#39;suggestion&#39;: an optional improvement if applicable&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">%...&gt;%</span> <span class="nf">print</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;```json\n[\n  {\n    \&#34;aspect\&#34;: \&#34;clarity\&#34;,\n    \&#34;feedback\&#34;: \&#34;The title is somewhat ambiguous - &#39;shiny side&#39; could mean positive aspects of LLMs or reference the Shiny framework. The wordplay may confuse rather than clarify the content focus.\&#34;,\n    \&#34;suggestion\&#34;: \&#34;Consider &#39;Building LLM Demos with Shiny&#39; or &#39;Interactive LLM Exploration Using Shiny&#39; to clearly communicate both the tool and topic.\&#34;\n  },\n  {\n    \&#34;aspect\&#34;: \&#34;tone\&#34;,\n    \&#34;feedback\&#34;: \&#34;The playful wordplay fits well with the lightning talk format and conference atmosphere. It&#39;s approachable and not intimidating for audiences with varying technical backgrounds.\&#34;,\n    \&#34;suggestion\&#34;: null\n  },\n  {\n    \&#34;aspect\&#34;: \&#34;relevance\&#34;,\n    \&#34;feedback\&#34;: \&#34;Highly relevant for posit::conf audience who are familiar with Shiny. The title connects a trending AI topic with a beloved R/Python tool, making LLMs accessible to the community.\&#34;,\n    \&#34;suggestion\&#34;: \&#34;Consider adding a subtitle for context: &#39;The Shiny Side of LLMs: Interactive Demos for AI Exploration&#39; to enhance relevance while keeping the clever wordplay.\&#34;\n  }\n]\n```&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<p><a href="https://ellmer.tidyverse.org/reference/Chat.html#method-chat-async-" target="_blank" rel="noopener"><code>chat_async()</code></a>
 starts the work and returns a promise, this special kind of placeholder. Then <a href="https://rstudio.github.io/promises/reference/pipes.html" target="_blank" rel="noopener"><code>%...&gt;%</code></a>
 attaches the next step, like printing the result, once it&rsquo;s ready. This keeps your R session running without waiting or freezing. Note that it resolves to a string (probably Markdown), which is slightly different than just using the <code>chat()</code> method. This is also why the output looks a little bit different compared to <a href="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/../../../blog/shiny/shiny-side-of-llms-part-2/">part two of this series</a>
.</p>
</div>
</div>
<p>And before you&rsquo;re thinking: &ldquo;hey, we were using structured output <a href="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/../../../blog/shiny/shiny-side-of-llms-part-2/">in the last part</a>
, right?&rdquo; Yes! Luckily there&rsquo;s also a method called <code>chat_structured_async()</code> (see docs for <a href="https://posit-dev.github.io/chatlas/reference/Chat.html#chatlas.Chat.chat_structured_async" target="_blank" rel="noopener">Python</a>
 and <a href="https://ellmer.tidyverse.org/reference/Chat.html#method-extract-data-async-" target="_blank" rel="noopener">R</a>
). How convenient! We&rsquo;ll use that a little bit later.</p>
<h1 id="chatting-with-an-llm-via-shiny">Chatting with an LLM via Shiny
</h1>
<p>Never developed a Shiny app before? That&rsquo;s ok! Before building our DeckCheck app, we&rsquo;ll start with a very basic example that allows you to chat with any LLM, just like you type in your question at ChatGPT. And while this series isn&rsquo;t about building &ldquo;just a chatbot&rdquo;, you can perfectly do so with Shiny. Minimal code required to get started. If you have experience with Shiny this code won&rsquo;t have much surprises, but if you&rsquo;re new to Shiny there&rsquo;s a mini crash-course-like explanation below the code.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-3" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-3-1">Python</a></li>
<li><a href="#tabset-3-2">R</a></li>
</ul>
<div id="tabset-3-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">App</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatAnthropic</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define UI</span>
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">h1</span><span class="p">(</span><span class="s2">&#34;DeckCheck&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Card with chat component</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="s2">&#34;Get started&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Ask me anything about your presentation 💡&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Chat component</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">chat_ui</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="s2">&#34;my_chat&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define server</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">Chat</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="s2">&#34;my_chat&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">chat_client</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">system_prompt</span><span class="o">=</span><span class="s2">&#34;You are a presentation coach for data scientists. You give constructive, focused, and practical feedback on titles, structure, and storytelling.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@chat.on_user_submit</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">handle_user_input</span><span class="p">(</span><span class="n">user_input</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="n">chat_client</span><span class="o">.</span><span class="n">stream_async</span><span class="p">(</span><span class="n">user_input</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="n">chat</span><span class="o">.</span><span class="n">append_message_stream</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create app</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In the above code <code>app_ui</code> defines the front end of the app. It&rsquo;s everything the user sees and interacts with. Here, it&rsquo;s created with <code>ui.page_fluid()</code> and contains a heading with our app title, a <code>ui.card()</code> with a header (<code>ui.card_header()</code>) and short paragraph (<code>ui.p()</code>). Below, there&rsquo;s a <code>ui.chat_ui()</code> component that serves as the chat interface. The <code>server</code> function controls the app&rsquo;s back end and this part is responsible for how it responds to user actions. Inside this server, we first create a <code>chat</code> object that connects to the chat UI via its ID (the o-so original <code>&quot;my_chat&quot;</code>). Then, we create a <code>chat_client</code> object via <code>chatlas</code>.</p>
<p>The key part is the <code>@chat.on_user_submit</code> decorator. <code>.on_user_submit</code> lets you define a function that runs when the user hits &ldquo;send&rdquo; in the chat UI. This function, in our case called <code>handle_user_input</code>, sends the message asynchronously to the model via <code>chat_client.stream_async()</code> and streams the reply back into the chat UI with <code>append_message_stream()</code>. Under the hood, Shiny makes use of the <a href="https://github.com/posit-dev/shinychat" target="_blank" rel="noopener"><code>shinychat</code></a>
 package.</p>
<p>When we asks a question (e.g. our simple &ldquo;I&rsquo;m working on a presentation with the title: &lsquo;The Shiny Side of LLMs&rsquo;. What&rsquo;s your feedback just based on that title?&rdquo;) we get a nicely formatted response back:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-chat-py.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
</div>
<div id="tabset-3-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shinychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">theme</span> <span class="o">=</span> <span class="nf">bs_theme</span><span class="p">(</span><span class="n">bootswatch</span> <span class="o">=</span> <span class="s">&#34;flatly&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># App title</span>
</span></span><span class="line"><span class="cl">  <span class="nf">h1</span><span class="p">(</span><span class="s">&#34;DeckCheck&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Create a card</span>
</span></span><span class="line"><span class="cl">  <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_header</span><span class="p">(</span><span class="s">&#34;Get started&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Ask me anything about your presentation 💡&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Chat component</span>
</span></span><span class="line"><span class="cl">    <span class="nf">chat_mod_ui</span><span class="p">(</span><span class="s">&#34;my_chat&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">chat_client</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span> <span class="o">=</span> <span class="s">&#34;You are a presentation coach for data scientists. 
</span></span></span><span class="line"><span class="cl"><span class="s">  You give constructive, focused, and practical feedback on titles, structure, and storytelling.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">chat_mod_server</span><span class="p">(</span><span class="s">&#34;my_chat&#34;</span><span class="p">,</span> <span class="n">chat_client</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><blockquote>
<p><strong>Custom streaming</strong></p>
<p>If you ever wanted to build something more custom, <code>shinychat::markdown_stream()</code> would let you stream model output into any Shiny interface, chat or not.</p>
</blockquote>
<p>In the above code, <code>ui</code> defines the front end of the app. It&rsquo;s everything the user sees and interacts with. Here, it&rsquo;s created with <code>bslib</code>&rsquo;s <code>page_fluid()</code> with a &ldquo;Flatly&rdquo; Bootstrap theme for styling. Don&rsquo;t like Flatly? There are many <a href="https://rstudio.github.io/bslib/articles/theming/index.html" target="_blank" rel="noopener">themes</a>
 to choose from! And if you don&rsquo;t like any, you can just skip the <code>theme</code> argument or get started with <a href="https://shiny.posit.co/r/articles/build/css/" target="_blank" rel="noopener">custom CSS</a>
.</p>
<p>After specifying the theme, there&rsquo;s a heading with our app title, followed by a <code>card()</code> containing a header, a short description, and most importantly, the chat interface provided by <code>chat_mod_ui(&quot;my_chat&quot;)</code> from the <a href="https://github.com/posit-dev/shinychat" target="_blank" rel="noopener"><code>shinychat</code></a>
 package.</p>
<p>The <code>server</code> function controls the app&rsquo;s back end and this part is responsible for how it responds to user actions. Inside this server, we first create a <code>chat_client</code> object using <code>chat_anthropic()</code>. Then we connect this chat object to the UI with <code>chat_mod_server(&quot;my_chat&quot;, chat_client)</code> via its ID (<code>&quot;my_chat&quot;</code>).</p>
<p><code>chat_mod_ui()</code> and <code>chat_mod_server()</code> together form a Shiny module. This module handles sending user messages to the model, receiving responses, and streaming those responses back into the UI, all asynchronously. The async streaming happens &ldquo;under the hood,&rdquo; so you don&rsquo;t have to manually manage partial chunks of text or async calls yourself.</p>
<p>When we asks a question (e.g. our simple &ldquo;I&rsquo;m working on a presentation with the title: &lsquo;The Shiny Side of LLMs&rsquo;. What&rsquo;s your feedback just based on that title?&rdquo;) we get a nicely formatted response back:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-chat-r.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
</div>
</div>
<h1 id="building-deckcheck">Building DeckCheck
</h1>
<p>All the ingredients are there now: we know how to programmatically talk to an LLM, we can make an informed choice when it comes to streaming (or not streaming) and async usage, and we&rsquo;ve seen how to combine it in a simple chat interface (with a bit of help from <code>shinychat</code>). Time to apply that knowledge to our DeckCheck app.</p>
<p>But, first things first&hellip; A design. Nothing too fancy, just a quick conceptual drawing of what our app will look like. No matter what kind of app you&rsquo;re developing, this is always the (and honestly, often overlooked) first step. If you know what you&rsquo;re building towards, it&rsquo;s way easier to start coding. Here&rsquo;s our design:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-design-1.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-design-2.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
<p>We have a section where users can upload information (most importantly, the Quarto presentation itself, but also the required information like audience), there are some value boxes, there&rsquo;s a graph, and a table. Elements you will frequently encounter when developing data-savvy apps. For a reason, of course, as we want to provide you with all the necessary building blocks.</p>
<p>To bring this design to life, here&rsquo;s what we&rsquo;re working towards:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-deckcheck-full.gif"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
<p>But first things first: let&rsquo;s start with building the basic UI before we connect the server part to it.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-4" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-4-1">Python</a></li>
<li><a href="#tabset-4-2">R</a></li>
</ul>
<div id="tabset-4-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">App</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">shinyswatch</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Icons</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># SVG icon copied from https://icons.getbootstrap.com/icons/file-slides-fill/</span>
</span></span><span class="line"><span class="cl"><span class="n">file_slides</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; fill=&#34;currentColor&#34; class=&#34;bi bi-file-slides-fill&#34; viewBox=&#34;0 0 16 16&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M7 7.78V5.22c0-.096.106-.156.19-.106l2.13 1.279a.125.125 0 0 1 0 .214l-2.13 1.28A.125.125 0 0 1 7 7.778z&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M12 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2M5 4h6a.5.5 0 0 1 .496.438l.5 4A.5.5 0 0 1 11.5 9h-3v2.016c.863.055 1.5.251 1.5.484 0 .276-.895.5-2 .5s-2-.224-2-.5c0-.233.637-.429 1.5-.484V9h-3a.5.5 0 0 1-.496-.562l.5-4A.5.5 0 0 1 5 4&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/svg&gt;&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">file_code</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; fill=&#34;currentColor&#34; class=&#34;bi bi-file-code-fill&#34; viewBox=&#34;0 0 16 16&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M12 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2M6.646 5.646a.5.5 0 1 1 .708.708L5.707 8l1.647 1.646a.5.5 0 0 1-.708.708l-2-2a.5.5 0 0 1 0-.708zm2.708 0 2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L10.293 8 8.646 6.354a.5.5 0 1 1 .708-.708&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/svg&gt;&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">file_image</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; fill=&#34;currentColor&#34; class=&#34;bi bi-file-image-fill&#34; viewBox=&#34;0 0 16 16&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M4 0h8a2 2 0 0 1 2 2v8.293l-2.73-2.73a1 1 0 0 0-1.52.127l-1.889 2.644-1.769-1.062a1 1 0 0 0-1.222.15L2 12.292V2a2 2 0 0 1 2-2m4.002 5.5a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M10.564 8.27 14 11.708V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-.293l3.578-3.577 2.56 1.536 2.426-3.395z&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/svg&gt;&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">robot</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;16&#34; height=&#34;16&#34; fill=&#34;currentColor&#34; class=&#34;bi bi-robot&#34; viewBox=&#34;0 0 16 16&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M6 12.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M3 8.062C3 6.76 4.235 5.765 5.53 5.886a26.6 26.6 0 0 0 4.94 0C11.765 5.765 13 6.76 13 8.062v1.157a.93.93 0 0 1-.765.935c-.845.147-2.34.346-4.235.346s-3.39-.2-4.235-.346A.93.93 0 0 1 3 9.219zm4.542-.827a.25.25 0 0 0-.217.068l-.92.9a25 25 0 0 1-1.871-.183.25.25 0 0 0-.068.495c.55.076 1.232.149 2.02.193a.25.25 0 0 0 .189-.071l.754-.736.847 1.71a.25.25 0 0 0 .404.062l.932-.97a25 25 0 0 0 1.922-.188.25.25 0 0 0-.068-.495c-.538.074-1.207.145-1.98.189a.25.25 0 0 0-.166.076l-.754.785-.842-1.7a.25.25 0 0 0-.182-.135&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M8.5 1.866a1 1 0 1 0-1 0V3h-2A4.5 4.5 0 0 0 1 7.5V8a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1v1a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v-.5A4.5 4.5 0 0 0 10.5 3h-2zM14 7.5V13a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7.5A3.5 3.5 0 0 1 5.5 4h5A3.5 3.5 0 0 1 14 7.5&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/svg&gt;&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Shiny App</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fillable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">layout_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">strong</span><span class="p">(</span><span class="s2">&#34;Hey, I am DeckCheck!&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;&#34;&#34;I can help you improve your Quarto presentations by analysing them and suggesting improvements. Before I can do that, I need some information about your presentation.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;file&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Upload your Quarto presentation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">accept</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;.qmd&#34;</span><span class="p">,</span> <span class="s2">&#34;.qmdx&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="n">multiple</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_text_area</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;audience&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Describe your audience&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">height</span><span class="o">=</span><span class="s2">&#34;150px&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">placeholder</span><span class="o">=</span><span class="s2">&#34;e.g. Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_numeric</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;length&#34;</span><span class="p">,</span> <span class="s2">&#34;Time cap for the presentation (minutes)&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="nb">min</span><span class="o">=</span><span class="mi">1</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_text</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;type&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Type of talk&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">placeholder</span><span class="o">=</span><span class="s2">&#34;e.g. lightning talk, workshop, or keynote&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_text</span><span class="p">(</span><span class="s2">&#34;event&#34;</span><span class="p">,</span> <span class="s2">&#34;Event name&#34;</span><span class="p">,</span> <span class="n">placeholder</span><span class="o">=</span><span class="s2">&#34;e.g. posit::conf(2025)&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_task_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;submit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">icon</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">robot</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Analyse presentation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">width</span><span class="o">=</span><span class="mi">400</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">layout_column_wrap</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Showtime&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;9 minutes&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">showcase</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">file_slides</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">theme</span><span class="o">=</span><span class="s2">&#34;primary&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Code Savviness&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;15%&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">showcase</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">file_code</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">theme</span><span class="o">=</span><span class="s2">&#34;primary&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Image Presence&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;7%&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">showcase</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">file_image</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">theme</span><span class="o">=</span><span class="s2">&#34;primary&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">width</span><span class="o">=</span><span class="mi">1</span> <span class="o">/</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">fill</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">layout_column_wrap</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">strong</span><span class="p">(</span><span class="s2">&#34;Scores per category&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;My beautiful interactive plot...&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">strong</span><span class="p">(</span><span class="s2">&#34;Suggested improvements per category&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;My beatiful table...&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">width</span><span class="o">=</span><span class="mi">1</span> <span class="o">/</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">fill</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Bootswatch theme</span>
</span></span><span class="line"><span class="cl">    <span class="n">theme</span><span class="o">=</span><span class="n">shinyswatch</span><span class="o">.</span><span class="n">theme</span><span class="o">.</span><span class="n">flatly</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Empty for demo purposes</span>
</span></span><span class="line"><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-4-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span><span class="lnt">83
</span><span class="lnt">84
</span><span class="lnt">85
</span><span class="lnt">86
</span><span class="lnt">87
</span><span class="lnt">88
</span><span class="lnt">89
</span><span class="lnt">90
</span><span class="lnt">91
</span><span class="lnt">92
</span><span class="lnt">93
</span><span class="lnt">94
</span><span class="lnt">95
</span><span class="lnt">96
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fillable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## General theme and styles</span>
</span></span><span class="line"><span class="cl">  <span class="n">theme</span> <span class="o">=</span> <span class="nf">bs_theme</span><span class="p">(</span><span class="n">bootswatch</span> <span class="o">=</span> <span class="s">&#34;flatly&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## Sidebar content</span>
</span></span><span class="line"><span class="cl">    <span class="n">sidebar</span> <span class="o">=</span> <span class="nf">sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">width</span> <span class="o">=</span> <span class="m">400</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Open sidebar on mobile devices and show above content</span>
</span></span><span class="line"><span class="cl">      <span class="n">open</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span><span class="n">mobile</span> <span class="o">=</span> <span class="s">&#34;always-above&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">strong</span><span class="p">(</span><span class="nf">p</span><span class="p">(</span><span class="s">&#34;Hey, I am DeckCheck!&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">p</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;I can help you improve your Quarto presentations by analysing them and suggesting improvements.
</span></span></span><span class="line"><span class="cl"><span class="s">      Before I can do that, I need some information about your presentation.&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">fileInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">inputId</span> <span class="o">=</span> <span class="s">&#34;file&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Upload your Quarto presentation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">accept</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;.qmd&#34;</span><span class="p">,</span> <span class="s">&#34;.qmdx&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">textAreaInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">inputId</span> <span class="o">=</span> <span class="s">&#34;audience&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">height</span> <span class="o">=</span> <span class="s">&#34;150px&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Describe your audience&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">placeholder</span> <span class="o">=</span> <span class="s">&#34;e.g. Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">numericInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">inputId</span> <span class="o">=</span> <span class="s">&#34;length&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Time cap for the presentation (minutes)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">value</span> <span class="o">=</span> <span class="m">10</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">textInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">inputId</span> <span class="o">=</span> <span class="s">&#34;type&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Type of talk&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">placeholder</span> <span class="o">=</span> <span class="s">&#34;e.g. lightning talk, workshop, or keynote&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">textInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">inputId</span> <span class="o">=</span> <span class="s">&#34;event&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Event name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">placeholder</span> <span class="o">=</span> <span class="s">&#34;e.g. posit::conf(2025)&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">input_task_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;submit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="n">shiny</span><span class="o">::</span><span class="nf">tagList</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;robot&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;Analyse presentation&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">label_busy</span> <span class="o">=</span> <span class="s">&#34;DeckCheck is checking...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">type</span> <span class="o">=</span> <span class="s">&#34;default&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## Main content</span>
</span></span><span class="line"><span class="cl">    <span class="nf">layout_column_wrap</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">fill</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="c1">### Value boxes for metrics</span>
</span></span><span class="line"><span class="cl">      <span class="nf">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;Showtime&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">value</span> <span class="o">=</span> <span class="s">&#34;9 minutes&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;file-slides&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">theme</span> <span class="o">=</span> <span class="s">&#34;primary&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;Code Savviness&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">value</span> <span class="o">=</span> <span class="s">&#34;15%&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;file-code&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">theme</span> <span class="o">=</span> <span class="s">&#34;primary&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;Image Presence&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">value</span> <span class="o">=</span> <span class="s">&#34;7%&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;file-image&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">theme</span> <span class="o">=</span> <span class="s">&#34;primary&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">layout_column_wrap</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">fill</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">width</span> <span class="o">=</span> <span class="m">1</span> <span class="o">/</span> <span class="m">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="c1">### Graph with scoring metrics</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">card_header</span><span class="p">(</span><span class="nf">strong</span><span class="p">(</span><span class="s">&#34;Scores per category&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;My beatiful interactive plot...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="c1">### Table with suggested improvements</span>
</span></span><span class="line"><span class="cl">      <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">card_header</span><span class="p">(</span><span class="nf">strong</span><span class="p">(</span><span class="s">&#34;Suggested improvements per category&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;My beatiful table...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>No matter what language you use to display this basic UI, the result is the same:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-ui-only.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
<p>That&rsquo;s already a start! Of course it doesn&rsquo;t do anything yet and is filled with placeholders, so we need some logic in the server part. The main engine behind DeckCheck is our conversation with the LLM. This logic is <em>almost</em> a copy-paste from part two, <a href="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/../../../blog/shiny/shiny-side-of-llms-part-2/">Talking to LLMs: From Prompt to Response</a>
, combined with what we learned earlier in this article about async.</p>
<p>The star of this main engine is something called &ldquo;extended task&rdquo;. As mentioned previously, by default, Shiny runs code synchronously. That means if we ask it to render a Quarto presentation or send a request to an LLM, the app would block until that job is done. The whole interface would freeze. That&rsquo;s no fun for the user. That&rsquo;s why Shiny has an option to run extended tasks (<a href="https://shiny.posit.co/py/docs/nonblocking.html" target="_blank" rel="noopener"><code>extended_task</code></a>
 in Python, <a href="https://shiny.posit.co/r/articles/improve/nonblocking/" target="_blank" rel="noopener"><code>ExtendedTask</code></a>
 in R). It lets us run non-blocking jobs asynchronously in the background, so our app can stay responsive. It works together with a special action button, <code>input_task_button</code>, which is designed to trigger long running tasks. In order for this button to work you need to <strong>bind</strong> the button to the extended task with <code>bind_task_button</code>.</p>
<p>For DeckCheck, there are two main jobs to do:</p>
<ol>
<li><strong>Render the presentation (we call this task<code>quarto_task</code>)</strong>
<ul>
<li>Takes the uploaded Quarto file.</li>
<li>Runs Quarto to produce Markdown and HTML versions.</li>
<li>Returns the file path to the Markdown document.</li>
</ul>
</li>
<li><strong>Analyse our Markdown slides with the LLM (we call this task<code>chat_task</code>)</strong>
<ul>
<li>Waits for the Markdown to be ready, so it can read the content of the Markdown file using the file path.</li>
<li>Starts a chat session with the model.</li>
<li>Runs two subtasks:
<ul>
<li>A &ldquo;regular&rdquo; async chat where the LLM can use our tool to count slides and calculate percentages.</li>
<li>A structured async chat that returns clean, structured data according to our data model.</li>
</ul>
</li>
</ul>
</li>
</ol>
<p>Because the LLM depends on the Markdown output, we have to <strong>chain these tasks</strong>: the button click first kicks off <code>quarto_task</code>, and only when that&rsquo;s finished, we run <code>chat_task</code>.</p>
<p>This chaining is done through Shiny&rsquo;s reactive system:</p>
<ul>
<li>A reactive effect (<code>run_quarto</code> in Python) / observer (R) responds to the button press and invokes our first extended task: <code>quarto_task</code>.</li>
<li>Another reactive effect (<code>run_chat</code> in Python) / observer (R) listens for <code>quarto_task</code> to finish, then reads the rendered Markdown using the file path and invokes our second extended task: <code>chat_task</code>.</li>
<li><code>analysis_result</code> is a reactive that listens for <code>chat_task</code> to complete and prepares the final output for the UI.</li>
</ul>
<p>You can think of it as a pipeline:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-pipeline.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
<p>This setup keeps the app responsive, ensures tasks run in the right order, and makes the logic clear: the button starts things off, the tasks are executed async (but in order!), and our desired data ends up in a reactive that we can use as information source for all the UI components.</p>
<blockquote>
<p><strong>The world of async programming</strong></p>
<p>If you&rsquo;re used to synchronous code and just running scripts, using asynchronous tasks might feel a bit&hellip; complicated? Confusing, perhaps? Ultimately, it requires you to think differently. If you&rsquo;re keen to learn more, and getting hands on with a bunch of different examples, you can check out the <a href="https://shiny.posit.co/py/docs/nonblocking.html" target="_blank" rel="noopener">Python</a>
 or <a href="https://shiny.posit.co/r/articles/improve/nonblocking/" target="_blank" rel="noopener">R</a>
 docs about non-blocking operations. The <a href="https://github.com/hypebright/async_shiny/tree/main" target="_blank" rel="noopener">async_shiny</a>
 repo also contains examples.</p>
</blockquote>
<blockquote>
<p><strong>A note on code snippets</strong></p>
<p>Big chunks of code are generally not nice to look at. So, to make sure it&rsquo;s not too overwhelming we&rsquo;ll take a look at some snippets from the finalised DeckCheck app. Note that you can&rsquo;t run these snippets on their own. If you want to run the full DeckCheck application you can head over to the <a href="#the-end-result">the end result</a>
.</p>
</blockquote>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-5" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-5-1">Python</a></li>
<li><a href="#tabset-5-2">R</a></li>
</ul>
<div id="tabset-5-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span><span class="lnt">149
</span><span class="lnt">150
</span><span class="lnt">151
</span><span class="lnt">152
</span><span class="lnt">153
</span><span class="lnt">154
</span><span class="lnt">155
</span><span class="lnt">156
</span><span class="lnt">157
</span><span class="lnt">158
</span><span class="lnt">159
</span><span class="lnt">160
</span><span class="lnt">161
</span><span class="lnt">162
</span><span class="lnt">163
</span><span class="lnt">164
</span><span class="lnt">165
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Data Structure</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mi">10</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">PercentType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mf">100.0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">MinutesType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">SlideCount</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ScoringCategory</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">score</span><span class="p">:</span> <span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Score from 1–10.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">justification</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Brief explanation of the score.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">improvements</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Concise, actionable improvements, mentioning slide numbers if applicable.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">score_after_improvements</span><span class="p">:</span> <span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Estimated score after suggested improvements.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DeckAnalysis</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">presentation_title</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;The presentation title.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_slides</span><span class="p">:</span> <span class="n">SlideCount</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_code</span><span class="p">:</span> <span class="n">PercentType</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_images</span><span class="p">:</span> <span class="n">PercentType</span>
</span></span><span class="line"><span class="cl">    <span class="n">estimated_duration_minutes</span><span class="p">:</span> <span class="n">MinutesType</span>
</span></span><span class="line"><span class="cl">    <span class="n">tone</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Brief description of the tone of the presentation.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">clarity</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">relevance</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Assess how well the content matches the audience&#39;s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Truncated for brevity</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Shiny App</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fillable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## Our UI</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">input_task_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;submit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">icon</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">robot</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Analyse presentation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@ui.bind_task_button</span><span class="p">(</span><span class="n">button_id</span><span class="o">=</span><span class="s2">&#34;submit&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.extended_task</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">quarto_task</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="n">temp_dir</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># We&#39;re using an Extended Task to avoid blocking. Note that</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># a temporary directory called within mirai will be</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># different from the one in the &#34;main&#34; Shiny session. Hence,</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># we pass a temp_dir parameter to the task and use that.</span>
</span></span><span class="line"><span class="cl">        <span class="n">qmd_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">temp_dir</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&#34;my-presentation.qmd&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">shutil</span><span class="o">.</span><span class="n">copy</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="n">qmd_file</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Run asyncio subprocess</span>
</span></span><span class="line"><span class="cl">        <span class="n">proc</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_subprocess_exec</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;quarto&#34;</span><span class="p">,</span> <span class="s2">&#34;render&#34;</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">qmd_file</span><span class="p">),</span> <span class="s2">&#34;--to&#34;</span><span class="p">,</span> <span class="s2">&#34;markdown,html&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="n">proc</span><span class="o">.</span><span class="n">communicate</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Return the path to the markdown file</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">Path</span><span class="p">(</span><span class="n">temp_dir</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&#34;my-presentation.md&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@ui.bind_task_button</span><span class="p">(</span><span class="n">button_id</span><span class="o">=</span><span class="s2">&#34;submit&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.extended_task</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">chat_task</span><span class="p">(</span><span class="n">system_prompt</span><span class="p">,</span> <span class="n">markdown_content</span><span class="p">,</span> <span class="n">DeckAnalysis</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># We&#39;re using an extended task to avoid blocking the session and</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># we start a fresh chat session each time.</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># For a feedback loop, we would use a persistent chat session.</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">system_prompt</span><span class="o">=</span><span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Set model parameters (optional)</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat</span><span class="o">.</span><span class="n">set_model_params</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">temperature</span><span class="o">=</span><span class="mf">0.8</span><span class="p">,</span>  <span class="c1"># default is 1</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Register the tool with the chat</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat</span><span class="o">.</span><span class="n">register_tool</span><span class="p">(</span><span class="n">calculate_slide_metric</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Task 1: regular chat to extract meta-data</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat_res1</span> <span class="o">=</span> <span class="k">await</span> <span class="n">chat</span><span class="o">.</span><span class="n">chat_async</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Execute Task 1 (counts). Here are the slides in Markdown: {{ markdown_content }}&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">chat_res1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Task 2: structured chat to further analyse the slides</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat_res2</span> <span class="o">=</span> <span class="k">await</span> <span class="n">chat</span><span class="o">.</span><span class="n">chat_structured_async</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Execute Task 2 (suggestions)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">data_model</span><span class="o">=</span><span class="n">DeckAnalysis</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">chat_res2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">submit</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">run_quarto</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">req</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">file</span><span class="p">()</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Get file path of the uploaded file</span>
</span></span><span class="line"><span class="cl">        <span class="n">file_path</span> <span class="o">=</span> <span class="nb">input</span><span class="o">.</span><span class="n">file</span><span class="p">()[</span><span class="mi">0</span><span class="p">][</span><span class="s2">&#34;datapath&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">quarto_task</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">gettempdir</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">run_chat</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># require quarto_task result to be available</span>
</span></span><span class="line"><span class="cl">        <span class="n">req</span><span class="p">(</span><span class="n">quarto_task</span><span class="o">.</span><span class="n">result</span><span class="p">()</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1"># Get the Markdown file path from the complete quarto_task</span>
</span></span><span class="line"><span class="cl">        <span class="n">markdown_file</span> <span class="o">=</span> <span class="n">quarto_task</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Read the generated Markdown file containing the slides</span>
</span></span><span class="line"><span class="cl">        <span class="n">markdown_content</span> <span class="o">=</span> <span class="n">markdown_file</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl">        <span class="n">system_prompt_file</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ROOT_DIR</span> <span class="o">/</span> <span class="s2">&#34;prompts&#34;</span> <span class="o">/</span> <span class="s2">&#34;prompt-analyse-slides-structured-tool.md&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl">        <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">variables</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;audience&#34;</span><span class="p">:</span> <span class="nb">input</span><span class="o">.</span><span class="n">audience</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;length&#34;</span><span class="p">:</span> <span class="nb">input</span><span class="o">.</span><span class="n">length</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="nb">input</span><span class="o">.</span><span class="n">type</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;event&#34;</span><span class="p">:</span> <span class="nb">input</span><span class="o">.</span><span class="n">event</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;markdown_content&#34;</span><span class="p">:</span> <span class="n">markdown_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Trigger the chat task with the provided inputs</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat_task</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span><span class="n">system_prompt</span><span class="p">,</span> <span class="n">markdown_content</span><span class="p">,</span> <span class="n">DeckAnalysis</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># This reactive will be used as information source for</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># all the UI elements. It uses the result from the chat_task,</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># and does some data wrangling</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">analysis_result</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">res</span> <span class="o">=</span> <span class="n">chat_task</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">res</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="c1"># Some data wrangling</span>
</span></span><span class="line"><span class="cl">                <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">res</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that <code>quarto_task</code> is marked <code>async</code>, so it automatically returns a coroutine. You need a coroutine for <code>extended_task</code> to work. The subprocess call to Quarto runs without blocking (<code>await asyncio.create_subprocess_exec</code>). Once Quarto finishes, the task resolves with the path to the Markdown file. The async chat tasks from <code>chatlas</code> return a coroutine too.</p>
</div>
<div id="tabset-5-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span><span class="lnt">149
</span><span class="lnt">150
</span><span class="lnt">151
</span><span class="lnt">152
</span><span class="lnt">153
</span><span class="lnt">154
</span><span class="lnt">155
</span><span class="lnt">156
</span><span class="lnt">157
</span><span class="lnt">158
</span><span class="lnt">159
</span><span class="lnt">160
</span><span class="lnt">161
</span><span class="lnt">162
</span><span class="lnt">163
</span><span class="lnt">164
</span><span class="lnt">165
</span><span class="lnt">166
</span><span class="lnt">167
</span><span class="lnt">168
</span><span class="lnt">169
</span><span class="lnt">170
</span><span class="lnt">171
</span><span class="lnt">172
</span><span class="lnt">173
</span><span class="lnt">174
</span><span class="lnt">175
</span><span class="lnt">176
</span><span class="lnt">177
</span><span class="lnt">178
</span><span class="lnt">179
</span><span class="lnt">180
</span><span class="lnt">181
</span><span class="lnt">182
</span><span class="lnt">183
</span><span class="lnt">184
</span><span class="lnt">185
</span><span class="lnt">186
</span><span class="lnt">187
</span><span class="lnt">188
</span><span class="lnt">189
</span><span class="lnt">190
</span><span class="lnt">191
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Data Structure</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Reusable scoring category</span>
</span></span><span class="line"><span class="cl"><span class="n">type_scoring_category</span> <span class="o">&lt;-</span> <span class="nf">type_object</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">score</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Score from 1 to 10.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">justification</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Brief explanation of the score.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">improvements</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Concise, actionable improvements, mentioning slide numbers if applicable.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">required</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">score_after_improvements</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimated score after suggested improvements.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Top-level deck analysis object</span>
</span></span><span class="line"><span class="cl"><span class="n">type_deck_analysis</span> <span class="o">&lt;-</span> <span class="nf">type_object</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">presentation_title</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span><span class="n">description</span> <span class="o">=</span> <span class="s">&#34;The presentation title.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">total_slides</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span><span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Total number of slides.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">percent_with_code</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Percentage of slides containing code blocks (0–100).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">percent_with_images</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Percentage of slides containing images (0–100).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">estimated_duration_minutes</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimated presentation length in minutes, assuming ~1 minute per text slide and 2–3 minutes per code or image-heavy slide.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">tone</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Brief description of the presentation tone (e.g., informal, technical, playful).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">clarity</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">relevance</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Asses how well the content matches the audience&#39;s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Truncated for brevity</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Shiny App</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fillable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## Our UI</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">  <span class="nf">input_task_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;submit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">label</span> <span class="o">=</span> <span class="n">shiny</span><span class="o">::</span><span class="nf">tagList</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;robot&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;Analyse presentation&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">label_busy</span> <span class="o">=</span> <span class="s">&#34;DeckCheck is checking...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type</span> <span class="o">=</span> <span class="s">&#34;default&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">quarto_task</span> <span class="o">&lt;-</span> <span class="n">ExtendedTask</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span><span class="kr">function</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="n">temp_dir</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># We&#39;re using an Extended Task to avoid blocking. Note that</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># a temporary directory called within mirai will be</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># different from the one in the &#34;main&#34; Shiny session. Hence,</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># we pass a temp_dir parameter to the task and use that.</span>
</span></span><span class="line"><span class="cl">    <span class="nf">mirai</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">qmd_file</span> <span class="o">&lt;-</span> <span class="nf">file.path</span><span class="p">(</span><span class="n">temp_dir</span><span class="p">,</span> <span class="s">&#34;my-presentation.qmd&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nf">file.copy</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="n">qmd_file</span><span class="p">,</span> <span class="n">overwrite</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">quarto</span><span class="o">::</span><span class="nf">quarto_render</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">input</span> <span class="o">=</span> <span class="n">qmd_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">output_format</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;markdown&#34;</span><span class="p">,</span> <span class="s">&#34;html&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Return the path to the markdown file</span>
</span></span><span class="line"><span class="cl">        <span class="nf">file.path</span><span class="p">(</span><span class="n">temp_dir</span><span class="p">,</span> <span class="s">&#34;my-presentation.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Use the same environment as the Shiny app</span>
</span></span><span class="line"><span class="cl">      <span class="nf">environment</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">bind_task_button</span><span class="p">(</span><span class="s">&#34;submit&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">chat_task</span> <span class="o">&lt;-</span> <span class="n">ExtendedTask</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span><span class="kr">function</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">markdown_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_deck_analysis</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># We&#39;re using an Extended Task to avoid blocking the session and</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># we start a fresh chat session each time.</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># For a feedback loop, we would use a persistent chat session.</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">params</span> <span class="o">=</span> <span class="nf">params</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">temperature</span> <span class="o">=</span> <span class="m">0.8</span> <span class="c1"># default is 1</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Register the tool with the chat</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat</span><span class="o">$</span><span class="nf">register_tool</span><span class="p">(</span><span class="n">calculate_slide_metric</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Task 1: regular chat to extract meta-data</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat_res</span> <span class="o">&lt;-</span> <span class="n">chat</span><span class="o">$</span><span class="nf">chat_async</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Execute Task 1 (counts). Here are the slides in Markdown: {{ markdown_content }}&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">chat_res</span><span class="o">$</span><span class="nf">then</span><span class="p">(</span><span class="kr">function</span><span class="p">(</span><span class="n">res</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Print the response from Task 1</span>
</span></span><span class="line"><span class="cl">      <span class="nf">cat</span><span class="p">(</span><span class="s">&#34;Response from Task 1:\n&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">cat</span><span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="s">&#34;\n\n&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1"># Execute next task</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Task 2: structured chat to further analyse the slides</span>
</span></span><span class="line"><span class="cl">      <span class="n">chat</span><span class="o">$</span><span class="nf">chat_structured_async</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Execute Task 2 (suggestions)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">type</span> <span class="o">=</span> <span class="n">type_deck_analysis</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">bind_task_button</span><span class="p">(</span><span class="s">&#34;submit&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observe</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">file</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">audience</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">length</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">type</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">event</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Get file path of the uploaded file</span>
</span></span><span class="line"><span class="cl">    <span class="n">file_path</span> <span class="o">&lt;-</span> <span class="n">input</span><span class="o">$</span><span class="n">file</span><span class="o">$</span><span class="n">datapath</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">quarto_task</span><span class="o">$</span><span class="nf">invoke</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="n">temp_dir</span> <span class="o">=</span> <span class="nf">tempdir</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">bindEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">submit</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observe</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">quarto_task</span><span class="o">$</span><span class="nf">result</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># Get the Markdown file path from the completed quarto_task</span>
</span></span><span class="line"><span class="cl">    <span class="n">markdown_file</span> <span class="o">&lt;-</span> <span class="n">quarto_task</span><span class="o">$</span><span class="nf">result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Read the generated Markdown file containing the slides</span>
</span></span><span class="line"><span class="cl">    <span class="n">markdown_content</span> <span class="o">&lt;-</span> <span class="nf">readChar</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">,</span> <span class="nf">file.size</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt_file</span> <span class="o">&lt;-</span> <span class="n">here</span><span class="o">::</span><span class="nf">here</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;prompts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;prompt-analyse-slides-structured-tool.md&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span> <span class="o">&lt;-</span> <span class="nf">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">path</span> <span class="o">=</span> <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">audience</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">audience</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">length</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">length</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">type</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">type</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">event</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">event</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Trigger the chat task with the provided inputs</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat_task</span><span class="o">$</span><span class="nf">invoke</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">markdown_content</span> <span class="o">=</span> <span class="n">markdown_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">type_deck_analysis</span> <span class="o">=</span> <span class="n">type_deck_analysis</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># This reactive will be used as information source for</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># all the UI elements. It uses the result from the chat_task,</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># and does some data wrangling</span>
</span></span><span class="line"><span class="cl">  <span class="n">analysis_result</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">named_list</span> <span class="o">&lt;-</span> <span class="n">chat_task</span><span class="o">$</span><span class="nf">result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Some data wrangling</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">    <span class="n">named_list</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that we wrap the Quarto task in <code>mirai</code> because we need a promise. Basically an object that says: &ldquo;I don&rsquo;t have the answer yet, but I will later&rdquo;. <code>ExtendedTask</code> is built to work with promises and it expects whatever you give it to eventually resolve with a value. The async chat tasks from <code>ellmer</code> return a promise too.</p>
</div>
</div>
<p>In this case, we choose to start a fresh chat session each time. Another way to do this would be to bring up the code that initialises the chat client, set model parameters, and registers our tool so it only runs once at the start of the session. Then, we could use <a href="https://posit-dev.github.io/chatlas/reference/Chat.html#chatlas.Chat.set_turns" target="_blank" rel="noopener"><code>chat.set_turns([])</code></a>
 (Python) / <a href="https://ellmer.tidyverse.org/reference/Chat.html#method-set-turns-" target="_blank" rel="noopener"><code>chat$set_turns([])</code></a>
 (R) before each new &ldquo;chat task&rdquo;. This way, we won&rsquo;t accumulate chat history.</p>
<h1 id="towards-a-better-ui">Towards a better UI
</h1>
<p>There&rsquo;s an engine, and there&rsquo;s a basic UI. Time to put them together. But if we want a Shiny app to truly shine, we need to give the UI a little extra love. In this section, we&rsquo;ll go over all the UI elements that we have in our design, plus some extra goodies. The suggestions here are just examples though, the possibilities go far beyond what we&rsquo;ll cover here.</p>
<h2 id="loading-experience">Loading experience
</h2>
<p>The fact that we&rsquo;re executing tasks asynchronously doesn&rsquo;t change anything about the execution speed of those tasks. It still takes time to render Quarto, and it still takes time to get a response from the LLM. And since we do need to finish one task before starting the other, there&rsquo;s zero time gain compared to running the code synchronously for <strong>individual users</strong>. We simply made the app non-blocking, which is especially valuable if there are multiple users running the app at the same time (concurrently).</p>
<p>We already talked about the impatience of users and how visual feedback can make waiting a bit more enjoyable. So, in DeckCheck, we also need to make sure to provide such feedback. We want it to be informative (e.g. &ldquo;Processing your Quarto presentation&hellip;&rdquo; or &ldquo;The LLM is doing its magic&hellip;&rdquo;) and we want it to be entertaining (bouncing robot anyone?!).</p>
<p>A nice way to add such a custom loading experience is with the help of <a href="https://shiny.posit.co/py/api/core/ui.output_ui.html" target="_blank" rel="noopener"><code>output_ui</code></a>
 (Python) / <a href="https://shiny.posit.co/r/reference/shiny/latest/renderui.html" target="_blank" rel="noopener"><code>uiOutput</code></a>
 (R). It serves as a placeholder in your UI that gets filled later with server-generated UI via <code>render.ui()</code> (Python) / <code>renderUI()</code> (R). This lets you create dynamic interfaces that change depending on app state. For example, we can start by rendering a loading animation (like a bouncing icon), and once the results are ready, replace it with more complex UI elements such as value boxes, a graph, and a table.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-6" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-6-1">Python</a></li>
<li><a href="#tabset-6-2">R</a></li>
</ul>
<div id="tabset-6-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Shiny App</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fillable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">layout_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Sidebar content</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Main content is now dynamic</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">output_ui</span><span class="p">(</span><span class="s2">&#34;results&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@render.ui</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">results</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">quarto_task</span><span class="o">.</span><span class="n">status</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;running&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">file_slides_loader</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Processing your Quarto presentation...&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">class_</span><span class="o">=</span><span class="s2">&#34;text-center d-flex flex-column justify-content-center align-items-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">style</span><span class="o">=</span><span class="s2">&#34;height: 100%&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">chat_task</span><span class="o">.</span><span class="n">status</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;running&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">robot_loader</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;The LLM is doing its magic...&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">class_</span><span class="o">=</span><span class="s2">&#34;text-center d-flex flex-column justify-content-center align-items-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">style</span><span class="o">=</span><span class="s2">&#34;height: 100%&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">chat_task</span><span class="o">.</span><span class="n">status</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;success&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ui</span><span class="o">.</span><span class="n">TagList</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="c1"># Final results UI components</span>
</span></span><span class="line"><span class="cl">                <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-6-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Shiny App</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fillable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Sidebar content</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Main content is now dynamic</span>
</span></span><span class="line"><span class="cl">    <span class="nf">uiOutput</span><span class="p">(</span><span class="s">&#34;results&#34;</span><span class="p">,</span> <span class="n">height</span> <span class="o">=</span> <span class="s">&#34;100%&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">results</span> <span class="o">&lt;-</span> <span class="nf">renderUI</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="n">quarto_task</span><span class="o">$</span><span class="nf">status</span><span class="p">()</span> <span class="o">==</span> <span class="s">&#34;running&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-center d-flex flex-column justify-content-center align-items-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">style</span> <span class="o">=</span> <span class="s">&#34;height: 100%;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;file-slides&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">size</span> <span class="o">=</span> <span class="s">&#34;6em&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-primary bounce&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Processing your Quarto presentation...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="kr">else</span> <span class="kr">if</span> <span class="p">(</span><span class="n">chat_task</span><span class="o">$</span><span class="nf">status</span><span class="p">()</span> <span class="o">==</span> <span class="s">&#34;running&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-center d-flex flex-column justify-content-center align-items-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">style</span> <span class="o">=</span> <span class="s">&#34;height: 100%;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;robot&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">size</span> <span class="o">=</span> <span class="s">&#34;6em&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-primary bounce&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;The LLM is doing its magic...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="kr">else</span> <span class="kr">if</span> <span class="p">(</span><span class="n">chat_task</span><span class="o">$</span><span class="nf">status</span><span class="p">()</span> <span class="o">==</span> <span class="s">&#34;success&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">tagList</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Final results UI components</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>Using extended task, we can easily monitor the status with <code>quarto_task$status()</code> and <code>chat_task$status()</code>. The status can be <code>&quot;initial&quot;</code>, <code>&quot;running&quot;</code>, <code>&quot;success&quot;</code>, or <code>&quot;error&quot;</code>. So whenever our Quarto task is running, we can display a bouncing presentation easel (or whatever you like). And once that task is finished and we start with our chat task, we can show a bouncing robot. The HTML for those bouncing icons is pretty straightforward: a simple div that puts the icon in the middle. For the bounce effect we need some custom CSS that we can add with the <code>.bounce</code> class. It looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">.</span><span class="nc">bounce</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">animation</span><span class="p">:</span> <span class="n">bounce</span> <span class="mi">2</span><span class="kt">s</span> <span class="kc">infinite</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">keyframes</span> <span class="nt">bounce</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">0</span><span class="o">%,</span> <span class="nt">100</span><span class="o">%</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">transform</span><span class="p">:</span> <span class="nb">translateY</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nt">50</span><span class="o">%</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">transform</span><span class="p">:</span> <span class="nb">translateY</span><span class="p">(</span><span class="mi">-20</span><span class="kt">px</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>We&rsquo;ll look at how to add this custom CSS to your Shiny app a little later.</p>
<blockquote>
<p><strong>Built-in busy indicators</strong></p>
<p>By default, a page-level pulsing banner and a spinner will be shown on recalculating outputs like plots and tables. This means that whenever the app is busy with calculations (like getting the results from an LLM) there will be some visual feedback for the user. You can change the appearance and options of these busy indicators with <a href="https://shiny.posit.co/py/api/core/ui.busy_indicators.options.html#shiny.ui.busy_indicators.options" target="_blank" rel="noopener">ui.busy_indicators.options</a>
 (Python) / <a href="https://shiny.posit.co/r/reference/shiny/latest/busyindicatoroptions.html" target="_blank" rel="noopener">busyIndicatorOptions</a>
 (R). In Python, a spinner shows by default on <code>output_plot</code> and <code>output_data_frame</code>. In R, on <code>plotOutput</code> and <code>tableOutput</code>. Spinners won&rsquo;t be shown on value boxes and HTML widgets, that&rsquo;s why a spinner overlay like in our example works well.</p>
</blockquote>
<p>What a lovely result 🤖:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-loading-ux.gif"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
<h2 id="value-boxes-with-tooltips">Value boxes with tooltips
</h2>
<p>With value boxes you can display key numbers. The idea is simple: show a number or short text, add an image, icon, or sparkline, and make sure the value updates whenever the underlying data changes.</p>
<p>Value boxes can be enhanced with tooltips. Tooltips are one of those small details that make a big difference. They&rsquo;re perfect for adding extra information without cluttering up your interface. Think of them as extra context that appears when someone hovers or taps. You can use them to explain numbers or tricky terms, add short instructions, highlight what a button actually does, or even drop in a quick example. In our case, we could add tooltips to the value boxes to tell our users how the numbers were calculated.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-7" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-7-1">Python</a></li>
<li><a href="#tabset-7-2">R</a></li>
</ul>
<div id="tabset-7-1">
<p>UI:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># SVG icon copied from https://icons.getbootstrap.com/icons/file-slides-fill/</span>
</span></span><span class="line"><span class="cl"><span class="n">file_slides</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; fill=&#34;currentColor&#34; class=&#34;bi bi-file-slides-fill&#34; viewBox=&#34;0 0 16 16&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M7 7.78V5.22c0-.096.106-.156.19-.106l2.13 1.279a.125.125 0 0 1 0 .214l-2.13 1.28A.125.125 0 0 1 7 7.778z&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M12 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2M5 4h6a.5.5 0 0 1 .496.438l.5 4A.5.5 0 0 1 11.5 9h-3v2.016c.863.055 1.5.251 1.5.484 0 .276-.895.5-2 .5s-2-.224-2-.5c0-.233.637-.429 1.5-.484V9h-3a.5.5 0 0 1-.496-.562l.5-4A.5.5 0 0 1 5 4&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/svg&gt;&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">tooltip</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Showtime&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">output_text</span><span class="p">(</span><span class="s2">&#34;showtime&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">showcase</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">file_slides</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">theme</span><span class="o">=</span><span class="s2">&#34;primary&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Slides are being counted based on the provided Quarto presentation, then an educated guess is made about the time it will take to present them.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Server:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">showtime</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">res</span> <span class="o">=</span> <span class="n">analysis_result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">req</span><span class="p">(</span><span class="n">res</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">res</span><span class="p">[</span><span class="s1">&#39;meta&#39;</span><span class="p">][</span><span class="s1">&#39;estimated_duration_minutes&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2"> minutes&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In this case, the value contains some text (the length of the presentation in minutes) and a showcase (an icon), but use your creativity to build anything you want! You can find some fun examples <a href="https://shiny.posit.co/py/components/outputs/value-box/" target="_blank" rel="noopener">here</a>
.</p>
<p>You can add a <a href="https://shiny.posit.co/py/components/display-messages/tooltips/" target="_blank" rel="noopener">tooltip</a>
 to any UI element with <code>ui.tooltip()</code>. In this case, the tooltip is applied to the complete value box.</p>
<p>The result:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-tooltips-py.gif"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
</div>
<div id="tabset-7-2">
<p>UI:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">title</span> <span class="o">=</span> <span class="nf">tooltip</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">span</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;Showtime &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;question-circle-fill&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Slides are being counted based on the provided Quarto presentation, then an educated guess is made about the time it will take to present them.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">value</span> <span class="o">=</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;showtime&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;file-slides&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">theme</span> <span class="o">=</span> <span class="s">&#34;primary&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Server:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">output</span><span class="o">$</span><span class="n">showtime</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nf">req</span><span class="p">(</span><span class="nf">analysis_result</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">  <span class="nf">paste0</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">analysis_result</span><span class="p">()</span><span class="o">$</span><span class="n">meta</span><span class="o">$</span><span class="n">estimated_duration_minutes</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34; minutes&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Looking for more fun examples? Take a look <a href="https://shiny.posit.co/r/components/outputs/value-box/" target="_blank" rel="noopener">here</a>
.</p>
<p>You can add a tooltip to any UI element with <code>tooltip()</code>. In this case, the tooltip is applied to a little info icon (generated with <code>span</code> and <code>bs_icon</code>). Alternatively, you could also apply it to the complete value box.</p>
<p>The result:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-tooltips-r.gif"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
</div>
</div>
<h2 id="interactive-plots">Interactive plots
</h2>
<p>Is it even a data-powered app if there isn&rsquo;t a good plot?!</p>
<p>There are plenty of options when it comes to visualisation libraries, and many of them support interactivity as well. With Shiny you can easily add interactive plots, and you can even link events (clicking, brushing, hovering) to other parts of your app. For example, adding click and brushing would let users dig deeper into the results. Clicking on a bar in our score graph could filter the table down to just that category&rsquo;s feedback, while brushing across multiple bars would make it possible to look at several categories side by side. And lets not forget about hovering: users expect to see more information when they hover over a score. You can control click, brush, and hover events with <a href="https://shiny.posit.co/py/api/core/ui.output_plot.html" target="_blank" rel="noopener"><code>output_plot</code></a>
 (Python) / <a href="https://shiny.posit.co/r/reference/shiny/latest/plotoutput.html" target="_blank" rel="noopener"><code>plotOutput</code></a>
 (R). This makes it easy to send values back to the server. To give an example: you can show a modal when someone clicks on a bar.</p>
<p>To keep the demo light, we&rsquo;re not going to focus too much on these events. But there&rsquo;s one thing that we can add very easily and is supported by most interactive visualisation libraries: tooltips on hover! When you hover over a bar (or a line, or a point, you get it), you&rsquo;ll see a quick popup with more information. In our case: the score and the justification. Even this small touch already makes the chart feel more alive, and it&rsquo;s easy to imagine how combining clicks, brushing, and tooltips could make DeckCheck even cooler.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-8" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-8-1">Python</a></li>
<li><a href="#tabset-8-2">R</a></li>
</ul>
<div id="tabset-8-1">
<blockquote>
<p><strong>Tip</strong></p>
<p>Want to create plots based on the grammar of graphics? Take a look at <a href="https://plotnine.org" target="_blank" rel="noopener">plotnine</a>
! Unfortunately not interactive (yet), but you can make really pretty figures that would fit nicely into an app.</p>
</blockquote>
<p>In Python, you have plenty of options for interactive plots, like <a href="https://shiny.posit.co/py/components/outputs/plot-plotly" target="_blank" rel="noopener">Plotly</a>
 or <a href="https://altair-viz.github.io" target="_blank" rel="noopener">Altair</a>
. The choice is yours! Our weapon of choice for DeckCheck: Plotly.</p>
<p>To make a Plotly plot interactive, you need <a href="https://github.com/posit-dev/py-shinywidgets" target="_blank" rel="noopener"><code>shinywidgets</code></a>
. More specifically: <code>output_widget</code> on the UI side, and <code>render_widget</code> on the server side. It connects Shiny with ipywidgets, letting your sliders, dropdowns, and buttons actually control the plot.</p>
<p>UI:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">strong</span><span class="p">(</span><span class="s2">&#34;Scores per category&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="n">output_widget</span><span class="p">(</span><span class="s2">&#34;scores&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">height</span><span class="o">=</span><span class="s2">&#34;600px&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Server:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@render_widget</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">scores</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">res</span> <span class="o">=</span> <span class="n">analysis_result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">req</span><span class="p">(</span><span class="n">res</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">evals</span> <span class="o">=</span> <span class="n">res</span><span class="p">[</span><span class="s2">&#34;evals&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">evals</span> <span class="o">=</span> <span class="n">evals</span><span class="o">.</span><span class="n">sort_values</span><span class="p">(</span><span class="s2">&#34;score&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;category&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">Categorical</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;category&#34;</span><span class="p">],</span> <span class="n">categories</span><span class="o">=</span><span class="n">evals</span><span class="p">[</span><span class="s2">&#34;category&#34;</span><span class="p">],</span> <span class="n">ordered</span><span class="o">=</span><span class="kc">True</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># apply to the justification column</span>
</span></span><span class="line"><span class="cl">    <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;justification_wrapped&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;justification&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">apply</span><span class="p">(</span><span class="n">add_line_breaks</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Create a custom tooltip column</span>
</span></span><span class="line"><span class="cl">    <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;tooltip&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Score: &#34;</span>
</span></span><span class="line"><span class="cl">        <span class="o">+</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;score&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="o">+</span> <span class="s2">&#34;&lt;br&gt;After improvements: &#34;</span>
</span></span><span class="line"><span class="cl">        <span class="o">+</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;score_after_improvements&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="o">+</span> <span class="s2">&#34;&lt;br&gt;Justification: &#34;</span>
</span></span><span class="line"><span class="cl">        <span class="o">+</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;justification_wrapped&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">plot</span> <span class="o">=</span> <span class="n">px</span><span class="o">.</span><span class="n">bar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">evals</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">x</span><span class="o">=</span><span class="s2">&#34;score&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">y</span><span class="o">=</span><span class="s2">&#34;category&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">orientation</span><span class="o">=</span><span class="s2">&#34;h&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">labels</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;category&#34;</span><span class="p">:</span> <span class="s2">&#34;Category&#34;</span><span class="p">,</span> <span class="s2">&#34;score&#34;</span><span class="p">:</span> <span class="s2">&#34;Score&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">hover_data</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;tooltip&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">},</span>  <span class="c1"># include the tooltip column</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Set hovertemplate to use our custom tooltip</span>
</span></span><span class="line"><span class="cl">    <span class="n">plot</span><span class="o">.</span><span class="n">update_traces</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">hovertemplate</span><span class="o">=</span><span class="s2">&#34;%</span><span class="si">{customdata[0]}</span><span class="s2">&lt;extra&gt;&lt;/extra&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">customdata</span><span class="o">=</span><span class="n">evals</span><span class="p">[[</span><span class="s2">&#34;tooltip&#34;</span><span class="p">]]</span><span class="o">.</span><span class="n">values</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">plot</span><span class="o">.</span><span class="n">update_traces</span><span class="p">(</span><span class="n">marker_color</span><span class="o">=</span><span class="s2">&#34;#18bc9c&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">plot</span><span class="o">.</span><span class="n">update_layout</span><span class="p">(</span><span class="n">template</span><span class="o">=</span><span class="s2">&#34;simple_white&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">plot</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>To construct the tooltip we create a little helper to make sure there are line breaks. Otherwise the tooltip runs of the screen!</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Tooltip helper</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add_line_breaks</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="mi">50</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">text</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">words</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="n">current_line</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">words</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># +1 accounts for the space if current_line isn&#39;t empty</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">current_line</span><span class="p">)</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="n">word</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="mi">1</span> <span class="k">if</span> <span class="n">current_line</span> <span class="k">else</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="n">width</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">current_line</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34; &#34;</span> <span class="k">if</span> <span class="n">current_line</span> <span class="k">else</span> <span class="s2">&#34;&#34;</span><span class="p">)</span> <span class="o">+</span> <span class="n">word</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_line</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">current_line</span> <span class="o">=</span> <span class="n">word</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">current_line</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_line</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s2">&#34;&lt;br&gt;&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The result is simple, but effective:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-plot-py.gif"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
</div>
<div id="tabset-8-2">
<blockquote>
<p><strong>Cross-widget interactions</strong></p>
<p>Interested in cross-widget interactions like linked brushing and filtering? Take a look at <a href="https://rstudio.github.io/crosstalk/shiny.html" target="_blank" rel="noopener">crosstalk</a>
.</p>
</blockquote>
<p>Where would we be without our beloved <code>ggplot2</code>&hellip; So of course it would be nice if we can could make our <code>ggplot2</code> interactive. The solution: <a href="https://davidgohel.github.io/ggiraph/" target="_blank" rel="noopener"><code>ggiraph</code></a>
! With <code>ggiraph</code> you can simply add tooltips, hover effects, and JavaScript actions to your plots. There&rsquo;s plenty of options to alter the look and feel of the interactive elements, so you can be very creative. If you&rsquo;re looking for more inspiration with <code>ggiraph</code>, check out <a href="https://posit.co/blog/shiny-dashboards-with-ggiraph-and-databases/" target="_blank" rel="noopener">this post</a>
 by Isabella Velásquez.</p>
<p>We&rsquo;ll stick to the basic with DeckCheck by adding <a href="https://davidgohel.github.io/ggiraph/reference/geom_bar_interactive.html" target="_blank" rel="noopener"><code>geom_bar_interactive</code></a>
 and some tooltip options. To make it all work we need to use <code>girafeOutput</code> and <code>renderGirafe</code> (as opposed to <code>plotOutput</code> and <code>renderPlot</code>).</p>
<p>UI:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">height</span> <span class="o">=</span> <span class="m">600</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">strong</span><span class="p">(</span><span class="s">&#34;Scores per category&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">girafeOutput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">outputId</span> <span class="o">=</span> <span class="s">&#34;scores&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Server:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">scores</span> <span class="o">&lt;-</span> <span class="nf">renderGirafe</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="nf">analysis_result</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">evals</span> <span class="o">&lt;-</span> <span class="nf">analysis_result</span><span class="p">()</span><span class="o">$</span><span class="n">evals</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Order by score</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">&lt;-</span> <span class="n">evals</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">arrange</span><span class="p">(</span><span class="n">score</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">mutate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">category</span> <span class="o">=</span> <span class="nf">factor</span><span class="p">(</span><span class="n">category</span><span class="p">,</span> <span class="n">levels</span> <span class="o">=</span> <span class="n">category</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">tooltip</span> <span class="o">=</span> <span class="nf">paste0</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;Score: &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">score</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;\n&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;After improvements: &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">score_after_improvements</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;\n&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;Justification: &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">justification</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">select</span><span class="p">(</span><span class="n">category</span><span class="p">,</span> <span class="n">score</span><span class="p">,</span> <span class="n">score_after_improvements</span><span class="p">,</span> <span class="n">tooltip</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">p</span> <span class="o">&lt;-</span> <span class="nf">ggplot</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nf">aes</span><span class="p">(</span><span class="n">x</span> <span class="o">=</span> <span class="n">category</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">score</span><span class="p">,</span> <span class="n">tooltip</span> <span class="o">=</span> <span class="n">tooltip</span><span class="p">,</span> <span class="n">data_id</span> <span class="o">=</span> <span class="n">category</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">      <span class="nf">geom_bar_interactive</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">stat</span> <span class="o">=</span> <span class="s">&#34;identity&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">fill</span> <span class="o">=</span> <span class="s">&#34;#18bc9c&#34;</span> <span class="c1"># Success color of Flatly theme</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">      <span class="nf">labs</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">x</span> <span class="o">=</span> <span class="s">&#34;Category&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">y</span> <span class="o">=</span> <span class="s">&#34;Score&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Flip to make horizontal bar chart</span>
</span></span><span class="line"><span class="cl">      <span class="nf">coord_flip</span><span class="p">()</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">      <span class="nf">theme_minimal</span><span class="p">(</span><span class="n">base_family</span> <span class="o">=</span> <span class="s">&#34;Lato&#34;</span><span class="p">,</span> <span class="n">base_size</span> <span class="o">=</span> <span class="m">14</span><span class="p">)</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">      <span class="nf">theme</span><span class="p">(</span><span class="n">legend.position</span> <span class="o">=</span> <span class="s">&#34;none&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">girafe</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">ggobj</span> <span class="o">=</span> <span class="n">p</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">options</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">opts_selection</span><span class="p">(</span><span class="n">type</span> <span class="o">=</span> <span class="s">&#34;none&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">opts_sizing</span><span class="p">(</span><span class="n">rescale</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">opts_tooltip</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">css</span> <span class="o">=</span> <span class="s">&#34;background-color: #f0f0f0; color: #333; padding: 5px; border-radius: 5px; width: 200px;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">opts_hover</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">css</span> <span class="o">=</span> <span class="s">&#34;.&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">opts_hover_inv</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">css</span> <span class="o">=</span> <span class="s">&#34;opacity: 0.5;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>And that&rsquo;s how you add interactivity to your <code>ggplot2</code> fast:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-plot-r.gif"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
</div>
</div>
<h2 id="tables">Tables
</h2>
<p>Another element that you&rsquo;ll see in web apps: tables. The good news is: displaying your data in Shiny is super easy. Got a pandas/polars/narwhals DataFrame in Python? Or a data.frame/tibble/data.table in R? You can drop it straight into <a href="https://shiny.posit.co/py/api/core/render.data_frame.html" target="_blank" rel="noopener"><code>render.data_frame</code></a>
 / <a href="https://shiny.posit.co/py/api/core/ui.output_data_frame.html" target="_blank" rel="noopener"><code>output_data_frame</code></a>
 (Python) or <a href="https://shiny.posit.co/r/reference/shiny/latest/rendertable.html" target="_blank" rel="noopener"><code>renderTable</code></a>
 / <a href="https://shiny.posit.co/r/reference/shiny/latest/rendertable.html" target="_blank" rel="noopener"><code>tableOutput</code></a>
 (R) (and many other similar functions for some variety).</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-9" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-9-1">Python</a></li>
<li><a href="#tabset-9-2">R</a></li>
</ul>
<div id="tabset-9-1">
<blockquote>
<p><strong>Other ways to render your table</strong></p>
<p>You can also take a look at <a href="https://shiny.posit.co/py/api/core/render.DataGrid.html" target="_blank" rel="noopener">render.DataGrid</a>
 (spreadsheet-like view), <a href="https://shiny.posit.co/py/api/core/render.table.html" target="_blank" rel="noopener">render.table</a>
 (basic HTML table, not as nice as <a href="https://shiny.posit.co/py/api/core/render.data_frame.html" target="_blank" rel="noopener">render.data_frame</a>
) and <a href="https://shiny.posit.co/py/api/core/render.DataTable.html#shiny.render.DataTable" target="_blank" rel="noopener">render.DataTable</a>
 (more tabular view of data).</p>
</blockquote>
<p>UI:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">strong</span><span class="p">(</span><span class="s2">&#34;Suggested improvements per category&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">output_data_frame</span><span class="p">(</span><span class="s2">&#34;suggested_improvements&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">height</span><span class="o">=</span><span class="s2">&#34;600px&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Server:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@render.data_frame</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">suggested_improvements</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">res</span> <span class="o">=</span> <span class="n">analysis_result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">req</span><span class="p">(</span><span class="n">res</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">evals</span> <span class="o">=</span> <span class="n">res</span><span class="p">[</span><span class="s2">&#34;evals&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;Gain&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;score_after_improvements&#34;</span><span class="p">]</span> <span class="o">-</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;score&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">result_table</span> <span class="o">=</span> <span class="n">evals</span><span class="o">.</span><span class="n">assign</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">Category</span><span class="o">=</span><span class="n">evals</span><span class="p">[</span><span class="s2">&#34;category&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="o">**</span><span class="p">{</span><span class="s2">&#34;Current score&#34;</span><span class="p">:</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;score&#34;</span><span class="p">]},</span>
</span></span><span class="line"><span class="cl">        <span class="n">Improvements</span><span class="o">=</span><span class="n">evals</span><span class="p">[</span><span class="s2">&#34;improvements&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="o">**</span><span class="p">{</span><span class="s2">&#34;Score After Improvements&#34;</span><span class="p">:</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;score_after_improvements&#34;</span><span class="p">]},</span>
</span></span><span class="line"><span class="cl">        <span class="n">Gain</span><span class="o">=</span><span class="n">evals</span><span class="p">[</span><span class="s2">&#34;Gain&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">)[</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Category&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Current score&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Improvements&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Score After Improvements&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Gain&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span><span class="o">.</span><span class="n">sort_values</span><span class="p">(</span><span class="s2">&#34;Gain&#34;</span><span class="p">,</span> <span class="n">ascending</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">result_table</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-9-2">
<blockquote>
<p><strong>More fun table libraries</strong></p>
<p>Want to have some fun with JavaScript based tables? Check out <a href="https://glin.github.io/reactable/" target="_blank" rel="noopener">reactable</a>
, which is based on the <a href="https://github.com/tanstack/table/tree/v7" target="_blank" rel="noopener">React Table</a>
 library. Or, go for <a href="https://gt.rstudio.com" target="_blank" rel="noopener">gt</a>
! You can get some inspo from the <a href="https://shiny.posit.co/r/components/" target="_blank" rel="noopener">Shiny components gallery</a>
.</p>
</blockquote>
<p>UI:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">height</span> <span class="o">=</span> <span class="m">600</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nf">card_header</span><span class="p">(</span><span class="nf">strong</span><span class="p">(</span><span class="s">&#34;Suggested improvements per category&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">tableOutput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">outputId</span> <span class="o">=</span> <span class="s">&#34;suggested_improvements&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Server:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">output</span><span class="o">$</span><span class="n">suggested_improvements</span> <span class="o">&lt;-</span> <span class="nf">renderTable</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nf">req</span><span class="p">(</span><span class="nf">analysis_result</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">evals</span> <span class="o">&lt;-</span> <span class="nf">analysis_result</span><span class="p">()</span><span class="o">$</span><span class="n">evals</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">evals</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">arrange</span><span class="p">(</span><span class="n">score</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">mutate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">Gain</span> <span class="o">=</span> <span class="n">score_after_improvements</span> <span class="o">-</span> <span class="n">score</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">Category</span> <span class="o">=</span> <span class="n">category</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">`Current score`</span> <span class="o">=</span> <span class="n">score</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">Improvements</span> <span class="o">=</span> <span class="n">improvements</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">`Score After Improvements`</span> <span class="o">=</span> <span class="n">score_after_improvements</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">Gain</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">arrange</span><span class="p">(</span><span class="nf">desc</span><span class="p">(</span><span class="n">Gain</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<h2 id="error-catching">Error catching
</h2>
<p>An error. The thing we don&rsquo;t want to see in our app. Errors reduce user experience, big time. A frozen app or red error messages don&rsquo;t make your users happy. And for you, as a developer, you&rsquo;re not too happy about them either. Unfortunately, it&rsquo;s hard to completely avoid getting errors: you&rsquo;ll always see that there&rsquo;s a specific scenario that you haven&rsquo;t thought about. But that doesn&rsquo;t mean you have to just let it happen: you can catch errors so your users are not confronted with a frozen app or messages they can&rsquo;t understand. Instead, you can confront your users with a friendly (and hopefully useful) message.</p>
<p>One thing worth knowing: in Shiny, if an error happens inside a reactive expression or an observer, the default behaviour is for the app to crash. That&rsquo;s because Shiny has no way of knowing whether the error is fatal or not, and it doesn&rsquo;t really have a natural place to show that error to the user. With LLM apps, this becomes a bigger deal: errors happen often, they&rsquo;re not always fatal, and it&rsquo;s really frustrating for your users if the whole app crashes and they lose their conversation history. Models can return unexpected output, an API call might time out, the API might be overloaded by your requests (been there, done that), or the user could upload something your app doesn&rsquo;t know how to handle. That&rsquo;s why you want to deal with errors &ldquo;gracefully&rdquo;. And luckily, you don&rsquo;t have to reinvent the wheel here. For example, the <code>Chat</code> component automatically catches errors that happen while streaming and shows the user a short explanation instead of breaking the whole app.</p>
<p>However, in a bespoke app like DeckCheck, where we&rsquo;re streaming inside our own reactive expression or observer, you&rsquo;ll want to think about setting up your own error handling. The idea is simple: don&rsquo;t crash the app, and let the user know what went wrong in a friendly way. In our case, we split up our error messages: we can display one when something goes wrong with processing the Quarto file, and we can display one when our chat didn&rsquo;t go as planned. These two error-catching &ldquo;wrappers&rdquo; serve as some inspiration for your next friendly error message.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-10" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-10-1">Python</a></li>
<li><a href="#tabset-10-2">R</a></li>
</ul>
<div id="tabset-10-1">
<p>To demonstrate what an error-catching &ldquo;wrapper&rdquo; could look like, let&rsquo;s take a look at error handling for the chat task:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">run_chat</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># require quarto_task result to be available</span>
</span></span><span class="line"><span class="cl">    <span class="n">req</span><span class="p">(</span><span class="n">quarto_task</span><span class="o">.</span><span class="n">result</span><span class="p">()</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Error for testing</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># raise ValueError(&#34;Test error&#34;)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Trigger the chat task with the provided inputs</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat_task</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span><span class="n">system_prompt</span><span class="p">,</span> <span class="n">markdown_content</span><span class="p">,</span> <span class="n">DeckAnalysis</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Error when trying to invoke chat_task: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Print stack trace to the console</span>
</span></span><span class="line"><span class="cl">        <span class="n">traceback</span><span class="o">.</span><span class="n">print_exc</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Return value that triggers modal in UI</span>
</span></span><span class="line"><span class="cl">        <span class="n">m</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">modal</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="c1"># Sad bootstrap icon</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">sad_icon</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;The not so Shiny Side of LLMs. Unfortunately, chatting didn&#39;t work out. Do you have enough credits left?&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="c1"># add class to center the content</span>
</span></span><span class="line"><span class="cl">                <span class="n">class_</span><span class="o">=</span><span class="s2">&#34;text-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">title</span><span class="o">=</span><span class="s2">&#34;Oops, something went wrong!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">easy_close</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">footer</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">modal_button</span><span class="p">(</span><span class="s2">&#34;Close&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">modal_show</span><span class="p">(</span><span class="n">m</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The same error handling gets applied to the Quarto task.</p>
<p>The Quarto task and the chat task chain together various tasks: copying an uploaded Quarto file, rendering it to Markdown and HTML, building a system prompt, and then invoking a conversation with an LLM. Any of these steps could fail (a bad upload, Quarto not rendering, the model returning something unexpected), but the <code>try/except</code> makes sure the app doesn&rsquo;t just crash or leave the user hanging. Instead, if something goes wrong, it logs the error for debugging and then shows the user a clean modal with a simple message.</p>
<blockquote>
<p><strong>Error notifications</strong></p>
<p>There&rsquo;s also a nice helper for error notifications: <a href="https://shiny.posit.co/py/docs/genai-stream.html#error-handling" target="_blank" rel="noopener"><code>shiny.types.NotifyException</code></a>
. This is what <code>ui.Chat</code> from <a href="https://posit-dev.github.io/shinychat/py/" target="_blank" rel="noopener">shinychat</a>
 uses for its error notifications. You can use it like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;An error occurred: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">raise</span> <span class="n">NotifyException</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span></code></pre></td></tr></table>
</div>
</div></blockquote>
</div>
<div id="tabset-10-2">
<p>To demonstrate what an error-catching &ldquo;wrapper&rdquo; could look like, let&rsquo;s take a look at error handling for the chat task:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl">  <span class="nf">observe</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">quarto_task</span><span class="o">$</span><span class="nf">result</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="nf">tryCatch</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Error for testing</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># stop(&#34;This is a test error.&#34;)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Trigger the chat task with the provided inputs</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat_task</span><span class="o">$</span><span class="nf">invoke</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">markdown_content</span> <span class="o">=</span> <span class="n">markdown_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">type_deck_analysis</span> <span class="o">=</span> <span class="n">type_deck_analysis</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="n">error</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">rlang</span><span class="o">::</span><span class="nf">warn</span><span class="p">(</span><span class="nf">paste</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;Error when trying to invoke chat_task:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">e</span><span class="o">$</span><span class="n">message</span>
</span></span><span class="line"><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1"># Print stack trace</span>
</span></span><span class="line"><span class="cl">        <span class="nf">print</span><span class="p">(</span><span class="n">rlang</span><span class="o">::</span><span class="nf">trace_back</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Show modal to the user</span>
</span></span><span class="line"><span class="cl">        <span class="nf">showModal</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="nf">modalDialog</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;Oops! Something went wrong&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s">&#34;emoji-frown-fill&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">size</span> <span class="o">=</span> <span class="s">&#34;2em&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-warning&#34;</span>
</span></span><span class="line"><span class="cl">              <span class="p">),</span>
</span></span><span class="line"><span class="cl">              <span class="nf">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">              <span class="nf">p</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s">&#34;The not so Shiny Side of LLMs. Unfortunately, chatting didn&#39;t work out. Do you have enough credits left?&#34;</span>
</span></span><span class="line"><span class="cl">              <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">easyClose</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">footer</span> <span class="o">=</span> <span class="nf">modalButton</span><span class="p">(</span><span class="s">&#34;Close&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The same error handling gets applied to the Quarto task.</p>
<p>The Quarto task and the chat task chain together various tasks: copying an uploaded Quarto file, rendering it to Markdown and HTML, building a system prompt, and then invoking a conversation with an LLM. Any of these steps could fail (a bad upload, Quarto not rendering, the model returning something unexpected), but the <code>tryCatch</code> wrapper makes sure the app doesn&rsquo;t just crash or leave the user hanging. Instead, if something goes wrong, it logs the error for debugging and then shows the user a clean modal with a simple message.</p>
</div>
</div>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-error-msg.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
<p>Note that the error messages just say &ldquo;something went wrong&rdquo; and a little direction as to what to do next. This is the cleanest and most &ldquo;sanitised&rdquo; way of handling errors. If you were to pass the real error message straight through to the modal, you&rsquo;d risk showing users technical details they&rsquo;re not supposed to see.</p>
<blockquote>
<p><strong>Oops, an error</strong></p>
<p>The <code>result()</code> method of <code>extended_task</code> (Python) / <code>ExtendedTask</code> (R) can return <code>&quot;error&quot;</code> too. If there&rsquo;s an error in the task, the error will be re-thrown if you call the <code>result()</code> method.</p>
</blockquote>
<h2 id="custom-csssass">Custom CSS/Sass
</h2>
<p>So far, we&rsquo;ve been happily using a preset theme in our app (for DeckCheck: the Bootswatch Flatly theme). Nothing wrong with that, but maybe your organisation has a &ldquo;house style&rdquo; (company colours, logos, and fonts that need to be everywhere). The good news: Shiny doesn&rsquo;t get in your way here. Under the hood, a Shiny app is still just HTML, CSS, and JavaScript. And just like any other web app, you can tweak the look and feel with CSS/<a href="https://rstudio.github.io/sass/" target="_blank" rel="noopener">Sass</a>
 until it matches whatever look you&rsquo;re going for.</p>
<p>In Shiny, adding that CSS/Sass is easy. There are actually a few options:</p>
<ul>
<li><strong>Inline CSS (global)</strong>: you can attach CSS directly using a <code>&lt;style&gt;</code> tag (<code>tags.style()</code> in Python or <code>tags$style()</code> in R) at the top of your UI. This is handy when you just need a couple of tweaks that apply globally. You can use it for CSS classes that you use throughout your app, or for things like global fonts. For simplicity, we&rsquo;re going for this option in DeckCheck. Note that if you want to add add styles in the <code>&lt;head&gt;</code> specifically, you need <code>ui.head_content()</code> (Python) / <code>tags$head()</code> (R).</li>
<li><strong>Inline CSS (individual components)</strong>: when you only need to tweak one particular element (e.g. center a particular element), you can append styles directly to that tag. In Python, you can call <a href="https://shiny.posit.co/py/api/core/TagTypes.html#htmltools.Tag.add_style" target="_blank" rel="noopener"><code>.add_style()</code></a>
 on the tag itself. In R, you&rsquo;d use <a href="https://shiny.posit.co/r/reference/shiny/latest/tagappendattributes.html" target="_blank" rel="noopener"><code>tagAppendAttributes()</code></a>
. Note that all HTML tags and a lot of UI components also support <code>style</code> and <code>class</code> arguments, so make sure to check out the documentation if you&rsquo;re looking to make adjustments.</li>
<li><strong>External stylesheets</strong>: if your styles grow beyond a few lines, you&rsquo;ll want to put it in a (S)CSS file inside the <code>www/</code> folder. Shiny will automatically serve that file, and you can include it with <code>tags.link()</code> / <code>include_css()</code> in Python or <code>tags$link()</code> / <code>includeCSS()</code> in R. This keeps your UI code clean and makes your styles easier to manage.</li>
</ul>
<p>In DeckCheck, we add the styles of the <code>.bounce</code> class that we use for our <a href="#loading-experience">bouncing robot</a>
.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-11" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-11-1">Python</a></li>
<li><a href="#tabset-11-2">R</a></li>
</ul>
<div id="tabset-11-1">
<blockquote>
<p><strong>Custom CSS in Shiny</strong></p>
<p>If you want to learn more about styling your app, check out <a href="https://shiny.posit.co/py/docs/ui-customize.html#custom-css" target="_blank" rel="noopener">Custom CSS in Shiny for Python</a>
.</p>
</blockquote>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fillable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## General theme and styles</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## 1. Custom CSS</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">tags</span><span class="o">.</span><span class="n">style</span><span class="p">(</span><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">        .bounce {
</span></span></span><span class="line"><span class="cl"><span class="s2">            animation: bounce 2s infinite;
</span></span></span><span class="line"><span class="cl"><span class="s2">        }
</span></span></span><span class="line"><span class="cl"><span class="s2">        @keyframes bounce {
</span></span></span><span class="line"><span class="cl"><span class="s2">            0%, 100% {
</span></span></span><span class="line"><span class="cl"><span class="s2">                transform: translateY(0);
</span></span></span><span class="line"><span class="cl"><span class="s2">            }
</span></span></span><span class="line"><span class="cl"><span class="s2">            50% {
</span></span></span><span class="line"><span class="cl"><span class="s2">                transform: translateY(-20px);
</span></span></span><span class="line"><span class="cl"><span class="s2">            }
</span></span></span><span class="line"><span class="cl"><span class="s2">        }
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">layout_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="c1">## Sidebar content</span>
</span></span><span class="line"><span class="cl">            <span class="c1">## ...</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="c1">## Main content</span>
</span></span><span class="line"><span class="cl">        <span class="c1">## ...</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Bootswatch theme</span>
</span></span><span class="line"><span class="cl">    <span class="n">theme</span><span class="o">=</span><span class="n">shinyswatch</span><span class="o">.</span><span class="n">theme</span><span class="o">.</span><span class="n">flatly</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Looking for more &ldquo;high-level&rdquo; styling? Check out <a href="https://posit-dev.github.io/brand-yml/" target="_blank" rel="noopener">brand.yml</a>
 for branding with a simple YAML file.</p>
</div>
<div id="tabset-11-2">
<blockquote>
<p><strong>Custom CSS in Shiny</strong></p>
<p>If you want to learn more about styling your app, check out <a href="https://shiny.posit.co/r/articles/build/css" target="_blank" rel="noopener">Custom CSS in Shiny for R</a>
.</p>
</blockquote>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fillable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## Options</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## 1. Bootswatch theme</span>
</span></span><span class="line"><span class="cl">  <span class="n">theme</span> <span class="o">=</span> <span class="nf">bs_theme</span><span class="p">(</span><span class="n">bootswatch</span> <span class="o">=</span> <span class="s">&#34;flatly&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## 2. Custom CSS</span>
</span></span><span class="line"><span class="cl">  <span class="n">tags</span><span class="o">$</span><span class="nf">style</span><span class="p">(</span><span class="nf">HTML</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">.bounce</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">animation</span><span class="o">:</span> <span class="n">bounce</span> <span class="m">2</span><span class="n">s</span> <span class="n">infinite</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">@</span><span class="n">keyframes</span> <span class="n">bounce</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="m">0</span><span class="o">%, 100%</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">transform</span><span class="o">:</span> <span class="nf">translateY</span><span class="p">(</span><span class="m">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="m">50</span>% <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">transform</span><span class="o">:</span> <span class="nf">translateY</span><span class="p">(</span><span class="m">-20</span><span class="n">px</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)),</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## Layout</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## Sidebar content</span>
</span></span><span class="line"><span class="cl">    <span class="n">sidebar</span> <span class="o">=</span> <span class="nf">sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## Sidebar content</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## ...</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## Main content</span>
</span></span><span class="line"><span class="cl">      <span class="c1">## ...</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that these are fairly &ldquo;low-level&rdquo; techniques for achieving custom styling. To learn more about higher-level options you can take a look at <a href="https://rstudio.github.io/bslib/articles/theming/index.html" target="_blank" rel="noopener">theming</a>
 or <a href="https://rstudio.github.io/bslib/articles/brand-yml/index.html" target="_blank" rel="noopener">brand.yml</a>
.</p>
</div>
</div>
<h1 id="the-end-result">The end result
</h1>
<p>If we combine everything we talked about, we end up with a polished DeckCheck app! A sidebar layout with users inputs, a nice loading experience, non-blocking async operations, fancy looking graphs and tables, tooltips, you name it.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-deckcheck-full.gif"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-12" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-12-1">Python</a></li>
<li><a href="#tabset-12-2">R</a></li>
</ul>
<div id="tabset-12-1">
<blockquote>
<p><strong>Get this code from GitHub</strong></p>
<p>You can grab the code directly from <a href="https://github.com/hypebright/the-shiny-side-of-llms/blob/d1094d2774f9d0c213c7ddf6e17f94da706b1b76/Py/deckcheck/app.py" target="_blank" rel="noopener">here</a>
.</p>
</blockquote>
<details class="code-fold">
<summary>See full app</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span><span class="lnt">149
</span><span class="lnt">150
</span><span class="lnt">151
</span><span class="lnt">152
</span><span class="lnt">153
</span><span class="lnt">154
</span><span class="lnt">155
</span><span class="lnt">156
</span><span class="lnt">157
</span><span class="lnt">158
</span><span class="lnt">159
</span><span class="lnt">160
</span><span class="lnt">161
</span><span class="lnt">162
</span><span class="lnt">163
</span><span class="lnt">164
</span><span class="lnt">165
</span><span class="lnt">166
</span><span class="lnt">167
</span><span class="lnt">168
</span><span class="lnt">169
</span><span class="lnt">170
</span><span class="lnt">171
</span><span class="lnt">172
</span><span class="lnt">173
</span><span class="lnt">174
</span><span class="lnt">175
</span><span class="lnt">176
</span><span class="lnt">177
</span><span class="lnt">178
</span><span class="lnt">179
</span><span class="lnt">180
</span><span class="lnt">181
</span><span class="lnt">182
</span><span class="lnt">183
</span><span class="lnt">184
</span><span class="lnt">185
</span><span class="lnt">186
</span><span class="lnt">187
</span><span class="lnt">188
</span><span class="lnt">189
</span><span class="lnt">190
</span><span class="lnt">191
</span><span class="lnt">192
</span><span class="lnt">193
</span><span class="lnt">194
</span><span class="lnt">195
</span><span class="lnt">196
</span><span class="lnt">197
</span><span class="lnt">198
</span><span class="lnt">199
</span><span class="lnt">200
</span><span class="lnt">201
</span><span class="lnt">202
</span><span class="lnt">203
</span><span class="lnt">204
</span><span class="lnt">205
</span><span class="lnt">206
</span><span class="lnt">207
</span><span class="lnt">208
</span><span class="lnt">209
</span><span class="lnt">210
</span><span class="lnt">211
</span><span class="lnt">212
</span><span class="lnt">213
</span><span class="lnt">214
</span><span class="lnt">215
</span><span class="lnt">216
</span><span class="lnt">217
</span><span class="lnt">218
</span><span class="lnt">219
</span><span class="lnt">220
</span><span class="lnt">221
</span><span class="lnt">222
</span><span class="lnt">223
</span><span class="lnt">224
</span><span class="lnt">225
</span><span class="lnt">226
</span><span class="lnt">227
</span><span class="lnt">228
</span><span class="lnt">229
</span><span class="lnt">230
</span><span class="lnt">231
</span><span class="lnt">232
</span><span class="lnt">233
</span><span class="lnt">234
</span><span class="lnt">235
</span><span class="lnt">236
</span><span class="lnt">237
</span><span class="lnt">238
</span><span class="lnt">239
</span><span class="lnt">240
</span><span class="lnt">241
</span><span class="lnt">242
</span><span class="lnt">243
</span><span class="lnt">244
</span><span class="lnt">245
</span><span class="lnt">246
</span><span class="lnt">247
</span><span class="lnt">248
</span><span class="lnt">249
</span><span class="lnt">250
</span><span class="lnt">251
</span><span class="lnt">252
</span><span class="lnt">253
</span><span class="lnt">254
</span><span class="lnt">255
</span><span class="lnt">256
</span><span class="lnt">257
</span><span class="lnt">258
</span><span class="lnt">259
</span><span class="lnt">260
</span><span class="lnt">261
</span><span class="lnt">262
</span><span class="lnt">263
</span><span class="lnt">264
</span><span class="lnt">265
</span><span class="lnt">266
</span><span class="lnt">267
</span><span class="lnt">268
</span><span class="lnt">269
</span><span class="lnt">270
</span><span class="lnt">271
</span><span class="lnt">272
</span><span class="lnt">273
</span><span class="lnt">274
</span><span class="lnt">275
</span><span class="lnt">276
</span><span class="lnt">277
</span><span class="lnt">278
</span><span class="lnt">279
</span><span class="lnt">280
</span><span class="lnt">281
</span><span class="lnt">282
</span><span class="lnt">283
</span><span class="lnt">284
</span><span class="lnt">285
</span><span class="lnt">286
</span><span class="lnt">287
</span><span class="lnt">288
</span><span class="lnt">289
</span><span class="lnt">290
</span><span class="lnt">291
</span><span class="lnt">292
</span><span class="lnt">293
</span><span class="lnt">294
</span><span class="lnt">295
</span><span class="lnt">296
</span><span class="lnt">297
</span><span class="lnt">298
</span><span class="lnt">299
</span><span class="lnt">300
</span><span class="lnt">301
</span><span class="lnt">302
</span><span class="lnt">303
</span><span class="lnt">304
</span><span class="lnt">305
</span><span class="lnt">306
</span><span class="lnt">307
</span><span class="lnt">308
</span><span class="lnt">309
</span><span class="lnt">310
</span><span class="lnt">311
</span><span class="lnt">312
</span><span class="lnt">313
</span><span class="lnt">314
</span><span class="lnt">315
</span><span class="lnt">316
</span><span class="lnt">317
</span><span class="lnt">318
</span><span class="lnt">319
</span><span class="lnt">320
</span><span class="lnt">321
</span><span class="lnt">322
</span><span class="lnt">323
</span><span class="lnt">324
</span><span class="lnt">325
</span><span class="lnt">326
</span><span class="lnt">327
</span><span class="lnt">328
</span><span class="lnt">329
</span><span class="lnt">330
</span><span class="lnt">331
</span><span class="lnt">332
</span><span class="lnt">333
</span><span class="lnt">334
</span><span class="lnt">335
</span><span class="lnt">336
</span><span class="lnt">337
</span><span class="lnt">338
</span><span class="lnt">339
</span><span class="lnt">340
</span><span class="lnt">341
</span><span class="lnt">342
</span><span class="lnt">343
</span><span class="lnt">344
</span><span class="lnt">345
</span><span class="lnt">346
</span><span class="lnt">347
</span><span class="lnt">348
</span><span class="lnt">349
</span><span class="lnt">350
</span><span class="lnt">351
</span><span class="lnt">352
</span><span class="lnt">353
</span><span class="lnt">354
</span><span class="lnt">355
</span><span class="lnt">356
</span><span class="lnt">357
</span><span class="lnt">358
</span><span class="lnt">359
</span><span class="lnt">360
</span><span class="lnt">361
</span><span class="lnt">362
</span><span class="lnt">363
</span><span class="lnt">364
</span><span class="lnt">365
</span><span class="lnt">366
</span><span class="lnt">367
</span><span class="lnt">368
</span><span class="lnt">369
</span><span class="lnt">370
</span><span class="lnt">371
</span><span class="lnt">372
</span><span class="lnt">373
</span><span class="lnt">374
</span><span class="lnt">375
</span><span class="lnt">376
</span><span class="lnt">377
</span><span class="lnt">378
</span><span class="lnt">379
</span><span class="lnt">380
</span><span class="lnt">381
</span><span class="lnt">382
</span><span class="lnt">383
</span><span class="lnt">384
</span><span class="lnt">385
</span><span class="lnt">386
</span><span class="lnt">387
</span><span class="lnt">388
</span><span class="lnt">389
</span><span class="lnt">390
</span><span class="lnt">391
</span><span class="lnt">392
</span><span class="lnt">393
</span><span class="lnt">394
</span><span class="lnt">395
</span><span class="lnt">396
</span><span class="lnt">397
</span><span class="lnt">398
</span><span class="lnt">399
</span><span class="lnt">400
</span><span class="lnt">401
</span><span class="lnt">402
</span><span class="lnt">403
</span><span class="lnt">404
</span><span class="lnt">405
</span><span class="lnt">406
</span><span class="lnt">407
</span><span class="lnt">408
</span><span class="lnt">409
</span><span class="lnt">410
</span><span class="lnt">411
</span><span class="lnt">412
</span><span class="lnt">413
</span><span class="lnt">414
</span><span class="lnt">415
</span><span class="lnt">416
</span><span class="lnt">417
</span><span class="lnt">418
</span><span class="lnt">419
</span><span class="lnt">420
</span><span class="lnt">421
</span><span class="lnt">422
</span><span class="lnt">423
</span><span class="lnt">424
</span><span class="lnt">425
</span><span class="lnt">426
</span><span class="lnt">427
</span><span class="lnt">428
</span><span class="lnt">429
</span><span class="lnt">430
</span><span class="lnt">431
</span><span class="lnt">432
</span><span class="lnt">433
</span><span class="lnt">434
</span><span class="lnt">435
</span><span class="lnt">436
</span><span class="lnt">437
</span><span class="lnt">438
</span><span class="lnt">439
</span><span class="lnt">440
</span><span class="lnt">441
</span><span class="lnt">442
</span><span class="lnt">443
</span><span class="lnt">444
</span><span class="lnt">445
</span><span class="lnt">446
</span><span class="lnt">447
</span><span class="lnt">448
</span><span class="lnt">449
</span><span class="lnt">450
</span><span class="lnt">451
</span><span class="lnt">452
</span><span class="lnt">453
</span><span class="lnt">454
</span><span class="lnt">455
</span><span class="lnt">456
</span><span class="lnt">457
</span><span class="lnt">458
</span><span class="lnt">459
</span><span class="lnt">460
</span><span class="lnt">461
</span><span class="lnt">462
</span><span class="lnt">463
</span><span class="lnt">464
</span><span class="lnt">465
</span><span class="lnt">466
</span><span class="lnt">467
</span><span class="lnt">468
</span><span class="lnt">469
</span><span class="lnt">470
</span><span class="lnt">471
</span><span class="lnt">472
</span><span class="lnt">473
</span><span class="lnt">474
</span><span class="lnt">475
</span><span class="lnt">476
</span><span class="lnt">477
</span><span class="lnt">478
</span><span class="lnt">479
</span><span class="lnt">480
</span><span class="lnt">481
</span><span class="lnt">482
</span><span class="lnt">483
</span><span class="lnt">484
</span><span class="lnt">485
</span><span class="lnt">486
</span><span class="lnt">487
</span><span class="lnt">488
</span><span class="lnt">489
</span><span class="lnt">490
</span><span class="lnt">491
</span><span class="lnt">492
</span><span class="lnt">493
</span><span class="lnt">494
</span><span class="lnt">495
</span><span class="lnt">496
</span><span class="lnt">497
</span><span class="lnt">498
</span><span class="lnt">499
</span><span class="lnt">500
</span><span class="lnt">501
</span><span class="lnt">502
</span><span class="lnt">503
</span><span class="lnt">504
</span><span class="lnt">505
</span><span class="lnt">506
</span><span class="lnt">507
</span><span class="lnt">508
</span><span class="lnt">509
</span><span class="lnt">510
</span><span class="lnt">511
</span><span class="lnt">512
</span><span class="lnt">513
</span><span class="lnt">514
</span><span class="lnt">515
</span><span class="lnt">516
</span><span class="lnt">517
</span><span class="lnt">518
</span><span class="lnt">519
</span><span class="lnt">520
</span><span class="lnt">521
</span><span class="lnt">522
</span><span class="lnt">523
</span><span class="lnt">524
</span><span class="lnt">525
</span><span class="lnt">526
</span><span class="lnt">527
</span><span class="lnt">528
</span><span class="lnt">529
</span><span class="lnt">530
</span><span class="lnt">531
</span><span class="lnt">532
</span><span class="lnt">533
</span><span class="lnt">534
</span><span class="lnt">535
</span><span class="lnt">536
</span><span class="lnt">537
</span><span class="lnt">538
</span><span class="lnt">539
</span><span class="lnt">540
</span><span class="lnt">541
</span><span class="lnt">542
</span><span class="lnt">543
</span><span class="lnt">544
</span><span class="lnt">545
</span><span class="lnt">546
</span><span class="lnt">547
</span><span class="lnt">548
</span><span class="lnt">549
</span><span class="lnt">550
</span><span class="lnt">551
</span><span class="lnt">552
</span><span class="lnt">553
</span><span class="lnt">554
</span><span class="lnt">555
</span><span class="lnt">556
</span><span class="lnt">557
</span><span class="lnt">558
</span><span class="lnt">559
</span><span class="lnt">560
</span><span class="lnt">561
</span><span class="lnt">562
</span><span class="lnt">563
</span><span class="lnt">564
</span><span class="lnt">565
</span><span class="lnt">566
</span><span class="lnt">567
</span><span class="lnt">568
</span><span class="lnt">569
</span><span class="lnt">570
</span><span class="lnt">571
</span><span class="lnt">572
</span><span class="lnt">573
</span><span class="lnt">574
</span><span class="lnt">575
</span><span class="lnt">576
</span><span class="lnt">577
</span><span class="lnt">578
</span><span class="lnt">579
</span><span class="lnt">580
</span><span class="lnt">581
</span><span class="lnt">582
</span><span class="lnt">583
</span><span class="lnt">584
</span><span class="lnt">585
</span><span class="lnt">586
</span><span class="lnt">587
</span><span class="lnt">588
</span><span class="lnt">589
</span><span class="lnt">590
</span><span class="lnt">591
</span><span class="lnt">592
</span><span class="lnt">593
</span><span class="lnt">594
</span><span class="lnt">595
</span><span class="lnt">596
</span><span class="lnt">597
</span><span class="lnt">598
</span><span class="lnt">599
</span><span class="lnt">600
</span><span class="lnt">601
</span><span class="lnt">602
</span><span class="lnt">603
</span><span class="lnt">604
</span><span class="lnt">605
</span><span class="lnt">606
</span><span class="lnt">607
</span><span class="lnt">608
</span><span class="lnt">609
</span><span class="lnt">610
</span><span class="lnt">611
</span><span class="lnt">612
</span><span class="lnt">613
</span><span class="lnt">614
</span><span class="lnt">615
</span><span class="lnt">616
</span><span class="lnt">617
</span><span class="lnt">618
</span><span class="lnt">619
</span><span class="lnt">620
</span><span class="lnt">621
</span><span class="lnt">622
</span><span class="lnt">623
</span><span class="lnt">624
</span><span class="lnt">625
</span><span class="lnt">626
</span><span class="lnt">627
</span><span class="lnt">628
</span><span class="lnt">629
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">App</span><span class="p">,</span> <span class="n">reactive</span><span class="p">,</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span><span class="p">,</span> <span class="n">req</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shinywidgets</span> <span class="kn">import</span> <span class="n">output_widget</span><span class="p">,</span> <span class="n">render_widget</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">shinyswatch</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">plotly.express</span> <span class="k">as</span> <span class="nn">px</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dotenv</span> <span class="kn">import</span> <span class="n">load_dotenv</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatAnthropic</span><span class="p">,</span> <span class="n">interpolate_file</span><span class="p">,</span> <span class="n">interpolate</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pydantic</span> <span class="kn">import</span> <span class="n">BaseModel</span><span class="p">,</span> <span class="n">Field</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Annotated</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Union</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">tempfile</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">shutil</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">warnings</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">traceback</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">load_dotenv</span><span class="p">()</span>  <span class="c1"># Loads key from the .env file</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Path to the current file</span>
</span></span><span class="line"><span class="cl"><span class="n">APP_DIR</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Root directory of the project</span>
</span></span><span class="line"><span class="cl"><span class="n">ROOT_DIR</span> <span class="o">=</span> <span class="n">APP_DIR</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Data Structure</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mi">10</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">PercentType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mf">100.0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">MinutesType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">SlideCount</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ScoringCategory</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">score</span><span class="p">:</span> <span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Score from 1–10.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">justification</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Brief explanation of the score.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">improvements</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Concise, actionable improvements, mentioning slide numbers if applicable.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">score_after_improvements</span><span class="p">:</span> <span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Estimated score after suggested improvements.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DeckAnalysis</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">presentation_title</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;The presentation title.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_slides</span><span class="p">:</span> <span class="n">SlideCount</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_code</span><span class="p">:</span> <span class="n">PercentType</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_images</span><span class="p">:</span> <span class="n">PercentType</span>
</span></span><span class="line"><span class="cl">    <span class="n">estimated_duration_minutes</span><span class="p">:</span> <span class="n">MinutesType</span>
</span></span><span class="line"><span class="cl">    <span class="n">tone</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Brief description of the tone of the presentation.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">clarity</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">relevance</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Assess how well the content matches the audience&#39;s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">visual_design</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">engagement</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">pacing</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Analyze the distribution of content across slides. Are some slides too dense or too light? &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">structure</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">consistency</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Evaluatue whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">accessibility</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Tool definition</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">calculate_slide_metric</span><span class="p">(</span><span class="n">metric</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Union</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">float</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    Calculates the total number of slides, percentage of slides with code blocks,
</span></span></span><span class="line"><span class="cl"><span class="s2">    and percentage of slides with images in a Quarto presentation HTML file.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    Parameters
</span></span></span><span class="line"><span class="cl"><span class="s2">    ----------
</span></span></span><span class="line"><span class="cl"><span class="s2">    metric : str
</span></span></span><span class="line"><span class="cl"><span class="s2">        The metric to calculate: &#34;total_slides&#34; for total number of slides,
</span></span></span><span class="line"><span class="cl"><span class="s2">        &#34;code&#34; for percentage of slides containing fenced code blocks,
</span></span></span><span class="line"><span class="cl"><span class="s2">        or &#34;images&#34; for percentage of slides containing images.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    Returns
</span></span></span><span class="line"><span class="cl"><span class="s2">    -------
</span></span></span><span class="line"><span class="cl"><span class="s2">    float or int
</span></span></span><span class="line"><span class="cl"><span class="s2">        The calculated metric value.
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">html_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./Quarto/docs/my-presentation.html&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">html_file</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;HTML file </span><span class="si">{</span><span class="n">html_file</span><span class="si">}</span><span class="s2"> does not exist.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Read HTML file</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">html_file</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">html_content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Split on &lt;section&gt; tags to get individual slides</span>
</span></span><span class="line"><span class="cl">    <span class="n">slides</span> <span class="o">=</span> <span class="n">html_content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;&lt;section&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_slides</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">metric</span> <span class="o">==</span> <span class="s2">&#34;total_slides&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">total_slides</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">metric</span> <span class="o">==</span> <span class="s2">&#34;code&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">slides_with_code</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="s1">&#39;class=&#34;sourceCode&#34;&#39;</span> <span class="ow">in</span> <span class="n">slide</span> <span class="k">for</span> <span class="n">slide</span> <span class="ow">in</span> <span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="nb">round</span><span class="p">((</span><span class="n">slides_with_code</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">metric</span> <span class="o">==</span> <span class="s2">&#34;images&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">slides_with_image</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="s2">&#34;&lt;img&#34;</span> <span class="ow">in</span> <span class="n">slide</span> <span class="k">for</span> <span class="n">slide</span> <span class="ow">in</span> <span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="nb">round</span><span class="p">((</span><span class="n">slides_with_image</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;Unknown metric: choose &#39;total_slides&#39;, &#39;code&#39;, or &#39;images&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Data wrangling</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">make_frames</span><span class="p">(</span><span class="n">d</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    Convert the dictionary returned by the LLM into a meta dictionary and
</span></span></span><span class="line"><span class="cl"><span class="s2">    a DataFrame for the eval categories.
</span></span></span><span class="line"><span class="cl"><span class="s2">    Parameters
</span></span></span><span class="line"><span class="cl"><span class="s2">    ----------
</span></span></span><span class="line"><span class="cl"><span class="s2">    d : dict
</span></span></span><span class="line"><span class="cl"><span class="s2">        The dictionary returned by the LLM.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    Returns
</span></span></span><span class="line"><span class="cl"><span class="s2">    -------
</span></span></span><span class="line"><span class="cl"><span class="s2">    dict
</span></span></span><span class="line"><span class="cl"><span class="s2">        A dictionary with two keys: &#34;meta&#34; and &#34;evals&#34;. &#34;meta&#34; contains the
</span></span></span><span class="line"><span class="cl"><span class="s2">        meta information as a dictionary, and &#34;evals&#34; contains a DataFrame with
</span></span></span><span class="line"><span class="cl"><span class="s2">        the eval categories.
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">meta_keys</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;presentation_title&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;total_slides&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;percent_with_code&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;percent_with_images&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;estimated_duration_minutes&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;tone&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">meta</span> <span class="o">=</span> <span class="p">{</span><span class="n">k</span><span class="p">:</span> <span class="n">d</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="n">meta_keys</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># eval categories (everything else)</span>
</span></span><span class="line"><span class="cl">    <span class="n">evals</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">d</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">k</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">meta_keys</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># fix typo</span>
</span></span><span class="line"><span class="cl">            <span class="n">evals</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;category&#34;</span><span class="p">:</span> <span class="n">k</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;score&#34;</span><span class="p">:</span> <span class="n">v</span><span class="p">[</span><span class="s2">&#34;score&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;justification&#34;</span><span class="p">:</span> <span class="n">v</span><span class="p">[</span><span class="s2">&#34;justification&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;improvements&#34;</span><span class="p">:</span> <span class="n">v</span><span class="p">[</span><span class="s2">&#34;improvements&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;score_after_improvements&#34;</span><span class="p">:</span> <span class="n">v</span><span class="p">[</span><span class="s2">&#34;score_after_improvements&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">evals_df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">(</span><span class="n">evals</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;meta&#34;</span><span class="p">:</span> <span class="n">meta</span><span class="p">,</span> <span class="s2">&#34;evals&#34;</span><span class="p">:</span> <span class="n">evals_df</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Tooltip helper</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add_line_breaks</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="mi">50</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">text</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">words</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="n">current_line</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">words</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># +1 accounts for the space if current_line isn&#39;t empty</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">current_line</span><span class="p">)</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="n">word</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="mi">1</span> <span class="k">if</span> <span class="n">current_line</span> <span class="k">else</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="n">width</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">current_line</span> <span class="o">+=</span> <span class="p">(</span><span class="s2">&#34; &#34;</span> <span class="k">if</span> <span class="n">current_line</span> <span class="k">else</span> <span class="s2">&#34;&#34;</span><span class="p">)</span> <span class="o">+</span> <span class="n">word</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_line</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">current_line</span> <span class="o">=</span> <span class="n">word</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">current_line</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_line</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s2">&#34;&lt;br&gt;&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Icons</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># SVG icon copied from https://icons.getbootstrap.com/icons/file-slides-fill/</span>
</span></span><span class="line"><span class="cl"><span class="n">file_slides</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; fill=&#34;currentColor&#34; class=&#34;bi bi-file-slides-fill&#34; viewBox=&#34;0 0 16 16&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M7 7.78V5.22c0-.096.106-.156.19-.106l2.13 1.279a.125.125 0 0 1 0 .214l-2.13 1.28A.125.125 0 0 1 7 7.778z&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M12 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2M5 4h6a.5.5 0 0 1 .496.438l.5 4A.5.5 0 0 1 11.5 9h-3v2.016c.863.055 1.5.251 1.5.484 0 .276-.895.5-2 .5s-2-.224-2-.5c0-.233.637-.429 1.5-.484V9h-3a.5.5 0 0 1-.496-.562l.5-4A.5.5 0 0 1 5 4&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/svg&gt;&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">file_slides_loader</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;6em&#34; height=&#34;6em&#34; fill=&#34;currentColor&#34; class=&#34;bi bi-file-slides bounce&#34; viewBox=&#34;0 0 16 16&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M5 4a.5.5 0 0 0-.496.438l-.5 4A.5.5 0 0 0 4.5 9h3v2.016c-.863.055-1.5.251-1.5.484 0 .276.895.5 2 .5s2-.224 2-.5c0-.233-.637-.429-1.5-.484V9h3a.5.5 0 0 0 .496-.562l-.5-4A.5.5 0 0 0 11 4zm2 3.78V5.22c0-.096.106-.156.19-.106l2.13 1.279a.125.125 0 0 1 0 .214l-2.13 1.28A.125.125 0 0 1 7 7.778z&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/svg&gt;&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># SVG icon copied from https://icons.getbootstrap.com/icons/file-code-fill/</span>
</span></span><span class="line"><span class="cl"><span class="n">file_code</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; fill=&#34;currentColor&#34; class=&#34;bi bi-file-code-fill&#34; viewBox=&#34;0 0 16 16&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M12 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2M6.646 5.646a.5.5 0 1 1 .708.708L5.707 8l1.647 1.646a.5.5 0 0 1-.708.708l-2-2a.5.5 0 0 1 0-.708zm2.708 0 2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L10.293 8 8.646 6.354a.5.5 0 1 1 .708-.708&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/svg&gt;&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># SVG icon copied from https://icons.getbootstrap.com/icons/file-image-fill/</span>
</span></span><span class="line"><span class="cl"><span class="n">file_image</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; fill=&#34;currentColor&#34; class=&#34;bi bi-file-image-fill&#34; viewBox=&#34;0 0 16 16&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M4 0h8a2 2 0 0 1 2 2v8.293l-2.73-2.73a1 1 0 0 0-1.52.127l-1.889 2.644-1.769-1.062a1 1 0 0 0-1.222.15L2 12.292V2a2 2 0 0 1 2-2m4.002 5.5a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M10.564 8.27 14 11.708V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-.293l3.578-3.577 2.56 1.536 2.426-3.395z&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/svg&gt;&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># SVG icon copied from https://icons.getbootstrap.com/icons/robot/</span>
</span></span><span class="line"><span class="cl"><span class="n">robot</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;16&#34; height=&#34;16&#34; fill=&#34;currentColor&#34; class=&#34;bi bi-robot&#34; viewBox=&#34;0 0 16 16&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M6 12.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M3 8.062C3 6.76 4.235 5.765 5.53 5.886a26.6 26.6 0 0 0 4.94 0C11.765 5.765 13 6.76 13 8.062v1.157a.93.93 0 0 1-.765.935c-.845.147-2.34.346-4.235.346s-3.39-.2-4.235-.346A.93.93 0 0 1 3 9.219zm4.542-.827a.25.25 0 0 0-.217.068l-.92.9a25 25 0 0 1-1.871-.183.25.25 0 0 0-.068.495c.55.076 1.232.149 2.02.193a.25.25 0 0 0 .189-.071l.754-.736.847 1.71a.25.25 0 0 0 .404.062l.932-.97a25 25 0 0 0 1.922-.188.25.25 0 0 0-.068-.495c-.538.074-1.207.145-1.98.189a.25.25 0 0 0-.166.076l-.754.785-.842-1.7a.25.25 0 0 0-.182-.135&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M8.5 1.866a1 1 0 1 0-1 0V3h-2A4.5 4.5 0 0 0 1 7.5V8a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1v1a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v-.5A4.5 4.5 0 0 0 10.5 3h-2zM14 7.5V13a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7.5A3.5 3.5 0 0 1 5.5 4h5A3.5 3.5 0 0 1 14 7.5&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/svg&gt;&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">robot_loader</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;6em&#34; height=&#34;6em&#34; fill=&#34;currentColor&#34; class=&#34;bi bi-robot text-primary bounce&#34; viewBox=&#34;0 0 16 16&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M6 12.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M3 8.062C3 6.76 4.235 5.765 5.53 5.886a26.6 26.6 0 0 0 4.94 0C11.765 5.765 13 6.76 13 8.062v1.157a.93.93 0 0 1-.765.935c-.845.147-2.34.346-4.235.346s-3.39-.2-4.235-.346A.93.93 0 0 1 3 9.219zm4.542-.827a.25.25 0 0 0-.217.068l-.92.9a25 25 0 0 1-1.871-.183.25.25 0 0 0-.068.495c.55.076 1.232.149 2.02.193a.25.25 0 0 0 .189-.071l.754-.736.847 1.71a.25.25 0 0 0 .404.062l.932-.97a25 25 0 0 0 1.922-.188.25.25 0 0 0-.068-.495c-.538.074-1.207.145-1.98.189a.25.25 0 0 0-.166.076l-.754.785-.842-1.7a.25.25 0 0 0-.182-.135&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M8.5 1.866a1 1 0 1 0-1 0V3h-2A4.5 4.5 0 0 0 1 7.5V8a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1v1a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v-.5A4.5 4.5 0 0 0 10.5 3h-2zM14 7.5V13a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7.5A3.5 3.5 0 0 1 5.5 4h5A3.5 3.5 0 0 1 14 7.5&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/svg&gt;&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># SVG icon copied from https://icons.getbootstrap.com/icons/emoji-frown-fill/</span>
</span></span><span class="line"><span class="cl"><span class="n">sad_icon</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;32&#34; height=&#34;32&#34; fill=&#34;currentColor&#34; class=&#34;bi bi-emoji-frown-fill text-warning&#34; viewBox=&#34;0 0 16 16&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">  &lt;path d=&#34;M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16M7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5m-2.715 5.933a.5.5 0 0 1-.183-.683A4.5 4.5 0 0 1 8 9.5a4.5 4.5 0 0 1 3.898 2.25.5.5 0 0 1-.866.5A3.5 3.5 0 0 0 8 10.5a3.5 3.5 0 0 0-3.032 1.75.5.5 0 0 1-.683.183M10 8c-.552 0-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5S10.552 8 10 8&#34;/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">&lt;/svg&gt;&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Shiny App</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fillable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## General theme and styles</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## 1. Custom CSS</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">tags</span><span class="o">.</span><span class="n">style</span><span class="p">(</span><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">        .bounce {
</span></span></span><span class="line"><span class="cl"><span class="s2">            animation: bounce 2s infinite;
</span></span></span><span class="line"><span class="cl"><span class="s2">        }
</span></span></span><span class="line"><span class="cl"><span class="s2">        @keyframes bounce {
</span></span></span><span class="line"><span class="cl"><span class="s2">            0%, 100% {
</span></span></span><span class="line"><span class="cl"><span class="s2">                transform: translateY(0);
</span></span></span><span class="line"><span class="cl"><span class="s2">            }
</span></span></span><span class="line"><span class="cl"><span class="s2">            50% {
</span></span></span><span class="line"><span class="cl"><span class="s2">                transform: translateY(-20px);
</span></span></span><span class="line"><span class="cl"><span class="s2">            }
</span></span></span><span class="line"><span class="cl"><span class="s2">        }
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">layout_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="c1">## Sidebar content</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">strong</span><span class="p">(</span><span class="s2">&#34;Hey, I am DeckCheck!&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;&#34;&#34;I can help you improve your Quarto presentations by analysing them and suggesting improvements. Before I can do that, I need some information about your presentation.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;file&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Upload your Quarto presentation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">accept</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;.qmd&#34;</span><span class="p">,</span> <span class="s2">&#34;.qmdx&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="n">multiple</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_text_area</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;audience&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Describe your audience&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">height</span><span class="o">=</span><span class="s2">&#34;150px&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">placeholder</span><span class="o">=</span><span class="s2">&#34;e.g. Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_numeric</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;length&#34;</span><span class="p">,</span> <span class="s2">&#34;Time cap for the presentation (minutes)&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="nb">min</span><span class="o">=</span><span class="mi">1</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_text</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;type&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Type of talk&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">placeholder</span><span class="o">=</span><span class="s2">&#34;e.g. lightning talk, workshop, or keynote&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_text</span><span class="p">(</span><span class="s2">&#34;event&#34;</span><span class="p">,</span> <span class="s2">&#34;Event name&#34;</span><span class="p">,</span> <span class="n">placeholder</span><span class="o">=</span><span class="s2">&#34;e.g. posit::conf(2025)&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_task_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;submit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">icon</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">robot</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Analyse presentation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">width</span><span class="o">=</span><span class="mi">400</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="c1">## Main content</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">output_ui</span><span class="p">(</span><span class="s2">&#34;results&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Bootswatch theme</span>
</span></span><span class="line"><span class="cl">    <span class="n">theme</span><span class="o">=</span><span class="n">shinyswatch</span><span class="o">.</span><span class="n">theme</span><span class="o">.</span><span class="n">flatly</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@ui.bind_task_button</span><span class="p">(</span><span class="n">button_id</span><span class="o">=</span><span class="s2">&#34;submit&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.extended_task</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">quarto_task</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="n">temp_dir</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># We&#39;re using an Extended Task to avoid blocking. Note that</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># a temporary directory called within mirai will be</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># different from the one in the &#34;main&#34; Shiny session. Hence,</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># we pass a temp_dir parameter to the task and use that.</span>
</span></span><span class="line"><span class="cl">        <span class="n">qmd_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">temp_dir</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&#34;my-presentation.qmd&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">shutil</span><span class="o">.</span><span class="n">copy</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="n">qmd_file</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Run asyncio subprocess</span>
</span></span><span class="line"><span class="cl">        <span class="n">proc</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_subprocess_exec</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;quarto&#34;</span><span class="p">,</span> <span class="s2">&#34;render&#34;</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">qmd_file</span><span class="p">),</span> <span class="s2">&#34;--to&#34;</span><span class="p">,</span> <span class="s2">&#34;markdown,html&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="n">proc</span><span class="o">.</span><span class="n">communicate</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Return the path to the markdown file</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">Path</span><span class="p">(</span><span class="n">temp_dir</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&#34;my-presentation.md&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@ui.bind_task_button</span><span class="p">(</span><span class="n">button_id</span><span class="o">=</span><span class="s2">&#34;submit&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.extended_task</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">chat_task</span><span class="p">(</span><span class="n">system_prompt</span><span class="p">,</span> <span class="n">markdown_content</span><span class="p">,</span> <span class="n">DeckAnalysis</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># We&#39;re using an extended task to avoid blocking the session and</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># we start a fresh chat session each time.</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># For a feedback loop, we would use a persistent chat session.</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">system_prompt</span><span class="o">=</span><span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Set model parameters (optional)</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat</span><span class="o">.</span><span class="n">set_model_params</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">temperature</span><span class="o">=</span><span class="mf">0.8</span><span class="p">,</span>  <span class="c1"># default is 1</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Register the tool with the chat</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat</span><span class="o">.</span><span class="n">register_tool</span><span class="p">(</span><span class="n">calculate_slide_metric</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Task 1: regular chat to extract meta-data</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat_res1</span> <span class="o">=</span> <span class="k">await</span> <span class="n">chat</span><span class="o">.</span><span class="n">chat_async</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Execute Task 1 (counts). Here are the slides in Markdown: {{ markdown_content }}&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">chat_res1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Task 2: structured chat to further analyse the slides</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat_res2</span> <span class="o">=</span> <span class="k">await</span> <span class="n">chat</span><span class="o">.</span><span class="n">chat_structured_async</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Execute Task 2 (suggestions)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">data_model</span><span class="o">=</span><span class="n">DeckAnalysis</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">chat_res2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">submit</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">run_quarto</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">req</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">file</span><span class="p">()</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Error for testing</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># raise ValueError(&#34;Test error&#34;)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1"># Get file path of the uploaded file</span>
</span></span><span class="line"><span class="cl">            <span class="n">file_path</span> <span class="o">=</span> <span class="nb">input</span><span class="o">.</span><span class="n">file</span><span class="p">()[</span><span class="mi">0</span><span class="p">][</span><span class="s2">&#34;datapath&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="n">quarto_task</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">gettempdir</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Error when trying to invoke quarto_task: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1"># Print stack trace to the console</span>
</span></span><span class="line"><span class="cl">            <span class="n">traceback</span><span class="o">.</span><span class="n">print_exc</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1"># Return value that triggers modal in UI</span>
</span></span><span class="line"><span class="cl">            <span class="n">m</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">modal</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="c1"># Sad bootstrap icon</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">sad_icon</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;The not so Shiny Side of LLMs. Please check that your Quarto presentation is valid and contains slides.&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="c1"># add class to center the content</span>
</span></span><span class="line"><span class="cl">                    <span class="n">class_</span><span class="o">=</span><span class="s2">&#34;text-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">title</span><span class="o">=</span><span class="s2">&#34;Oops, something went wrong!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">easy_close</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">footer</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">modal_button</span><span class="p">(</span><span class="s2">&#34;Close&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">modal_show</span><span class="p">(</span><span class="n">m</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">run_chat</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># require quarto_task result to be available</span>
</span></span><span class="line"><span class="cl">        <span class="n">req</span><span class="p">(</span><span class="n">quarto_task</span><span class="o">.</span><span class="n">result</span><span class="p">()</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Error for testing</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># raise ValueError(&#34;Test error&#34;)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1"># Get the Markdown file path from the complete quarto_task</span>
</span></span><span class="line"><span class="cl">            <span class="n">markdown_file</span> <span class="o">=</span> <span class="n">quarto_task</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Read the generated Markdown file containing the slides</span>
</span></span><span class="line"><span class="cl">            <span class="n">markdown_content</span> <span class="o">=</span> <span class="n">markdown_file</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl">            <span class="n">system_prompt_file</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ROOT_DIR</span> <span class="o">/</span> <span class="s2">&#34;prompts&#34;</span> <span class="o">/</span> <span class="s2">&#34;prompt-analyse-slides-structured-tool.md&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl">            <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">variables</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;audience&#34;</span><span class="p">:</span> <span class="nb">input</span><span class="o">.</span><span class="n">audience</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;length&#34;</span><span class="p">:</span> <span class="nb">input</span><span class="o">.</span><span class="n">length</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="nb">input</span><span class="o">.</span><span class="n">type</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;event&#34;</span><span class="p">:</span> <span class="nb">input</span><span class="o">.</span><span class="n">event</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;markdown_content&#34;</span><span class="p">:</span> <span class="n">markdown_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1"># Trigger the chat task with the provided inputs</span>
</span></span><span class="line"><span class="cl">            <span class="n">chat_task</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span><span class="n">system_prompt</span><span class="p">,</span> <span class="n">markdown_content</span><span class="p">,</span> <span class="n">DeckAnalysis</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Error when trying to invoke chat_task: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1"># Print stack trace to the console</span>
</span></span><span class="line"><span class="cl">            <span class="n">traceback</span><span class="o">.</span><span class="n">print_exc</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1"># Return value that triggers modal in UI</span>
</span></span><span class="line"><span class="cl">            <span class="n">m</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">modal</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="c1"># Sad bootstrap icon</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">sad_icon</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;The not so Shiny Side of LLMs. Unfortunately, chatting didn&#39;t work out. Do you have enough credits left?&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="c1"># add class to center the content</span>
</span></span><span class="line"><span class="cl">                    <span class="n">class_</span><span class="o">=</span><span class="s2">&#34;text-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">title</span><span class="o">=</span><span class="s2">&#34;Oops, something went wrong!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">easy_close</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">footer</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">modal_button</span><span class="p">(</span><span class="s2">&#34;Close&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">modal_show</span><span class="p">(</span><span class="n">m</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@reactive.calc</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">analysis_result</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">res</span> <span class="o">=</span> <span class="n">chat_task</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">res</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">make_frames</span><span class="p">(</span><span class="n">res</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@render.ui</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">results</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">quarto_task</span><span class="o">.</span><span class="n">status</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;running&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">file_slides_loader</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;Processing your Quarto presentation...&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">class_</span><span class="o">=</span><span class="s2">&#34;text-center d-flex flex-column justify-content-center align-items-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">style</span><span class="o">=</span><span class="s2">&#34;height: 100%&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">chat_task</span><span class="o">.</span><span class="n">status</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;running&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ui</span><span class="o">.</span><span class="n">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">robot_loader</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s2">&#34;The LLM is doing its magic...&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">class_</span><span class="o">=</span><span class="s2">&#34;text-center d-flex flex-column justify-content-center align-items-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">style</span><span class="o">=</span><span class="s2">&#34;height: 100%&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">chat_task</span><span class="o">.</span><span class="n">status</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;success&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ui</span><span class="o">.</span><span class="n">TagList</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">layout_column_wrap</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">tooltip</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;Showtime&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">ui</span><span class="o">.</span><span class="n">output_text</span><span class="p">(</span><span class="s2">&#34;showtime&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">showcase</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">file_slides</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">theme</span><span class="o">=</span><span class="s2">&#34;primary&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;Slides are being counted based on the provided Quarto presentation, then an educated guess is made about the time it will take to present them.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">tooltip</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;Code Savviness&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">ui</span><span class="o">.</span><span class="n">output_text</span><span class="p">(</span><span class="s2">&#34;code_savviness&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">showcase</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">file_code</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">theme</span><span class="o">=</span><span class="s2">&#34;primary&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;Code Saviness is calculated based on the slides that contain code chunks. The percentage is the ratio of those slides to total slides.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">tooltip</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;Image Presence&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                            <span class="n">ui</span><span class="o">.</span><span class="n">output_text</span><span class="p">(</span><span class="s2">&#34;image_presence&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">showcase</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">HTML</span><span class="p">(</span><span class="n">file_image</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">theme</span><span class="o">=</span><span class="s2">&#34;primary&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;Image Presence is calculated based on the slides that contain images. The percentage is the ratio of those slides to total slides.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">width</span><span class="o">=</span><span class="mi">1</span> <span class="o">/</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">fill</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ui</span><span class="o">.</span><span class="n">layout_column_wrap</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span><span class="n">ui</span><span class="o">.</span><span class="n">strong</span><span class="p">(</span><span class="s2">&#34;Scores per category&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">output_widget</span><span class="p">(</span><span class="s2">&#34;scores&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">height</span><span class="o">=</span><span class="s2">&#34;600px&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">ui</span><span class="o">.</span><span class="n">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                            <span class="n">ui</span><span class="o">.</span><span class="n">strong</span><span class="p">(</span><span class="s2">&#34;Suggested improvements per category&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                        <span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ui</span><span class="o">.</span><span class="n">output_data_frame</span><span class="p">(</span><span class="s2">&#34;suggested_improvements&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">height</span><span class="o">=</span><span class="s2">&#34;600px&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">width</span><span class="o">=</span><span class="mi">1</span> <span class="o">/</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">fill</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@render_widget</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">scores</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">res</span> <span class="o">=</span> <span class="n">analysis_result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">req</span><span class="p">(</span><span class="n">res</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">evals</span> <span class="o">=</span> <span class="n">res</span><span class="p">[</span><span class="s2">&#34;evals&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">evals</span> <span class="o">=</span> <span class="n">evals</span><span class="o">.</span><span class="n">sort_values</span><span class="p">(</span><span class="s2">&#34;score&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;category&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">Categorical</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;category&#34;</span><span class="p">],</span> <span class="n">categories</span><span class="o">=</span><span class="n">evals</span><span class="p">[</span><span class="s2">&#34;category&#34;</span><span class="p">],</span> <span class="n">ordered</span><span class="o">=</span><span class="kc">True</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># apply to the justification column</span>
</span></span><span class="line"><span class="cl">        <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;justification_wrapped&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;justification&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">apply</span><span class="p">(</span><span class="n">add_line_breaks</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Create a custom tooltip column</span>
</span></span><span class="line"><span class="cl">        <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;tooltip&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;Score: &#34;</span>
</span></span><span class="line"><span class="cl">            <span class="o">+</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;score&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">+</span> <span class="s2">&#34;&lt;br&gt;After improvements: &#34;</span>
</span></span><span class="line"><span class="cl">            <span class="o">+</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;score_after_improvements&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">+</span> <span class="s2">&#34;&lt;br&gt;Justification: &#34;</span>
</span></span><span class="line"><span class="cl">            <span class="o">+</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;justification_wrapped&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">plot</span> <span class="o">=</span> <span class="n">px</span><span class="o">.</span><span class="n">bar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">evals</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">x</span><span class="o">=</span><span class="s2">&#34;score&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">y</span><span class="o">=</span><span class="s2">&#34;category&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">orientation</span><span class="o">=</span><span class="s2">&#34;h&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">labels</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;category&#34;</span><span class="p">:</span> <span class="s2">&#34;Category&#34;</span><span class="p">,</span> <span class="s2">&#34;score&#34;</span><span class="p">:</span> <span class="s2">&#34;Score&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="n">hover_data</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;tooltip&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">},</span>  <span class="c1"># include the tooltip column</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Set hovertemplate to use our custom tooltip</span>
</span></span><span class="line"><span class="cl">        <span class="n">plot</span><span class="o">.</span><span class="n">update_traces</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">hovertemplate</span><span class="o">=</span><span class="s2">&#34;%</span><span class="si">{customdata[0]}</span><span class="s2">&lt;extra&gt;&lt;/extra&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">customdata</span><span class="o">=</span><span class="n">evals</span><span class="p">[[</span><span class="s2">&#34;tooltip&#34;</span><span class="p">]]</span><span class="o">.</span><span class="n">values</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">plot</span><span class="o">.</span><span class="n">update_traces</span><span class="p">(</span><span class="n">marker_color</span><span class="o">=</span><span class="s2">&#34;#18bc9c&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">plot</span><span class="o">.</span><span class="n">update_layout</span><span class="p">(</span><span class="n">template</span><span class="o">=</span><span class="s2">&#34;simple_white&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">plot</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@render.data_frame</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">suggested_improvements</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">res</span> <span class="o">=</span> <span class="n">analysis_result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">req</span><span class="p">(</span><span class="n">res</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">evals</span> <span class="o">=</span> <span class="n">res</span><span class="p">[</span><span class="s2">&#34;evals&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;Gain&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;score_after_improvements&#34;</span><span class="p">]</span> <span class="o">-</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;score&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">result_table</span> <span class="o">=</span> <span class="n">evals</span><span class="o">.</span><span class="n">assign</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">Category</span><span class="o">=</span><span class="n">evals</span><span class="p">[</span><span class="s2">&#34;category&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="o">**</span><span class="p">{</span><span class="s2">&#34;Current score&#34;</span><span class="p">:</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;score&#34;</span><span class="p">]},</span>
</span></span><span class="line"><span class="cl">            <span class="n">Improvements</span><span class="o">=</span><span class="n">evals</span><span class="p">[</span><span class="s2">&#34;improvements&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="o">**</span><span class="p">{</span><span class="s2">&#34;Score After Improvements&#34;</span><span class="p">:</span> <span class="n">evals</span><span class="p">[</span><span class="s2">&#34;score_after_improvements&#34;</span><span class="p">]},</span>
</span></span><span class="line"><span class="cl">            <span class="n">Gain</span><span class="o">=</span><span class="n">evals</span><span class="p">[</span><span class="s2">&#34;Gain&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="p">)[</span>
</span></span><span class="line"><span class="cl">            <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Category&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Current score&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Improvements&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Score After Improvements&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;Gain&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span><span class="o">.</span><span class="n">sort_values</span><span class="p">(</span><span class="s2">&#34;Gain&#34;</span><span class="p">,</span> <span class="n">ascending</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">result_table</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">showtime</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">res</span> <span class="o">=</span> <span class="n">analysis_result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">req</span><span class="p">(</span><span class="n">res</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">res</span><span class="p">[</span><span class="s1">&#39;meta&#39;</span><span class="p">][</span><span class="s1">&#39;estimated_duration_minutes&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2"> minutes&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">code_savviness</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">res</span> <span class="o">=</span> <span class="n">analysis_result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">req</span><span class="p">(</span><span class="n">res</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">res</span><span class="p">[</span><span class="s1">&#39;meta&#39;</span><span class="p">][</span><span class="s1">&#39;percent_with_code&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2"> %&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">image_presence</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">res</span> <span class="o">=</span> <span class="n">analysis_result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">req</span><span class="p">(</span><span class="n">res</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">res</span><span class="p">[</span><span class="s1">&#39;meta&#39;</span><span class="p">][</span><span class="s1">&#39;percent_with_images&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2"> %&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
<div id="tabset-12-2">
<blockquote>
<p><strong>Get this code from GitHub</strong></p>
<p>You can grab the code directly from <a href="https://github.com/hypebright/the-shiny-side-of-llms/blob/d1094d2774f9d0c213c7ddf6e17f94da706b1b76/R/deckcheck/app.R" target="_blank" rel="noopener">here</a>
.</p>
</blockquote>
<details class="code-fold">
<summary>See full app</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span><span class="lnt">149
</span><span class="lnt">150
</span><span class="lnt">151
</span><span class="lnt">152
</span><span class="lnt">153
</span><span class="lnt">154
</span><span class="lnt">155
</span><span class="lnt">156
</span><span class="lnt">157
</span><span class="lnt">158
</span><span class="lnt">159
</span><span class="lnt">160
</span><span class="lnt">161
</span><span class="lnt">162
</span><span class="lnt">163
</span><span class="lnt">164
</span><span class="lnt">165
</span><span class="lnt">166
</span><span class="lnt">167
</span><span class="lnt">168
</span><span class="lnt">169
</span><span class="lnt">170
</span><span class="lnt">171
</span><span class="lnt">172
</span><span class="lnt">173
</span><span class="lnt">174
</span><span class="lnt">175
</span><span class="lnt">176
</span><span class="lnt">177
</span><span class="lnt">178
</span><span class="lnt">179
</span><span class="lnt">180
</span><span class="lnt">181
</span><span class="lnt">182
</span><span class="lnt">183
</span><span class="lnt">184
</span><span class="lnt">185
</span><span class="lnt">186
</span><span class="lnt">187
</span><span class="lnt">188
</span><span class="lnt">189
</span><span class="lnt">190
</span><span class="lnt">191
</span><span class="lnt">192
</span><span class="lnt">193
</span><span class="lnt">194
</span><span class="lnt">195
</span><span class="lnt">196
</span><span class="lnt">197
</span><span class="lnt">198
</span><span class="lnt">199
</span><span class="lnt">200
</span><span class="lnt">201
</span><span class="lnt">202
</span><span class="lnt">203
</span><span class="lnt">204
</span><span class="lnt">205
</span><span class="lnt">206
</span><span class="lnt">207
</span><span class="lnt">208
</span><span class="lnt">209
</span><span class="lnt">210
</span><span class="lnt">211
</span><span class="lnt">212
</span><span class="lnt">213
</span><span class="lnt">214
</span><span class="lnt">215
</span><span class="lnt">216
</span><span class="lnt">217
</span><span class="lnt">218
</span><span class="lnt">219
</span><span class="lnt">220
</span><span class="lnt">221
</span><span class="lnt">222
</span><span class="lnt">223
</span><span class="lnt">224
</span><span class="lnt">225
</span><span class="lnt">226
</span><span class="lnt">227
</span><span class="lnt">228
</span><span class="lnt">229
</span><span class="lnt">230
</span><span class="lnt">231
</span><span class="lnt">232
</span><span class="lnt">233
</span><span class="lnt">234
</span><span class="lnt">235
</span><span class="lnt">236
</span><span class="lnt">237
</span><span class="lnt">238
</span><span class="lnt">239
</span><span class="lnt">240
</span><span class="lnt">241
</span><span class="lnt">242
</span><span class="lnt">243
</span><span class="lnt">244
</span><span class="lnt">245
</span><span class="lnt">246
</span><span class="lnt">247
</span><span class="lnt">248
</span><span class="lnt">249
</span><span class="lnt">250
</span><span class="lnt">251
</span><span class="lnt">252
</span><span class="lnt">253
</span><span class="lnt">254
</span><span class="lnt">255
</span><span class="lnt">256
</span><span class="lnt">257
</span><span class="lnt">258
</span><span class="lnt">259
</span><span class="lnt">260
</span><span class="lnt">261
</span><span class="lnt">262
</span><span class="lnt">263
</span><span class="lnt">264
</span><span class="lnt">265
</span><span class="lnt">266
</span><span class="lnt">267
</span><span class="lnt">268
</span><span class="lnt">269
</span><span class="lnt">270
</span><span class="lnt">271
</span><span class="lnt">272
</span><span class="lnt">273
</span><span class="lnt">274
</span><span class="lnt">275
</span><span class="lnt">276
</span><span class="lnt">277
</span><span class="lnt">278
</span><span class="lnt">279
</span><span class="lnt">280
</span><span class="lnt">281
</span><span class="lnt">282
</span><span class="lnt">283
</span><span class="lnt">284
</span><span class="lnt">285
</span><span class="lnt">286
</span><span class="lnt">287
</span><span class="lnt">288
</span><span class="lnt">289
</span><span class="lnt">290
</span><span class="lnt">291
</span><span class="lnt">292
</span><span class="lnt">293
</span><span class="lnt">294
</span><span class="lnt">295
</span><span class="lnt">296
</span><span class="lnt">297
</span><span class="lnt">298
</span><span class="lnt">299
</span><span class="lnt">300
</span><span class="lnt">301
</span><span class="lnt">302
</span><span class="lnt">303
</span><span class="lnt">304
</span><span class="lnt">305
</span><span class="lnt">306
</span><span class="lnt">307
</span><span class="lnt">308
</span><span class="lnt">309
</span><span class="lnt">310
</span><span class="lnt">311
</span><span class="lnt">312
</span><span class="lnt">313
</span><span class="lnt">314
</span><span class="lnt">315
</span><span class="lnt">316
</span><span class="lnt">317
</span><span class="lnt">318
</span><span class="lnt">319
</span><span class="lnt">320
</span><span class="lnt">321
</span><span class="lnt">322
</span><span class="lnt">323
</span><span class="lnt">324
</span><span class="lnt">325
</span><span class="lnt">326
</span><span class="lnt">327
</span><span class="lnt">328
</span><span class="lnt">329
</span><span class="lnt">330
</span><span class="lnt">331
</span><span class="lnt">332
</span><span class="lnt">333
</span><span class="lnt">334
</span><span class="lnt">335
</span><span class="lnt">336
</span><span class="lnt">337
</span><span class="lnt">338
</span><span class="lnt">339
</span><span class="lnt">340
</span><span class="lnt">341
</span><span class="lnt">342
</span><span class="lnt">343
</span><span class="lnt">344
</span><span class="lnt">345
</span><span class="lnt">346
</span><span class="lnt">347
</span><span class="lnt">348
</span><span class="lnt">349
</span><span class="lnt">350
</span><span class="lnt">351
</span><span class="lnt">352
</span><span class="lnt">353
</span><span class="lnt">354
</span><span class="lnt">355
</span><span class="lnt">356
</span><span class="lnt">357
</span><span class="lnt">358
</span><span class="lnt">359
</span><span class="lnt">360
</span><span class="lnt">361
</span><span class="lnt">362
</span><span class="lnt">363
</span><span class="lnt">364
</span><span class="lnt">365
</span><span class="lnt">366
</span><span class="lnt">367
</span><span class="lnt">368
</span><span class="lnt">369
</span><span class="lnt">370
</span><span class="lnt">371
</span><span class="lnt">372
</span><span class="lnt">373
</span><span class="lnt">374
</span><span class="lnt">375
</span><span class="lnt">376
</span><span class="lnt">377
</span><span class="lnt">378
</span><span class="lnt">379
</span><span class="lnt">380
</span><span class="lnt">381
</span><span class="lnt">382
</span><span class="lnt">383
</span><span class="lnt">384
</span><span class="lnt">385
</span><span class="lnt">386
</span><span class="lnt">387
</span><span class="lnt">388
</span><span class="lnt">389
</span><span class="lnt">390
</span><span class="lnt">391
</span><span class="lnt">392
</span><span class="lnt">393
</span><span class="lnt">394
</span><span class="lnt">395
</span><span class="lnt">396
</span><span class="lnt">397
</span><span class="lnt">398
</span><span class="lnt">399
</span><span class="lnt">400
</span><span class="lnt">401
</span><span class="lnt">402
</span><span class="lnt">403
</span><span class="lnt">404
</span><span class="lnt">405
</span><span class="lnt">406
</span><span class="lnt">407
</span><span class="lnt">408
</span><span class="lnt">409
</span><span class="lnt">410
</span><span class="lnt">411
</span><span class="lnt">412
</span><span class="lnt">413
</span><span class="lnt">414
</span><span class="lnt">415
</span><span class="lnt">416
</span><span class="lnt">417
</span><span class="lnt">418
</span><span class="lnt">419
</span><span class="lnt">420
</span><span class="lnt">421
</span><span class="lnt">422
</span><span class="lnt">423
</span><span class="lnt">424
</span><span class="lnt">425
</span><span class="lnt">426
</span><span class="lnt">427
</span><span class="lnt">428
</span><span class="lnt">429
</span><span class="lnt">430
</span><span class="lnt">431
</span><span class="lnt">432
</span><span class="lnt">433
</span><span class="lnt">434
</span><span class="lnt">435
</span><span class="lnt">436
</span><span class="lnt">437
</span><span class="lnt">438
</span><span class="lnt">439
</span><span class="lnt">440
</span><span class="lnt">441
</span><span class="lnt">442
</span><span class="lnt">443
</span><span class="lnt">444
</span><span class="lnt">445
</span><span class="lnt">446
</span><span class="lnt">447
</span><span class="lnt">448
</span><span class="lnt">449
</span><span class="lnt">450
</span><span class="lnt">451
</span><span class="lnt">452
</span><span class="lnt">453
</span><span class="lnt">454
</span><span class="lnt">455
</span><span class="lnt">456
</span><span class="lnt">457
</span><span class="lnt">458
</span><span class="lnt">459
</span><span class="lnt">460
</span><span class="lnt">461
</span><span class="lnt">462
</span><span class="lnt">463
</span><span class="lnt">464
</span><span class="lnt">465
</span><span class="lnt">466
</span><span class="lnt">467
</span><span class="lnt">468
</span><span class="lnt">469
</span><span class="lnt">470
</span><span class="lnt">471
</span><span class="lnt">472
</span><span class="lnt">473
</span><span class="lnt">474
</span><span class="lnt">475
</span><span class="lnt">476
</span><span class="lnt">477
</span><span class="lnt">478
</span><span class="lnt">479
</span><span class="lnt">480
</span><span class="lnt">481
</span><span class="lnt">482
</span><span class="lnt">483
</span><span class="lnt">484
</span><span class="lnt">485
</span><span class="lnt">486
</span><span class="lnt">487
</span><span class="lnt">488
</span><span class="lnt">489
</span><span class="lnt">490
</span><span class="lnt">491
</span><span class="lnt">492
</span><span class="lnt">493
</span><span class="lnt">494
</span><span class="lnt">495
</span><span class="lnt">496
</span><span class="lnt">497
</span><span class="lnt">498
</span><span class="lnt">499
</span><span class="lnt">500
</span><span class="lnt">501
</span><span class="lnt">502
</span><span class="lnt">503
</span><span class="lnt">504
</span><span class="lnt">505
</span><span class="lnt">506
</span><span class="lnt">507
</span><span class="lnt">508
</span><span class="lnt">509
</span><span class="lnt">510
</span><span class="lnt">511
</span><span class="lnt">512
</span><span class="lnt">513
</span><span class="lnt">514
</span><span class="lnt">515
</span><span class="lnt">516
</span><span class="lnt">517
</span><span class="lnt">518
</span><span class="lnt">519
</span><span class="lnt">520
</span><span class="lnt">521
</span><span class="lnt">522
</span><span class="lnt">523
</span><span class="lnt">524
</span><span class="lnt">525
</span><span class="lnt">526
</span><span class="lnt">527
</span><span class="lnt">528
</span><span class="lnt">529
</span><span class="lnt">530
</span><span class="lnt">531
</span><span class="lnt">532
</span><span class="lnt">533
</span><span class="lnt">534
</span><span class="lnt">535
</span><span class="lnt">536
</span><span class="lnt">537
</span><span class="lnt">538
</span><span class="lnt">539
</span><span class="lnt">540
</span><span class="lnt">541
</span><span class="lnt">542
</span><span class="lnt">543
</span><span class="lnt">544
</span><span class="lnt">545
</span><span class="lnt">546
</span><span class="lnt">547
</span><span class="lnt">548
</span><span class="lnt">549
</span><span class="lnt">550
</span><span class="lnt">551
</span><span class="lnt">552
</span><span class="lnt">553
</span><span class="lnt">554
</span><span class="lnt">555
</span><span class="lnt">556
</span><span class="lnt">557
</span><span class="lnt">558
</span><span class="lnt">559
</span><span class="lnt">560
</span><span class="lnt">561
</span><span class="lnt">562
</span><span class="lnt">563
</span><span class="lnt">564
</span><span class="lnt">565
</span><span class="lnt">566
</span><span class="lnt">567
</span><span class="lnt">568
</span><span class="lnt">569
</span><span class="lnt">570
</span><span class="lnt">571
</span><span class="lnt">572
</span><span class="lnt">573
</span><span class="lnt">574
</span><span class="lnt">575
</span><span class="lnt">576
</span><span class="lnt">577
</span><span class="lnt">578
</span><span class="lnt">579
</span><span class="lnt">580
</span><span class="lnt">581
</span><span class="lnt">582
</span><span class="lnt">583
</span><span class="lnt">584
</span><span class="lnt">585
</span><span class="lnt">586
</span><span class="lnt">587
</span><span class="lnt">588
</span><span class="lnt">589
</span><span class="lnt">590
</span><span class="lnt">591
</span><span class="lnt">592
</span><span class="lnt">593
</span><span class="lnt">594
</span><span class="lnt">595
</span><span class="lnt">596
</span><span class="lnt">597
</span><span class="lnt">598
</span><span class="lnt">599
</span><span class="lnt">600
</span><span class="lnt">601
</span><span class="lnt">602
</span><span class="lnt">603
</span><span class="lnt">604
</span><span class="lnt">605
</span><span class="lnt">606
</span><span class="lnt">607
</span><span class="lnt">608
</span><span class="lnt">609
</span><span class="lnt">610
</span><span class="lnt">611
</span><span class="lnt">612
</span><span class="lnt">613
</span><span class="lnt">614
</span><span class="lnt">615
</span><span class="lnt">616
</span><span class="lnt">617
</span><span class="lnt">618
</span><span class="lnt">619
</span><span class="lnt">620
</span><span class="lnt">621
</span><span class="lnt">622
</span><span class="lnt">623
</span><span class="lnt">624
</span><span class="lnt">625
</span><span class="lnt">626
</span><span class="lnt">627
</span><span class="lnt">628
</span><span class="lnt">629
</span><span class="lnt">630
</span><span class="lnt">631
</span><span class="lnt">632
</span><span class="lnt">633
</span><span class="lnt">634
</span><span class="lnt">635
</span><span class="lnt">636
</span><span class="lnt">637
</span><span class="lnt">638
</span><span class="lnt">639
</span><span class="lnt">640
</span><span class="lnt">641
</span><span class="lnt">642
</span><span class="lnt">643
</span><span class="lnt">644
</span><span class="lnt">645
</span><span class="lnt">646
</span><span class="lnt">647
</span><span class="lnt">648
</span><span class="lnt">649
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">mirai</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ggplot2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ggiraph</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">gdtools</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">purrr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">dplyr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Data Structure</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Reusable scoring category</span>
</span></span><span class="line"><span class="cl"><span class="n">type_scoring_category</span> <span class="o">&lt;-</span> <span class="nf">type_object</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">score</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Score from 1 to 10.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">justification</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Brief explanation of the score.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">improvements</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Concise, actionable improvements, mentioning slide numbers if applicable.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">required</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">score_after_improvements</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimated score after suggested improvements.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Top-level deck analysis object</span>
</span></span><span class="line"><span class="cl"><span class="n">type_deck_analysis</span> <span class="o">&lt;-</span> <span class="nf">type_object</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">presentation_title</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span><span class="n">description</span> <span class="o">=</span> <span class="s">&#34;The presentation title.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">total_slides</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span><span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Total number of slides.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">percent_with_code</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Percentage of slides containing code blocks (0–100).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">percent_with_images</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Percentage of slides containing images (0–100).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">estimated_duration_minutes</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimated presentation length in minutes, assuming ~1 minute per text slide and 2–3 minutes per code or image-heavy slide.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">tone</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Brief description of the presentation tone (e.g., informal, technical, playful).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">clarity</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">relevance</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Asses how well the content matches the audience&#39;s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">visual_design</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">engagement</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">pacing</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Analyze the distribution of content across slides. Are some slides too dense or too light? &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">structure</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">consistency</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Evaluate whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">accessibility</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Tool definition</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; Calculates the total number of slides, percentage of slides with code blocks,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; and percentage of slides with images in a Quarto presentation HTML file.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; @param metric The metric to calculate: &#34;total_slides&#34; for total number of slides,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; &#34;code&#34; for percentage of slides containing fenced code blocks, or &#34;images&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; for percentage of slides containing images.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; @return The calculated metric value.</span>
</span></span><span class="line"><span class="cl"><span class="n">calculate_slide_metric</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">metric</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">html_file</span> <span class="o">&lt;-</span> <span class="nf">paste0</span><span class="p">(</span><span class="nf">tempdir</span><span class="p">(),</span> <span class="s">&#34;/my-presentation.html&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="kr">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">file.exists</span><span class="p">(</span><span class="n">html_file</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">stop</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;HTML file does not exist. Please render your Quarto presentation first.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Read HTML file</span>
</span></span><span class="line"><span class="cl">  <span class="n">html_content</span> <span class="o">&lt;-</span> <span class="nf">readChar</span><span class="p">(</span><span class="n">html_file</span><span class="p">,</span> <span class="nf">file.size</span><span class="p">(</span><span class="n">html_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Split on &lt;section&gt; tags to get individual slides</span>
</span></span><span class="line"><span class="cl">  <span class="n">slides</span> <span class="o">&lt;-</span> <span class="nf">unlist</span><span class="p">(</span><span class="nf">strsplit</span><span class="p">(</span><span class="n">html_content</span><span class="p">,</span> <span class="s">&#34;&lt;section&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">total_slides</span> <span class="o">&lt;-</span> <span class="nf">length</span><span class="p">(</span><span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">if</span> <span class="p">(</span><span class="n">metric</span> <span class="o">==</span> <span class="s">&#34;total_slides&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">&lt;-</span> <span class="n">total_slides</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="kr">else</span> <span class="kr">if</span> <span class="p">(</span><span class="n">metric</span> <span class="o">==</span> <span class="s">&#34;code&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Count slides where we see the &#34;sourceCode&#34; class</span>
</span></span><span class="line"><span class="cl">    <span class="n">slides_with_code</span> <span class="o">&lt;-</span> <span class="nf">sum</span><span class="p">(</span><span class="nf">grepl</span><span class="p">(</span><span class="s">&#39;class=&#34;sourceCode&#34;&#39;</span><span class="p">,</span> <span class="n">slides</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">&lt;-</span> <span class="nf">round</span><span class="p">((</span><span class="n">slides_with_code</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="m">100</span><span class="p">,</span> <span class="m">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="kr">else</span> <span class="kr">if</span> <span class="p">(</span><span class="n">metric</span> <span class="o">==</span> <span class="s">&#34;images&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Count slides with image tag</span>
</span></span><span class="line"><span class="cl">    <span class="n">slides_with_image</span> <span class="o">&lt;-</span> <span class="nf">sum</span><span class="p">(</span><span class="nf">grepl</span><span class="p">(</span><span class="s">&#39;&lt;img&#39;</span><span class="p">,</span> <span class="n">slides</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">&lt;-</span> <span class="nf">round</span><span class="p">((</span><span class="n">slides_with_image</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="m">100</span><span class="p">,</span> <span class="m">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="kr">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">stop</span><span class="p">(</span><span class="s">&#34;Unknown metric: choose &#39;total_slides&#39;, &#39;code&#39;, or &#39;images&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">return</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Optionally, to avoid manual work:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># create_tool_def(calculate_slide_metric)</span>
</span></span><span class="line"><span class="cl"><span class="n">calculate_slide_metric</span> <span class="o">&lt;-</span> <span class="nf">tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">calculate_slide_metric</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Returns the calculated metric value&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">metric</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#39;The metric to calculate: &#34;total_slides&#34; for total number of slides, 
</span></span></span><span class="line"><span class="cl"><span class="s">      &#34;code&#34; for percentage of slides containing fenced code blocks, or &#34;images&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">      for percentage of slides containing images.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">required</span> <span class="o">=</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Data wrangling</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; Convert named list to tidy data frames</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; @param named_list Named list as returned by the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; @return List of two tibbles: meta and evals</span>
</span></span><span class="line"><span class="cl"><span class="n">make_frames</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">named_list</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">meta</span> <span class="o">&lt;-</span> <span class="nf">tibble</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">presentation_title</span> <span class="o">=</span> <span class="n">named_list</span><span class="o">$</span><span class="n">presentation_title</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_slides</span> <span class="o">=</span> <span class="n">named_list</span><span class="o">$</span><span class="n">total_slides</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_code</span> <span class="o">=</span> <span class="n">named_list</span><span class="o">$</span><span class="n">percent_with_code</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_images</span> <span class="o">=</span> <span class="n">named_list</span><span class="o">$</span><span class="n">percent_with_images</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">estimated_duration_minutes</span> <span class="o">=</span> <span class="n">named_list</span><span class="o">$</span><span class="n">estimated_duration_minutes</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">tone</span> <span class="o">=</span> <span class="n">named_list</span><span class="o">$</span><span class="n">tone</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Evaluation sections (clarity, relevance, etc.)</span>
</span></span><span class="line"><span class="cl">  <span class="n">eval_sections</span> <span class="o">&lt;-</span> <span class="nf">c</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;clarity&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;relevance&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;visual_design&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;engagement&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;pacing&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;structure&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;consistency&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;accessibility&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">evals</span> <span class="o">&lt;-</span> <span class="nf">map_dfr</span><span class="p">(</span><span class="n">eval_sections</span><span class="p">,</span> <span class="kr">function</span><span class="p">(</span><span class="n">section</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">as_tibble</span><span class="p">(</span><span class="n">named_list[[section]][[1]]</span><span class="p">)</span> <span class="o">%&gt;%</span>
</span></span><span class="line"><span class="cl">      <span class="nf">mutate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">category</span> <span class="o">=</span> <span class="n">section</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">.before</span> <span class="o">=</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Final tidy data frame</span>
</span></span><span class="line"><span class="cl">  <span class="n">final</span> <span class="o">&lt;-</span> <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">meta</span> <span class="o">=</span> <span class="n">meta</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">evals</span> <span class="o">=</span> <span class="n">evals</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Other helpers</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Register Monteserrat font</span>
</span></span><span class="line"><span class="cl"><span class="nf">register_gfont</span><span class="p">(</span><span class="s">&#34;Lato&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Shiny App</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ======================</span>
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fillable</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## Options</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## Busy indication is enabled by default for UI created with bslib (which we use here),</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## but must be enabled otherwise with useBusyIndicators().</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## useBusyIndicators(),</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## General theme and styles</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## 1. Bootswatch theme</span>
</span></span><span class="line"><span class="cl">  <span class="n">theme</span> <span class="o">=</span> <span class="nf">bs_theme</span><span class="p">(</span><span class="n">bootswatch</span> <span class="o">=</span> <span class="s">&#34;flatly&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## 2. Custom CSS</span>
</span></span><span class="line"><span class="cl">  <span class="n">tags</span><span class="o">$</span><span class="nf">style</span><span class="p">(</span><span class="nf">HTML</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">    .bounce {
</span></span></span><span class="line"><span class="cl"><span class="s">      animation: bounce 2s infinite;
</span></span></span><span class="line"><span class="cl"><span class="s">    }
</span></span></span><span class="line"><span class="cl"><span class="s">    @keyframes bounce {
</span></span></span><span class="line"><span class="cl"><span class="s">      0%, 100% {
</span></span></span><span class="line"><span class="cl"><span class="s">        transform: translateY(0);
</span></span></span><span class="line"><span class="cl"><span class="s">      }
</span></span></span><span class="line"><span class="cl"><span class="s">      50% {
</span></span></span><span class="line"><span class="cl"><span class="s">        transform: translateY(-20px);
</span></span></span><span class="line"><span class="cl"><span class="s">      }
</span></span></span><span class="line"><span class="cl"><span class="s">    }
</span></span></span><span class="line"><span class="cl"><span class="s">  &#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)),</span>
</span></span><span class="line"><span class="cl">  <span class="c1">## Layout</span>
</span></span><span class="line"><span class="cl">  <span class="nf">layout_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## Sidebar content</span>
</span></span><span class="line"><span class="cl">    <span class="n">sidebar</span> <span class="o">=</span> <span class="nf">sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">width</span> <span class="o">=</span> <span class="m">400</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Open sidebar on mobile devices and show above content</span>
</span></span><span class="line"><span class="cl">      <span class="n">open</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span><span class="n">mobile</span> <span class="o">=</span> <span class="s">&#34;always-above&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">strong</span><span class="p">(</span><span class="nf">p</span><span class="p">(</span><span class="s">&#34;Hey, I am DeckCheck!&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">p</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;I can help you improve your Quarto presentations by analysing them and suggesting improvements.
</span></span></span><span class="line"><span class="cl"><span class="s">      Before I can do that, I need some information about your presentation.&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">fileInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">inputId</span> <span class="o">=</span> <span class="s">&#34;file&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Upload your Quarto presentation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">accept</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;.qmd&#34;</span><span class="p">,</span> <span class="s">&#34;.qmdx&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">textAreaInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">inputId</span> <span class="o">=</span> <span class="s">&#34;audience&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">height</span> <span class="o">=</span> <span class="s">&#34;150px&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Describe your audience&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">placeholder</span> <span class="o">=</span> <span class="s">&#34;e.g. Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">numericInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">inputId</span> <span class="o">=</span> <span class="s">&#34;length&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Time cap for the presentation (minutes)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">value</span> <span class="o">=</span> <span class="m">10</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">textInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">inputId</span> <span class="o">=</span> <span class="s">&#34;type&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Type of talk&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">placeholder</span> <span class="o">=</span> <span class="s">&#34;e.g. lightning talk, workshop, or keynote&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">textInput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">inputId</span> <span class="o">=</span> <span class="s">&#34;event&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="s">&#34;Event name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">placeholder</span> <span class="o">=</span> <span class="s">&#34;e.g. posit::conf(2025)&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nf">input_task_button</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;submit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">label</span> <span class="o">=</span> <span class="n">shiny</span><span class="o">::</span><span class="nf">tagList</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;robot&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;Analyse presentation&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">label_busy</span> <span class="o">=</span> <span class="s">&#34;DeckCheck is checking...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">type</span> <span class="o">=</span> <span class="s">&#34;default&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## Main content</span>
</span></span><span class="line"><span class="cl">    <span class="nf">uiOutput</span><span class="p">(</span><span class="s">&#34;results&#34;</span><span class="p">,</span> <span class="n">height</span> <span class="o">=</span> <span class="s">&#34;100%&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">quarto_task</span> <span class="o">&lt;-</span> <span class="n">ExtendedTask</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span><span class="kr">function</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="n">temp_dir</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># We&#39;re using an Extended Task to avoid blocking. Note that</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># a temporary directory called within mirai will be</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># different from the one in the &#34;main&#34; Shiny session. Hence,</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># we pass a temp_dir parameter to the task and use that.</span>
</span></span><span class="line"><span class="cl">    <span class="nf">mirai</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">qmd_file</span> <span class="o">&lt;-</span> <span class="nf">file.path</span><span class="p">(</span><span class="n">temp_dir</span><span class="p">,</span> <span class="s">&#34;my-presentation.qmd&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nf">file.copy</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="n">qmd_file</span><span class="p">,</span> <span class="n">overwrite</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">quarto</span><span class="o">::</span><span class="nf">quarto_render</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">input</span> <span class="o">=</span> <span class="n">qmd_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">output_format</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;markdown&#34;</span><span class="p">,</span> <span class="s">&#34;html&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Return the path to the markdown file</span>
</span></span><span class="line"><span class="cl">        <span class="nf">file.path</span><span class="p">(</span><span class="n">temp_dir</span><span class="p">,</span> <span class="s">&#34;my-presentation.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Use the same environment as the Shiny app</span>
</span></span><span class="line"><span class="cl">      <span class="nf">environment</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">bind_task_button</span><span class="p">(</span><span class="s">&#34;submit&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">chat_task</span> <span class="o">&lt;-</span> <span class="n">ExtendedTask</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span><span class="kr">function</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">markdown_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_deck_analysis</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># We&#39;re using an Extended Task to avoid blocking the session and</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># we start a fresh chat session each time.</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># For a feedback loop, we would use a persistent chat session.</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">params</span> <span class="o">=</span> <span class="nf">params</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">temperature</span> <span class="o">=</span> <span class="m">0.8</span> <span class="c1"># default is 1</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Register the tool with the chat</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat</span><span class="o">$</span><span class="nf">register_tool</span><span class="p">(</span><span class="n">calculate_slide_metric</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Task 1: regular chat to extract meta-data</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat_res</span> <span class="o">&lt;-</span> <span class="n">chat</span><span class="o">$</span><span class="nf">chat_async</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Execute Task 1 (counts). Here are the slides in Markdown: {{ markdown_content }}&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">chat_res</span><span class="o">$</span><span class="nf">then</span><span class="p">(</span><span class="kr">function</span><span class="p">(</span><span class="n">res</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Print the response from Task 1</span>
</span></span><span class="line"><span class="cl">      <span class="nf">cat</span><span class="p">(</span><span class="s">&#34;Response from Task 1:\n&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nf">cat</span><span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="s">&#34;\n\n&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1"># Execute next task</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Task 2: structured chat to further analyse the slides</span>
</span></span><span class="line"><span class="cl">      <span class="n">chat</span><span class="o">$</span><span class="nf">chat_structured_async</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Execute Task 2 (suggestions)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">type</span> <span class="o">=</span> <span class="n">type_deck_analysis</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">bind_task_button</span><span class="p">(</span><span class="s">&#34;submit&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observe</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">file</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">audience</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">length</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">type</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">event</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">tryCatch</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Error for testing</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># stop(&#34;This is a test error.&#34;)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Get file path of the uploaded file</span>
</span></span><span class="line"><span class="cl">        <span class="n">file_path</span> <span class="o">&lt;-</span> <span class="n">input</span><span class="o">$</span><span class="n">file</span><span class="o">$</span><span class="n">datapath</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">quarto_task</span><span class="o">$</span><span class="nf">invoke</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="n">temp_dir</span> <span class="o">=</span> <span class="nf">tempdir</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="n">error</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">rlang</span><span class="o">::</span><span class="nf">warn</span><span class="p">(</span><span class="nf">paste</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;Error when trying to invoke quarto_task:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">e</span><span class="o">$</span><span class="n">message</span>
</span></span><span class="line"><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1"># Print stack trace</span>
</span></span><span class="line"><span class="cl">        <span class="nf">print</span><span class="p">(</span><span class="n">rlang</span><span class="o">::</span><span class="nf">trace_back</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Show modal to the user</span>
</span></span><span class="line"><span class="cl">        <span class="nf">showModal</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="nf">modalDialog</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;Oops! Something went wrong&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s">&#34;emoji-frown-fill&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">size</span> <span class="o">=</span> <span class="s">&#34;2em&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-warning&#34;</span>
</span></span><span class="line"><span class="cl">              <span class="p">),</span>
</span></span><span class="line"><span class="cl">              <span class="nf">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">              <span class="nf">p</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s">&#34;The not so Shiny Side of LLMs. Please check that your Quarto presentation is valid and contains slides.&#34;</span>
</span></span><span class="line"><span class="cl">              <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">easyClose</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">footer</span> <span class="o">=</span> <span class="nf">modalButton</span><span class="p">(</span><span class="s">&#34;Close&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nf">bindEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">submit</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observe</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="n">quarto_task</span><span class="o">$</span><span class="nf">result</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="nf">tryCatch</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Error for testing</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># stop(&#34;This is a test error.&#34;)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Get the Markdown file path from the completed quarto_task</span>
</span></span><span class="line"><span class="cl">        <span class="n">markdown_file</span> <span class="o">&lt;-</span> <span class="n">quarto_task</span><span class="o">$</span><span class="nf">result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Read the generated Markdown file containing the slides</span>
</span></span><span class="line"><span class="cl">        <span class="n">markdown_content</span> <span class="o">&lt;-</span> <span class="nf">readChar</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">,</span> <span class="nf">file.size</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl">        <span class="n">system_prompt_file</span> <span class="o">&lt;-</span> <span class="n">here</span><span class="o">::</span><span class="nf">here</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;prompts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;prompt-analyse-slides-structured-tool.md&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl">        <span class="n">system_prompt</span> <span class="o">&lt;-</span> <span class="nf">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">path</span> <span class="o">=</span> <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">audience</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">audience</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">length</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">length</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">type</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">type</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">event</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">event</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Trigger the chat task with the provided inputs</span>
</span></span><span class="line"><span class="cl">        <span class="n">chat_task</span><span class="o">$</span><span class="nf">invoke</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">markdown_content</span> <span class="o">=</span> <span class="n">markdown_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">type_deck_analysis</span> <span class="o">=</span> <span class="n">type_deck_analysis</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="n">error</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">rlang</span><span class="o">::</span><span class="nf">warn</span><span class="p">(</span><span class="nf">paste</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;Error when trying to invoke chat_task:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">e</span><span class="o">$</span><span class="n">message</span>
</span></span><span class="line"><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1"># Print stack trace</span>
</span></span><span class="line"><span class="cl">        <span class="nf">print</span><span class="p">(</span><span class="n">rlang</span><span class="o">::</span><span class="nf">trace_back</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Show modal to the user</span>
</span></span><span class="line"><span class="cl">        <span class="nf">showModal</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="nf">modalDialog</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;Oops! Something went wrong&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s">&#34;emoji-frown-fill&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">size</span> <span class="o">=</span> <span class="s">&#34;2em&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-warning&#34;</span>
</span></span><span class="line"><span class="cl">              <span class="p">),</span>
</span></span><span class="line"><span class="cl">              <span class="nf">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">              <span class="nf">p</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s">&#34;The not so Shiny Side of LLMs. Unfortunately, chatting didn&#39;t work out. Do you have enough credits left?&#34;</span>
</span></span><span class="line"><span class="cl">              <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">easyClose</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">footer</span> <span class="o">=</span> <span class="nf">modalButton</span><span class="p">(</span><span class="s">&#34;Close&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Reactive expression to hold the analysis result</span>
</span></span><span class="line"><span class="cl">  <span class="n">analysis_result</span> <span class="o">&lt;-</span> <span class="nf">reactive</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">named_list</span> <span class="o">&lt;-</span> <span class="n">chat_task</span><span class="o">$</span><span class="nf">result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">make_frames</span><span class="p">(</span><span class="n">named_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">results</span> <span class="o">&lt;-</span> <span class="nf">renderUI</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="n">quarto_task</span><span class="o">$</span><span class="nf">status</span><span class="p">()</span> <span class="o">==</span> <span class="s">&#34;running&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-center d-flex flex-column justify-content-center align-items-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">style</span> <span class="o">=</span> <span class="s">&#34;height: 100%;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;file-slides&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">size</span> <span class="o">=</span> <span class="s">&#34;6em&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-primary bounce&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Processing your Quarto presentation...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="kr">else</span> <span class="kr">if</span> <span class="p">(</span><span class="n">chat_task</span><span class="o">$</span><span class="nf">status</span><span class="p">()</span> <span class="o">==</span> <span class="s">&#34;running&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">div</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-center d-flex flex-column justify-content-center align-items-center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">style</span> <span class="o">=</span> <span class="s">&#34;height: 100%;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;robot&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">size</span> <span class="o">=</span> <span class="s">&#34;6em&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">class</span> <span class="o">=</span> <span class="s">&#34;text-primary bounce&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">br</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">p</span><span class="p">(</span><span class="s">&#34;The LLM is doing its magic...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="kr">else</span> <span class="kr">if</span> <span class="p">(</span><span class="n">chat_task</span><span class="o">$</span><span class="nf">status</span><span class="p">()</span> <span class="o">==</span> <span class="s">&#34;success&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">tagList</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">layout_column_wrap</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">fill</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="c1">### Value boxes for metrics</span>
</span></span><span class="line"><span class="cl">          <span class="nf">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">title</span> <span class="o">=</span> <span class="nf">tooltip</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="nf">span</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s">&#34;Showtime &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;question-circle-fill&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">              <span class="p">),</span>
</span></span><span class="line"><span class="cl">              <span class="s">&#34;Slides are being counted based on the provided Quarto presentation, then an educated guess is made about the time it will take to present them.&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;showtime&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;file-slides&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">theme</span> <span class="o">=</span> <span class="s">&#34;primary&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">title</span> <span class="o">=</span> <span class="nf">tooltip</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="nf">span</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s">&#34;Code Savviness &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;question-circle-fill&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">              <span class="p">),</span>
</span></span><span class="line"><span class="cl">              <span class="s">&#34;Code Saviness is calculated based on the slides that contain code chunks. The percentage is the ratio of those slides to total slides.&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;code_savviness&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;file-code&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">theme</span> <span class="o">=</span> <span class="s">&#34;primary&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="nf">value_box</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">title</span> <span class="o">=</span> <span class="nf">tooltip</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="nf">span</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s">&#34;Image Presence &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;question-circle-fill&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">              <span class="p">),</span>
</span></span><span class="line"><span class="cl">              <span class="s">&#34;Image Presence is calculated based on the slides that contain images. The percentage is the ratio of those slides to total slides.&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;image_presence&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">showcase</span> <span class="o">=</span> <span class="n">bsicons</span><span class="o">::</span><span class="nf">bs_icon</span><span class="p">(</span><span class="s">&#34;file-image&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">theme</span> <span class="o">=</span> <span class="s">&#34;primary&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">layout_column_wrap</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">fill</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">width</span> <span class="o">=</span> <span class="m">1</span> <span class="o">/</span> <span class="m">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="c1">### Graph with scoring metrics</span>
</span></span><span class="line"><span class="cl">          <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">height</span> <span class="o">=</span> <span class="m">600</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nf">card_header</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="nf">strong</span><span class="p">(</span><span class="s">&#34;Scores per category&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">girafeOutput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">outputId</span> <span class="o">=</span> <span class="s">&#34;scores&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="c1">### Table with suggested improvements</span>
</span></span><span class="line"><span class="cl">          <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">height</span> <span class="o">=</span> <span class="m">600</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nf">card_header</span><span class="p">(</span><span class="nf">strong</span><span class="p">(</span><span class="s">&#34;Suggested improvements per category&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">            <span class="nf">tableOutput</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">              <span class="n">outputId</span> <span class="o">=</span> <span class="s">&#34;suggested_improvements&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">scores</span> <span class="o">&lt;-</span> <span class="nf">renderGirafe</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="nf">analysis_result</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">evals</span> <span class="o">&lt;-</span> <span class="nf">analysis_result</span><span class="p">()</span><span class="o">$</span><span class="n">evals</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Order by score</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">&lt;-</span> <span class="n">evals</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">arrange</span><span class="p">(</span><span class="n">score</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">mutate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">category</span> <span class="o">=</span> <span class="nf">factor</span><span class="p">(</span><span class="n">category</span><span class="p">,</span> <span class="n">levels</span> <span class="o">=</span> <span class="n">category</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">tooltip</span> <span class="o">=</span> <span class="nf">paste0</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;Score: &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">score</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;\n&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;After improvements: &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">score_after_improvements</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;\n&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s">&#34;Justification: &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">justification</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">select</span><span class="p">(</span><span class="n">category</span><span class="p">,</span> <span class="n">score</span><span class="p">,</span> <span class="n">score_after_improvements</span><span class="p">,</span> <span class="n">tooltip</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">p</span> <span class="o">&lt;-</span> <span class="nf">ggplot</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nf">aes</span><span class="p">(</span><span class="n">x</span> <span class="o">=</span> <span class="n">category</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">score</span><span class="p">,</span> <span class="n">tooltip</span> <span class="o">=</span> <span class="n">tooltip</span><span class="p">,</span> <span class="n">data_id</span> <span class="o">=</span> <span class="n">category</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">      <span class="nf">geom_bar_interactive</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">stat</span> <span class="o">=</span> <span class="s">&#34;identity&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">fill</span> <span class="o">=</span> <span class="s">&#34;#18bc9c&#34;</span> <span class="c1"># Success color of Flatly theme</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">      <span class="nf">labs</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">x</span> <span class="o">=</span> <span class="s">&#34;Category&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">y</span> <span class="o">=</span> <span class="s">&#34;Score&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Flip to make horizontal bar chart</span>
</span></span><span class="line"><span class="cl">      <span class="nf">coord_flip</span><span class="p">()</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">      <span class="nf">theme_minimal</span><span class="p">(</span><span class="n">base_family</span> <span class="o">=</span> <span class="s">&#34;Lato&#34;</span><span class="p">,</span> <span class="n">base_size</span> <span class="o">=</span> <span class="m">14</span><span class="p">)</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">      <span class="nf">theme</span><span class="p">(</span><span class="n">legend.position</span> <span class="o">=</span> <span class="s">&#34;none&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">girafe</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">ggobj</span> <span class="o">=</span> <span class="n">p</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">options</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">opts_selection</span><span class="p">(</span><span class="n">type</span> <span class="o">=</span> <span class="s">&#34;none&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">opts_sizing</span><span class="p">(</span><span class="n">rescale</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">opts_tooltip</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">css</span> <span class="o">=</span> <span class="s">&#34;background-color: #f0f0f0; color: #333; padding: 5px; border-radius: 5px; width: 200px;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">opts_hover</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">css</span> <span class="o">=</span> <span class="s">&#34;.&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nf">opts_hover_inv</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">css</span> <span class="o">=</span> <span class="s">&#34;opacity: 0.5;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">suggested_improvements</span> <span class="o">&lt;-</span> <span class="nf">renderTable</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="nf">analysis_result</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">evals</span> <span class="o">&lt;-</span> <span class="nf">analysis_result</span><span class="p">()</span><span class="o">$</span><span class="n">evals</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">evals</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">arrange</span><span class="p">(</span><span class="n">score</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">mutate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">Gain</span> <span class="o">=</span> <span class="n">score_after_improvements</span> <span class="o">-</span> <span class="n">score</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">select</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">Category</span> <span class="o">=</span> <span class="n">category</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">`Current score`</span> <span class="o">=</span> <span class="n">score</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">Improvements</span> <span class="o">=</span> <span class="n">improvements</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">`Score After Improvements`</span> <span class="o">=</span> <span class="n">score_after_improvements</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">Gain</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nf">arrange</span><span class="p">(</span><span class="nf">desc</span><span class="p">(</span><span class="n">Gain</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Update value boxes based on analysis_result()$meta</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">showtime</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="nf">analysis_result</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="nf">paste0</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">analysis_result</span><span class="p">()</span><span class="o">$</span><span class="n">meta</span><span class="o">$</span><span class="n">estimated_duration_minutes</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34; minutes&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">code_savviness</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="nf">analysis_result</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="nf">paste0</span><span class="p">(</span><span class="nf">analysis_result</span><span class="p">()</span><span class="o">$</span><span class="n">meta</span><span class="o">$</span><span class="n">percent_with_code</span><span class="p">,</span> <span class="s">&#34; %&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">image_presence</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">req</span><span class="p">(</span><span class="nf">analysis_result</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="nf">paste0</span><span class="p">(</span><span class="nf">analysis_result</span><span class="p">()</span><span class="o">$</span><span class="n">meta</span><span class="o">$</span><span class="n">percent_with_images</span><span class="p">,</span> <span class="s">&#34; %&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
</div>
<h1 id="ready-for-the-world-deployment">Ready for the world: deployment
</h1>
<blockquote>
<p><strong>TL;DR</strong></p>
<p>There are various ways to make your Shiny app available to a wider audience via the web. Posit offers the following solutions:</p>
<ul>
<li><a href="https://connect.posit.cloud/" target="_blank" rel="noopener">Posit Connect Cloud</a>
 (cloud hosting, free and paid plans)</li>
<li><a href="http://shinyapps.io" target="_blank" rel="noopener">shinyapps.io</a>
 (cloud hosting, free and paid plans)</li>
<li><a href="https://rstudio.com/products/shiny/shiny-server/" target="_blank" rel="noopener">Shiny Server</a>
 (self-hosted, free and open source)</li>
<li><a href="https://www.rstudio.com/products/connect/" target="_blank" rel="noopener">Posit Connect</a>
 (self-hosted, professional plans)</li>
</ul>
</blockquote>
<p>We didn&rsquo;t develop DeckCheck all for ourselves: we want to help every presenting Data Scientist with a polished presentation! So it&rsquo;s time to put our Shiny app on the web. Luckily, there are a couple of main ways to do it:</p>
<ol>
<li><strong>Cloud hosting:</strong> the &ldquo;click-and-play&rdquo; option</li>
<li><strong>Self-hosted deployments:</strong> the &ldquo;I like control and flexibility&rdquo; option</li>
</ol>
<p>Cloud hosting is perfect if you just want your app to be live and don&rsquo;t feel like babysitting a server (although babysitting can be fun!). A good place to start is <a href="https://connect.posit.cloud/" target="_blank" rel="noopener">Posit Connect Cloud</a>
 or <a href="https://www.shinyapps.io/" target="_blank" rel="noopener">shinyapps.io</a>
. You push your code, and in a few clicks, your app has its own URL and is accessible to anyone with an internet connection. It&rsquo;s fast, convenient, and you can scale if traffic grows.</p>
<p>Self-hosted give you full control and flexibility. <a href="https://rstudio.com/products/shiny/shiny-server/" target="_blank" rel="noopener">Shiny Server</a>
 is a free and open source solution that lets you run Python or R Shiny apps in a controlled environment (e.g. on your own server). You decide when and how updates happen, how many users can connect, and whether to open the app to the public or keep it behind the corporate firewall. <a href="https://www.rstudio.com/products/connect/" target="_blank" rel="noopener">Posit Connect</a>
 takes that control a step further and wraps it in a professional enterprise solution: scheduling, user authentication, email notifications, and support for Shiny, FastAPI, Plumber, Quarto, and other popular Python and R frameworks.</p>
<p>In short: click-and-play = fast, simple, and ready to share. I-like-control = more setup, full flexibility, and enterprise-ready features. Either way, your Shiny app is ready for the world! And of course, beyond what&rsquo;s mentioned here, there also many other solutions at any cloud service provider (Azure, AWS, GCP&hellip;).</p>
<h1 id="every-series-has-its-shiny-ending">Every series has its Shiny ending
</h1>
<p>That&rsquo;s the end of &ldquo;The Shiny Side of LLMs&rdquo;! Over three parts, you learned what an LLM actually is (and what it definitely isn&rsquo;t), how to have a conversation with one using <code>chatlas</code> (Python) and <code>ellmer</code> (R), and how to make your ideas come to life in a Shiny app. Need some inspiration? Check out the <a href="https://shiny.posit.co/py/docs/genai-inspiration.html" target="_blank" rel="noopener">Generative AI docs for Python</a>
 or the examples in the <a href="https://posit-dev.github.io/shinychat/r/index.html" target="_blank" rel="noopener"><code>shinychat</code> docs for R</a>
.</p>
<p>I hope this series showed you the shiny side of LLMs, beyond the hype. My goal with this series was to make LLMs feel a little less like mysterious black boxes and a little more like tools you can actually use. Hopefully you&rsquo;ve picked up a few tricks and now feel ready to try them out in your own work. I&rsquo;m curious to see what you&rsquo;ll create! Feel free to stay in touch via <a href="https://www.linkedin.com/in/veerlevanleemput/" target="_blank" rel="noopener">LinkedIn</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-3/shiny-side-of-llms-header.png" length="387952" type="image/png" />
    </item>
    <item>
      <title>Talking to LLMs: From Prompt to Response</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-2/</link>
      <pubDate>Fri, 05 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-2/</guid>
      <dc:creator>Veerle Eeftink - Van Leemput</dc:creator><description><![CDATA[<p>Welcome back to &ldquo;The Shiny Side of LLMs&rdquo;: a blog series for Python and R users who want to build real, useful LLM-powered apps without getting buried in jargon or deep learning theory.</p>
<p>In <a href="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-2/../../../blog/shiny/shiny-side-of-llms-part-1/">Part 1: What LLMs Actually Do (and What They Don&rsquo;t)</a>
, we looked at how large language models generate responses, why they sometimes seem so smart (and other times so confidently wrong), and what kinds of tasks they&rsquo;re actually good at. Now it&rsquo;s time to get hands-on!</p>
<p>In this part, we&rsquo;ll focus on what it means to talk to an LLM. We&rsquo;ll cover crafting prompts to parsing responses, and everything in between. You&rsquo;ll learn:</p>
<ul>
<li>How to call an LLM via <a href="https://posit-dev.github.io/chatlas/" target="_blank" rel="noopener"><code>chatlas</code></a>
 (Python) and <a href="https://ellmer.tidyverse.org/" target="_blank" rel="noopener"><code>ellmer</code></a>
 (R)</li>
<li>The basics of prompt design: roles, instructions, system messages, temperature, and more</li>
<li>What goes in and what comes out, and how your own data fits into that loop</li>
<li>How to work with model outputs: parsing, structuring, and getting predictable results</li>
<li>How to give LLMs extra power with tools</li>
<li>Costs involved when talking to LLMs</li>
</ul>
<p>By the end of this part, you&rsquo;ll be ready to go from &ldquo;I asked ChatGPT a thing&rdquo; to &ldquo;I can programmatically talk to an LLM by sending custom prompts and doing something useful with the results&rdquo;.</p>
<blockquote>
<p><strong>No time for the walkthrough?</strong></p>
<p>Jump straight to the <a href="#full-workflow">full workflow</a>
.</p>
</blockquote>
<h1 id="the-app-deckcheck">The app: DeckCheck
</h1>
<p>In this series we&rsquo;re working towards a genius app that gets rid of lengthy unfocused presentations, like a perfect &ldquo;Presentation Rehearsal Buddy&rdquo;. No app without a snappy name, so hello &ldquo;DeckCheck&rdquo;. The goal: let users upload their Quarto presentation and provide them with feedback on how to make it better.</p>
<p>So how would we go about that? To give meaningful feedback on a presentation, we need to ask the LLM the right questions. For example:</p>
<ul>
<li>Does the presentation have a clear structure?</li>
<li>Are the key messages easy to spot?</li>
<li>Does the tone match the intended audience?</li>
<li>Are the slides too wordy, too vague, or just right?</li>
<li>Is there a good balance between words, visuals, and code?</li>
</ul>
<p>If you&rsquo;ve used ChatGPT or another LLM in a chat interface, the process feels straightforward: you ask any of the above questions, and the model replies. But when you&rsquo;re building an app, you&rsquo;re not just having a conversation. You want to have some form of control. After all, you&rsquo;re designing a system. You need to construct prompts carefully, handle responses programmatically, and ensure the whole exchange is stable enough to support user-facing features. In our case, that means feeding slide content into the LLM and asking targeted questions about structure, clarity, and tone. And we&rsquo;re not just hoping for a helpful paragraph with some witty comments. For our app we need output that&rsquo;s predictable, parseable, and ready to plug into our app logic. Eventually we want to be able to extract suggestions, metrics, scores: things that we also need to retrieve programmatically.</p>
<p>So in this part, it&rsquo;s not just about asking questions, it&rsquo;s about:</p>
<ul>
<li>How to design prompts that get reliable, structured answers</li>
<li>How to &ldquo;control&rdquo; model behaviour (as far as you can, at least)</li>
<li>How to send slide content (or any other data) to an LLM</li>
<li>And how to process the model&rsquo;s output into something our future app can actually use</li>
</ul>
<p>Let&rsquo;s build!</p>
<blockquote>
<p><strong>What can you expect from this second part?</strong></p>
<p>This part focusses on programmatically talking to an LLM. We leave the Shiny app building to the third and last part of &ldquo;The Shiny Side of LLMs&rdquo; series. The code we end up with in this part serves as a basis for the back-end of our app in this next part.</p>
</blockquote>
<h1 id="what-do-you-need">What do you need?
</h1>
<p>Ah, is it even a tutorial if there isn&rsquo;t a &ldquo;getting started&rdquo; step? But there&rsquo;s one obvious thing we need: access to an LLM. To get that, you&rsquo;ll need to sign up for a (developer) account with a provider like OpenAI or Anthropic. And with that comes something that feels like a huge barrier: setting up payment details. I feel you when you might be getting anxious about an invoice with a lot of zeros, but really, don&rsquo;t worry too much about that. Almost all platforms offer free trial credits, and you can usually set usage limits or buy fixed prepaid credits (Anthropic makes this especially easy). So that invoice-nightmare won&rsquo;t come alive easily. Still a little bit worried? Google&rsquo;s model Gemini offers a generous free tier.</p>
<p>Once you&rsquo;ve registered, you can get an API key. You need this key to authenticate with the LLM provider. Never, ever hardcode the key directly into your script. You&rsquo;ll be amazed how many keys are publicly available on GitHub repos. Don&rsquo;t be that developer. As always with secrets, store it as an environment variable. Just note that the exact name of the key depends on the provider. For example, Anthropic expects <code>ANTHROPIC_API_KEY=yourkey</code>, while OpenAI uses <code>OPENAI_API_KEY=yourkey</code>.</p>
<p>To keep things simple, we&rsquo;ll go with Anthropic for the examples in this blog series, but feel free to choose any provider you want. Don&rsquo;t worry about lock-in either: you&rsquo;ll see that switching providers and models is super easy.</p>
<h2 id="where-to-store-your-api-key">Where to store your API key
</h2>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-1" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-1-1">Python</a></li>
<li><a href="#tabset-1-2">R</a></li>
</ul>
<div id="tabset-1-1">
<p>In Python, the recommended approach is to create a <code>.env</code> file in your project folder:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ANTHROPIC_API_KEY</span><span class="o">=</span><span class="n">yourkey</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>It&rsquo;s recommended to use the <code>dotenv</code> package to load the <code>.env</code> file into your environment:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dotenv</span> <span class="kn">import</span> <span class="n">load_dotenv</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">load_dotenv</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-1-2">
<p>In R, environment variables are typically stored in a <code>.Renviron</code> file. You can create this file in your project root or in your home directory (<code>~/.Renviron</code>). Or, if you want to make it yourself really easy: you can also open/edit the relevant file with <code>usethis::edit_r_environ()</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">ANTHROPIC_API_KEY</span><span class="o">=</span><span class="n">yourkey</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>Just be sure to add <code>.env</code> and <code>.Renviron</code> to your <code>.gitignore</code> file so you don&rsquo;t accidentally publish your keys.</p>
<h2 id="installing-chatlas-or-ellmer">Installing <code>chatlas</code> or <code>ellmer</code>
</h2>
<p>Working with different LLM providers can be a pain, but luckily there is the perfect solution: <a href="https://posit-dev.github.io/chatlas/" target="_blank" rel="noopener"><code>chatlas</code></a>
 for Python and <a href="https://ellmer.tidyverse.org" target="_blank" rel="noopener"><code>ellmer</code></a>
 for R. Hello simple and consistent interface! From Anthropic&rsquo;s Claude to (Azure) OpenAI and Google Gemini: it&rsquo;s all possible. And bonus: <code>chatlas</code> and <code>ellmer</code> both have the same interface, making it easy to translate your LLM projects between Python and R!</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-2" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-2-1">Python</a></li>
<li><a href="#tabset-2-2">R</a></li>
</ul>
<div id="tabset-2-1">
<p>For Python, <code>chatlas</code> is available on PyPI, so you can install it easily with <code>pip</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install chatlas
</span></span></code></pre></td></tr></table>
</div>
</div><p>Or, if you&rsquo;re using <code>uv</code>, add it like so:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">uv add chatlas
</span></span></code></pre></td></tr></table>
</div>
</div><p>Once installed, import it like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">chatlas</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Depending on the LLM provider you will choose, you might need to install additional packages (e.g. <code>anthropic</code> for, surprise, models from Anthropic). For every provider, the relevant function reference page in the <a href="https://posit-dev.github.io/chatlas/reference/" target="_blank" rel="noopener"><code>chatlas</code> docs</a>
 will tell you what&rsquo;s necessary together with credential acquisition tips (e.g. <a href="https://posit-dev.github.io/chatlas/reference/ChatAnthropic.html" target="_blank" rel="noopener"><code>ChatAnthropic</code></a>
).</p>
</div>
<div id="tabset-2-2">
<p>You can install <code>ellmer</code> from CRAN using:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;ellmer&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Once installed, load the package as usual:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<blockquote>
<p><strong>Switching between Python and R</strong></p>
<p>Did you know you can very easily switch between Python and R in <a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a>
? Just select the interpreter you want (you can even choose between Python and R installs), and you&rsquo;re good to go!</p>
</blockquote>
<h1 id="your-first-conversation">Your first conversation
</h1>
<p>Time to chat! Whether you&rsquo;re using Python or R, the workflow is the same. First, you need to create a chat object. You can give it any name you like, but <code>chat</code> seems the most obvious choice here. This <code>chat</code> object is created with a specific function for your chosen provider, where you (optionally) can specify the model. While the <code>model</code> argument isn&rsquo;t required, it is strongly recommended you do specify it. After all, we&rsquo;re looking for consistent behaviour in our Shiny app, and a change in default model choice isn&rsquo;t going to give us that. In our case we will go for claude-sonnet-4-20250514 from <a href="https://docs.anthropic.com/en/docs/about-claude/models/overview" target="_blank" rel="noopener">Anthropic</a>
, which is the latest (reasonably priced) model at the time of writing.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-3" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-3-1">Python</a></li>
<li><a href="#tabset-3-2">R</a></li>
</ul>
<div id="tabset-3-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dotenv</span> <span class="kn">import</span> <span class="n">load_dotenv</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatAnthropic</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">load_dotenv</span><span class="p">()</span>  <span class="c1"># Loads key from the .env file</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-3-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In <code>ellmer</code> 0.3.0 you can also use <code>chat()</code> using a string like <code>chat(&quot;anthropic&quot;)</code> or <code>chat(&quot;openai/gpt-4.1-nano&quot;)</code>.</p>
<p>Not sure what models are available? You can use <code>models_*()</code>, e.g. <code>models_anthropic()</code>, to get a list of available models.</p>
</div>
</div>
<blockquote>
<p><strong>API keys and environment variables</strong></p>
<p>There&rsquo;s no need to explicitly provide your API key to <code>chatlas</code> or <code>ellmer</code>. If you specify it with the correct name in your environment file (<code>.env</code> or <code>.Renviron</code>), <code>chatlas</code> or <code>ellmer</code> will automatically find it. There&rsquo;s an <code>api_key</code> argument, but generally you won&rsquo;t use this. For Python, it&rsquo;s recommended to use the <code>dotenv</code> package to load the <code>.env</code> file into your environment.</p>
</blockquote>
<p>You can call the <code>chat</code> method on the <code>chat</code> object. When you use the <code>chat</code> method:</p>
<ul>
<li>You see the response appear in your console, piece by piece, like it&rsquo;s being typed out. That&rsquo;s the streaming part.</li>
<li>The full message is also (invisibly) saved as a character vector, so you can use it in your code later.</li>
</ul>
<p>For simplicity, let&rsquo;s start with our very basic first prompt: &ldquo;I&rsquo;m working on a presentation with the title: &lsquo;The Shiny Side of LLMs&rsquo;. What&rsquo;s your feedback just based on that title?&rdquo;</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-4" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-4-1">Python</a></li>
<li><a href="#tabset-4-2">R</a></li>
</ul>
<div id="tabset-4-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dotenv</span> <span class="kn">import</span> <span class="n">load_dotenv</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatAnthropic</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">load_dotenv</span><span class="p">()</span>  <span class="c1"># Loads key from the .env file</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;I&#39;m working on a presentation with the title: &#39;The Shiny Side of LLMs&#39;. </span>
</span></span><span class="line"><span class="cl">  <span class="n">What</span><span class="s1">&#39;s your feedback just based on that title?&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">That&#39;s a clever and engaging title! Here&#39;s my feedback:                                           
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">What works well:                                                                                            
</span></span></span><span class="line"><span class="cl"><span class="s2">  • The wordplay on &#34;shiny&#34; is effective - it suggests both the exciting/promising aspects of LLMs and has a 
</span></span></span><span class="line"><span class="cl"><span class="s2">  nice tech-forward feel                                                                                   
</span></span></span><span class="line"><span class="cl"><span class="s2">  • It&#39;s concise and memorable                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">  • Sets an optimistic, forward-looking tone for your presentation                                           
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Potential considerations:                                                                         
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">  • The phrase &#34;shiny side&#34; might evoke &#34;bright side&#34; or even &#34;dark side,&#34; which could work for or against   
</span></span></span><span class="line"><span class="cl"><span class="s2">  you depending on your audience&#39;s associations                                                            
</span></span></span><span class="line"><span class="cl"><span class="s2">  • It&#39;s somewhat informal - great for most audiences, but consider if your context requires more formal     
</span></span></span><span class="line"><span class="cl"><span class="s2">  language                                                                                                 
</span></span></span><span class="line"><span class="cl"><span class="s2">  • Without additional context, it&#39;s not immediately clear what specific aspects you&#39;ll cover (capabilities, 
</span></span></span><span class="line"><span class="cl"><span class="s2">  applications, benefits, etc.)                                                                            
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Questions to consider:                                                                            
</span></span></span><span class="line"><span class="cl"><span class="s2">  
</span></span></span><span class="line"><span class="cl"><span class="s2">  • Who&#39;s your audience? (Technical experts, business leaders, general public?)                              
</span></span></span><span class="line"><span class="cl"><span class="s2">  • Are you contrasting with challenges/limitations, or purely focusing on positives?                        
</span></span></span><span class="line"><span class="cl"><span class="s2">  • What&#39;s the main takeaway you want people to remember?                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Overall, it&#39;s an attention-grabbing title that should work well for most presentations about LLM benefits   
</span></span></span><span class="line"><span class="cl"><span class="s2">and capabilities. You might consider a subtitle if you want to hint at your specific focus areas. 
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
<div id="tabset-4-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;I&#39;m working on a presentation with the title: &#39;The Shiny Side of LLMs&#39;. 
</span></span></span><span class="line"><span class="cl"><span class="s">  What&#39;s your feedback just based on that title?&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#&gt; That&#39;s a clever and engaging title! Here&#39;s my feedback:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; **Strengths:**</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - **Memorable wordplay** – &#34;shiny&#34; is a nice play on both the positive aspects and the tech/AI aesthetic  </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - **Clear focus** – Immediately signals you&#39;re highlighting the benefits/successes rather than risks or limitations  </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - **Accessible language** – Avoids jargon while still being specific to your audience  </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; **Considerations:**</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - **Audience expectations** – The playful tone suggests this might be more of an overview/promotional presentation rather than a deep technical dive. Make sure that aligns with your goals.  </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - **Balance question** – Will you acknowledge any limitations, or is this specifically meant to be a positive-focused presentation? The title suggests the latter.  </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; **Potential alternatives** (if you want to explore):  </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - &#34;LLMs: The Bright Side of AI&#34;  </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - &#34;When LLMs Shine: Success Stories and Breakthroughs&#34;  </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; - &#34;The Promise of LLMs: Real-World Wins&#34;  </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; But honestly, your original title works well – it&#39;s catchy and sets clear expectations. The success will depend on whether your content delivers on that promise of showcasing genuinely impressive LLM capabilities and applications.  </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; What&#39;s the context/audience for this presentation?</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
</div>
<p>The <code>chat</code> object is a stateful object, which means it &ldquo;remembers stuff&rdquo;. It accumulates conversation history by default. This history is provided to the LLM with every subsequent question. So if you send a second question, it will package your prompt so it contains the first question, the first answer, and your second question. This is desired behaviour for multi-turn conversations since it allows the model to &ldquo;remember&rdquo; previous interactions. That&rsquo;s important, because out of the box, the model doesn&rsquo;t remember what you said two messages ago. It only has the input you provide, right here, right now. That&rsquo;s its entire world. So every time (with every request) you have to remind the LLM of prior context. You can learn more about this in <a href="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-2/../../../blog/shiny/shiny-side-of-llms-part-1/">the first part of this series</a>
.</p>
<blockquote>
<p><strong>Some chat terminology</strong></p>
<p>Let&rsquo;s get some terms straight: you (the user) have a chat with the LLM (the assistant). Every chat, aka conversation, consists of pairs of user and assistant turns. The questions you ask correspond to an HTTP request, and the response that gets returned corresponds to an HTTP response.</p>
</blockquote>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-5" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-5-1">Python</a></li>
<li><a href="#tabset-5-2">R</a></li>
</ul>
<div id="tabset-5-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dotenv</span> <span class="kn">import</span> <span class="n">load_dotenv</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatAnthropic</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">load_dotenv</span><span class="p">()</span>  <span class="c1"># Loads key from the .env file</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;I&#39;m working on a presentation with the title: &#39;The Shiny Side of LLMs&#39;. </span>
</span></span><span class="line"><span class="cl">    <span class="n">What</span><span class="s1">&#39;s your feedback just based on that title?&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span><span class="s2">&#34;What is my presentation title?&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">Your presentation title is: &#34;The Shiny Side of LLMs&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In the remainder of this article, we will leave the <code>dotenv</code> part out for brevity. You do need it for the <code>.env</code> approach, but there are also other ways to load environment variables, like setting them in your bash/zsh profile.</p>
</div>
<div id="tabset-5-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;I&#39;m working on a presentation with the title: &#39;The Shiny Side of LLMs&#39;. 
</span></span></span><span class="line"><span class="cl"><span class="s">  What&#39;s your feedback just based on that title?&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat</span><span class="p">(</span><span class="s">&#34;What is my presentation title?&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#&gt; Your presentation title is: &#34;The Shiny Side of LLMs&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<h1 id="designing-effective-prompts">Designing effective prompts
</h1>
<blockquote>
<p><strong>TL;DR</strong></p>
<p>Defining the <strong>role</strong>, <strong>task</strong>, <strong>context</strong>, and <strong>output format</strong> sets the foundation for a good conversation with the model.</p>
</blockquote>
<p>In our first conversation, our prompt (&ldquo;I&rsquo;m working on a presentation with the title: &lsquo;The Shiny Side of LLMs&rsquo;. What&rsquo;s your feedback just based on that title?&rdquo;) is very general and leads to some issues: the model, Claude, doesn&rsquo;t know who the audience is, what kind of feedback you&rsquo;re looking for (tone, clarity, technical depth?), or what role it should take. As a result, the response is polite and somewhat helpful, but not really focused or consistent. It&rsquo;s basically guessing, and it will guess something different every single time. Our prompt is very important for the model&rsquo;s output, so we need to make it better by including:</p>
<ul>
<li><strong>Role</strong>: who the model is acting as. We didn&rsquo;t tell the model who it should be or how it should respond. Saying something like: &ldquo;You are a presentation coach for data scientists&rdquo;, helps to get more relevant feedback. It also referred to as adding &ldquo;colour&rdquo; to your prompt, or framing.</li>
<li><strong>Task</strong>: what the model should do. We asked for &ldquo;feedback&rdquo;, but that&rsquo;s not explicit at all. It&rsquo;s better to ask something like: &ldquo;Evaluate the title&rsquo;s clarity, tone, and relevance for a non-technical audience&rdquo;.</li>
<li><strong>Context</strong>: a bit more information about the situation. The more a model knows, the more on-point an answer will be. Who&rsquo;s our intended audience? Is it a keynote, a lightning talk or a workshop? In our to-be app, we can get this information directly from the user when they are uploading their Quarto slides, and include it in our prompt. It&rsquo;s as simple as adding: &ldquo;This is for a 10-minute lightning talk at <a href="https://posit.co/conference/" target="_blank" rel="noopener">posit::conf(2025)</a>
 for people curious about AI&rdquo;. How much context (aka tokens) you can add is dependent on the model. Claude Sonnet 4 recently <a href="https://www.anthropic.com/news/1m-context" target="_blank" rel="noopener">supports 1M tokens of context</a>
, which really is a lot! Context is also important for refusal protection: if there&rsquo;s not enough context, sometimes the model refuses to answer. Giving clear background and reassuring details helps avoid these unnecessary refusals, making the interaction smoother and more reliable. Of course, keep in mind that you are responsible the model&rsquo;s output: sometimes refusals are there for a reason.</li>
<li><strong>Output format</strong>: how you want the answer. If you want a list, markdown, a table, or a short paragraph, say so! You can (and should) be very detailed here. For example: &ldquo;Return your answer as a JSON array of objects with keys &lsquo;aspect&rsquo; and &lsquo;feedback&rsquo;&rdquo;. Choose something that&rsquo;s easily parsable in Python or R later. Also remember the few-shot learning we talked about in Part 1: if you provide an example of what your output should look like, it&rsquo;s easier for the model to repeat that pattern.</li>
</ul>
<p>Up to this point we&rsquo;ve talked about adding role, task, context, and output format directly into &ldquo;the&rdquo; prompt. Like it&rsquo;s just one big thing. But there are actually two kinds of prompts you can use when chatting with a model: the system prompt and the user prompt.</p>
<ul>
<li>The <strong>system prompt</strong> is where you set the ground rules: it tells the model who it is, how it should behave, and what the answer should look like. Think of it as the &ldquo;job description&rdquo; that stays the same no matter what question you ask later. It&rsquo;s stable context.</li>
<li>The <strong>user prompt</strong> is the specific request you make right now, like checking the clarity of a particular title. It can change every time. The model responds to this request within the &ldquo;job description&rdquo;.</li>
</ul>
<p>If you put everything into the user prompt, you end up repeating rules and formatting instructions over and over, which makes the conversation messy and can confuse the model (aka &ldquo;context pollution&rdquo;). By keeping the stable rules and output format in the system prompt, and putting specifics or content in the user prompt, you get clearer, more reliable answers. In both <code>chatlas</code> and <code>ellmer</code> you can use the <code>system_prompt</code> argument when initialising the <code>chat</code> object.</p>
<blockquote>
<p><strong>Why put the details in a system prompt?</strong></p>
<p>Even though past messages are sent along in a conversation, putting the fixed details in the system prompt still makes a difference. The model pays more attention to instructions in the system prompt, so they&rsquo;re less likely to be ignored or overridden. It also keeps the chat history cleaner, since you don&rsquo;t need to repeat the same background and formatting rules in every user message. This way, the user prompt stays focused on just the part that changes (like a new title) while the system prompt handles the rest.</p>
</blockquote>
<p>So what to do if you did all that and still get answers that are not up to your standards? In that case, you can try <strong>Chain-of-Thought prompting</strong>, where you ask the model to explain its reasoning step-by-step before answering. This makes complex tasks easier to follow and helps you understand how the model thinks. It&rsquo;s great for debugging to see where the model started to get off.</p>
<p>Now, with all that knowledge, let&rsquo;s change our prompt from:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">I&#39;m working on a presentation with the title: &#39;The Shiny Side of LLMs&#39;. What&#39;s your feedback just based on that title?
</span></span></code></pre></td></tr></table>
</div>
</div><p>To the following system prompt:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">You are a presentation coach for data scientists. 
</span></span><span class="line"><span class="cl">You give constructive, focused, and practical feedback on titles, structure, and storytelling. 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">The presentation you are helping with is a 10-minute lightning talk at posit::conf(2025).  
</span></span><span class="line"><span class="cl">The audience is Python and R users who are curious about AI and large language models, 
</span></span><span class="line"><span class="cl">but not all of them have a deep technical background. 
</span></span><span class="line"><span class="cl">The talk uses Shiny as a way to demo and explore LLMs in practice. 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Always return your feedback as a JSON array of objects, where each object has the following keys:
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#39;aspect&#39;: one of &#39;clarity&#39;, &#39;tone&#39;, or &#39;relevance&#39;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#39;feedback&#39;: a concise assessment
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#39;suggestion&#39;: an optional improvement if applicable
</span></span></code></pre></td></tr></table>
</div>
</div><p>And related user prompt for the task at hand:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">Evaluate the title: &#39;The Shiny Side of LLMs&#39;
</span></span></code></pre></td></tr></table>
</div>
</div><div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-6" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-6-1">Python</a></li>
<li><a href="#tabset-6-2">R</a></li>
</ul>
<div id="tabset-6-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatAnthropic</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span><span class="o">=</span><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    You are a presentation coach for data scientists. 
</span></span></span><span class="line"><span class="cl"><span class="s2">    You give constructive, focused, and practical feedback on titles, structure, and storytelling. 
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    The presentation you are helping with is a 10-minute lightning talk at posit::conf(2025).  
</span></span></span><span class="line"><span class="cl"><span class="s2">    The audience is Python and R users who are curious about AI and large language models, 
</span></span></span><span class="line"><span class="cl"><span class="s2">    but not all of them have a deep technical background. 
</span></span></span><span class="line"><span class="cl"><span class="s2">    The talk uses Shiny as a way to demo and explore LLMs in practice. 
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    Always return your feedback as a JSON array of objects, where each object has the following keys:
</span></span></span><span class="line"><span class="cl"><span class="s2">    - &#39;aspect&#39;: one of &#39;clarity&#39;, &#39;tone&#39;, or &#39;relevance&#39;
</span></span></span><span class="line"><span class="cl"><span class="s2">    - &#39;feedback&#39;: a concise assessment
</span></span></span><span class="line"><span class="cl"><span class="s2">    - &#39;suggestion&#39;: an optional improvement if applicable
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span><span class="s2">&#34;Evaluate the title: &#39;The Shiny Side of LLMs&#39;&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  [                                                                                                                 
</span></span></span><span class="line"><span class="cl"><span class="s2">   {                                                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;aspect&#34;: &#34;clarity&#34;,                                                                                          
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;feedback&#34;: &#34;The title uses a clever play on words with &#39;Shiny&#39; but may create confusion about whether this i 
</span></span></span><span class="line"><span class="cl"><span class="s2"> primarily about the Shiny framework or LLMs. The wordplay might obscure the main focus for attendees scanning the 
</span></span></span><span class="line"><span class="cl"><span class="s2"> program.&#34;,                                                                                                        
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;suggestion&#34;: &#34;Consider a title that more clearly positions Shiny as the tool and LLMs as the subject, such a 
</span></span></span><span class="line"><span class="cl"><span class="s2"> &#39;Exploring LLMs with Shiny: Interactive AI Demos Made Simple&#39;&#34;                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                                                              
</span></span></span><span class="line"><span class="cl"><span class="s2">   {                                                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;aspect&#34;: &#34;tone&#34;,                                                                                             
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;feedback&#34;: &#34;The playful tone with &#39;Shiny Side&#39; is appropriate for a lightning talk and fits the Posit        
</span></span></span><span class="line"><span class="cl"><span class="s2"> conference atmosphere. It suggests an accessible, positive approach that aligns well with the audience&#39;s curiosit 
</span></span></span><span class="line"><span class="cl"><span class="s2"> about AI.&#34;,                                                                                                       
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;suggestion&#34;: null                                                                                            
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                                                              
</span></span></span><span class="line"><span class="cl"><span class="s2">   {                                                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;aspect&#34;: &#34;relevance&#34;,                                                                                        
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;feedback&#34;: &#34;Highly relevant for the audience - Python/R users at Posit::conf would immediately recognize     
</span></span></span><span class="line"><span class="cl"><span class="s2"> &#39;Shiny&#39; and appreciate learning how to apply it to trending AI/LLM topics. The focus on practical demos matches   
</span></span></span><span class="line"><span class="cl"><span class="s2"> what this audience values.&#34;,                                                                                      
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;suggestion&#34;: &#34;Consider adding a subtitle to reinforce the practical angle: &#39;The Shiny Side of LLMs: Building 
</span></span></span><span class="line"><span class="cl"><span class="s2"> Interactive AI Demos&#39;&#34;                                                                                            
</span></span></span><span class="line"><span class="cl"><span class="s2">   }                                                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2"> ]
</span></span></span><span class="line"><span class="cl"><span class="s2"> &#34;&#34;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
<div id="tabset-6-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">system_prompt</span> <span class="o">=</span> <span class="s">&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  You are a presentation coach for data scientists. 
</span></span></span><span class="line"><span class="cl"><span class="s">  You give constructive, focused, and practical feedback on titles, structure, and storytelling. 
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">  The presentation you are helping with is a 10-minute lightning talk at posit::conf(2025).  
</span></span></span><span class="line"><span class="cl"><span class="s">  The audience is Python and R users who are curious about AI and large language models, 
</span></span></span><span class="line"><span class="cl"><span class="s">  but not all of them have a deep technical background. 
</span></span></span><span class="line"><span class="cl"><span class="s">  The talk uses Shiny as a way to demo and explore LLMs in practice. 
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">  Always return your feedback as a JSON array of objects, where each object has the following keys:
</span></span></span><span class="line"><span class="cl"><span class="s">  - &#39;aspect&#39;: one of &#39;clarity&#39;, &#39;tone&#39;, or &#39;relevance&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">  - &#39;feedback&#39;: a concise assessment
</span></span></span><span class="line"><span class="cl"><span class="s">  - &#39;suggestion&#39;: an optional improvement if applicable
</span></span></span><span class="line"><span class="cl"><span class="s">  &#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat</span><span class="p">(</span><span class="s">&#34;Evaluate the title: &#39;The Shiny Side of LLMs&#39;&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#&gt; ```json</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;aspect&#34;: &#34;clarity&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;feedback&#34;: &#34;The title is somewhat ambiguous - &#39;shiny side&#39; could refer to positive aspects of LLMs or literally to the Shiny framework. This wordplay might confuse audiences unfamiliar with both concepts.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;suggestion&#34;: &#34;Consider &#39;Exploring LLMs with Shiny: Interactive AI Demos&#39; or &#39;Building LLM Interfaces with Shiny&#39; for clearer technical focus&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;aspect&#34;: &#34;tone&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;feedback&#34;: &#34;The playful wordplay fits well with the posit::conf audience and lightning talk format. It&#39;s approachable and not intimidating, which works for mixed technical backgrounds.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;suggestion&#34;: null</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;aspect&#34;: &#34;relevance&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;feedback&#34;: &#34;Highly relevant for the audience - directly connects familiar Shiny technology with trending AI/LLM topics. Perfect for Python/R users wanting practical AI applications.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;suggestion&#34;: null</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   }</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ]</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ```</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
</div>
<p>That&rsquo;s already something we can programmatically work with. Note that there are still differences between runs and between Python and R (just take a look at both examples). That is expected, even though our prompt is exactly the same. We&rsquo;ll come back to that a bit later.</p>
<h1 id="choosing-the-right-input-format">Choosing the right input format
</h1>
<p>The goal of the DeckCheck app is to do more than just looking at a title: we want to analyse Quarto slides. That means we need to provide our presentation in the chat. The question is&hellip; How do we do that? The most straightforward choice to provide this kind of data to an LLM is markdown. Markdown is lightweight, readable, and has a nice structure. It captures headings, bullet points, code blocks, and text, which is perfect.</p>
<p>But markdown isn&rsquo;t the only option:</p>
<ul>
<li><strong>Plain text</strong> is the most basic. It&rsquo;s simple, but you lose structure. Titles become regular text, as well as bullet points, code blocks etc. This makes it harder to analyse.</li>
<li><strong>HTML</strong> keeps all the structure but comes with a lot of noise (tags, attributes) that the LLM might happily read but we might not want to include.</li>
<li><strong>Structured JSON chunks</strong> give the most control, but is a big step from Quarto slides compared to plain markdown.</li>
</ul>
<p>If we&rsquo;re starting from a Quarto <code>.qmd</code> or <code>.ipynb</code> file, we can:</p>
<ul>
<li>Directly render Quarto slides to markdown via <code>quarto render</code> with <code>-to markdown</code></li>
<li>We&rsquo;re not getting into the nitty gritty of <a href="https://quarto.org/docs/authoring/markdown-basics.html" target="_blank" rel="noopener">markdown vs Quarto</a>
, but Quarto is basically markdown with some fancy extras, which can cause noise for our goal. So we might want to strip slide metadata, speaker notes, and other extras with custom filters or scripts, depending on what we want to keep (and to be honest: how sophisticated we want it to be).</li>
</ul>
<p>Let&rsquo;s say we have a very basic Quarto presentation that talks about &ldquo;The Shiny Side of LLMs&rdquo;, loosely resembling what we&rsquo;ve already talked about in this article, but is written in a sub-optimal way. The file is named <a href="https://github.com/hypebright/the-shiny-side-of-llms/blob/8857ca5ae4f74a86105ce9d1f04df3354eeee866/Quarto/my-presentation.qmd" target="_blank" rel="noopener">&ldquo;my-presentation.qmd&rdquo;</a>
 and lives in a Quarto folder in our working directory.</p>
<blockquote>
<p><strong>About the presentation file</strong></p>
<p>It&rsquo;s not necessary to read this presentation line-by-line, it is just there for demo purposes and for reference if you want to make sense of the output that Claude is going to give later. In subsequent code we will simply refer to it as &ldquo;my-presentation.qmd&rdquo;.</p>
</blockquote>
<details class="code-fold">
<summary>See full Quarto presentation</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span><span class="lnt">149
</span><span class="lnt">150
</span><span class="lnt">151
</span><span class="lnt">152
</span><span class="lnt">153
</span><span class="lnt">154
</span><span class="lnt">155
</span><span class="lnt">156
</span><span class="lnt">157
</span><span class="lnt">158
</span><span class="lnt">159
</span><span class="lnt">160
</span><span class="lnt">161
</span><span class="lnt">162
</span><span class="lnt">163
</span><span class="lnt">164
</span><span class="lnt">165
</span><span class="lnt">166
</span><span class="lnt">167
</span><span class="lnt">168
</span><span class="lnt">169
</span><span class="lnt">170
</span><span class="lnt">171
</span><span class="lnt">172
</span><span class="lnt">173
</span><span class="lnt">174
</span><span class="lnt">175
</span><span class="lnt">176
</span><span class="lnt">177
</span><span class="lnt">178
</span><span class="lnt">179
</span><span class="lnt">180
</span><span class="lnt">181
</span><span class="lnt">182
</span><span class="lnt">183
</span><span class="lnt">184
</span><span class="lnt">185
</span><span class="lnt">186
</span><span class="lnt">187
</span><span class="lnt">188
</span><span class="lnt">189
</span><span class="lnt">190
</span><span class="lnt">191
</span><span class="lnt">192
</span><span class="lnt">193
</span><span class="lnt">194
</span><span class="lnt">195
</span><span class="lnt">196
</span><span class="lnt">197
</span><span class="lnt">198
</span><span class="lnt">199
</span><span class="lnt">200
</span><span class="lnt">201
</span><span class="lnt">202
</span><span class="lnt">203
</span><span class="lnt">204
</span><span class="lnt">205
</span><span class="lnt">206
</span><span class="lnt">207
</span><span class="lnt">208
</span><span class="lnt">209
</span><span class="lnt">210
</span><span class="lnt">211
</span><span class="lnt">212
</span><span class="lnt">213
</span><span class="lnt">214
</span><span class="lnt">215
</span><span class="lnt">216
</span><span class="lnt">217
</span><span class="lnt">218
</span><span class="lnt">219
</span><span class="lnt">220
</span><span class="lnt">221
</span><span class="lnt">222
</span><span class="lnt">223
</span><span class="lnt">224
</span><span class="lnt">225
</span><span class="lnt">226
</span><span class="lnt">227
</span><span class="lnt">228
</span><span class="lnt">229
</span><span class="lnt">230
</span><span class="lnt">231
</span><span class="lnt">232
</span><span class="lnt">233
</span><span class="lnt">234
</span><span class="lnt">235
</span><span class="lnt">236
</span><span class="lnt">237
</span><span class="lnt">238
</span><span class="lnt">239
</span><span class="lnt">240
</span><span class="lnt">241
</span><span class="lnt">242
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="o">---</span>
</span></span><span class="line"><span class="cl"><span class="n">title</span><span class="o">:</span> <span class="s">&#34;The Shiny Side of LLMs&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">author</span><span class="o">:</span> <span class="s">&#34;Veerle Eeftink - van Leemput&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">format</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">  <span class="n">revealjs</span><span class="o">:</span> 
</span></span><span class="line"><span class="cl">    <span class="n">theme</span><span class="o">:</span> <span class="n">[default]</span>
</span></span><span class="line"><span class="cl"><span class="n">include</span><span class="o">-</span><span class="kr">in</span><span class="o">-</span><span class="n">header</span><span class="o">:</span> 
</span></span><span class="line"><span class="cl">  <span class="n">text</span><span class="o">:</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;</span><span class="n">link</span> <span class="n">href</span><span class="o">=</span><span class="s">&#34;https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css&#34;</span> <span class="n">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="n">integrity</span><span class="o">=</span><span class="s">&#34;sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC&#34;</span> <span class="n">crossorigin</span><span class="o">=</span><span class="s">&#34;anonymous&#34;</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="n">highlight</span><span class="o">-</span><span class="n">style</span><span class="o">:</span> <span class="s">&#34;nord&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">---</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Hey 👋</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="n">this</span> <span class="n">demo</span> <span class="n">presentation</span> <span class="n">we</span> <span class="n">will</span> <span class="n">explore</span> <span class="n">how</span> <span class="n">to</span> <span class="n">use</span> <span class="n">LLMs</span> <span class="kr">in</span> <span class="n">Shiny</span> <span class="n">applications.</span> <span class="n">Because</span> <span class="n">everybody</span> <span class="n">is</span> <span class="n">doing</span> <span class="n">it</span><span class="p">,</span> <span class="n">right</span><span class="o">?</span> 😉
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># What do you need?</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">-</span> <span class="n">The</span> <span class="n">means</span> <span class="n">to</span> <span class="n">authenticate.</span> <span class="n">Depending</span> <span class="n">on</span> <span class="n">your</span> <span class="n">provider</span><span class="p">,</span> <span class="n">you</span> <span class="n">might</span> <span class="n">need</span> <span class="n">an</span> <span class="n">API</span> <span class="nf">key </span><span class="p">(</span><span class="n">e.g.</span> <span class="n">Anthropic</span><span class="p">,</span> <span class="n">OpenAI</span><span class="p">),</span> <span class="n">a</span> <span class="nf">token </span><span class="p">(</span><span class="n">e.g.</span> <span class="n">Hugging</span> <span class="n">Face</span><span class="p">),</span> <span class="n">or</span> <span class="n">other</span> <span class="n">authentication</span> <span class="n">methods.</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span> <span class="n">Put</span> <span class="n">those</span> <span class="n">credentials</span> <span class="kr">in</span> <span class="n">a</span> <span class="n">suitable</span> <span class="nf">place </span><span class="p">(</span><span class="kr">for</span> <span class="n">R</span><span class="o">:</span> <span class="n">`.Renviron`</span><span class="p">,</span> <span class="kr">for</span> <span class="n">Python</span><span class="o">:</span> <span class="n">`.env`</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span> <span class="n">Install</span> <span class="n">the</span> <span class="n">necessary</span> <span class="n">packages</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">  <span class="o">-</span> <span class="n">For</span> <span class="n">Python</span><span class="o">:</span> <span class="n">`chatlas`</span><span class="p">,</span> <span class="n">`shiny`</span>
</span></span><span class="line"><span class="cl">  <span class="o">-</span> <span class="n">For</span> <span class="n">R</span><span class="o">:</span> <span class="n">`ellmer`</span><span class="p">,</span> <span class="n">`shiny`</span><span class="p">,</span> <span class="n">`shinychat`</span><span class="p">,</span> <span class="n">and</span> <span class="n">optionally</span> <span class="n">`bslib`</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span> <span class="n">An</span> <span class="n">creative</span> <span class="n">idea</span> <span class="kr">for</span> <span class="n">your</span> <span class="n">LLM</span><span class="o">-</span><span class="n">powered</span> <span class="n">app</span><span class="o">!</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># What are we building</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">We</span><span class="s">&#39;re building an app called DeckCheck: it&#39;</span><span class="n">s</span> <span class="n">your</span> <span class="n">favourite</span> <span class="n">Presentation</span> <span class="n">Rehearsal</span> <span class="n">Buddy.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">With</span> <span class="n">DeckCheck</span><span class="p">,</span> <span class="n">you</span> <span class="n">can</span> <span class="n">upload</span> <span class="n">your</span> <span class="n">Quarto</span> <span class="n">presentation</span> <span class="n">and</span> <span class="n">get</span> <span class="n">tailored</span> <span class="n">feedback</span><span class="o">!</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">No</span> <span class="n">more</span> <span class="n">lengthy</span><span class="p">,</span> <span class="n">boring</span> <span class="n">presentations</span> 🤠<span class="n">.
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Where to start?</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="m">1</span><span class="n">. Talk</span> <span class="n">to</span> <span class="n">an</span> <span class="n">LLM</span> <span class="n">with</span> <span class="n">the</span> <span class="n">help</span> <span class="n">of</span> <span class="nf">`chatlas` </span><span class="p">(</span><span class="n">Python</span><span class="p">)</span> <span class="n">and</span> <span class="nf">`ellmer` </span><span class="p">(</span><span class="n">R</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="m">2</span><span class="n">. Get</span> <span class="n">a</span> <span class="n">basic</span> <span class="n">`shiny`</span> <span class="n">app</span> <span class="n">ready</span>
</span></span><span class="line"><span class="cl"><span class="m">3</span><span class="n">. Integrate</span> <span class="n">`chatlas`</span><span class="o">/</span><span class="n">`ellmer`</span> <span class="kr">in</span> <span class="n">that</span> <span class="n">`shiny`</span> <span class="n">app</span> <span class="n">using</span> <span class="n">a</span> <span class="n">simple</span> <span class="n">chat</span> <span class="n">interface</span>
</span></span><span class="line"><span class="cl"><span class="m">4</span><span class="n">. Build</span> <span class="n">it</span> <span class="n">out</span> <span class="n">to</span> <span class="n">an</span> <span class="n">app</span> <span class="n">where</span> <span class="n">you</span> <span class="n">can</span> <span class="n">upload</span> <span class="n">slides</span><span class="p">,</span> <span class="n">see</span> <span class="n">metrics</span> <span class="n">about</span> <span class="n">your</span> <span class="n">presentation</span><span class="p">,</span> <span class="n">and</span> <span class="n">more.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Talk to an LLM with chatlas</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">For</span> <span class="n">Python</span><span class="p">,</span> <span class="n">we</span> <span class="n">can</span> <span class="n">use</span> <span class="n">`chatlas`</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">\`\`\`python
</span></span></span><span class="line"><span class="cl"><span class="n">from chatlas import ChatAnthropic
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">chat = ChatAnthropic(
</span></span></span><span class="line"><span class="cl"><span class="n">    model=&#34;claude-sonnet-4-20250514&#34;,
</span></span></span><span class="line"><span class="cl"><span class="n">    system_prompt=&#34;You are a presentation coach for data scientists. You give constructive, focused, and practical feedback on titles, structure, and storytelling.&#34;,
</span></span></span><span class="line"><span class="cl"><span class="n">)
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">chat.chat(&#34;I&#39;m working on a presentation with the title: &#39;The Shiny Side of LLMs&#39;. What&#39;s your feedback just based on that title?&#34;)
</span></span></span><span class="line"><span class="cl"><span class="n">\`\`\`
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">\# Talk to an LLM with ellmer
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">For R, we can use `ellmer`:
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">\`\`\`r
</span></span></span><span class="line"><span class="cl"><span class="n">library(ellmer)
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">chat &lt;- chat_anthropic(
</span></span></span><span class="line"><span class="cl"><span class="n">    model = &#34;claude-sonnet-4-20250514&#34;,
</span></span></span><span class="line"><span class="cl"><span class="n">    system_prompt = &#34;You are a presentation coach for data scientists. You give constructive, focused, and practical feedback on titles, structure, and storytelling.&#34;
</span></span></span><span class="line"><span class="cl"><span class="n">)
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">chat$chat(&#34;I&#39;m working on a presentation with the title: &#39;The Shiny Side of LLMs&#39;. What&#39;s your feedback just based on that title?&#34;)
</span></span></span><span class="line"><span class="cl"><span class="n">\`\`\`
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">\# Why Shiny?
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">-   Shiny is a web application framework for R and Python that allows you to build interactive web applications.
</span></span></span><span class="line"><span class="cl"><span class="n">-   It provides a reactive programming model, making it easy to create dynamic user interfaces and handle user inputs.
</span></span></span><span class="line"><span class="cl"><span class="n">-   Perfect for your LLM-powered app!
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">\# Shiny basics: Python
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">\`\`\`python
</span></span></span><span class="line"><span class="cl"><span class="n">from shiny import App, ui, render
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n"># Define UI
</span></span></span><span class="line"><span class="cl"><span class="n">app_ui = ui.page_fluid(
</span></span></span><span class="line"><span class="cl"><span class="n">    ui.h1(&#34;DeckCheck&#34;),
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">    # Card with content
</span></span></span><span class="line"><span class="cl"><span class="n">    ui.card(
</span></span></span><span class="line"><span class="cl"><span class="n">        ui.card_header(&#34;Welcome&#34;),
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">        ui.p(&#34;This is a basic Shiny for Python application.&#34;),
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">        # Input: text
</span></span></span><span class="line"><span class="cl"><span class="n">        ui.input_text_area(&#34;text&#34;, &#34;What&#39;s your question?&#34;),
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">        # Output: echo text back
</span></span></span><span class="line"><span class="cl"><span class="n">        ui.output_text(&#34;echo&#34;)
</span></span></span><span class="line"><span class="cl"><span class="n">    )
</span></span></span><span class="line"><span class="cl"><span class="n">)
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n"># Define server
</span></span></span><span class="line"><span class="cl"><span class="n">def server(input, output, session):
</span></span></span><span class="line"><span class="cl"><span class="n">    @render.text
</span></span></span><span class="line"><span class="cl"><span class="n">    def echo():
</span></span></span><span class="line"><span class="cl"><span class="n">        return f&#34;You asked: {input.text()}&#34;
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n"># Create app
</span></span></span><span class="line"><span class="cl"><span class="n">app = App(app_ui, server)
</span></span></span><span class="line"><span class="cl"><span class="n">\`\`\`
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">\# Shiny basics: R
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">Tip: combine with `bslib` for fancy looks!
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">\`\`\`r
</span></span></span><span class="line"><span class="cl"><span class="n">library(shiny)
</span></span></span><span class="line"><span class="cl"><span class="n">library(bslib)
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">ui &lt;- page_fluid(
</span></span></span><span class="line"><span class="cl"><span class="n">  theme = bs_theme(bootswatch = &#34;minty&#34;),
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">  # App title
</span></span></span><span class="line"><span class="cl"><span class="n">  h1(&#34;DeckCheck&#34;),
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">  # Create a card
</span></span></span><span class="line"><span class="cl"><span class="n">  card(
</span></span></span><span class="line"><span class="cl"><span class="n">    card_header(&#34;Welcome&#34;),
</span></span></span><span class="line"><span class="cl"><span class="n">    p(&#34;This is a simple Shiny app using bslib for layout and styling.&#34;),
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">    # Input: text
</span></span></span><span class="line"><span class="cl"><span class="n">    textAreaInput(&#34;text&#34;, &#34;What&#39;s your question?&#34;),
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">    # Output: echo the text back
</span></span></span><span class="line"><span class="cl"><span class="n">    textOutput(&#34;echo&#34;)
</span></span></span><span class="line"><span class="cl"><span class="n">  )
</span></span></span><span class="line"><span class="cl"><span class="n">)
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">server &lt;- function(input, output, session) {
</span></span></span><span class="line"><span class="cl"><span class="n">  # Reactive output
</span></span></span><span class="line"><span class="cl"><span class="n">  output$echo &lt;- renderText({
</span></span></span><span class="line"><span class="cl"><span class="n">    paste(&#34;You asked:&#34;, input$text)
</span></span></span><span class="line"><span class="cl"><span class="n">  })
</span></span></span><span class="line"><span class="cl"><span class="n">}
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">shinyApp(ui, server)
</span></span></span><span class="line"><span class="cl"><span class="n">\`\`\`
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">\# Where&#39;s the magic?
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">We&#39;re here for some AI magic, right? So let&#39;s talk to an LLM via our Shiny application!
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">![AI Magic](https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExMmZ0cmd1aGZzM3JqdW1zbng0czU2c2RwejNtM25jMW8xdnYzNXVrdCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/OlxeZT285uhFydNetO/giphy.gif)
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">\# Shiny and chatlas
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">We can add a chat component to our UI with `ui.chat_ui` from `shiny`, and use `ChatAnthropic` from `chatlas` to send our messages to Claude. With minimal change, our Shiny for Python app is already LLM-powered!
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">`\`\`python
</span></span></span><span class="line"><span class="cl"><span class="n">from shiny import App, ui
</span></span></span><span class="line"><span class="cl"><span class="n">from chatlas import ChatAnthropic
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n"># Define UI
</span></span></span><span class="line"><span class="cl"><span class="n">app_ui = ui.page_fluid(
</span></span></span><span class="line"><span class="cl"><span class="n">    ui.h1(&#34;DeckCheck&#34;),
</span></span></span><span class="line"><span class="cl"><span class="n">    # Card with chat component
</span></span></span><span class="line"><span class="cl"><span class="n">    ui.card(
</span></span></span><span class="line"><span class="cl"><span class="n">        ui.card_header(&#34;Get started&#34;),
</span></span></span><span class="line"><span class="cl"><span class="n">        ui.p(&#34;Ask me anything about your presentation 💡&#34;),
</span></span></span><span class="line"><span class="cl"><span class="n">        # Chat component
</span></span></span><span class="line"><span class="cl"><span class="n">        ui.chat_ui(id=&#34;my_chat&#34;),
</span></span></span><span class="line"><span class="cl"><span class="n">    ),
</span></span></span><span class="line"><span class="cl"><span class="n">)
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n"># Define server
</span></span></span><span class="line"><span class="cl"><span class="n">def server(input, output, session):
</span></span></span><span class="line"><span class="cl"><span class="n">    chat_component = ui.Chat(id=&#34;my_chat&#34;)
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">    chat = ChatAnthropic(
</span></span></span><span class="line"><span class="cl"><span class="n">        model=&#34;claude-sonnet-4-20250514&#34;,
</span></span></span><span class="line"><span class="cl"><span class="n">        system_prompt=&#34;You are a presentation coach for data scientists. You give constructive, focused, and practical feedback on titles, structure, and storytelling.&#34;,
</span></span></span><span class="line"><span class="cl"><span class="n">    )
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">    @chat_component.on_user_submit
</span></span></span><span class="line"><span class="cl"><span class="n">    async def handle_user_input(user_input: str):
</span></span></span><span class="line"><span class="cl"><span class="n">        response = await chat.stream_async(user_input)
</span></span></span><span class="line"><span class="cl"><span class="n">        await chat_component.append_message_stream(response)
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n"># Create app
</span></span></span><span class="line"><span class="cl"><span class="n">app = App(app_ui, server)
</span></span></span><span class="line"><span class="cl"><span class="n">\`\`\`
</span></span></span><span class="line"><span class="cl"><span class="n">
</span></span></span><span class="line"><span class="cl"><span class="n">\# Shiny, shinychat and ellmer We can add a chat component to our UI with `chat_mod_ui` from `shinychat`, and use `chat_anthropic` from `ellmer`</span> <span class="n">to</span> <span class="n">send</span> <span class="n">our</span> <span class="n">messages</span> <span class="n">to</span> <span class="n">Claude.</span> <span class="n">With</span> <span class="n">minimal</span> <span class="n">change</span><span class="p">,</span> <span class="n">our</span> <span class="n">Shiny</span> <span class="kr">for</span> <span class="n">R</span> <span class="n">app</span> <span class="n">is</span> <span class="n">already</span> <span class="n">LLM</span><span class="o">-</span><span class="n">powered</span><span class="o">!</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">\`\`\`r</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shinychat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">theme</span> <span class="o">=</span> <span class="nf">bs_theme</span><span class="p">(</span><span class="n">bootswatch</span> <span class="o">=</span> <span class="s">&#34;minty&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># App title</span>
</span></span><span class="line"><span class="cl">  <span class="nf">h1</span><span class="p">(</span><span class="s">&#34;DeckCheck&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Create a card</span>
</span></span><span class="line"><span class="cl">  <span class="nf">card</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">card_header</span><span class="p">(</span><span class="s">&#34;Get started&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">p</span><span class="p">(</span><span class="s">&#34;Ask me anything about your presentation 💡&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Chat component</span>
</span></span><span class="line"><span class="cl">    <span class="nf">chat_mod_ui</span><span class="p">(</span><span class="s">&#34;chat_component&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span> <span class="o">=</span> <span class="s">&#34;You are a presentation coach for data scientists. 
</span></span></span><span class="line"><span class="cl"><span class="s">  You give constructive, focused, and practical feedback on titles, structure, and storytelling.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">chat_mod_server</span><span class="p">(</span><span class="s">&#34;chat_component&#34;</span><span class="p">,</span> <span class="n">chat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">\`\`\`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">\</span><span class="c1"># User experience</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">When</span> <span class="n">using</span> <span class="n">an</span> <span class="n">LLM</span> <span class="kr">in</span> <span class="n">a</span> <span class="n">Shiny</span> <span class="n">application</span><span class="p">,</span> <span class="n">it</span> <span class="n">is</span> <span class="n">important</span> <span class="n">to</span> <span class="n">consider</span> <span class="n">the</span> <span class="n">user</span> <span class="n">experience.</span> <span class="n">The</span> <span class="n">LLM</span> <span class="n">should</span> <span class="n">enhance</span> <span class="n">the</span> <span class="n">application</span><span class="p">,</span> <span class="n">not</span> <span class="n">overwhelm</span> <span class="n">it.</span> <span class="n">This</span> <span class="n">means</span> <span class="n">that</span> <span class="n">the</span> <span class="n">LLM</span><span class="s">&#39;s responses should be concise and relevant to the user&#39;</span><span class="n">s</span> <span class="n">query.</span> <span class="n">Additionally</span><span class="p">,</span> <span class="n">it</span> <span class="n">is</span> <span class="n">crucial</span> <span class="n">to</span> <span class="n">handle</span> <span class="n">errors</span> <span class="n">gracefully</span><span class="p">,</span> <span class="n">providing</span> <span class="n">users</span> <span class="n">with</span> <span class="n">helpful</span> <span class="n">feedback</span> <span class="kr">if</span> <span class="n">the</span> <span class="n">LLM</span> <span class="n">cannot</span> <span class="n">generate</span> <span class="n">a</span> <span class="n">response</span> <span class="n">or</span> <span class="kr">if</span> <span class="n">there</span> <span class="n">are</span> <span class="n">issues</span> <span class="n">with</span> <span class="n">the</span> <span class="n">API.</span> <span class="n">Therefore</span><span class="p">,</span> <span class="n">implementing</span> <span class="n">proper</span> <span class="n">error</span> <span class="n">handling</span> <span class="n">and</span> <span class="n">fallback</span> <span class="n">mechanisms</span> <span class="n">is</span> <span class="n">essential.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">\</span><span class="c1"># Up next</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Of</span> <span class="n">course</span> <span class="n">we</span><span class="s">&#39;</span><span class="err">re not ready yet. Next steps include:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">-</span>   <span class="n">Adding</span> <span class="n">a</span> <span class="n">file</span> <span class="n">upload</span> <span class="n">button</span>
</span></span><span class="line"><span class="cl"><span class="c1">## Reading the content of the presentation</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>   <span class="n">Crafting</span> <span class="n">our</span> <span class="n">prompt</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>   <span class="n">Extract</span> <span class="n">information</span> <span class="kr">in</span> <span class="n">a</span> <span class="n">structured</span> <span class="n">way</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span>   <span class="n">Display</span> <span class="n">that</span> <span class="n">information</span> <span class="kr">in</span> <span class="n">a</span> <span class="n">visually</span> <span class="n">appealing</span> <span class="nf">manner </span><span class="p">(</span><span class="n">value</span> <span class="n">boxes</span><span class="p">,</span> <span class="n">use</span> <span class="n">of</span> <span class="n">colours</span><span class="p">,</span> <span class="n">icons</span><span class="p">,</span> <span class="n">functionality</span> <span class="n">to</span> <span class="n">export</span> <span class="n">the</span> <span class="n">feedback...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">\</span><span class="c1"># Thank you!</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<p>This presentation would be suitable for a lightning talk (a maximum of 10 minutes), and for an audience that is not yet familiar with <code>shiny</code> and <code>chatlas</code>/<code>ellmer</code>.</p>
<p>To turn this <code>.qmd</code> file programmatically into markdown (<code>.md</code>) we would need to call quarto from our system. This can be done via the CLI or, in the case of R, via the <a href="https://quarto-dev.github.io/quarto-r/" target="_blank" rel="noopener"><code>quarto</code> package</a>
. Note that our simple presentation doesn&rsquo;t make much use of the fancy stuff you can do with Quarto, so our <code>.md</code> file will almost look exactly the same as our <code>.qmd</code> file. But keep in mind we&rsquo;re building for a wider audience!</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-7" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-7-1">Python</a></li>
<li><a href="#tabset-7-2">R</a></li>
</ul>
<div id="tabset-7-1">
<p>We can run quarto from the CLI with <code>subprocess</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">([</span><span class="s2">&#34;quarto&#34;</span><span class="p">,</span> <span class="s2">&#34;render&#34;</span><span class="p">,</span> <span class="s2">&#34;my-presentation.qmd&#34;</span><span class="p">,</span> <span class="s2">&#34;--to&#34;</span><span class="p">,</span> <span class="s2">&#34;markdown&#34;</span><span class="p">])</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-7-2">
<p>Using the <code>quarto</code> package:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">quarto</span><span class="o">::</span><span class="nf">quarto_render</span><span class="p">(</span><span class="s">&#34;my-presentation.qmd&#34;</span><span class="p">,</span> <span class="n">output_format</span> <span class="o">=</span> <span class="s">&#34;markdown&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>or as a sytem command:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">system</span><span class="p">(</span><span class="s">&#34;quarto render my-presentation.qmd --to markdown&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>Now our system prompt can look something like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">You are a presentation coach for data scientists that analyses presentation slide decks written in Markdown. 
</span></span><span class="line"><span class="cl">You extract key information, evaluate quality, and return structured feedback that is constructive, focused and practical.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">The presentation you are helping with is a 10-minute lightning talk at posit::conf(2025).  
</span></span><span class="line"><span class="cl">The audience is Python and R users who are curious about AI and large language models, 
</span></span><span class="line"><span class="cl">but not all of them have a deep technical background. 
</span></span><span class="line"><span class="cl">The talk uses Shiny as a way to demo and explore LLMs in practice. 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">You extract the following information:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">You score the presentation on the following categories (from 1–10), and give a concise explanation:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Always return your answer as a JSON object with the following structure:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show full prompt</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">You are a presentation coach for data scientists that analyses presentation slide decks written in Markdown. 
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract key information, evaluate quality, and return structured feedback that is constructive, focused and practical.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">The presentation you are helping with is a 10-minute lightning talk at posit::conf(2025).  
</span></span></span><span class="line"><span class="cl"><span class="s2">The audience is Python and R users who are curious about AI and large language models, 
</span></span></span><span class="line"><span class="cl"><span class="s2">but not all of them have a deep technical background. 
</span></span></span><span class="line"><span class="cl"><span class="s2">The talk uses Shiny as a way to demo and explore LLMs in practice. 
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract the following information:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">1. The presentation title
</span></span></span><span class="line"><span class="cl"><span class="s2">2. Total number of slides
</span></span></span><span class="line"><span class="cl"><span class="s2">3. Percentage of slides containing code blocks
</span></span></span><span class="line"><span class="cl"><span class="s2">4. Percentage of slides containing images
</span></span></span><span class="line"><span class="cl"><span class="s2">5. Estimated presentation length (in minutes, assuming ~1 minute per text slide and 2–3 minutes per code or image-heavy slide)
</span></span></span><span class="line"><span class="cl"><span class="s2">6. Tone (a brief description)
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You score the presentation on the following categories (from 1–10), and give a concise explanation:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">1. Clarity of content: evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?
</span></span></span><span class="line"><span class="cl"><span class="s2">2. Relevance for intended audience: assess how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?
</span></span></span><span class="line"><span class="cl"><span class="s2">3. Visual design: judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?
</span></span></span><span class="line"><span class="cl"><span class="s2">4. Engagement: estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?
</span></span></span><span class="line"><span class="cl"><span class="s2">5. Pacing: analyze the distribution of content across slides. Are some slides too dense or too light? 
</span></span></span><span class="line"><span class="cl"><span class="s2">6. Structure: review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?
</span></span></span><span class="line"><span class="cl"><span class="s2">7. consistency: evaluatue whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?
</span></span></span><span class="line"><span class="cl"><span class="s2">8. Accessibility: consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Always return your answer as a JSON object with the following structure:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">{
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;presentation_title&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;total_slides&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;percent_with_code&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;percent_with_images&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;estimated_duration_minutes&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;tone&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;clarity&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;relevance&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;visual_design&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;engagement&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;pacing&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;structure&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;concistency&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;accessibility&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  }
</span></span></span><span class="line"><span class="cl"><span class="s2">}
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<p>And our user prompt:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">Here are the slides in Markdown: ...
</span></span></code></pre></td></tr></table>
</div>
</div><h1 id="storing-prompts-and-dynamic-data">Storing prompts and dynamic data
</h1>
<p>As you know by know, you need to provide very specific instructions in your prompt if you&rsquo;re looking for good outcomes. And the more you specify, the lengthier your prompt becomes. Eventually this is going to be very annoying to put in your code directly. Therefore, it&rsquo;s a good idea to store your prompt somewhere. We determined markdown is the way to go for inputting slide content, but the same motivation applies to our prompt. Besides that, markdown allows us to structure our prompt more clearly too: headers, bullet points, numbered lists&hellip; That&rsquo;s nice for the LLM and for you!</p>
<p>The recommended file naming convention is to use <code>prompt.md</code> if you only have one prompt, and use informative names like <code>prompt-analyse-slides.md</code> if you have multiple. For clarity, we&rsquo;ll go with the latter even though we only have one prompt (so far).</p>
<p>Since your prompt(s) are very important for the outcome of your LLM-powered app, it&rsquo;s also a good idea to track these files using git. You don&rsquo;t want to lose that precious carefully prompt that worked so well before you decided to change the whole thing.</p>
<p>Prompt(s) don&rsquo;t have to be static: they can be dynamic too. We want our app to improve Quarto presentations world-wide: many users, many Quarto slides, many audiences and presentation lengths. Our prompt needs to be able to work with this dynamic data, so our prompt needs to change depending on those variables. Luckily, we can use the <code>interpolate_file()</code> or <code>interpolate()</code> function, which, as the names suggest, can interpolate variables into a prompt template stored in a file, or in a simple string. To do so, we use the <code>{ x }</code> syntax, making our <code>prompt-analyse-slides.md</code> file look like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">The presentation you are helping with is a {{ length }}-minute {{ type }} at {{ event }}.  
</span></span><span class="line"><span class="cl">The audience is {{ audience }}. 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show full prompt</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">You are a presentation coach for data scientists that analyses presentation slide decks written in Markdown. 
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract key information, evaluate quality, and return structured feedback that is constructive, focused and practical.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">The presentation you are helping with is a {{ length }}-minute {{ type }} at {{ event }}.  
</span></span></span><span class="line"><span class="cl"><span class="s2">The audience is {{ audience }}. 
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract the following information:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">1. The presentation title
</span></span></span><span class="line"><span class="cl"><span class="s2">2. Total number of slides
</span></span></span><span class="line"><span class="cl"><span class="s2">3. Percentage of slides containing code blocks
</span></span></span><span class="line"><span class="cl"><span class="s2">4. Percentage of slides containing images
</span></span></span><span class="line"><span class="cl"><span class="s2">5. Estimated presentation length (in minutes, assuming ~1 minute per text slide and 2–3 minutes per code or image-heavy slide)
</span></span></span><span class="line"><span class="cl"><span class="s2">6. Tone (a brief description)
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You score the presentation on the following categories (from 1–10), and give a concise explanation:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">1. Clarity of content: evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?
</span></span></span><span class="line"><span class="cl"><span class="s2">2. Relevance for intended audience: assess how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?
</span></span></span><span class="line"><span class="cl"><span class="s2">3. Visual design: judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?
</span></span></span><span class="line"><span class="cl"><span class="s2">4. Engagement: estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?
</span></span></span><span class="line"><span class="cl"><span class="s2">5. Pacing: analyze the distribution of content across slides. Are some slides too dense or too light? 
</span></span></span><span class="line"><span class="cl"><span class="s2">6. Structure: review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?
</span></span></span><span class="line"><span class="cl"><span class="s2">7. consistency: evaluatue whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?
</span></span></span><span class="line"><span class="cl"><span class="s2">8. Accessibility: consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Always return your answer as a JSON object with the following structure:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">{
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;presentation_title&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;total_slides&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;percent_with_code&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;percent_with_images&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;estimated_duration_minutes&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;tone&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;clarity&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;relevance&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;visual_design&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;engagement&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;pacing&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;structure&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;concistency&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;accessibility&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  }
</span></span></span><span class="line"><span class="cl"><span class="s2">}
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<p>Our user prompt is going directly into the <code>chat</code> method and will be:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">Here are the slides in Markdown: {{ markdown }}
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note: if your user prompt is long you might want to put this in a separate prompt file as well.</p>
<blockquote>
<p><strong>Note</strong></p>
<p>You might wonder why we don&rsquo;t we put the Markdown content in our system prompt. The reason is that we don&rsquo;t want to overload our system prompt with big chunks of data like our Markdown slides. As mentioned previously, the system prompt sets the role, the rules, and provides task instructions. It is stable context. The Markdown slides are the actual content to analyse, and they change from one run to the other. This fits better in a user prompt. It&rsquo;s also good practice to keep the system prompt clean so it is reusable across runs.</p>
</blockquote>
<p>Those are prompts we can work with! So let&rsquo;s chat away:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-8" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-8-1">Python</a></li>
<li><a href="#tabset-8-2">R</a></li>
</ul>
<div id="tabset-8-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatAnthropic</span><span class="p">,</span> <span class="n">interpolate_file</span><span class="p">,</span> <span class="n">interpolate</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get Quarto presentation and convert to plain Markdown</span>
</span></span><span class="line"><span class="cl"><span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="s2">&#34;quarto&#34;</span><span class="p">,</span> <span class="s2">&#34;render&#34;</span><span class="p">,</span> <span class="s2">&#34;./Quarto/my-presentation.qmd&#34;</span><span class="p">,</span> <span class="s2">&#34;--to&#34;</span><span class="p">,</span> <span class="s2">&#34;markdown&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Dynamic data</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Audience, length in minutes, type, and event</span>
</span></span><span class="line"><span class="cl"><span class="n">audience_content</span> <span class="o">=</span> <span class="s2">&#34;Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">length_content</span> <span class="o">=</span> <span class="s2">&#34;10&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">type_content</span> <span class="o">=</span> <span class="s2">&#34;lightning talk&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">event_content</span> <span class="o">=</span> <span class="s2">&#34;posit::conf(2025)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the generated Markdown file containing our slides</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./Quarto/docs/my-presentation.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_content</span> <span class="o">=</span> <span class="n">markdown_file</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./prompts/prompt-analyse-slides.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt</span> <span class="o">=</span> <span class="n">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">variables</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;audience&#34;</span><span class="p">:</span> <span class="n">audience_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;length&#34;</span><span class="p">:</span> <span class="n">length_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="n">type_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;event&#34;</span><span class="p">:</span> <span class="n">event_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span><span class="o">=</span><span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Since all the instructions are in the system prompt, we can just</span>
</span></span><span class="line"><span class="cl"><span class="c1"># provide the Markdown content as a message</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span><span class="n">interpolate</span><span class="p">(</span><span class="s2">&#34;Here are the slides in Markdown: {{ markdown_content }}&#34;</span><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  {                                                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;presentation_title&#34;: &#34;The Shiny Side of LLMs&#34;,                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;total_slides&#34;: 16,                                                              
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;percent_with_code&#34;: 44,                                                         
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;percent_with_images&#34;: 6,                                                        
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;estimated_duration_minutes&#34;: 22,                                                
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;tone&#34;: &#34;Casual, enthusiastic, and approachable with emojis and informal languag 
</span></span></span><span class="line"><span class="cl"><span class="s2"> aimed at making technical concepts accessible&#34;,                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;clarity&#34;: {                                                                     
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 7,                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Content is generally clear with good step-by-step progressio 
</span></span></span><span class="line"><span class="cl"><span class="s2"> but some technical concepts could benefit from more explanation for non-technical  
</span></span></span><span class="line"><span class="cl"><span class="s2"> audience members. Code examples are well-structured but may be overwhelming for    
</span></span></span><span class="line"><span class="cl"><span class="s2"> beginners.&#34;                                                                        
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;relevance&#34;: {                                                                   
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 8,                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Well-targeted for the audience - provides both Python and R  
</span></span></span><span class="line"><span class="cl"><span class="s2"> examples, acknowledges varying technical backgrounds, and focuses on practical     
</span></span></span><span class="line"><span class="cl"><span class="s2"> implementation rather than deep theory. The DeckCheck example is relatable to the  
</span></span></span><span class="line"><span class="cl"><span class="s2"> conference audience.&#34;                                                              
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;visual_design&#34;: {                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 5,                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Heavy reliance on text and code blocks with minimal visual   
</span></span></span><span class="line"><span class="cl"><span class="s2"> elements. Only one image (GIF) used. Code blocks dominate several slides, making   
</span></span></span><span class="line"><span class="cl"><span class="s2"> them potentially overwhelming. Layout appears basic without clear visual hierarchy 
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;engagement&#34;: {                                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 6,                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Uses emojis and casual language to maintain interest, includ 
</span></span></span><span class="line"><span class="cl"><span class="s2"> a relatable example (DeckCheck), and has one engaging GIF. However, lacks          
</span></span></span><span class="line"><span class="cl"><span class="s2"> interactive elements beyond code examples and could benefit from more storytelling 
</span></span></span><span class="line"><span class="cl"><span class="s2"> elements.&#34;                                                                         
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;pacing&#34;: {                                                                      
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 4,                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Uneven pacing with several code-heavy slides that would      
</span></span></span><span class="line"><span class="cl"><span class="s2"> require significant time to explain, while some slides have minimal content. The   
</span></span></span><span class="line"><span class="cl"><span class="s2"> estimated 22-minute duration exceeds the 10-minute lightning talk format           
</span></span></span><span class="line"><span class="cl"><span class="s2"> considerably.&#34;                                                                     
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;structure&#34;: {                                                                   
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 8,                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Clear logical progression from introduction to requirements, 
</span></span></span><span class="line"><span class="cl"><span class="s2"> basic concepts, implementation examples, and next steps. Good parallel structure   
</span></span></span><span class="line"><span class="cl"><span class="s2"> showing both Python and R approaches. Solid beginning, middle, and end.&#34;           
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;concistency&#34;: {                                                                 
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 7,                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Maintains consistent casual tone and formatting throughout.  
</span></span></span><span class="line"><span class="cl"><span class="s2"> Code examples follow similar patterns for both languages. Some inconsistency in    
</span></span></span><span class="line"><span class="cl"><span class="s2"> slide density and the single image feels somewhat disconnected from the overall    
</span></span></span><span class="line"><span class="cl"><span class="s2"> style.&#34;                                                                            
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;accessibility&#34;: {                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 6,                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Code blocks may be challenging for those with visual         
</span></span></span><span class="line"><span class="cl"><span class="s2"> impairments due to syntax highlighting and smaller fonts. The casual tone and emoj 
</span></span></span><span class="line"><span class="cl"><span class="s2"> help with cognitive accessibility, but the technical density could be overwhelming 
</span></span></span><span class="line"><span class="cl"><span class="s2"> for some audience members.&#34;                                                        
</span></span></span><span class="line"><span class="cl"><span class="s2">   }                                                                                
</span></span></span><span class="line"><span class="cl"><span class="s2"> }
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
<div id="tabset-8-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get Quarto presentation and convert to plain Markdown</span>
</span></span><span class="line"><span class="cl"><span class="n">quarto</span><span class="o">::</span><span class="nf">quarto_render</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;./Quarto/my-presentation.qmd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">output_format</span> <span class="o">=</span> <span class="s">&#34;markdown&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Dynamic data</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Audience, length in minutes, type, and event</span>
</span></span><span class="line"><span class="cl"><span class="n">audience_content</span> <span class="o">&lt;-</span> <span class="s">&#34;Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">length_content</span> <span class="o">&lt;-</span> <span class="s">&#34;10&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">type_content</span> <span class="o">&lt;-</span> <span class="s">&#34;lightning talk&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">event_content</span> <span class="o">&lt;-</span> <span class="s">&#34;posit::conf(2025)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the generated Markdown file containing our slides</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./Quarto/docs/my-presentation.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_content</span> <span class="o">&lt;-</span> <span class="nf">readChar</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">,</span> <span class="nf">file.size</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./prompts/prompt-analyse-slides.md&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt</span> <span class="o">&lt;-</span> <span class="nf">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">path</span> <span class="o">=</span> <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">audience</span> <span class="o">=</span> <span class="n">audience_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">length</span> <span class="o">=</span> <span class="n">length_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="o">=</span> <span class="n">type_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">event</span> <span class="o">=</span> <span class="n">event_content</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">params</span> <span class="o">=</span> <span class="nf">params</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">temperature</span> <span class="o">=</span> <span class="m">0.8</span> <span class="c1"># default is 1</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Since all the instructions are in the system prompt, we can just</span>
</span></span><span class="line"><span class="cl"><span class="c1"># provide the Markdown content as a message</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat</span><span class="p">(</span><span class="nf">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Here are the slides in Markdown: {{ markdown_content }}&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#&gt; {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;presentation_title&#34;: &#34;The Shiny Side of LLMs&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;total_slides&#34;: 16,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;percent_with_code&#34;: 37.5,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;percent_with_images&#34;: 6.25,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;estimated_duration_minutes&#34;: 14,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;tone&#34;: &#34;Casual, friendly, and enthusiastic with emoji use and conversational </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; language that makes technical content approachable&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;clarity&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 7,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Content is generally clear with good progression from basics </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; to implementation. However, some technical details could benefit from more </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; explanation, and the system prompts in code examples could be better integrated </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; into the narrative.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;relevance&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 8,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Excellent match for the audience - shows practical </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; integration of LLMs with familiar tools (Shiny), provides both Python and R </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; examples, and focuses on a relatable use case (presentation feedback) that </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; resonates with the posit::conf audience.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;visual_design&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 6,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Basic markdown structure is clean but lacks visual hierarchy </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; and design elements. Code blocks are well-formatted, but slides could benefit from </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; more visual breaks, bullet points, and consistent formatting. The single GIF adds </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; some visual interest.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;engagement&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 7,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Good use of humor, emojis, and relatable scenarios (boring </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; presentations). The progression from simple to complex keeps interest, and the </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; practical demo concept is engaging. Could benefit from more interactive elements or</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; storytelling.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;pacing&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 6,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Some slides are too dense (UX slide, code-heavy slides) while</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; others are very light (Thank you slide). The progression is logical but could be </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; more evenly distributed. Code slides may take longer than estimated.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;structure&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 8,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Clear logical flow from introduction to requirements, basic </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; concepts, implementation, and next steps. Good parallel structure showing both </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Python and R implementations. Strong opening and clear progression toward building </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; the demo app.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;concistency&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 7,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Generally consistent tone and approach throughout. Code </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; formatting is consistent between languages. However, some slides vary significantly</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; in content density, and the casual tone occasionally conflicts with more technical </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; sections.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;accessibility&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 5,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Basic markdown structure is accessible, but lacks </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; considerations for screen readers (no alt text for the GIF), may have contrast </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; issues depending on theme, and code blocks could be challenging for some users. </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Font sizes and spacing depend on presentation software.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   }</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; }</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
</div>
<blockquote>
<p><strong>Inconsistencies in the output</strong></p>
<p>Did you already notice that some meta-data, like the percentage of slides with code and/or images is inconsistent? For example, the &ldquo;percent_with_code&rdquo; is 40, but other times it&rsquo;s 37.5 or 45. It seems like a broken calculator! To understand why this happens you can read <a href="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-2/../../../blog/shiny/shiny-side-of-llms-part-1/">part 1</a>
 again, where we talk about why LLMs are really good at some tasks, and others, well&hellip; Not so much. That doesn&rsquo;t mean it&rsquo;s completely out of our hands. We can help the LLM with a &ldquo;tool&rdquo;. Keep on reading to learn more about that concept.</p>
</blockquote>
<p>It&rsquo;s not too bad for a first try and it serves as a basis we can depart from. We can go back and forth many times, aka do some &ldquo;prompt engineering&rdquo;, to make sure we are getting results that are usable. The prompt that we used mostly returns a broad analysis (makes sense, we asked it to), but if we want our users to take action we need to provide specific improvements. So we could extend our prompt with &ldquo;provide specific and actionable improvements&rdquo; and provide specific instructions like &ldquo;keep each suggestion concise and mention the slide number(s) if applicable&rdquo;:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">You score the presentation on the following categories (from 1–10), and give a concise explanation:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">For each of the above scoring categories, provide specific and actionable improvements. Follow these instructions:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> Keep each suggestion concise and mention the slide number(s) if applicable.
</span></span><span class="line"><span class="cl"><span class="k">-</span> Do not invent issues and only suggest improvements when the content would clearly benefit from them.
</span></span><span class="line"><span class="cl"><span class="k">-</span> For each catogory, estimate what the new score would be if these improvements are implemented.
</span></span><span class="line"><span class="cl"><span class="k">-</span> Return the improvement and new score as part of the response for that category.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show full prompt</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span><span class="lnt">83
</span><span class="lnt">84
</span><span class="lnt">85
</span><span class="lnt">86
</span><span class="lnt">87
</span><span class="lnt">88
</span><span class="lnt">89
</span><span class="lnt">90
</span><span class="lnt">91
</span><span class="lnt">92
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">You are a presentation coach for data scientists that analyses presentation slide decks written in Markdown. 
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract key information, evaluate quality, and return structured feedback that is constructive, focused and practical.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">The presentation you are helping with is a {{ length }}-minute {{ type }} at {{ event }}.  
</span></span></span><span class="line"><span class="cl"><span class="s2">The audience is {{ audience }}. 
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract the following information:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">1. The presentation title
</span></span></span><span class="line"><span class="cl"><span class="s2">2. Total number of slides
</span></span></span><span class="line"><span class="cl"><span class="s2">3. Percentage of slides containing code blocks
</span></span></span><span class="line"><span class="cl"><span class="s2">4. Percentage of slides containing images
</span></span></span><span class="line"><span class="cl"><span class="s2">5. Estimated presentation length (in minutes, assuming ~1 minute per text slide and 2–3 minutes per code or image-heavy slide)
</span></span></span><span class="line"><span class="cl"><span class="s2">6. Tone (a brief description)
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You score the presentation on the following categories (from 1–10), and give a concise explanation:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">1. Clarity of content: evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?
</span></span></span><span class="line"><span class="cl"><span class="s2">2. Relevance for intended audience: assess how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?
</span></span></span><span class="line"><span class="cl"><span class="s2">3. Visual design: judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?
</span></span></span><span class="line"><span class="cl"><span class="s2">4. Engagement: estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?
</span></span></span><span class="line"><span class="cl"><span class="s2">5. Pacing: analyze the distribution of content across slides. Are some slides too dense or too light? 
</span></span></span><span class="line"><span class="cl"><span class="s2">6. Structure: review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?
</span></span></span><span class="line"><span class="cl"><span class="s2">7. consistency: evaluatue whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?
</span></span></span><span class="line"><span class="cl"><span class="s2">8. Accessibility: consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">For each of the above scoring categories, provide specific and actionable improvements. Follow these instructions:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">- Keep each suggestion concise and mention the slide number(s) if applicable.
</span></span></span><span class="line"><span class="cl"><span class="s2">- Do not invent issues and only suggest improvements when the content would clearly benefit from them.
</span></span></span><span class="line"><span class="cl"><span class="s2">- For each catogory, estimate what the new score would be if these improvements are implemented.
</span></span></span><span class="line"><span class="cl"><span class="s2">- Return the improvement and new score as part of the response for that category.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Always return your answer as a JSON object with the following structure:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">{
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;presentation_title&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;total_slides&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;percent_with_code&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;percent_with_images&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;estimated_duration_minutes&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;tone&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;clarity&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;improvements&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score_after_improvements&#34;: 0
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;relevance&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;improvements&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score_after_improvements&#34;: 0
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;visual_design&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;improvements&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score_after_improvements&#34;: 0
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;engagement&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;improvements&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score_after_improvements&#34;: 0
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;pacing&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;improvements&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score_after_improvements&#34;: 0
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;structure&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;improvements&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score_after_improvements&#34;: 0
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;consistency&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;improvements&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score_after_improvements&#34;: 0
</span></span></span><span class="line"><span class="cl"><span class="s2">  },
</span></span></span><span class="line"><span class="cl"><span class="s2">  &#34;accessibility&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;justification&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;improvements&#34;: &#34;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;score_after_improvements&#34;: 0
</span></span></span><span class="line"><span class="cl"><span class="s2">  }
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-9" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-9-1">Python</a></li>
<li><a href="#tabset-9-2">R</a></li>
</ul>
<div id="tabset-9-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./prompts/prompt-analyse-slides-with-improvements.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt</span> <span class="o">=</span> <span class="n">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">variables</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;audience&#34;</span><span class="p">:</span> <span class="n">audience_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;length&#34;</span><span class="p">:</span> <span class="n">length_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="n">type_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;event&#34;</span><span class="p">:</span> <span class="n">event_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span><span class="o">=</span><span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span><span class="n">interpolate</span><span class="p">(</span><span class="s2">&#34;Here are the slides in Markdown: {{ markdown_content }}&#34;</span><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show complete code</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatAnthropic</span><span class="p">,</span> <span class="n">interpolate_file</span><span class="p">,</span> <span class="n">interpolate</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get Quarto presentation and convert to plain Markdown</span>
</span></span><span class="line"><span class="cl"><span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="s2">&#34;quarto&#34;</span><span class="p">,</span> <span class="s2">&#34;render&#34;</span><span class="p">,</span> <span class="s2">&#34;./Quarto/my-presentation.qmd&#34;</span><span class="p">,</span> <span class="s2">&#34;--to&#34;</span><span class="p">,</span> <span class="s2">&#34;markdown&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Dynamic data</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Audience, length in minutes, type, and event</span>
</span></span><span class="line"><span class="cl"><span class="n">audience_content</span> <span class="o">=</span> <span class="s2">&#34;Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">length_content</span> <span class="o">=</span> <span class="s2">&#34;10&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">type_content</span> <span class="o">=</span> <span class="s2">&#34;lightning talk&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">event_content</span> <span class="o">=</span> <span class="s2">&#34;posit::conf(2025)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the generated Markdown file containing our slides</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./Quarto/docs/my-presentation.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_content</span> <span class="o">=</span> <span class="n">markdown_file</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./prompts/prompt-analyse-slides-with-improvements.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt</span> <span class="o">=</span> <span class="n">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">variables</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;audience&#34;</span><span class="p">:</span> <span class="n">audience_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;length&#34;</span><span class="p">:</span> <span class="n">length_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="n">type_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;event&#34;</span><span class="p">:</span> <span class="n">event_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span><span class="o">=</span><span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Since all the instructions are in the system prompt, we can just</span>
</span></span><span class="line"><span class="cl"><span class="c1"># provide the Markdown content as a message</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span><span class="n">interpolate</span><span class="p">(</span><span class="s2">&#34;Here are the slides in Markdown: {{ markdown_content }}&#34;</span><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<details class="code-fold">
<summary>Show output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">  {                                                                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;presentation_title&#34;: &#34;The Shiny Side of LLMs&#34;,                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;total_slides&#34;: 16,                                                                              
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;percent_with_code&#34;: 43.75,                                                                      
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;percent_with_images&#34;: 6.25,                                                                     
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;estimated_duration_minutes&#34;: 23,                                                                
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;tone&#34;: &#34;Casual, enthusiastic, and developer-friendly with emojis and informal language&#34;,        
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;clarity&#34;: {                                                                                     
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 7,                                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Content is generally clear with good explanations of technical concepts, but 
</span></span></span><span class="line"><span class="cl"><span class="s2"> some slides lack necessary context or detail for the intended audience&#34;,                           
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;improvements&#34;: &#34;Define &#39;chatlas&#39; and &#39;ellmer&#39; when first introduced (slides 5-6). Add brief   
</span></span></span><span class="line"><span class="cl"><span class="s2"> explanation of what &#39;system_prompt&#39; does. Explain what &#39;reactive programming model&#39; means in       
</span></span></span><span class="line"><span class="cl"><span class="s2"> practical terms (slide 7).&#34;,                                                                       
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score_after_improvements&#34;: 8                                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;relevance&#34;: {                                                                                   
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 8,                                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Very relevant for Python and R users interested in AI/LLMs. Code examples ar 
</span></span></span><span class="line"><span class="cl"><span class="s2"> appropriate and the practical focus matches audience needs&#34;,                                       
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;improvements&#34;: &#34;Add a brief comparison of when to choose Python vs R approach (slide 4).      
</span></span></span><span class="line"><span class="cl"><span class="s2"> Include mention of cost considerations for API usage in slide 2.&#34;,                                 
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score_after_improvements&#34;: 9                                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;visual_design&#34;: {                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 5,                                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Heavy text density on many slides, particularly slides with code blocks.     
</span></span></span><span class="line"><span class="cl"><span class="s2"> Limited visual elements beyond one GIF&#34;,                                                           
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;improvements&#34;: &#34;Break up text-heavy slides (2, 14) into bullet points or multiple slides. Add 
</span></span></span><span class="line"><span class="cl"><span class="s2"> diagrams showing app architecture (slides 4, 7). Use consistent formatting for code blocks and     
</span></span></span><span class="line"><span class="cl"><span class="s2"> ensure proper syntax highlighting.&#34;,                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score_after_improvements&#34;: 7                                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;engagement&#34;: {                                                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 6,                                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Casual tone and emojis add personality, and the practical demo concept is    
</span></span></span><span class="line"><span class="cl"><span class="s2"> engaging, but lacks interactive elements or storytelling&#34;,                                         
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;improvements&#34;: &#34;Add a brief demo video or screenshots of the final app. Include a &#39;What you&#39;l 
</span></span></span><span class="line"><span class="cl"><span class="s2"> learn today&#39; slide early on. Add audience poll question about their LLM experience (slide 2).&#34;,    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score_after_improvements&#34;: 8                                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;pacing&#34;: {                                                                                      
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 4,                                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Presentation is too long for 10-minute slot (estimated 23 minutes) with unev 
</span></span></span><span class="line"><span class="cl"><span class="s2"> distribution - some slides very dense, others very light&#34;,                                         
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;improvements&#34;: &#34;Combine slides 5-6 into one comparison slide. Merge slides 8-9 into single    
</span></span></span><span class="line"><span class="cl"><span class="s2"> Python/R comparison. Remove or significantly shorten slide 14. Condense setup requirements (slide  
</span></span></span><span class="line"><span class="cl"><span class="s2"> 2).&#34;,                                                                                              
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score_after_improvements&#34;: 7                                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;structure&#34;: {                                                                                   
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 7,                                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Good logical flow from introduction to implementation, but missing clear     
</span></span></span><span class="line"><span class="cl"><span class="s2"> conclusion and call-to-action&#34;,                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;improvements&#34;: &#34;Add agenda/outline slide after slide 1. Include key takeaways and resources   
</span></span></span><span class="line"><span class="cl"><span class="s2"> slide before &#39;Thank you&#39;. Make slide 15 more specific about next steps.&#34;,                          
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score_after_improvements&#34;: 8                                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;consistency&#34;: {                                                                                 
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 8,                                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Generally consistent tone and formatting throughout, with good parallel      
</span></span></span><span class="line"><span class="cl"><span class="s2"> structure between Python and R examples&#34;,                                                          
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;improvements&#34;: &#34;Ensure consistent code block formatting and indentation. Standardize bullet   
</span></span></span><span class="line"><span class="cl"><span class="s2"> point styles across all slides.&#34;,                                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score_after_improvements&#34;: 9                                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">   },                                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">   &#34;accessibility&#34;: {                                                                               
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score&#34;: 6,                                                                                    
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;justification&#34;: &#34;Code examples may have small font sizes, and some slides are text-heavy whic 
</span></span></span><span class="line"><span class="cl"><span class="s2"> could be challenging for some viewers&#34;,                                                            
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;improvements&#34;: &#34;Ensure code font size is at least 14pt. Add alt text description for the GIF  
</span></span></span><span class="line"><span class="cl"><span class="s2"> (slide 11). Break up dense paragraphs with more white space and bullet points.&#34;,                   
</span></span></span><span class="line"><span class="cl"><span class="s2">     &#34;score_after_improvements&#34;: 8                                                                  
</span></span></span><span class="line"><span class="cl"><span class="s2">   }                                                                                                
</span></span></span><span class="line"><span class="cl"><span class="s2"> }
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span> 
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
<div id="tabset-9-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./prompts/prompt-analyse-slides-with-improvements.md&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt</span> <span class="o">&lt;-</span> <span class="nf">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">path</span> <span class="o">=</span> <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">audience</span> <span class="o">=</span> <span class="n">audience_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">length</span> <span class="o">=</span> <span class="n">length_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="o">=</span> <span class="n">type_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">event</span> <span class="o">=</span> <span class="n">event_content</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">system_prompt</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat</span><span class="p">(</span><span class="nf">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Here are the slides in Markdown: {{ markdown_content }}&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show complete code</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get Quarto presentation and convert to plain Markdown</span>
</span></span><span class="line"><span class="cl"><span class="n">quarto</span><span class="o">::</span><span class="nf">quarto_render</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;./Quarto/my-presentation.qmd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">output_format</span> <span class="o">=</span> <span class="s">&#34;markdown&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Dynamic data</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Audience, length in minutes, type, and event</span>
</span></span><span class="line"><span class="cl"><span class="n">audience_content</span> <span class="o">&lt;-</span> <span class="s">&#34;Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">length_content</span> <span class="o">&lt;-</span> <span class="s">&#34;10&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">type_content</span> <span class="o">&lt;-</span> <span class="s">&#34;lightning talk&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">event_content</span> <span class="o">&lt;-</span> <span class="s">&#34;posit::conf(2025)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the generated Markdown file containing our slides</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./Quarto/docs/my-presentation.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_content</span> <span class="o">&lt;-</span> <span class="nf">readChar</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">,</span> <span class="nf">file.size</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./prompts/prompt-analyse-slides-with-improvements.md&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt</span> <span class="o">&lt;-</span> <span class="nf">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">path</span> <span class="o">=</span> <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">audience</span> <span class="o">=</span> <span class="n">audience_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">length</span> <span class="o">=</span> <span class="n">length_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="o">=</span> <span class="n">type_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">event</span> <span class="o">=</span> <span class="n">event_content</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">system_prompt</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Since all the instructions are in the system prompt, we can just</span>
</span></span><span class="line"><span class="cl"><span class="c1"># provide the Markdown content as a message</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat</span><span class="p">(</span><span class="nf">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Here are the slides in Markdown: {{ markdown_content }}&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<details class="code-fold">
<summary>Show output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span><span class="lnt">83
</span><span class="lnt">84
</span><span class="lnt">85
</span><span class="lnt">86
</span><span class="lnt">87
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#&gt; {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;presentation_title&#34;: &#34;The Shiny Side of LLMs&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;total_slides&#34;: 16,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;percent_with_code&#34;: 37.5,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;percent_with_images&#34;: 6.25,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;estimated_duration_minutes&#34;: 14,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;tone&#34;: &#34;Casual, friendly, and practical with a playful touch (emojis, informal </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; language, and humor)&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;clarity&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 7,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Content is generally clear with good code examples and </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; explanations. However, some technical terms could use more definition for the </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; mixed-technical audience.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;improvements&#34;: &#34;Define key terms like &#39;reactive programming&#39; (slide 7), </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; explain what API keys/tokens are (slide 2), and clarify what &#39;system_prompt&#39; does </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; in the code examples (slides 5-6).&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score_after_improvements&#34;: 8</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;relevance&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 8,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Excellent match for the audience - Python and R users curious</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; about AI/LLMs. Provides practical, hands-on examples in both languages and focuses </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; on implementation rather than deep theory.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;improvements&#34;: &#34;Add a brief slide explaining what LLMs are and their basic </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; capabilities for audience members completely new to AI (slide 3 area).&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score_after_improvements&#34;: 9</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;visual_design&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 5,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Heavy reliance on text and code blocks. Most slides are </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; text-dense with minimal visual elements. Only one image used, and code blocks </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; dominate several slides.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;improvements&#34;: &#34;Add visual diagrams showing the Shiny-LLM integration flow </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; (slide 4), include screenshots of the final app (slide 3), reduce text density on </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; slides 13 and 15, and add icons or visual elements to break up text-heavy slides.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score_after_improvements&#34;: 7</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;engagement&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 6,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Casual tone with emojis and humor helps engagement, but heavy</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; code blocks and text-dense slides could lose audience attention. The practical </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; &#39;build along&#39; approach is engaging.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;improvements&#34;: &#34;Add interactive moments like &#39;raise your hand if you&#39;ve used </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Shiny before&#39; (slide 7), include a live demo or video clip of the app in action </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; (slide 11), and break up slide 13 with bullet points or visual elements.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score_after_improvements&#34;: 8</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;pacing&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 6,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Some slides are too dense (slides 8, 9, 11, 12, 13) while </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; others are very light (slides 1, 16). The code-heavy slides will take longer to </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; present than estimated.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;improvements&#34;: &#34;Split slide 11 into two slides (one for Python UI, one for </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; server), break slide 13 into key points with visuals, and add more content to the </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; thank you slide (slide 16) with contact info or resources.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score_after_improvements&#34;: 8</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;structure&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 8,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Clear logical flow from introduction to requirements to </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; implementation to next steps. Good progression from basic concepts to integration. </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Clear beginning, middle, and end.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;improvements&#34;: &#34;Add a brief agenda slide after slide 1 to preview the journey,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; and include a &#39;key takeaways&#39; slide before the thank you slide.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score_after_improvements&#34;: 9</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;concistency&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 7,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Generally consistent tone and formatting. Code blocks are </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; well-formatted. Some inconsistency in slide density and the mix of casual/technical</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; language.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;improvements&#34;: &#34;Standardize slide titles (some are questions, some </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; statements), ensure consistent bullet point formatting across slides, and maintain </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; consistent emoji usage throughout.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score_after_improvements&#34;: 8</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   },</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;accessibility&#34;: {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score&#34;: 6,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;justification&#34;: &#34;Code blocks may be challenging to read for some viewers. </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Heavy text density on several slides could be overwhelming. No obvious color </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; contrast issues mentioned.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;improvements&#34;: &#34;Increase font size in code blocks (slides 5-6, 8-9, 11-12), </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; add alt text descriptions for the GIF image (slide 10), reduce text density on </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; slide 13, and ensure sufficient white space between elements.&#34;,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     &#34;score_after_improvements&#34;: 8</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   }</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; }</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
</div>
<p>And of course Claude believes that all the suggested improvements increase the relevant scores. However, don&rsquo;t be surprised if the numbers are off. Remember from <a href="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-2/../../../blog/shiny/shiny-side-of-llms-part-1/">part 1</a>
 that it&rsquo;s just a model that predicts a next token. It&rsquo;s good to do some sanity checks.</p>
<h1 id="ensuring-structured-and-consistent-output">Ensuring structured and consistent output
</h1>
<p>As mentioned previously, specifying an output format makes your outcomes better. That&rsquo;s why we included: <em>&ldquo;Please extract the following information&hellip; Return your answer as a JSON object with the following structure&hellip;&rdquo;</em> in our prompt. Formats like JSON, YAML, and Markdown tables are common, and JSON tends to be a safe bet for both Python and R. It&rsquo;s clean, structured, and easy to parse.</p>
<p>And even though we&rsquo;re explicitly guiding the model toward structured JSON output, we don&rsquo;t always get what we expect: the model might wrap its output in a code block (e.g., ```json), or forget a comma, or produce something that looks like JSON but isn&rsquo;t quite valid. That&rsquo;s weird, right? It&rsquo;s because specifying the format in the prompt is essentially giving the model strong instructions in plain language. You&rsquo;re still relying on the model to follow your instructions correctly and generate the output in that format. This works reasonably well, but it&rsquo;s still a natural language generation task under the hood. And as we learned in part I: it&rsquo;s all just predicting one token after the other. If the probability of a space instead of a comma is higher&hellip; Well, goodbye parsable JSON.</p>
<p>So we need something smarter. That&rsquo;s where something called &ldquo;structured output&rdquo; comes in. Structured output refers to LLMs that support structured schemas as part of the API or function call and not just as instructions in the prompt. These tell the model not just what to output, but how to constrain the generation itself. The model generates a structure directly (like a JSON object or typed dictionary), and the output is guaranteed to match that structure.</p>
<p>The difference is:</p>
<ul>
<li>Prompting: &ldquo;Please follow this format&rdquo; (model might comply)</li>
<li>Structured output: &ldquo;You must generate output that fits this exact shape&rdquo; (enforced)</li>
</ul>
<p>With <code>chatlas</code> and <code>ellmer</code> we can make use of this structured data feature. Bonus: <code>chatlas</code> will convert the response into a structured Python dictionary, and <code>ellmer</code> into an R data structure so it&rsquo;s immediately ready to use!</p>
<p>To return structured data we need to change two things: call <code>chat_structured()</code> and provide a specification. In this specification we can specify the fields, their type, and add a description for each of them. Optionally, to prevent duplication, we can remove the field descriptions from our prompt, and refer to the &ldquo;data model&rdquo; instead:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Always return the result as a JSON object that conforms to the provided data model.
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show full prompt</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">You are a presentation coach for data scientists that analyses presentation slide decks written in Markdown. 
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract key information, evaluate quality, and return structured feedback that is constructive, focused and practical.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">The presentation you are helping with is a {{ length }}-minute {{ type }} at {{ event }}.  
</span></span></span><span class="line"><span class="cl"><span class="s2">The audience is {{ audience }}. 
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract the following information:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">1. The presentation title
</span></span></span><span class="line"><span class="cl"><span class="s2">2. Total number of slides
</span></span></span><span class="line"><span class="cl"><span class="s2">3. Percentage of slides containing code blocks
</span></span></span><span class="line"><span class="cl"><span class="s2">4. Percentage of slides containing images
</span></span></span><span class="line"><span class="cl"><span class="s2">5. Estimated presentation length (in minutes, assuming ~1 minute per text slide and 2–3 minutes per code or image-heavy slide)
</span></span></span><span class="line"><span class="cl"><span class="s2">6. Tone (a brief description)
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You score the presentation on the following categories (from 1–10), and give a concise explanation:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">1. Clarity of content: evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?
</span></span></span><span class="line"><span class="cl"><span class="s2">2. Relevance for intended audience: assess how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?
</span></span></span><span class="line"><span class="cl"><span class="s2">3. Visual design: judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?
</span></span></span><span class="line"><span class="cl"><span class="s2">4. Engagement: estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?
</span></span></span><span class="line"><span class="cl"><span class="s2">5. Pacing: analyze the distribution of content across slides. Are some slides too dense or too light? 
</span></span></span><span class="line"><span class="cl"><span class="s2">6. Structure: review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?
</span></span></span><span class="line"><span class="cl"><span class="s2">7. consistency: evaluatue whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?
</span></span></span><span class="line"><span class="cl"><span class="s2">8. Accessibility: consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">For each of the above scoring categories, provide specific and actionable improvements. Follow these instructions:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">- Keep each suggestion concise and mention the slide number(s) if applicable.
</span></span></span><span class="line"><span class="cl"><span class="s2">- Do not invent issues and only suggest improvements when the content would clearly benefit from them.
</span></span></span><span class="line"><span class="cl"><span class="s2">- For each catogory, estimate what the new score would be if these improvements are implemented.
</span></span></span><span class="line"><span class="cl"><span class="s2">- Return the improvement and new score as part of the response for that category.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Always return the result as a JSON object that conforms to the provided data model.
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<blockquote>
<p><strong>Double work or Don&rsquo;t Repeat Yourself (DRY)?</strong></p>
<p>This approach assumes the model actually has access to the data model definition, either via a tool, API schema, or injected example. Without that, results may be inconsistent. It seems that Claude&rsquo;s results are quite good this way, but it may vary for other models, so it&rsquo;s good to try different things.</p>
<p>For educational purposes it&rsquo;s nice to see that the LLM is getting the required information from the provided data model, and not from the system prompt.</p>
<p>Having both the description in the prompt and in a data model is an option to, especially when:</p>
<ul>
<li>You want strong guidance and fallback validation.</li>
<li>You&rsquo;re building for reliability or multi-model use (not all models treat schemas equally).</li>
<li>You&rsquo;re iterating and want to troubleshoot what part is helping.</li>
</ul>
</blockquote>
<p>We&rsquo;ll come back to why the Don&rsquo;t-Repeat-Yourself (DRY) principle might be worthwhile for the token count and cost too.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-10" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-10-1">Python</a></li>
<li><a href="#tabset-10-2">R</a></li>
</ul>
<div id="tabset-10-1">
<p>To use structured output, you give the model some input and a <a href="https://docs.pydantic.dev/latest/concepts/models/" target="_blank" rel="noopener">Pydantic model</a>
 that describes the kind of data you want. The Pydantic model is like a form with specific fields and types, like <code>title: string</code> or <code>duration: integer</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Define data structure to extract from the input</span>
</span></span><span class="line"><span class="cl"><span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mi">10</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">PercentType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mf">100.0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">MinutesType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">SlideCount</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ScoringCategory</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">score</span><span class="p">:</span> <span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Score from 1–10.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">justification</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Brief explanation of the score.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">improvements</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Concise, actionable improvements, mentioning slide numbers if applicable.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">score_after_improvements</span><span class="p">:</span> <span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Estimated score after suggested improvements.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DeckAnalysis</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">presentation_title</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;The presentation title.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_slides</span><span class="p">:</span> <span class="n">SlideCount</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_code</span><span class="p">:</span> <span class="n">PercentType</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_images</span><span class="p">:</span> <span class="n">PercentType</span>
</span></span><span class="line"><span class="cl">    <span class="n">estimated_duration_minutes</span><span class="p">:</span> <span class="n">MinutesType</span>
</span></span><span class="line"><span class="cl">    <span class="n">tone</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Brief description of the tone of the presentation.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">clarity</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">relevance</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Assess how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">visual_design</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">engagement</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">pacing</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Analyze the distribution of content across slides. Are some slides too dense or too light? &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">structure</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">concistency</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>  <span class="c1"># spelling kept as-is</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Evaluatue whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">accessibility</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span><span class="o">=</span><span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Since all the instructions are in the system prompt, we can just</span>
</span></span><span class="line"><span class="cl"><span class="c1"># provide the Markdown content as a message</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat_structured</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">interpolate</span><span class="p">(</span><span class="s2">&#34;Here are the slides in Markdown: {{ markdown_content }}&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">data_model</span><span class="o">=</span><span class="n">DeckAnalysis</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show complete code</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatAnthropic</span><span class="p">,</span> <span class="n">interpolate_file</span><span class="p">,</span> <span class="n">interpolate</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pydantic</span> <span class="kn">import</span> <span class="n">BaseModel</span><span class="p">,</span> <span class="n">Field</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Annotated</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get Quarto presentation and convert to plain Markdown</span>
</span></span><span class="line"><span class="cl"><span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="s2">&#34;quarto&#34;</span><span class="p">,</span> <span class="s2">&#34;render&#34;</span><span class="p">,</span> <span class="s2">&#34;./Quarto/my-presentation.qmd&#34;</span><span class="p">,</span> <span class="s2">&#34;--to&#34;</span><span class="p">,</span> <span class="s2">&#34;markdown&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Dynamic data</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Audience, length in minutes, type, and event</span>
</span></span><span class="line"><span class="cl"><span class="n">audience_content</span> <span class="o">=</span> <span class="s2">&#34;Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">length_content</span> <span class="o">=</span> <span class="s2">&#34;10&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">type_content</span> <span class="o">=</span> <span class="s2">&#34;lightning talk&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">event_content</span> <span class="o">=</span> <span class="s2">&#34;posit::conf(2025)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the generated Markdown file containing our slides</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./Quarto/docs/my-presentation.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_content</span> <span class="o">=</span> <span class="n">markdown_file</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./prompts/prompt-analyse-slides-structured.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt</span> <span class="o">=</span> <span class="n">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">variables</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;audience&#34;</span><span class="p">:</span> <span class="n">audience_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;length&#34;</span><span class="p">:</span> <span class="n">length_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="n">type_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;event&#34;</span><span class="p">:</span> <span class="n">event_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define data structure to extract from the input</span>
</span></span><span class="line"><span class="cl"><span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mi">10</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">PercentType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mf">100.0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">MinutesType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">SlideCount</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ScoringCategory</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">score</span><span class="p">:</span> <span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Score from 1–10.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">justification</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Brief explanation of the score.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">improvements</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Concise, actionable improvements, mentioning slide numbers if applicable.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">score_after_improvements</span><span class="p">:</span> <span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Estimated score after suggested improvements.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DeckAnalysis</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">presentation_title</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;The presentation title.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_slides</span><span class="p">:</span> <span class="n">SlideCount</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_code</span><span class="p">:</span> <span class="n">PercentType</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_images</span><span class="p">:</span> <span class="n">PercentType</span>
</span></span><span class="line"><span class="cl">    <span class="n">estimated_duration_minutes</span><span class="p">:</span> <span class="n">MinutesType</span>
</span></span><span class="line"><span class="cl">    <span class="n">tone</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Brief description of the tone of the presentation.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">clarity</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">relevance</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Assess how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">visual_design</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">engagement</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">pacing</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Analyze the distribution of content across slides. Are some slides too dense or too light? &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">structure</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">concistency</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>  <span class="c1"># spelling kept as-is</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Evaluatue whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">accessibility</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span><span class="o">=</span><span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Since all the instructions are in the system prompt, we can just</span>
</span></span><span class="line"><span class="cl"><span class="c1"># provide the Markdown content as a message</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat_structured</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">interpolate</span><span class="p">(</span><span class="s2">&#34;Here are the slides in Markdown: {{ markdown_content }}&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">data_model</span><span class="o">=</span><span class="n">DeckAnalysis</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<details class="code-fold">
<summary>Show output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="p">{</span><span class="s1">&#39;presentation_title&#39;</span><span class="p">:</span> <span class="s1">&#39;The Shiny Side of LLMs&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;total_slides&#39;</span><span class="p">:</span> <span class="mi">16</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;percent_with_code&#39;</span><span class="p">:</span> <span class="mf">31.25</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;percent_with_images&#39;</span><span class="p">:</span> <span class="mf">6.25</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;estimated_duration_minutes&#39;</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;tone&#39;</span><span class="p">:</span> <span class="s1">&#39;Casual and friendly with a teaching approach, using emojis and humor to make technical content approachable&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;clarity&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Good step-by-step approach and clear explanations, but heavy code blocks may confuse non-technical audience members. Key concepts are well-defined.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s2">&#34;Simplify code examples on slides 5-6, 8-9, 11-12 to show only essential lines with comments. Add brief explanations of technical terms like &#39;reactive programming&#39; on slide 7.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;relevance&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Highly relevant to the posit::conf audience with practical Python and R examples. Addresses real need for AI integration in data science workflows.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s1">&#39;Add a brief mention of common use cases data scientists might have for LLM-powered apps beyond presentation feedback (slide 3 or 14).&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;visual_design&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Very text-heavy with large code blocks that will be difficult to read in a presentation setting. Limited visual hierarchy and only one image/GIF.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s1">&#39;Replace full code blocks on slides 5-6, 8-9, 11-12 with key snippets only. Add diagrams showing the app architecture on slide 4. Include screenshots of DeckCheck app on slide 3.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">7</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;engagement&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">7</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Good use of humor, emojis, and relatable examples (DeckCheck concept). The magic GIF adds personality. However, code-heavy sections may lose audience attention.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s1">&#39;Add interactive elements or polls. Include a brief live demo or recorded demo on slide 10 instead of the GIF. Show before/after examples of presentation feedback.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;pacing&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Uneven distribution with slides 5-6, 8-9, 11-12 being too dense for a 10-minute talk. Some slides like slide 1 and 15 are very light.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s1">&#39;Condense setup information from slides 2-4 into 2 slides. Reduce code examples to key snippets. Combine slides 1 and 2 into a single introduction slide.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">7</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;structure&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">7</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Logical flow from introduction through setup, basic concepts, and implementation. Clear progression from simple to complex. Good use of the DeckCheck example throughout.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s1">&#39;Add an agenda slide after slide 1. Create clearer section breaks between setup (slides 2-4), concepts (slides 5-7), and implementation (slides 8-12).&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;concistency&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Consistent tone, formatting, and use of the DeckCheck example throughout. Parallel structure for Python and R examples works well.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;accessibility&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Code blocks will be difficult to read for audience members, especially those with visual challenges. Font sizes in code examples are likely too small for presentation setting.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s1">&#39;Use larger fonts and fewer lines in code examples (slides 5-6, 8-9, 11-12). Add high contrast between text and background. Include alt text descriptions for the GIF on slide 10.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">7</span><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<p>The code above defines a set of Pydantic models that describe the expected structure of our output.<code>ScoringCategory</code> is a reusable model containing a score (an integer constrained between 0 and 10), a justification string, optional improvement suggestions, and an estimated score after implementing the improvements. Numeric fields in the top-level model, <code>DeckAnalysis</code>, are validated using <code>Annotated</code> types with constraints defined via <code>Field(...)</code> . For example, to ensure percentages are between 0.0 and 100.0, and counts are non-negative. The <code>DeckAnalysis</code> model nests eight <code>ScoringCategory</code> models. It saves some duplication. The top-level model is given to the <code>chat_structured()</code> method: <code>chat.chat_structured(interpolate(&quot;Here are the slides in Markdown: {{ markdown_content }}&quot;), data_model=DeckAnalysis)</code> .</p>
</div>
<div id="tabset-10-2">
<p>To use structured output, you use <code>$chat_structured()</code> instead of the <code>$chat()</code>method. You&rsquo;ll also need to define a type specification that describes the structure of the data that you want.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># Reusable scoring category</span>
</span></span><span class="line"><span class="cl"><span class="n">type_scoring_category</span> <span class="o">&lt;-</span> <span class="nf">type_object</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">score</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Score from 1 to 10.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">justification</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Brief explanation of the score.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">improvements</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Concise, actionable improvements, mentioning slide numbers if applicable.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">required</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">score_after_improvements</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimated score after suggested improvements.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Top-level deck analysis object</span>
</span></span><span class="line"><span class="cl"><span class="n">type_deck_analysis</span> <span class="o">&lt;-</span> <span class="nf">type_object</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">presentation_title</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span><span class="n">description</span> <span class="o">=</span> <span class="s">&#34;The presentation title.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">total_slides</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span><span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Total number of slides.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">percent_with_code</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Percentage of slides containing code blocks (0–100).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">percent_with_images</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Percentage of slides containing images (0–100).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">estimated_duration_minutes</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimated presentation length in minutes, assuming ~1 minute per text slide and 2–3 minutes per code or image-heavy slide.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">tone</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Brief description of the presentation tone (e.g., informal, technical, playful).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">clarity</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">relevance</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Asses how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">visual_design</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">engagement</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">pacing</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Analyze the distribution of content across slides. Are some slides too dense or too light? &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">structure</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">concistency</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Evaluate whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">accessibility</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">system_prompt</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Since all the instructions are in the system prompt, we can just</span>
</span></span><span class="line"><span class="cl"><span class="c1"># provide the Markdown content as a message</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat_structured</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Here are the slides in Markdown: {{ markdown_content }}&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="o">=</span> <span class="n">type_deck_analysis</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show complete code</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get Quarto presentation and convert to plain Markdown</span>
</span></span><span class="line"><span class="cl"><span class="n">quarto</span><span class="o">::</span><span class="nf">quarto_render</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;./Quarto/my-presentation.qmd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">output_format</span> <span class="o">=</span> <span class="s">&#34;markdown&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Dynamic data</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Audience, length in minutes, type, and event</span>
</span></span><span class="line"><span class="cl"><span class="n">audience_content</span> <span class="o">&lt;-</span> <span class="s">&#34;Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">length_content</span> <span class="o">&lt;-</span> <span class="s">&#34;10&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">type_content</span> <span class="o">&lt;-</span> <span class="s">&#34;lightning talk&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">event_content</span> <span class="o">&lt;-</span> <span class="s">&#34;posit::conf(2025)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the generated Markdown file containing our slides</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./Quarto/docs/my-presentation.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_content</span> <span class="o">&lt;-</span> <span class="nf">readChar</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">,</span> <span class="nf">file.size</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./prompts/prompt-analyse-slides-structured.md&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt</span> <span class="o">&lt;-</span> <span class="nf">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">path</span> <span class="o">=</span> <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">audience</span> <span class="o">=</span> <span class="n">audience_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">length</span> <span class="o">=</span> <span class="n">length_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="o">=</span> <span class="n">type_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">event</span> <span class="o">=</span> <span class="n">event_content</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Reusable scoring category</span>
</span></span><span class="line"><span class="cl"><span class="n">type_scoring_category</span> <span class="o">&lt;-</span> <span class="nf">type_object</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">score</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Score from 1 to 10.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">justification</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Brief explanation of the score.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">improvements</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Concise, actionable improvements, mentioning slide numbers if applicable.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">required</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">score_after_improvements</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimated score after suggested improvements.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Top-level deck analysis object</span>
</span></span><span class="line"><span class="cl"><span class="n">type_deck_analysis</span> <span class="o">&lt;-</span> <span class="nf">type_object</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">presentation_title</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span><span class="n">description</span> <span class="o">=</span> <span class="s">&#34;The presentation title.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">total_slides</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span><span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Total number of slides.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">percent_with_code</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Percentage of slides containing code blocks (0–100).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">percent_with_images</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Percentage of slides containing images (0–100).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">estimated_duration_minutes</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimated presentation length in minutes, assuming ~1 minute per text slide and 2–3 minutes per code or image-heavy slide.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">tone</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Brief description of the presentation tone (e.g., informal, technical, playful).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">clarity</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">relevance</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Asses how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">visual_design</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">engagement</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">pacing</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Analyze the distribution of content across slides. Are some slides too dense or too light? &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">structure</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">concistency</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Evaluate whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">accessibility</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">system_prompt</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Since all the instructions are in the system prompt, we can just</span>
</span></span><span class="line"><span class="cl"><span class="c1"># provide the Markdown content as a message</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat_structured</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Here are the slides in Markdown: {{ markdown_content }}&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="o">=</span> <span class="n">type_deck_analysis</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<details class="code-fold">
<summary>Show output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span><span class="lnt">83
</span><span class="lnt">84
</span><span class="lnt">85
</span><span class="lnt">86
</span><span class="lnt">87
</span><span class="lnt">88
</span><span class="lnt">89
</span><span class="lnt">90
</span><span class="lnt">91
</span><span class="lnt">92
</span><span class="lnt">93
</span><span class="lnt">94
</span><span class="lnt">95
</span><span class="lnt">96
</span><span class="lnt">97
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#&gt; $presentation_title</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;The Shiny Side of LLMs&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $total_slides</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] 16</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $percent_with_code</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] 56.25</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $percent_with_images</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] 6.25</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $estimated_duration_minutes</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] 12</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $tone</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;Informal, friendly, and technical with playful elements&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $clarity</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     7</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Content is generally clear with good step-by-step explanations, but some technical concepts could benefit from more context for non-technical audience members.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Add brief explanations of what Shiny and LLMs are in slides 2-3. Define technical terms like &#39;reactive programming&#39; in slide 7. Provide more context about API keys and authentication in slide 2.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        8</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $relevance</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     8</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Content is highly relevant to the Python and R user audience at posit::conf, with practical examples in both languages. Good mix of beginner-friendly and intermediate concepts.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Add a brief slide explaining why LLMs are relevant for data scientists specifically. Include more context about when to use LLM-powered apps in data science workflows.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        9</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $visual_design</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     6</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Code blocks are well-formatted, but slides are text-heavy with minimal visual hierarchy. Only one image used throughout the presentation.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Add visual diagrams showing the app architecture in slides 4-5. Use bullet points and icons to break up text in slides 2 and 15. Consider adding screenshots of the actual app interface.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        8</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $engagement</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     7</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Good use of emojis and casual tone keeps it engaging. The magic GIF adds personality, but more interactive elements could enhance engagement.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Add a live demo or screenshots of the working app. Include audience interaction prompts in slides 11-12. Add visual progress indicators showing the app development journey.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        8</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $pacing</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     6</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Some slides are code-heavy (slides 8-9, 12-13) while others are very light (slide 3). The transition from basics to implementation could be smoother.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Split the heavy code slides (8-9, 12-13) into smaller chunks. Add transition slides between major sections. Balance slide 14 with more concrete examples.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        7</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $structure</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     8</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Clear logical flow from introduction to requirements to implementation. Good progression from basic concepts to advanced integration.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Add an agenda slide after slide 1. Include a recap slide before the conclusion. Make the transition from slide 14 to 15 smoother with a bridge sentence.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        9</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $concistency</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     7</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Generally consistent formatting and tone, but code highlighting varies between Python and R sections. Some inconsistency in slide structure.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Ensure consistent code block styling across Python and R examples. Standardize the format for requirement lists in slide 2. Use consistent header styles throughout.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        8</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $accessibility</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     7</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Good contrast in code blocks and readable font sizes. However, the single GIF lacks alt text description and some code blocks are quite dense.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Add alt text description for the magic GIF in slide 11. Break up dense code blocks in slides 8-9, 12-13. Use larger font sizes for code comments.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        8</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<p>You can also specify the full schema that you want to get back from the LLM as a JSON schema. This could be handy if you&rsquo;re not keen on manually converting everything using the <code>type_*()</code>functions (or if an LLM doesn&rsquo;t provide you with the correct output after asking 6 times). Your friend: <code>type_from_schema()</code>.</p>
<blockquote>
<p><strong>Tip</strong></p>
<p><code>type_object()</code> returns a named list in R. If you want to extract a data frame from a single prompt (e.g. reading information from a markdown table), you can wrap <code>type_object()</code> in <code>type_array()</code> and create an array of objects. We do something similar here for the scoring categories. It can be hard to wrap your head around as an R user. The <code>ellmer</code> documentation contains some more <a href="https://ellmer.tidyverse.org/articles/structured-data.html#data-frames" target="_blank" rel="noopener">good examples</a>
.</p>
</blockquote>
</div>
</div>
<h1 id="when-llms-guess-tools-know">When LLMs guess, tools know
</h1>
<p>As you&rsquo;ve seen in our conversation with Claude so far, LLMs struggle with tasks like counting slides. This is because they don&rsquo;t actually run code or parse content structurally like a proper parser would. LLMs aren&rsquo;t doing magic, they are simply predicting the next word based on patterns in text. So when you ask an LLM, &ldquo;how many slides contain code?&rdquo;, it&rsquo;s making an educated guess based on the wording of the slides. It does not actually scan them line-by-line and checking for code blocks. The result: inconsistent answers. In our example, there are 16 slides, 6 with code, and 1 with an image (aka 37.5% code slides and 6.25% image slides). The LLM gets close, but its answers vary and we&rsquo;re not aiming for &ldquo;close enough.&rdquo; The solution: build something on top of the LLM that does this reliably for us. Hello something fancy called &ldquo;tool calling&rdquo;!</p>
<p>Everyone talks about tool calling these days, and for a good reason: it&rsquo;s that extra bit of power you give to an LLM. While it sounds complicated, it&rsquo;s easy to explain: you let your code handle tasks that the LLM struggles with, like accurately counting how many slides have code or images, and then the LLM uses those exact numbers. You basically help the LLM with its answer. This help comes in the form of a tool (aka a function) that you&rsquo;ve written and exposed to the model. Of course the LLM needs to know that the tool is there to begin with, and it needs to know how to use it. Luckily <code>chatlas</code> and <code>ellmer</code> can help us doing that.</p>
<p>So how would this work of we want to improve the slide counts for DeckCheck? First, we construct a simple Python or R function that reads our slides, spots code blocks and images, and gives exact percentages. To make our life a little bit easier, we&rsquo;re opting for the HTML version of Quarto slides here, and not Markdown. In this case, HTML is handy because each slide is neatly contained in a <code>&lt;section&gt;</code> tag, and code is clearly marked with a <code>sourceCode</code> class. For an LLM, HTML is noisy (full of CSS, scripts, and other distractions). But for a parsing function like we&rsquo;re going to write, it&rsquo;s straightforward to process. The nice thing about such a function is that you can fully customise it: maybe you only want to count Python or R code chunks only, or ignore very short examples. We call our function <code>calculate_slide_metric</code>:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-11" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-11-1">Python</a></li>
<li><a href="#tabset-11-2">R</a></li>
</ul>
<div id="tabset-11-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">calculate_slide_metric</span><span class="p">(</span><span class="n">metric</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Union</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">float</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    Calculates the total number of slides, percentage of slides with code blocks,
</span></span></span><span class="line"><span class="cl"><span class="s2">    and percentage of slides with images in a Quarto presentation HTML file.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    Parameters
</span></span></span><span class="line"><span class="cl"><span class="s2">    ----------
</span></span></span><span class="line"><span class="cl"><span class="s2">    metric : str
</span></span></span><span class="line"><span class="cl"><span class="s2">        The metric to calculate: &#34;total_slides&#34; for total number of slides,
</span></span></span><span class="line"><span class="cl"><span class="s2">        &#34;code&#34; for percentage of slides containing fenced code blocks,
</span></span></span><span class="line"><span class="cl"><span class="s2">        or &#34;images&#34; for percentage of slides containing images.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    Returns
</span></span></span><span class="line"><span class="cl"><span class="s2">    -------
</span></span></span><span class="line"><span class="cl"><span class="s2">    float or int
</span></span></span><span class="line"><span class="cl"><span class="s2">        The calculated metric value.
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">html_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./Quarto/docs/my-presentation.html&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">html_file</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;HTML file </span><span class="si">{</span><span class="n">html_file</span><span class="si">}</span><span class="s2"> does not exist.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Read HTML file</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">html_file</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">html_content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Split on &lt;section&gt; tags to get individual slides</span>
</span></span><span class="line"><span class="cl">    <span class="n">slides</span> <span class="o">=</span> <span class="n">html_content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;&lt;section&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_slides</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">metric</span> <span class="o">==</span> <span class="s2">&#34;total_slides&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">total_slides</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">metric</span> <span class="o">==</span> <span class="s2">&#34;code&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">slides_with_code</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="s1">&#39;class=&#34;sourceCode&#34;&#39;</span> <span class="ow">in</span> <span class="n">slide</span> <span class="k">for</span> <span class="n">slide</span> <span class="ow">in</span> <span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="nb">round</span><span class="p">((</span><span class="n">slides_with_code</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">metric</span> <span class="o">==</span> <span class="s2">&#34;images&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">slides_with_image</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="s2">&#34;&lt;img&#34;</span> <span class="ow">in</span> <span class="n">slide</span> <span class="k">for</span> <span class="n">slide</span> <span class="ow">in</span> <span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="nb">round</span><span class="p">((</span><span class="n">slides_with_image</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;Unknown metric: choose &#39;total_slides&#39;, &#39;code&#39;, or &#39;images&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">result</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-11-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#&#39; Calculates the total number of slides, percentage of slides with code blocks,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; and percentage of slides with images in a Quarto presentation HTML file.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; @param metric The metric to calculate: &#34;total_slides&#34; for total number of slides,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; &#34;code&#34; for percentage of slides containing fenced code blocks, or &#34;images&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; for percentage of slides containing images.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; @return The calculated metric value.</span>
</span></span><span class="line"><span class="cl"><span class="n">calculate_slide_metric</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">metric</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">html_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./Quarto/docs/my-presentation.html&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">file.exists</span><span class="p">(</span><span class="n">html_file</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">stop</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;HTML file does not exist. Please render your Quarto presentation first.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Read HTML file</span>
</span></span><span class="line"><span class="cl">  <span class="n">html_content</span> <span class="o">&lt;-</span> <span class="nf">readChar</span><span class="p">(</span><span class="n">html_file</span><span class="p">,</span> <span class="nf">file.size</span><span class="p">(</span><span class="n">html_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Split on &lt;section&gt; tags to get individual slides</span>
</span></span><span class="line"><span class="cl">  <span class="n">slides</span> <span class="o">&lt;-</span> <span class="nf">unlist</span><span class="p">(</span><span class="nf">strsplit</span><span class="p">(</span><span class="n">html_content</span><span class="p">,</span> <span class="s">&#34;&lt;section&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">total_slides</span> <span class="o">&lt;-</span> <span class="nf">length</span><span class="p">(</span><span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">if</span> <span class="p">(</span><span class="n">metric</span> <span class="o">==</span> <span class="s">&#34;total_slides&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">&lt;-</span> <span class="n">total_slides</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="kr">else</span> <span class="kr">if</span> <span class="p">(</span><span class="n">metric</span> <span class="o">==</span> <span class="s">&#34;code&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Count slides where we see the &#34;sourceCode&#34; class</span>
</span></span><span class="line"><span class="cl">    <span class="n">slides_with_code</span> <span class="o">&lt;-</span> <span class="nf">sum</span><span class="p">(</span><span class="nf">grepl</span><span class="p">(</span><span class="s">&#39;class=&#34;sourceCode&#34;&#39;</span><span class="p">,</span> <span class="n">slides</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">&lt;-</span> <span class="nf">round</span><span class="p">((</span><span class="n">slides_with_code</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="m">100</span><span class="p">,</span> <span class="m">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="kr">else</span> <span class="kr">if</span> <span class="p">(</span><span class="n">metric</span> <span class="o">==</span> <span class="s">&#34;images&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Count slides with image tag</span>
</span></span><span class="line"><span class="cl">    <span class="n">slides_with_image</span> <span class="o">&lt;-</span> <span class="nf">sum</span><span class="p">(</span><span class="nf">grepl</span><span class="p">(</span><span class="s">&#39;&lt;img&#39;</span><span class="p">,</span> <span class="n">slides</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">&lt;-</span> <span class="nf">round</span><span class="p">((</span><span class="n">slides_with_image</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="m">100</span><span class="p">,</span> <span class="m">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="kr">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">stop</span><span class="p">(</span><span class="s">&#34;Unknown metric: choose &#39;total_slides&#39;, &#39;code&#39;, or &#39;images&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">return</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>This function is the tool the LLM can call to accurately count slides. It has one input argument: <code>metric</code>. To let the model know that this tool is available, we register it with <code>register_tool</code> before we start talking to the LLM.</p>
<blockquote>
<p><strong>Who does the work?</strong></p>
<p>Note that the LLM itself is not going to read the HTML file. The underlying Python or R session that execute the function is.</p>
</blockquote>
<p>There&rsquo;s a little catch though: structured data extraction automatically disables tool calling. Hmm&hellip; But we used structured data! Luckily you can work around this by doing a regular <code>chat()</code> and then using <code>chat_structured()</code>. We&rsquo;re basically adding an extra pair of user and assistant turn by dividing one task into two. This means we need to reorganise our system prompt by telling the LLM that there is a first task, and a subsequent task. The first one going to focus on retrieving the meta data, and the second one on extracting scores and improvements. For clarity, the tasks are numbered and have short alias (e.g. &ldquo;Task 1 (counts)&rdquo;). This makes it easier to reference them in our user prompts.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">You can be asked for one of the following tasks. Each has a number and a name:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Task 1 (counts)
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Return only the JSON results, nothing else.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Task 2 (suggestions)
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">You bundle your results with the results from the first task. Always return the result as a JSON object that conforms to the provided data model.
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show full prompt</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">You are a presentation coach for data scientists that analyses presentation slide decks written in Markdown. 
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract key information, evaluate quality, and return structured feedback that is constructive, focused and practical.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">The presentation you are helping with is a {{ length }}-minute {{ type }} at {{ event }}.  
</span></span></span><span class="line"><span class="cl"><span class="s2">The audience is {{ audience }}. 
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You can be asked for one of the following tasks. Each has a number and a name:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2"># Task 1 (counts)
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract the following information:
</span></span></span><span class="line"><span class="cl"><span class="s2">- The number of slides
</span></span></span><span class="line"><span class="cl"><span class="s2">- The percentage of slides containing code blocks
</span></span></span><span class="line"><span class="cl"><span class="s2">- The percentage of slides containing images 
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Return only the JSON results, nothing else.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2"># Task 2 (suggestions)
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract the following information:
</span></span></span><span class="line"><span class="cl"><span class="s2">- The presentation title
</span></span></span><span class="line"><span class="cl"><span class="s2">- Estimated presentation length (in minutes, assuming ~1 minute per text slide and 2–3 minutes per code or image-heavy slide)
</span></span></span><span class="line"><span class="cl"><span class="s2">- Tone (a brief description)
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You score the presentation on the following categories (from 1–10), and give a concise explanation:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">1. Clarity of content: evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?
</span></span></span><span class="line"><span class="cl"><span class="s2">2. Relevance for intended audience: assess how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?
</span></span></span><span class="line"><span class="cl"><span class="s2">3. Visual design: judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?
</span></span></span><span class="line"><span class="cl"><span class="s2">4. Engagement: estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?
</span></span></span><span class="line"><span class="cl"><span class="s2">5. Pacing: analyze the distribution of content across slides. Are some slides too dense or too light? 
</span></span></span><span class="line"><span class="cl"><span class="s2">6. Structure: review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?
</span></span></span><span class="line"><span class="cl"><span class="s2">7. consistency: evaluatue whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?
</span></span></span><span class="line"><span class="cl"><span class="s2">8. Accessibility: consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">For each of the above scoring categories, provide specific and actionable improvements. Follow these instructions:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">- Keep each suggestion concise and mention the slide number(s) if applicable.
</span></span></span><span class="line"><span class="cl"><span class="s2">- Do not invent issues and only suggest improvements when the content would clearly benefit from them.
</span></span></span><span class="line"><span class="cl"><span class="s2">- For each catogory, estimate what the new score would be if these improvements are implemented.
</span></span></span><span class="line"><span class="cl"><span class="s2">- Return the improvement and new score as part of the response for that category.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You bundle your results with the results from the first task. Always return the result as a JSON object that conforms to the provided data model.
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<p>Because the <code>chat</code> object stateful (and therefore remembers previous responses), it can use the response from the first prompt for the response of the second one. Handy!</p>
<p>Altogether, this results in the following workflow:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-12" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-12-1">Python</a></li>
<li><a href="#tabset-12-2">R</a></li>
</ul>
<div id="tabset-12-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span><span class="o">=</span><span class="s2">&#34;Your system prompt&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Register the tool with the chat</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">register_tool</span><span class="p">(</span><span class="n">calculate_slide_metric</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Task 1: regular chat to extract meta-data</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Execute Task 1 (counts). Here are the slides in Markdown: ...&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Task 2: structured chat to further analyse the slides</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat_structured</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Execute Task 2 (suggestions)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">data_model</span><span class="o">=</span><span class="n">DeckAnalysis</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show complete code</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span><span class="lnt">149
</span><span class="lnt">150
</span><span class="lnt">151
</span><span class="lnt">152
</span><span class="lnt">153
</span><span class="lnt">154
</span><span class="lnt">155
</span><span class="lnt">156
</span><span class="lnt">157
</span><span class="lnt">158
</span><span class="lnt">159
</span><span class="lnt">160
</span><span class="lnt">161
</span><span class="lnt">162
</span><span class="lnt">163
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatAnthropic</span><span class="p">,</span> <span class="n">interpolate_file</span><span class="p">,</span> <span class="n">interpolate</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pydantic</span> <span class="kn">import</span> <span class="n">BaseModel</span><span class="p">,</span> <span class="n">Field</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Annotated</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Union</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get Quarto presentation and convert to plain Markdown + HTML</span>
</span></span><span class="line"><span class="cl"><span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="s2">&#34;quarto&#34;</span><span class="p">,</span> <span class="s2">&#34;render&#34;</span><span class="p">,</span> <span class="s2">&#34;./Quarto/my-presentation.qmd&#34;</span><span class="p">,</span> <span class="s2">&#34;--to&#34;</span><span class="p">,</span> <span class="s2">&#34;markdown,html&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Dynamic data</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Audience, length in minutes, type, and event</span>
</span></span><span class="line"><span class="cl"><span class="n">audience_content</span> <span class="o">=</span> <span class="s2">&#34;Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">length_content</span> <span class="o">=</span> <span class="s2">&#34;10&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">type_content</span> <span class="o">=</span> <span class="s2">&#34;lightning talk&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">event_content</span> <span class="o">=</span> <span class="s2">&#34;posit::conf(2025)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the generated Markdown file containing our slides</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./Quarto/docs/my-presentation.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_content</span> <span class="o">=</span> <span class="n">markdown_file</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./prompts/prompt-analyse-slides-structured-tool.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt</span> <span class="o">=</span> <span class="n">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">variables</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;audience&#34;</span><span class="p">:</span> <span class="n">audience_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;length&#34;</span><span class="p">:</span> <span class="n">length_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="n">type_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;event&#34;</span><span class="p">:</span> <span class="n">event_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;markdown_content&#34;</span><span class="p">:</span> <span class="n">markdown_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define data structure to extract from the input</span>
</span></span><span class="line"><span class="cl"><span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mi">10</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">PercentType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mf">100.0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">MinutesType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">SlideCount</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ScoringCategory</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">score</span><span class="p">:</span> <span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Score from 1–10.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">justification</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Brief explanation of the score.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">improvements</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Concise, actionable improvements, mentioning slide numbers if applicable.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">score_after_improvements</span><span class="p">:</span> <span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Estimated score after suggested improvements.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DeckAnalysis</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">presentation_title</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;The presentation title.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_slides</span><span class="p">:</span> <span class="n">SlideCount</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_code</span><span class="p">:</span> <span class="n">PercentType</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_images</span><span class="p">:</span> <span class="n">PercentType</span>
</span></span><span class="line"><span class="cl">    <span class="n">estimated_duration_minutes</span><span class="p">:</span> <span class="n">MinutesType</span>
</span></span><span class="line"><span class="cl">    <span class="n">tone</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Brief description of the tone of the presentation.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">clarity</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">relevance</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Assess how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">visual_design</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">engagement</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">pacing</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Analyze the distribution of content across slides. Are some slides too dense or too light? &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">structure</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">concistency</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>  <span class="c1"># spelling kept as-is</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Evaluatue whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">accessibility</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define a tool to calculate some metrics</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Start with a function:</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">calculate_slide_metric</span><span class="p">(</span><span class="n">metric</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Union</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">float</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    Calculates the total number of slides, percentage of slides with code blocks,
</span></span></span><span class="line"><span class="cl"><span class="s2">    and percentage of slides with images in a Quarto presentation HTML file.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    Parameters
</span></span></span><span class="line"><span class="cl"><span class="s2">    ----------
</span></span></span><span class="line"><span class="cl"><span class="s2">    metric : str
</span></span></span><span class="line"><span class="cl"><span class="s2">        The metric to calculate: &#34;total_slides&#34; for total number of slides,
</span></span></span><span class="line"><span class="cl"><span class="s2">        &#34;code&#34; for percentage of slides containing fenced code blocks,
</span></span></span><span class="line"><span class="cl"><span class="s2">        or &#34;images&#34; for percentage of slides containing images.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    Returns
</span></span></span><span class="line"><span class="cl"><span class="s2">    -------
</span></span></span><span class="line"><span class="cl"><span class="s2">    float or int
</span></span></span><span class="line"><span class="cl"><span class="s2">        The calculated metric value.
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">html_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./Quarto/docs/my-presentation.html&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">html_file</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;HTML file </span><span class="si">{</span><span class="n">html_file</span><span class="si">}</span><span class="s2"> does not exist.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Read HTML file</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">html_file</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">html_content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Split on &lt;section&gt; tags to get individual slides</span>
</span></span><span class="line"><span class="cl">    <span class="n">slides</span> <span class="o">=</span> <span class="n">html_content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;&lt;section&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_slides</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">metric</span> <span class="o">==</span> <span class="s2">&#34;total_slides&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">total_slides</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">metric</span> <span class="o">==</span> <span class="s2">&#34;code&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">slides_with_code</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="s1">&#39;class=&#34;sourceCode&#34;&#39;</span> <span class="ow">in</span> <span class="n">slide</span> <span class="k">for</span> <span class="n">slide</span> <span class="ow">in</span> <span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="nb">round</span><span class="p">((</span><span class="n">slides_with_code</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">metric</span> <span class="o">==</span> <span class="s2">&#34;images&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">slides_with_image</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="s2">&#34;&lt;img&#34;</span> <span class="ow">in</span> <span class="n">slide</span> <span class="k">for</span> <span class="n">slide</span> <span class="ow">in</span> <span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="nb">round</span><span class="p">((</span><span class="n">slides_with_image</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;Unknown metric: choose &#39;total_slides&#39;, &#39;code&#39;, or &#39;images&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span><span class="o">=</span><span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Register the tool with the chat</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">register_tool</span><span class="p">(</span><span class="n">calculate_slide_metric</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Task 1: regular chat to extract meta-data</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Execute Task 1 (counts). Here are the slides in Markdown: {{ markdown_content }}&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Task 2: structured chat to further analyse the slides</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat_structured</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Execute Task 2 (suggestions)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">data_model</span><span class="o">=</span><span class="n">DeckAnalysis</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<p>We can clearly see that our tool was being called 3 times for our 3 metrics, just like it should:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># 🔧 tool request (toolu_01JSsxrB8Jj9yDY35pg1g1bc)                      </span>
</span></span><span class="line"><span class="cl"> <span class="n">calculate_slide_metric</span><span class="p">(</span><span class="n">metric</span><span class="o">=</span><span class="n">total_slides</span><span class="p">)</span>                                                    
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1"># ✅ tool result (toolu_01JSsxrB8Jj9yDY35pg1g1bc)                        </span>
</span></span><span class="line"><span class="cl"> <span class="mi">16</span>                                                                      
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1"># 🔧 tool request (toolu_019635a7mRAotqTFNx2TfCsm)                      </span>
</span></span><span class="line"><span class="cl"> <span class="n">calculate_slide_metric</span><span class="p">(</span><span class="n">metric</span><span class="o">=</span><span class="n">code</span><span class="p">)</span>                                                            
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1"># ✅ tool result (toolu_019635a7mRAotqTFNx2TfCsm)                        </span>
</span></span><span class="line"><span class="cl"> <span class="mf">37.5</span>                                                                    
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1"># 🔧 tool request (toolu_01Kv6nPiUeZmABADZFeN8Qgi)                      </span>
</span></span><span class="line"><span class="cl"> <span class="n">calculate_slide_metric</span><span class="p">(</span><span class="n">metric</span><span class="o">=</span><span class="n">images</span><span class="p">)</span>                                                          
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1"># ✅ tool result (toolu_01Kv6nPiUeZmABADZFeN8Qgi)                       </span>
</span></span><span class="line"><span class="cl"> <span class="mf">6.25</span>                                                                    
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="p">{</span>                                                                       
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;total_slides&#34;</span><span class="p">:</span> <span class="mi">16</span><span class="p">,</span>                                                   
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;percentage_slides_with_code&#34;</span><span class="p">:</span> <span class="mf">37.5</span><span class="p">,</span>                                  
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;percentage_slides_with_images&#34;</span><span class="p">:</span> <span class="mf">6.25</span>                                 
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show the rest of the output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="p">{</span><span class="s1">&#39;presentation_title&#39;</span><span class="p">:</span> <span class="s1">&#39;The Shiny Side of LLMs&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;total_slides&#39;</span><span class="p">:</span> <span class="mi">16</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;percent_with_code&#39;</span><span class="p">:</span> <span class="mf">37.5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;percent_with_images&#39;</span><span class="p">:</span> <span class="mf">6.25</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;estimated_duration_minutes&#39;</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;tone&#39;</span><span class="p">:</span> <span class="s1">&#39;Conversational and friendly with technical depth, using emojis and casual language to make complex topics approachable&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;clarity&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Clear explanations with step-by-step progression from basics to implementation. Code examples are well-structured and concepts are explained in accessible terms.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s2">&#34;Add brief explanations of what chatlas/ellmer are on slides 5-6. Define &#39;reactive programming&#39; on slide 7 for non-technical audience members.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">9</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;relevance&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">9</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Highly relevant to the posit::conf audience of Python/R users interested in AI. Content matches the technical level and practical focus expected.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s1">&#39;Consider adding a brief mention of when NOT to use LLMs in presentations on slide 14 to provide balanced perspective.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">9</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;visual_design&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Slides are text-heavy with large code blocks. Only one visual element (GIF on slide 11). Layout is clean but could benefit from more visual variety.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s1">&#39;Add diagrams showing app architecture on slides 4 or 8. Break up dense text on slide 14 into bullet points. Consider visual examples of the DeckCheck app interface.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;engagement&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">7</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Good use of emojis, casual tone, and interactive elements. The GIF adds humor and the practical demo concept is engaging.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s1">&#39;Add a live demo screenshot or mockup of the DeckCheck app on slide 3. Include audience interaction prompts on slides 2-3 asking about their presentation challenges.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;pacing&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">7</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Generally well-paced but some slides are dense (slides 8-9, 12-13) while others are light (slide 11). Code-heavy slides may take longer to explain.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s1">&#39;Split slide 14 into two slides - one for UX principles, one for error handling. Reduce code on slides 8-9 to focus on key concepts only.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;structure&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Clear logical flow from introduction to requirements to implementation to next steps. Good narrative arc building toward a complete solution.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s2">&#34;Add a brief agenda/outline slide after slide 1 to preview the journey. Include a summary slide before &#39;Thank you&#39; recapping key takeaways.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">9</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;concistency&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Consistent formatting, tone, and code style throughout. Good parallel structure between Python and R examples.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s1">&#39;Ensure consistent capitalization in slide titles (slide 11 uses title case while others use sentence case). Standardize code comment styles across examples.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">9</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="s1">&#39;accessibility&#39;</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;score&#39;</span><span class="p">:</span> <span class="mi">7</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;justification&#39;</span><span class="p">:</span> <span class="s1">&#39;Text appears readable but code blocks are quite dense. Good contrast likely but some slides have significant cognitive load.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;improvements&#39;</span><span class="p">:</span> <span class="s1">&#39;Increase font size in code blocks on slides 5-6, 8-9, 12-13. Add alt text description for the GIF on slide 11. Use consistent heading hierarchy.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s1">&#39;score_after_improvements&#39;</span><span class="p">:</span> <span class="mi">8</span><span class="p">}}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
<div id="tabset-12-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">system_prompt</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Register the tool with the chat</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">register_tool</span><span class="p">(</span><span class="n">calculate_slide_metric</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Task 1: regular chat to extract meta-data</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Execute Task 1 (counts). Here are the slides in Markdown: {{ markdown_content }}&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Task 2: structured chat to further analyse the slides</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat_structured</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Execute Task 2 (suggestions)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="o">=</span> <span class="n">type_deck_analysis</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show complete code</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span><span class="lnt">149
</span><span class="lnt">150
</span><span class="lnt">151
</span><span class="lnt">152
</span><span class="lnt">153
</span><span class="lnt">154
</span><span class="lnt">155
</span><span class="lnt">156
</span><span class="lnt">157
</span><span class="lnt">158
</span><span class="lnt">159
</span><span class="lnt">160
</span><span class="lnt">161
</span><span class="lnt">162
</span><span class="lnt">163
</span><span class="lnt">164
</span><span class="lnt">165
</span><span class="lnt">166
</span><span class="lnt">167
</span><span class="lnt">168
</span><span class="lnt">169
</span><span class="lnt">170
</span><span class="lnt">171
</span><span class="lnt">172
</span><span class="lnt">173
</span><span class="lnt">174
</span><span class="lnt">175
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get Quarto presentation and convert to plain Markdown + HTML</span>
</span></span><span class="line"><span class="cl"><span class="n">quarto</span><span class="o">::</span><span class="nf">quarto_render</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;./Quarto/my-presentation.qmd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">output_format</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;markdown&#34;</span><span class="p">,</span> <span class="s">&#34;html&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Dynamic data</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Audience, length in minutes, type, and event</span>
</span></span><span class="line"><span class="cl"><span class="n">audience_content</span> <span class="o">&lt;-</span> <span class="s">&#34;Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">length_content</span> <span class="o">&lt;-</span> <span class="s">&#34;10&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">type_content</span> <span class="o">&lt;-</span> <span class="s">&#34;lightning talk&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">event_content</span> <span class="o">&lt;-</span> <span class="s">&#34;posit::conf(2025)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the generated Markdown file containing our slides</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./Quarto/docs/my-presentation.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_content</span> <span class="o">&lt;-</span> <span class="nf">readChar</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">,</span> <span class="nf">file.size</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./prompts/prompt-analyse-slides-structured-tool.md&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt</span> <span class="o">&lt;-</span> <span class="nf">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">path</span> <span class="o">=</span> <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">audience</span> <span class="o">=</span> <span class="n">audience_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">length</span> <span class="o">=</span> <span class="n">length_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="o">=</span> <span class="n">type_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">event</span> <span class="o">=</span> <span class="n">event_content</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Reusable scoring category</span>
</span></span><span class="line"><span class="cl"><span class="n">type_scoring_category</span> <span class="o">&lt;-</span> <span class="nf">type_object</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">score</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Score from 1 to 10.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">justification</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Brief explanation of the score.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">improvements</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Concise, actionable improvements, mentioning slide numbers if applicable.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">required</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">score_after_improvements</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimated score after suggested improvements.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Top-level deck analysis object</span>
</span></span><span class="line"><span class="cl"><span class="n">type_deck_analysis</span> <span class="o">&lt;-</span> <span class="nf">type_object</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">presentation_title</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span><span class="n">description</span> <span class="o">=</span> <span class="s">&#34;The presentation title.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">total_slides</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span><span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Total number of slides.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">percent_with_code</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Percentage of slides containing code blocks (0–100).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">percent_with_images</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Percentage of slides containing images (0–100).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">estimated_duration_minutes</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimated presentation length in minutes, assuming ~1 minute per text slide and 2–3 minutes per code or image-heavy slide.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">tone</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Brief description of the presentation tone (e.g., informal, technical, playful).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">clarity</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">relevance</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Asses how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">visual_design</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">engagement</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">pacing</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Analyze the distribution of content across slides. Are some slides too dense or too light? &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">structure</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">concistency</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Evaluate whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">accessibility</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define a tool to calculate some metrics</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Start with a function:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; Calculates the total number of slides, percentage of slides with code blocks,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; and percentage of slides with images in a Quarto presentation HTML file.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; @param metric The metric to calculate: &#34;total_slides&#34; for total number of slides,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; &#34;code&#34; for percentage of slides containing fenced code blocks, or &#34;images&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; for percentage of slides containing images.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; @return The calculated metric value.</span>
</span></span><span class="line"><span class="cl"><span class="n">calculate_slide_metric</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">metric</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">html_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./Quarto/docs/my-presentation.html&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">file.exists</span><span class="p">(</span><span class="n">html_file</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">stop</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;HTML file does not exist. Please render your Quarto presentation first.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Read HTML file</span>
</span></span><span class="line"><span class="cl">  <span class="n">html_content</span> <span class="o">&lt;-</span> <span class="nf">readChar</span><span class="p">(</span><span class="n">html_file</span><span class="p">,</span> <span class="nf">file.size</span><span class="p">(</span><span class="n">html_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Split on &lt;section&gt; tags to get individual slides</span>
</span></span><span class="line"><span class="cl">  <span class="n">slides</span> <span class="o">&lt;-</span> <span class="nf">unlist</span><span class="p">(</span><span class="nf">strsplit</span><span class="p">(</span><span class="n">html_content</span><span class="p">,</span> <span class="s">&#34;&lt;section&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">total_slides</span> <span class="o">&lt;-</span> <span class="nf">length</span><span class="p">(</span><span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">if</span> <span class="p">(</span><span class="n">metric</span> <span class="o">==</span> <span class="s">&#34;total_slides&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">&lt;-</span> <span class="n">total_slides</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="kr">else</span> <span class="kr">if</span> <span class="p">(</span><span class="n">metric</span> <span class="o">==</span> <span class="s">&#34;code&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Count slides where we see the &#34;sourceCode&#34; class</span>
</span></span><span class="line"><span class="cl">    <span class="n">slides_with_code</span> <span class="o">&lt;-</span> <span class="nf">sum</span><span class="p">(</span><span class="nf">grepl</span><span class="p">(</span><span class="s">&#39;class=&#34;sourceCode&#34;&#39;</span><span class="p">,</span> <span class="n">slides</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">&lt;-</span> <span class="nf">round</span><span class="p">((</span><span class="n">slides_with_code</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="m">100</span><span class="p">,</span> <span class="m">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="kr">else</span> <span class="kr">if</span> <span class="p">(</span><span class="n">metric</span> <span class="o">==</span> <span class="s">&#34;images&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Count slides with image tag</span>
</span></span><span class="line"><span class="cl">    <span class="n">slides_with_image</span> <span class="o">&lt;-</span> <span class="nf">sum</span><span class="p">(</span><span class="nf">grepl</span><span class="p">(</span><span class="s">&#39;&lt;img&#39;</span><span class="p">,</span> <span class="n">slides</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">&lt;-</span> <span class="nf">round</span><span class="p">((</span><span class="n">slides_with_image</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="m">100</span><span class="p">,</span> <span class="m">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="kr">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">stop</span><span class="p">(</span><span class="s">&#34;Unknown metric: choose &#39;total_slides&#39;, &#39;code&#39;, or &#39;images&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">return</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Optionally, to avoid manual work:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># create_tool_def(calculate_slide_metric)</span>
</span></span><span class="line"><span class="cl"><span class="n">calculate_slide_metric</span> <span class="o">&lt;-</span> <span class="nf">tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">calculate_slide_metric</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Returns the calculated metric value&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">metric</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#39;The metric to calculate: &#34;total_slides&#34; for total number of slides, 
</span></span></span><span class="line"><span class="cl"><span class="s">      &#34;code&#34; for percentage of slides containing fenced code blocks, or &#34;images&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">      for percentage of slides containing images.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">required</span> <span class="o">=</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">system_prompt</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Register the tool with the chat</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">register_tool</span><span class="p">(</span><span class="n">calculate_slide_metric</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Task 1: regular chat to extract meta-data</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Execute Task 1 (counts). Here are the slides in Markdown: {{ markdown_content }}&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Task 2: structured chat to further analyse the slides</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat_structured</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Execute Task 2 (suggestions)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="o">=</span> <span class="n">type_deck_analysis</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
<p>We can clearly see that our tool was being called 3 times for our 3 metrics, just like it should:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#&gt; ◯ [tool call] calculate_slide_metric(metric = &#34;total_slides&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ● #&gt; 16</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ◯ [tool call] calculate_slide_metric(metric = &#34;code&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ● #&gt; 37.5</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ◯ [tool call] calculate_slide_metrimetric = &#34;images&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ● #&gt; 6.25</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ```json</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;total_slides&#34;: 16,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;percentage_slides_with_code&#34;: 37.5,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;percentage_slides_with_images&#34;: 6.25</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; }</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ```</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details class="code-fold">
<summary>Show the rest of the output</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#&gt; ◯ [tool call] calculate_slide_metric(metric = &#34;total_slides&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ● #&gt; 16</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ◯ [tool call] calculate_slide_metric(metric = &#34;code&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ● #&gt; 37.5</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ◯ [tool call] calculate_slide_metric(metric = &#34;images&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ● #&gt; 6.25</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ```json</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; {</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;total_slides&#34;: 16,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;percentage_slides_with_code&#34;: 37.5,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;percentage_slides_with_images&#34;: 6.25</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; }</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ```</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $presentation_title</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;The Shiny Side of LLMs&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $total_slides</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] 16</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $percent_with_code</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] 37.5</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $percent_with_images</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] 6.25</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $estimated_duration_minutes</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] 12</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $tone</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;Informal and conversational with technical depth&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $clarity</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     8</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                  justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Content is well-explained with clear examples and step-by-step progression. Technical concepts are introduced appropriately.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                                                                      improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Add brief definition of LLMs on slide 2 for audience members new to the topic. Consider adding expected output examples on slides 5-6 to show what the LLM responses look like.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        9</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $relevance</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     9</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                                                justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Highly relevant for the posit::conf audience - focuses on practical implementation in both R and Python with Shiny, which is core to the conference theme.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                             improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Content is already well-targeted for the audience. No significant improvements needed.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        9</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $visual_design</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     6</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                              justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Layout is clean but quite text-heavy. Code blocks are well-formatted, but some slides like slide 8 and 14 contain large amounts of text.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                                                                                                                            improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Break up slide 8 (Shiny basics: Python) into 2 slides - separate UI and server logic. Split slide 14 (User experience) into bullet points or multiple slides. Add more visual elements like diagrams or screenshots of the final app.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        8</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $engagement</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     7</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                                           justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Good use of emojis and conversational tone. The magic GIF on slide 10 adds personality. However, mostly text-based with limited interactive elements.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                                                                     improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Add a live demo screenshot or video on slide 15 showing the final DeckCheck app in action. Consider adding a simple architecture diagram on slide 4 to visualize the workflow.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        8</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $pacing</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     7</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                        justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Good balance overall, but some slides are content-heavy (slides 8, 9, 12, 13) which may feel rushed in a 10-minute lightning talk.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                                                                                           improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Simplify code examples on slides 8-9 by showing only key parts. Break slide 12-13 into smaller, focused slides. Consider removing some implementation details to focus on the core concept and demo.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        8</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $structure</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     8</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                         justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Clear logical flow from introduction to implementation to next steps. Good progression from basic concepts to complete integration.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                                                          improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Add a brief agenda or roadmap slide after slide 1 to set expectations. Include a quick recap slide before the &#39;Up next&#39; section to summarize what was accomplished.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        9</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $concistency</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     8</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                          justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Consistent formatting for code blocks and good parallel structure between R and Python examples. Tone remains consistent throughout.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                                             improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Ensure slide titles follow consistent capitalization (slide 10 &#39;Where&#39;s the magic?&#39; vs others). Standardize bullet point formatting across all slides.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        9</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; $accessibility</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1     7</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                                justification</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Code is readable with good contrast. However, some slides have dense text that may be challenging to read quickly during a lightning talk.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                                                                                                                                                 improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 Increase font size for code blocks on slides 5-6, 8-9, 12-13. Reduce text density on slide 14. Ensure the GIF on slide 10 has alt text for screen readers.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   score_after_improvements</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1                        8</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
</div>
<p>So how does the LLM know that it needs to use a tool? We didn&rsquo;t specifically tell it to use it. We just registered it and let the LLM figure it out for itself. How does that work? The first step in the process is turning a vague, human-friendly request into something a tool can actually run. In our case, our prompt contained this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">You extract the following information:
</span></span><span class="line"><span class="cl"><span class="k">-</span> The number of slides
</span></span><span class="line"><span class="cl"><span class="k">-</span> The percentage of slides containing code blocks
</span></span><span class="line"><span class="cl"><span class="k">-</span> The percentage of slides containing images 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Return only the JSON results, nothing else.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></td></tr></table>
</div>
</div><p>When the LLM sees &ldquo;The percentage of slides containing images,&rdquo; it doesn&rsquo;t just guess. It translates that phrase into structured parameters for the registered tool: <code>calculate_slide_metric</code>. The LLM knows it needs to set <code>metric</code> to <code>&quot;images&quot;</code> or <code>&quot;code&quot;</code>, depending on the request. Once it has those details, it calls the tool with this parameter and responds with the result in JSON (and nothing else, as we told it to: LLMs love to talk).</p>
<p>This is where tool calling really shines. It&rsquo;s not just about running code: it&rsquo;s about structuring messy, open-ended requests into something that can be understood by our code. You could ask &ldquo;Show me how code-heavy my slides are,&rdquo; &ldquo;Can you tell me how many images are in this presentation?&rdquo; or &ldquo;What percentage of slides have images?&rdquo; and the LLM would know to call the same tool. The wording changes, but the intent is the same, and the LLM can map all those variations to the right parameters. It&rsquo;s pretty clever!</p>
<p>The parameters make it even more powerful. If we created a tool with no parameters (e.g. one that only ever returned the percentage of image slides), it could only answer that exact question. But by designing it with parameters, it&rsquo;s way more flexible. The same <code>calculate_slide_metric</code> function can handle requests for total slides, code slides, image slides, or anything else we choose to add later. And because the LLM can chain tool calls, aka run the tool more than once in a conversation, it can check for the 3 metrics all at once and bundle the results together in one tidy JSON.</p>
<h1 id="from-tools-to-agents">From tools to agents
</h1>
<p>Right now, DeckCheck&rsquo;s tool does one (or, ok, technically speaking, three) things: it calculates the metrics you&rsquo;ve defined. That&rsquo;s it. But tool calling can go much further. Imagine that, instead of passing parameters to code you&rsquo;ve already written, the LLM could actually write its own code to analyse your slides. Creepy, right? The LLM is just&hellip; doing its own thing. This is sometimes called &ldquo;agentic AI&rdquo;, where the LLM can formulate and execute coding tasks to learn about <em>and alter</em> the state of the world.</p>
<p>Sounds cool, but what if the LLM decides to do something that&rsquo;s harmful? Imagine it dropping database tables, overwriting files, or sending data somewhere it shouldn&rsquo;t. Without limits, arbitrary code execution can be as risky as it is powerful. That&rsquo;s why you need to take some safety measures. For example, if you let the LLM write SQL statements, you can limit it so it only allows read-only SQL executing. This is exactly what <a href="https://github.com/posit-dev/querychat" target="_blank" rel="noopener">querychat</a>
 does. Or, instead of giving the LLM complete freedom, you can add a human back into the loop by asking explicit user approval before anything gets executed. An example of such a project is the new (experimental) agent called <a href="https://open-vsx.org/extension/posit/databot" target="_blank" rel="noopener">Databot</a>
. Databot is there to assist you in your data analysis, making you faster and more efficient. But it doesn&rsquo;t mean it can replace you: to use these tools effectively and safely, <a href="https://posit.co/blog/databot-is-not-a-flotation-device/" target="_blank" rel="noopener">your human skills are still needed</a>
. Curious to learn more about Databot? Check out the <a href="https://posit.co/blog/introducing-databot/" target="_blank" rel="noopener">official announcement</a>
!</p>
<p>This is also where MCP (<a href="https://modelcontextprotocol.io/docs/getting-started/intro" target="_blank" rel="noopener">Model Context Protocol</a>
) comes in. You have data or tools (an R/Python function, an API, a database, spreadsheet, whatever). You want an LLM to use them. But the LLM doesn&rsquo;t know what you have or how to access it. The problem is, the LLM doesn&rsquo;t automatically know what you have or how to use it. MCP solves that by providing a standard way to describe these tools and make them available to the model. It&rsquo;s like handing the LLM a clearly written &ldquo;menu&rdquo; of what&rsquo;s on offer and how to order it. That&rsquo;s why MCP is often described as &ldquo;a USB-C port for LLM applications&rdquo;: it standardises how models can plug into all sorts of tools and data sources. It opens a world of possibilities. But the details of MCP are a whole topic on their own, so we&rsquo;ll leave that for another time. If you got curious about MCP, you can check out the relevant section in the <a href="https://posit-dev.github.io/chatlas/misc/mcp-tools.html" target="_blank" rel="noopener"><code>chatlas</code> documentation</a>
, or <a href="https://posit-dev.github.io/mcptools/" target="_blank" rel="noopener"><code>mcptools</code></a>
 for some R fun with MCP.</p>
<h1 id="model-parameters">Model parameters
</h1>
<p>We covered the &ldquo;big&rdquo; parts to get a response we can work with: we set a system prompt, crafted a nice detailed input prompt, used structured output, and gave the LLM some extra calculation power with a tool. But still we are getting slightly different results every single time: the suggested improvements are always different and even things like estimated duration vary. Ideally, we want to have as much &ldquo;control&rdquo; as possible over the response an LLM generates. And while we can&rsquo;t 100% control what an LLM does, we can tweak its behaviour by changing (some of) the settings. These settings are also called model parameters.</p>
<blockquote>
<p><strong>A bit playing around the edges</strong></p>
<p>Tweaking model parameters is playing around the edges: it won&rsquo;t magically fix a bad prompt. Writing a good prompt and knowing how to use <code>chatlas</code> or <code>ellmer</code> programmatically already give a solid foundation. Normally default settings for model parameters are the default for a reason: they tend to give the most desired results. But understanding which parameters there are and what kind of effect they have on the LLM&rsquo;s response is a good exercise anyway!</p>
</blockquote>
<p>There are many model parameters, and the ones you can change are different for each provider and model, but generally, these are the most noteworthy ones:</p>
<ul>
<li>
<p><strong>Temperature</strong>: when you ask an LLM the same question you might expect the same answer, just like a calculator always gives the same result for 2 + 2. But an LLM isn&rsquo;t a calculator: it&rsquo;s more like a smart writer. It &ldquo;knows&rdquo; how it should answer your question, but it can phrase it in many ways. That&rsquo;s because its default settings allow a little randomness, a little &ldquo;creativity&rdquo;. The wording, order, or formatting might change, even if the meaning stays the same. This creativity is also called the model&rsquo;s temperature. Typically, the temperature ranges from 0.0 to 1.0. A temperature closer to 1.0 is great for creative and generative tasks, and a temperature closer to 0.0 is best for analytical tasks. Note that a temperature of 0 doesn&rsquo;t mean an LLM will be fully deterministic: probability mechanisms will always cause slight variations in answers. Also note that a temperature of 1 might lead to very creative answers that don&rsquo;t make much sense anymore.</p>
</li>
<li>
<p><strong>Seed</strong>: adds a bit of predictability to a model that&rsquo;s normally unpredictable. As mentioned before, asking the same question twice (especially with a higher temperature) gives you slightly different answers. That&rsquo;s great for creativity, but not always what you want. By setting a seed you make randomness reproducible. It tells the model to &ldquo;start from the same random point&rdquo; every time. This is especially helpful for testing or generating consistent results in production. Note: not all LLM providers support the seed parameter! Looking at the <a href="https://docs.anthropic.com/en/api/messages" target="_blank" rel="noopener">documentation</a>
, Anthropic does not seem to support this parameter in API calls.</p>
</li>
<li>
<p><strong>Top-p</strong>: controls how many possible words the model can choose from when generating a response. It does this by adding up the most likely words until a probability threshold is reached. Let&rsquo;s take our original prompt as an example: &ldquo;I&rsquo;m working on a presentation with the title: &lsquo;The Shiny Side of LLMs&rsquo;. What&rsquo;s your feedback just based on that title?&rdquo;. The model considers several next phrases:</p>
<ul>
<li>&ldquo;is catchy and clear&rdquo; (50%)</li>
<li>&ldquo;could be more specific&rdquo; (25%)</li>
<li>&ldquo;is creative but vague&rdquo; (15%)</li>
<li>&ldquo;needs glitter&rdquo; (7%)</li>
<li>&ldquo;mentions Dalmatians&rdquo; (3%)</li>
</ul>
<p>If top-p = 0.9, only the top three are used (since 50% + 25% + 15% = 90%). The model randomly picks one of those three options, with the probability of each token still guiding the choice (so the 50% token is much more likely to be picked than the 15% one). If top-p = 0.75, the model only considers the first two options. A higher top-p value leads to more variety, while a lower top-p leads to more focussed and &ldquo;safe&rdquo; answers.</p>
<p>It&rsquo;s not recommended to control creativity via both temperature or top-p: pick one. If you start tweaking both parameters they might cancel each other out and it can lead to a weird balance. Changing the temperature is the simpler and more predictive way to control creativity: it&rsquo;s more linear and easier to wrap your head around.</p>
</li>
<li>
<p><strong>Maximum length or maximum output tokens</strong>: the limit on how long the model&rsquo;s response can be. Different models have different maximum values for this parameter. Note that with tokens, you&rsquo;re not counting characters or words. Instead, you&rsquo;re telling the model: &ldquo;Don&rsquo;t generate more than X chunks of language&rdquo;. For example, Claude Sonnet 4 has a <a href="https://docs.anthropic.com/en/docs/about-claude/models/overview#model-comparison-table" target="_blank" rel="noopener">maximum output of 64,000 tokens</a>
 (which is around 48,000 words).</p>
</li>
</ul>
<p>And to come back to some jargon, changing any of these model parameters is called inference tweaking. It&rsquo;s trying to control how creative, long, or focused responses are.</p>
<p>Model parameters are supported by most providers and with <code>chatlas</code> and <code>ellmer</code> you can quickly set them in a provider-agnostic way.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-13" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-13-1">Python</a></li>
<li><a href="#tabset-13-2">R</a></li>
</ul>
<div id="tabset-13-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Set model parameters (optional)</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">set_model_params</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">temperature</span><span class="o">=</span><span class="mf">0.8</span><span class="p">,</span> <span class="c1"># default is 1</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>By using the <a href="https://posit-dev.github.io/chatlas/reference/Chat.html#set_model_params.qmd" target="_blank" rel="noopener"><code>set_model_params()</code></a>
 method on the chat object, you can quickly set things like <code>temperature</code>, <code>max_tokens</code> or <code>top_p</code>. These model parameters apply to the whole chat. If you want to revert to the default parameters, you can pass <code>None</code> to the relevant parameter.</p>
</div>
<div id="tabset-13-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">params</span> <span class="o">=</span> <span class="nf">params</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">temperature</span> <span class="o">=</span> <span class="m">0.8</span> <span class="c1"># default is 1</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Adding parameters to our model is easy: we use the <a href="https://ellmer.tidyverse.org/reference/params.html" target="_blank" rel="noopener"><code>params</code> argument</a>
 in <code>chat_anthropic()</code>. We create those parameters with <a href="https://ellmer.tidyverse.org/reference/params.html" target="_blank" rel="noopener"><code>params()</code></a>
. This helper function creates a list of parameters, where parameter names are automatically standardised and included in the correctly place in the API call. You won&rsquo;t have to do a thing. Thanks <code>ellmer</code>!</p>
<p>Note: since <code>ellmer</code> 0.3.0 you can also use <a href="https://ellmer.tidyverse.org/reference/chat-any.html" target="_blank" rel="noopener"><code>chat()</code></a>
 and pass the <code>params</code> argument there.</p>
</div>
</div>
<p>Besides model parameters, which are commonly used across providers, there&rsquo;s also something called <a href="https://posit-dev.github.io/chatlas/get-started/parameters.html#chat-parameters" target="_blank" rel="noopener">chat parameters</a>
. The difference is that the latter are specific to the model provider you&rsquo;re using.</p>
<h1 id="full-workflow">Full workflow
</h1>
<p>A nice prompt, structured output, tool calling, model parameters&hellip; It all led us to the following workflow:</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-14" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-14-1">Python</a></li>
<li><a href="#tabset-14-2">R</a></li>
<li><a href="#tabset-14-3">System prompt</a></li>
</ul>
<div id="tabset-14-1">
<blockquote>
<p><strong>Get this code from GitHub</strong></p>
<p>You can grab the code directly from <a href="https://github.com/hypebright/the-shiny-side-of-llms/blob/d1094d2774f9d0c213c7ddf6e17f94da706b1b76/Py/demo/conversation-tool.py" target="_blank" rel="noopener">here</a>
.</p>
</blockquote>
<details class="code-fold">
<summary>See full workflow</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span><span class="lnt">149
</span><span class="lnt">150
</span><span class="lnt">151
</span><span class="lnt">152
</span><span class="lnt">153
</span><span class="lnt">154
</span><span class="lnt">155
</span><span class="lnt">156
</span><span class="lnt">157
</span><span class="lnt">158
</span><span class="lnt">159
</span><span class="lnt">160
</span><span class="lnt">161
</span><span class="lnt">162
</span><span class="lnt">163
</span><span class="lnt">164
</span><span class="lnt">165
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chatlas</span> <span class="kn">import</span> <span class="n">ChatAnthropic</span><span class="p">,</span> <span class="n">interpolate_file</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pydantic</span> <span class="kn">import</span> <span class="n">BaseModel</span><span class="p">,</span> <span class="n">Field</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Annotated</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Union</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatAnthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">system_prompt</span><span class="o">=</span><span class="s2">&#34;You are a presentation coach for data scientists. You give constructive, focused, and practical feedback on titles, structure, and storytelling.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Set model parameters (optional)</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">set_model_params</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">temperature</span><span class="o">=</span><span class="mf">0.8</span><span class="p">,</span>  <span class="c1"># default is 1</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get Quarto presentation and convert to plain Markdown + HTML</span>
</span></span><span class="line"><span class="cl"><span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="s2">&#34;quarto&#34;</span><span class="p">,</span> <span class="s2">&#34;render&#34;</span><span class="p">,</span> <span class="s2">&#34;./my-presentation.qmd&#34;</span><span class="p">,</span> <span class="s2">&#34;--to&#34;</span><span class="p">,</span> <span class="s2">&#34;markdown,html&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Use prompt file</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Step 1: first step of the analysis (meta-data)</span>
</span></span><span class="line"><span class="cl"><span class="n">prompt_file_1</span> <span class="o">=</span> <span class="s2">&#34;./prompts/prompt-analyse-slides-structured-tool-1.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Step 2: second step of the analysis (detailed analysis with improvements)</span>
</span></span><span class="line"><span class="cl"><span class="n">prompt_file_2</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./prompts/prompt-analyse-slides-structured-tool-2.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Dynamic data</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Audience, length in minutes, type, and event</span>
</span></span><span class="line"><span class="cl"><span class="n">audience_content</span> <span class="o">=</span> <span class="s2">&#34;Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">length_content</span> <span class="o">=</span> <span class="s2">&#34;10&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">type_content</span> <span class="o">=</span> <span class="s2">&#34;lightning talk&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">event_content</span> <span class="o">=</span> <span class="s2">&#34;posit::conf(2025)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the generated Markdown file containing our slides</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./docs/my-presentation.md&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_content</span> <span class="o">=</span> <span class="n">markdown_file</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Construct the first prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">prompt_complete_1</span> <span class="o">=</span> <span class="n">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">prompt_file_1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">variables</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;audience&#34;</span><span class="p">:</span> <span class="n">audience_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;length&#34;</span><span class="p">:</span> <span class="n">length_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="n">type_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;event&#34;</span><span class="p">:</span> <span class="n">event_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;markdown&#34;</span><span class="p">:</span> <span class="n">markdown_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the second prompt (no dynamic data)</span>
</span></span><span class="line"><span class="cl"><span class="n">prompt_complete_2</span> <span class="o">=</span> <span class="n">prompt_file_2</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define data structure to extract from the input</span>
</span></span><span class="line"><span class="cl"><span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mi">10</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">PercentType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">le</span><span class="o">=</span><span class="mf">100.0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">MinutesType</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="n">SlideCount</span> <span class="o">=</span> <span class="n">Annotated</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">Field</span><span class="p">(</span><span class="n">ge</span><span class="o">=</span><span class="mi">0</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ScoringCategory</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">score</span><span class="p">:</span> <span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Score from 1–10.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">justification</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Brief explanation of the score.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">improvements</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Concise, actionable improvements, mentioning slide numbers if applicable.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">score_after_improvements</span><span class="p">:</span> <span class="n">ScoreType</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Estimated score after suggested improvements.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DeckAnalysis</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">presentation_title</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;The presentation title.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_slides</span><span class="p">:</span> <span class="n">SlideCount</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_code</span><span class="p">:</span> <span class="n">PercentType</span>
</span></span><span class="line"><span class="cl">    <span class="n">percent_with_images</span><span class="p">:</span> <span class="n">PercentType</span>
</span></span><span class="line"><span class="cl">    <span class="n">estimated_duration_minutes</span><span class="p">:</span> <span class="n">MinutesType</span>
</span></span><span class="line"><span class="cl">    <span class="n">tone</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Brief description of the tone of the presentation.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">clarity</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">relevance</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Assess how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">visual_design</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">engagement</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">pacing</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Analyze the distribution of content across slides. Are some slides too dense or too light? &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">structure</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">concistency</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>  <span class="c1"># spelling kept as-is</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Evaluatue whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">accessibility</span><span class="p">:</span> <span class="n">ScoringCategory</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define a tool to calculate some metrics</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Start with a function:</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">calculate_slide_metric</span><span class="p">(</span><span class="n">metric</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Union</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">float</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    Calculates the total number of slides, percentage of slides with code blocks,
</span></span></span><span class="line"><span class="cl"><span class="s2">    and percentage of slides with images in a Quarto presentation HTML file.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    Parameters
</span></span></span><span class="line"><span class="cl"><span class="s2">    ----------
</span></span></span><span class="line"><span class="cl"><span class="s2">    metric : str
</span></span></span><span class="line"><span class="cl"><span class="s2">        The metric to calculate: &#34;total_slides&#34; for total number of slides,
</span></span></span><span class="line"><span class="cl"><span class="s2">        &#34;code&#34; for percentage of slides containing fenced code blocks,
</span></span></span><span class="line"><span class="cl"><span class="s2">        or &#34;images&#34; for percentage of slides containing images.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    Returns
</span></span></span><span class="line"><span class="cl"><span class="s2">    -------
</span></span></span><span class="line"><span class="cl"><span class="s2">    float or int
</span></span></span><span class="line"><span class="cl"><span class="s2">        The calculated metric value.
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">html_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./docs/my-presentation.html&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">html_file</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;HTML file </span><span class="si">{</span><span class="n">html_file</span><span class="si">}</span><span class="s2"> does not exist.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Read HTML file</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">html_file</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">html_content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Split on &lt;section&gt; tags to get individual slides</span>
</span></span><span class="line"><span class="cl">    <span class="n">slides</span> <span class="o">=</span> <span class="n">html_content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;&lt;section&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_slides</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">metric</span> <span class="o">==</span> <span class="s2">&#34;total_slides&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">total_slides</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">metric</span> <span class="o">==</span> <span class="s2">&#34;code&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">slides_with_code</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="s1">&#39;class=&#34;sourceCode&#34;&#39;</span> <span class="ow">in</span> <span class="n">slide</span> <span class="k">for</span> <span class="n">slide</span> <span class="ow">in</span> <span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="nb">round</span><span class="p">((</span><span class="n">slides_with_code</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">metric</span> <span class="o">==</span> <span class="s2">&#34;images&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">slides_with_image</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="s2">&#34;&lt;img&#34;</span> <span class="ow">in</span> <span class="n">slide</span> <span class="k">for</span> <span class="n">slide</span> <span class="ow">in</span> <span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="nb">round</span><span class="p">((</span><span class="n">slides_with_image</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;Unknown metric: choose &#39;total_slides&#39;, &#39;code&#39;, or &#39;images&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">register_tool</span><span class="p">(</span><span class="n">calculate_slide_metric</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Step 1: use regular chat to extract meta-data</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Note that this *should* make use of our tool</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span><span class="n">prompt_complete_1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Step 2: use structured chat to further analyse the slides</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">chat_structured</span><span class="p">(</span><span class="n">prompt_complete_2</span><span class="p">,</span> <span class="n">data_model</span><span class="o">=</span><span class="n">DeckAnalysis</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
<div id="tabset-14-2">
<blockquote>
<p><strong>Get this code from GitHub</strong></p>
<p>You can grab the code directly from <a href="https://github.com/hypebright/the-shiny-side-of-llms/blob/d1094d2774f9d0c213c7ddf6e17f94da706b1b76/R/demo/conversation-tool.R" target="_blank" rel="noopener">here</a>
.</p>
</blockquote>
<details class="code-fold">
<summary>See full workflow</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span><span class="lnt">149
</span><span class="lnt">150
</span><span class="lnt">151
</span><span class="lnt">152
</span><span class="lnt">153
</span><span class="lnt">154
</span><span class="lnt">155
</span><span class="lnt">156
</span><span class="lnt">157
</span><span class="lnt">158
</span><span class="lnt">159
</span><span class="lnt">160
</span><span class="lnt">161
</span><span class="lnt">162
</span><span class="lnt">163
</span><span class="lnt">164
</span><span class="lnt">165
</span><span class="lnt">166
</span><span class="lnt">167
</span><span class="lnt">168
</span><span class="lnt">169
</span><span class="lnt">170
</span><span class="lnt">171
</span><span class="lnt">172
</span><span class="lnt">173
</span><span class="lnt">174
</span><span class="lnt">175
</span><span class="lnt">176
</span><span class="lnt">177
</span><span class="lnt">178
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">ellmer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get Quarto presentation and convert to plain Markdown + HTML</span>
</span></span><span class="line"><span class="cl"><span class="n">quarto</span><span class="o">::</span><span class="nf">quarto_render</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;./Quarto/my-presentation.qmd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">output_format</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;markdown&#34;</span><span class="p">,</span> <span class="s">&#34;html&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Dynamic data</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Audience, length in minutes, type, and event</span>
</span></span><span class="line"><span class="cl"><span class="n">audience_content</span> <span class="o">&lt;-</span> <span class="s">&#34;Python and R users who are curious about AI and large language models, but not all of them have a deep technical background&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">length_content</span> <span class="o">&lt;-</span> <span class="s">&#34;10&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">type_content</span> <span class="o">&lt;-</span> <span class="s">&#34;lightning talk&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">event_content</span> <span class="o">&lt;-</span> <span class="s">&#34;posit::conf(2025)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the generated Markdown file containing our slides</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./Quarto/docs/my-presentation.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">markdown_content</span> <span class="o">&lt;-</span> <span class="nf">readChar</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">,</span> <span class="nf">file.size</span><span class="p">(</span><span class="n">markdown_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define prompt file</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./prompts/prompt-analyse-slides-structured-tool.md&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create system prompt</span>
</span></span><span class="line"><span class="cl"><span class="n">system_prompt</span> <span class="o">&lt;-</span> <span class="nf">interpolate_file</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">path</span> <span class="o">=</span> <span class="n">system_prompt_file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">audience</span> <span class="o">=</span> <span class="n">audience_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">length</span> <span class="o">=</span> <span class="n">length_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="o">=</span> <span class="n">type_content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">event</span> <span class="o">=</span> <span class="n">event_content</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Reusable scoring category</span>
</span></span><span class="line"><span class="cl"><span class="n">type_scoring_category</span> <span class="o">&lt;-</span> <span class="nf">type_object</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">score</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Score from 1 to 10.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">justification</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Brief explanation of the score.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">improvements</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Concise, actionable improvements, mentioning slide numbers if applicable.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">required</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">score_after_improvements</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimated score after suggested improvements.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Top-level deck analysis object</span>
</span></span><span class="line"><span class="cl"><span class="n">type_deck_analysis</span> <span class="o">&lt;-</span> <span class="nf">type_object</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">presentation_title</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span><span class="n">description</span> <span class="o">=</span> <span class="s">&#34;The presentation title.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">total_slides</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span><span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Total number of slides.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">percent_with_code</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Percentage of slides containing code blocks (0–100).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">percent_with_images</span> <span class="o">=</span> <span class="nf">type_number</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Percentage of slides containing images (0–100).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">estimated_duration_minutes</span> <span class="o">=</span> <span class="nf">type_integer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimated presentation length in minutes, assuming ~1 minute per text slide and 2–3 minutes per code or image-heavy slide.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">tone</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Brief description of the presentation tone (e.g., informal, technical, playful).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">clarity</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">relevance</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Asses how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">visual_design</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">engagement</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">pacing</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Analyze the distribution of content across slides. Are some slides too dense or too light? &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">structure</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">concistency</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Evaluate whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">accessibility</span> <span class="o">=</span> <span class="nf">type_array</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="o">=</span> <span class="s">&#34;Consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">type_scoring_category</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define a tool to calculate some metrics</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Start with a function:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; Calculates the total number of slides, percentage of slides with code blocks,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; and percentage of slides with images in a Quarto presentation HTML file.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; @param metric The metric to calculate: &#34;total_slides&#34; for total number of slides,</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; &#34;code&#34; for percentage of slides containing fenced code blocks, or &#34;images&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; for percentage of slides containing images.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&#39; @return The calculated metric value.</span>
</span></span><span class="line"><span class="cl"><span class="n">calculate_slide_metric</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">metric</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">html_file</span> <span class="o">&lt;-</span> <span class="s">&#34;./Quarto/docs/my-presentation.html&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">file.exists</span><span class="p">(</span><span class="n">html_file</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">stop</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;HTML file does not exist. Please render your Quarto presentation first.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Read HTML file</span>
</span></span><span class="line"><span class="cl">  <span class="n">html_content</span> <span class="o">&lt;-</span> <span class="nf">readChar</span><span class="p">(</span><span class="n">html_file</span><span class="p">,</span> <span class="nf">file.size</span><span class="p">(</span><span class="n">html_file</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Split on &lt;section&gt; tags to get individual slides</span>
</span></span><span class="line"><span class="cl">  <span class="n">slides</span> <span class="o">&lt;-</span> <span class="nf">unlist</span><span class="p">(</span><span class="nf">strsplit</span><span class="p">(</span><span class="n">html_content</span><span class="p">,</span> <span class="s">&#34;&lt;section&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">total_slides</span> <span class="o">&lt;-</span> <span class="nf">length</span><span class="p">(</span><span class="n">slides</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">if</span> <span class="p">(</span><span class="n">metric</span> <span class="o">==</span> <span class="s">&#34;total_slides&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">&lt;-</span> <span class="n">total_slides</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="kr">else</span> <span class="kr">if</span> <span class="p">(</span><span class="n">metric</span> <span class="o">==</span> <span class="s">&#34;code&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Count slides where we see the &#34;sourceCode&#34; class</span>
</span></span><span class="line"><span class="cl">    <span class="n">slides_with_code</span> <span class="o">&lt;-</span> <span class="nf">sum</span><span class="p">(</span><span class="nf">grepl</span><span class="p">(</span><span class="s">&#39;class=&#34;sourceCode&#34;&#39;</span><span class="p">,</span> <span class="n">slides</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">&lt;-</span> <span class="nf">round</span><span class="p">((</span><span class="n">slides_with_code</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="m">100</span><span class="p">,</span> <span class="m">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="kr">else</span> <span class="kr">if</span> <span class="p">(</span><span class="n">metric</span> <span class="o">==</span> <span class="s">&#34;images&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Count slides with image tag</span>
</span></span><span class="line"><span class="cl">    <span class="n">slides_with_image</span> <span class="o">&lt;-</span> <span class="nf">sum</span><span class="p">(</span><span class="nf">grepl</span><span class="p">(</span><span class="s">&#39;&lt;img&#39;</span><span class="p">,</span> <span class="n">slides</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">&lt;-</span> <span class="nf">round</span><span class="p">((</span><span class="n">slides_with_image</span> <span class="o">/</span> <span class="n">total_slides</span><span class="p">)</span> <span class="o">*</span> <span class="m">100</span><span class="p">,</span> <span class="m">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="kr">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">stop</span><span class="p">(</span><span class="s">&#34;Unknown metric: choose &#39;total_slides&#39;, &#39;code&#39;, or &#39;images&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">return</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Optionally, to avoid manual work:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># create_tool_def(calculate_slide_metric)</span>
</span></span><span class="line"><span class="cl"><span class="n">calculate_slide_metric</span> <span class="o">&lt;-</span> <span class="nf">tool</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">calculate_slide_metric</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Returns the calculated metric value&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">metric</span> <span class="o">=</span> <span class="nf">type_string</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#39;The metric to calculate: &#34;total_slides&#34; for total number of slides, 
</span></span></span><span class="line"><span class="cl"><span class="s">      &#34;code&#34; for percentage of slides containing fenced code blocks, or &#34;images&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">      for percentage of slides containing images.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">required</span> <span class="o">=</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Initialise chat with Claude Sonnet 4 model</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="nf">chat_anthropic</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span> <span class="o">=</span> <span class="s">&#34;claude-sonnet-4-20250514&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">system_prompt</span> <span class="o">=</span> <span class="n">system_prompt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">params</span> <span class="o">=</span> <span class="nf">params</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">temperature</span> <span class="o">=</span> <span class="m">0.8</span> <span class="c1"># default is 1</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Register the tool with the chat</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">register_tool</span><span class="p">(</span><span class="n">calculate_slide_metric</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Start conversation with the chat</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Task 1: regular chat to extract meta-data</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">interpolate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Execute Task 1 (counts). Here are the slides in Markdown: {{ markdown_content }}&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Task 2: structured chat to further analyse the slides</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">chat_structured</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Execute Task 2 (suggestions)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="o">=</span> <span class="n">type_deck_analysis</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
<div id="tabset-14-3">
<details class="code-fold">
<summary>Show full prompt</summary>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">You are a presentation coach for data scientists that analyses presentation slide decks written in Markdown. 
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract key information, evaluate quality, and return structured feedback that is constructive, focused and practical.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">The presentation you are helping with is a {{ length }}-minute {{ type }} at {{ event }}.  
</span></span></span><span class="line"><span class="cl"><span class="s2">The audience is {{ audience }}. 
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You can be asked for one of the following tasks. Each has a number and a name:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2"># Task 1 (counts)
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract the following information:
</span></span></span><span class="line"><span class="cl"><span class="s2">- The number of slides
</span></span></span><span class="line"><span class="cl"><span class="s2">- The percentage of slides containing code blocks
</span></span></span><span class="line"><span class="cl"><span class="s2">- The percentage of slides containing images 
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Return only the JSON results, nothing else.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2"># Task 2 (suggestions)
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You extract the following information:
</span></span></span><span class="line"><span class="cl"><span class="s2">- The presentation title
</span></span></span><span class="line"><span class="cl"><span class="s2">- Estimated presentation length (in minutes, assuming ~1 minute per text slide and 2–3 minutes per code or image-heavy slide)
</span></span></span><span class="line"><span class="cl"><span class="s2">- Tone (a brief description)
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You score the presentation on the following categories (from 1–10), and give a concise explanation:
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">1. Clarity of content: evaluate how clearly the ideas are communicated. Are the explanations easy to understand? Are terms defined when needed? Is the key message clear?
</span></span></span><span class="line"><span class="cl"><span class="s2">2. Relevance for intended audience: assess how well the content matches the audience’s background, needs, and expectations. Are examples, depth of detail, and terminology appropriate for the audience type?
</span></span></span><span class="line"><span class="cl"><span class="s2">3. Visual design: judge the visual effectiveness of the slides. Are they readable, visually balanced, and not overcrowded with text or visuals? Is layout used consistently?
</span></span></span><span class="line"><span class="cl"><span class="s2">4. Engagement: estimate how likely the presentation is to keep attention. Are there moments of interactivity, storytelling, humor, or visual interest that invite focus?
</span></span></span><span class="line"><span class="cl"><span class="s2">5. Pacing: analyze the distribution of content across slides. Are some slides too dense or too light? 
</span></span></span><span class="line"><span class="cl"><span class="s2">6. Structure: review the logical flow of the presentation. Is there a clear beginning, middle, and end? Are transitions between topics smooth? Does the presentation build toward a conclusion?
</span></span></span><span class="line"><span class="cl"><span class="s2">7. consistency: evaluatue whether the presentation is consistent when it comes to formatting, tone, and visual elements. Are there any elements that feel out of place?
</span></span></span><span class="line"><span class="cl"><span class="s2">8. Accessibility: consider how accessible the presentation would be for all viewers, including those with visual or cognitive challenges. Are font sizes readable? Is there sufficient contrast? Are visual elements not overwhelming?
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">For each of the above scoring categories, provide specific and actionable improvements. Follow these instructions:
</span></span></span><span class="line"><span class="cl"><span class="s2">- Keep each suggestion concise and mention the slide number(s) if applicable.
</span></span></span><span class="line"><span class="cl"><span class="s2">- Do not invent issues and only suggest improvements when the content would clearly benefit from them.
</span></span></span><span class="line"><span class="cl"><span class="s2">- For each catogory, estimate what the new score would be if these improvements are implemented.
</span></span></span><span class="line"><span class="cl"><span class="s2">- Return the improvement and new score as part of the response for that category.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">You bundle your results with the results from the first task. Always return the result as a JSON object that conforms to the provided data model.
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></details>
</div>
</div>
<p>But there&rsquo;s one questions that might still be lingering in your mind: how much does this cost?!</p>
<h1 id="understanding-tokens-and-costs">Understanding tokens and costs
</h1>
<p>Back to our nightmare from the beginning: a bill with a lot of zeros for using an LLM. Luckily you can prevent this pretty easily and it all starts with understanding how and for what you&rsquo;re being charged.</p>
<p>Whenever you talk to an LLM, you&rsquo;re essentially paying for how much you say and how much it says back. Responses are all measured in tokens, those small chunks of text we talked about in Part 1 of this series. For every x amount of tokens, you pay a particular price. For example, at the time of writing, <a href="https://docs.anthropic.com/en/docs/about-claude/pricing" target="_blank" rel="noopener">Claude Sonnet 4</a>
 has a price of 3 USD per million tokens for input, and a price of 15 USD per million tokens for output.</p>
<p>When talking about costs it&rsquo;s important to understand that you pay for both input and output. That means you&rsquo;re billed for what you send and for what the assistant replies with. So if you ask a really long question and get a really long answer, that&rsquo;s going to cost more than a question that requires a yes or no answer.</p>
<p>This is why your prompt and structure matters. If you keep repeating the same context or include an entire Quarto presentation each time you make a call, your token count will add up fast. Remember that the history is send to the LLM with every subsequent question! A deeply nested chat history can cause costs to go up quickly. This is also true for very long system prompts. Therefore, it&rsquo;s often worth trimming what you send, summarising where possible, and being intentional about what the model really needs to see. There&rsquo;s a fine balance between giving as much context as possible for the best possible response, and making sure you&rsquo;re not overdoing it.</p>
<p>Manually keeping track of your tokens would be annoying, so with <code>chatlas</code> or <code>ellmer</code>, you&rsquo;ve got a few handy functions to help estimate token use:</p>
<ul>
<li><code>get_tokens()</code> gives you a raw count of how many tokens you&rsquo;ve used so far.</li>
<li><code>get_cost()</code> gives you an estimate of how much your chat might cost. Note how it says &ldquo;estimate&rdquo;. Model providers can change their pricing overnight, and there might not even be a heads-up. This estimate therefore serves as a rough guide, not the truth.</li>
</ul>
<p>Now for the real thing you want to know: for our full workflow, we pay around 0.06 USD.</p>
<div class="panel-tabset" data-tabset-group="language">
<ul id="tabset-15" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-15-1">Python</a></li>
<li><a href="#tabset-15-2">R</a></li>
</ul>
<div id="tabset-15-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">get_tokens</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">.</span><span class="n">get_cost</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="p">[{</span><span class="s1">&#39;role&#39;</span><span class="p">:</span> <span class="s1">&#39;user&#39;</span><span class="p">,</span> <span class="s1">&#39;tokens&#39;</span><span class="p">:</span> <span class="mi">3611</span><span class="p">,</span> <span class="s1">&#39;tokens_cached&#39;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s1">&#39;tokens_total&#39;</span><span class="p">:</span> <span class="mi">3611</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span><span class="s1">&#39;role&#39;</span><span class="p">:</span> <span class="s1">&#39;assistant&#39;</span><span class="p">,</span> <span class="s1">&#39;tokens&#39;</span><span class="p">:</span> <span class="mi">133</span><span class="p">,</span> <span class="s1">&#39;tokens_cached&#39;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s1">&#39;tokens_total&#39;</span><span class="p">:</span> <span class="mi">133</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span><span class="s1">&#39;role&#39;</span><span class="p">:</span> <span class="s1">&#39;user&#39;</span><span class="p">,</span> <span class="s1">&#39;tokens&#39;</span><span class="p">:</span> <span class="mi">102</span><span class="p">,</span> <span class="s1">&#39;tokens_cached&#39;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s1">&#39;tokens_total&#39;</span><span class="p">:</span> <span class="mi">3846</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span><span class="s1">&#39;role&#39;</span><span class="p">:</span> <span class="s1">&#39;assistant&#39;</span><span class="p">,</span> <span class="s1">&#39;tokens&#39;</span><span class="p">:</span> <span class="mi">52</span><span class="p">,</span> <span class="s1">&#39;tokens_cached&#39;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s1">&#39;tokens_total&#39;</span><span class="p">:</span> <span class="mi">52</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span><span class="s1">&#39;role&#39;</span><span class="p">:</span> <span class="s1">&#39;user&#39;</span><span class="p">,</span> <span class="s1">&#39;tokens&#39;</span><span class="p">:</span> <span class="mi">2435</span><span class="p">,</span> <span class="s1">&#39;tokens_cached&#39;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s1">&#39;tokens_total&#39;</span><span class="p">:</span> <span class="mi">6333</span><span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span><span class="s1">&#39;role&#39;</span><span class="p">:</span> <span class="s1">&#39;assistant&#39;</span><span class="p">,</span> <span class="s1">&#39;tokens&#39;</span><span class="p">:</span> <span class="mi">947</span><span class="p">,</span> <span class="s1">&#39;tokens_cached&#39;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s1">&#39;tokens_total&#39;</span><span class="p">:</span> <span class="mi">947</span><span class="p">}]</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="mf">0.058350000000000006</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-15-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">get_tokens</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;1      user   3552         3552</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;2 assistant    148          148</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;3      user    101         3801</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;4 assistant     48           48</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;5      user   1839         5688</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;6 assistant    932          932</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span><span class="o">$</span><span class="nf">get_cost</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;[1] $0.06</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<blockquote>
<p><strong>Costs differ between runs!</strong></p>
<p>Token counts and cost vary because the answers of the LLM vary too (thanks to the creativity of the model).</p>
</blockquote>
<p>In our case, we might be working with pretty long Quarto presentations. The bigger the presentation, the more tokens our input will have. So far, we only played around with a demo presentation for a 10-minute talk, and basically put everything in one prompt. But we might have subsequent questions to the LLM, too. Overall, there are two strategies that can be used to reduce token count and cost:</p>
<ol>
<li>Batching: instead of sending multiple small calls, combine them into a single, well-structured one when possible. It reduces overhead and often results in fewer total tokens. Keep it DRY to: don&rsquo;t repeat yourself by providing information twice. For example, when using structured output it&rsquo;s not strictly necessary to put all the specifications for the output in the prompt itself too. And it shouldn&rsquo;t be a surprise, but batching is also possible in <code>ellmer</code> with <a href="https://ellmer.tidyverse.org/reference/batch_chat.html" target="_blank" rel="noopener"><code>batch_chat()</code></a>
 and (soon!) in <code>chatlas</code> with a similar function.</li>
<li>Summarising: if you&rsquo;re feeding in the same context repeatedly, have the model summarise it first. Then reuse that summary instead of the full text. It&rsquo;s a small change that can lead to big savings.</li>
</ol>
<p>Every token counts. And if you want to build something that scales, getting a grip on your usage early can save you a lot down the road.</p>
<h1 id="whats-next">What&rsquo;s next
</h1>
<p>The output that we are currently receiving is nice, but it&rsquo;s not something that would amaze our users. For us humans, it&rsquo;s annoying to read information from JSON as well. We need something different. To really get a wow-effect we need to design an appealing user interface. We need to make it easy for users to upload their content, give us additional information (aka our required variables), and analyse the results. Nobody likes a boring static app, so we need to add some shiny interactive features as well. And those shiny interactive features? That&rsquo;s a perfect job for Shiny!</p>
<p>In this part of &ldquo;The Shiny Side of LLMs&rdquo; we built a foundation for our Shiny app: using either <code>chatlas</code> (Python) or <code>ellmer</code> (R) we can now analyse any Quarto presentation. Our current workflow allows us to:</p>
<ul>
<li>Convert a Quarto file to plain markdown (and HTML), which can easily be consumed by an LLM</li>
<li>Call any LLM from any (or ok, most) providers through their API (our chosen model being Claude from Anthropic). Thanks <code>chatlas</code> and <code>ellmer</code>!</li>
<li>Send a carefully crafted prompt that guides the LLM to analyse the markdown content</li>
<li>Let the LLM use a tool to accurately calculate some metrics</li>
<li>Tweak model parameters if we really want to go the extra mile</li>
<li>Receive consistent, structured and useful JSON output that we can use in our Shiny app</li>
</ul>
<p>The only thing left to do? Wrap it in that Shiny interface. See you in the third and last part of &ldquo;The Shiny Side of LLMs&rdquo; series!</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-2/shiny-side-of-llms-header.png" length="401909" type="image/png" />
    </item>
    <item>
      <title>What LLMs Actually Do (and What They Don’t)</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-1/</link>
      <pubDate>Thu, 31 Jul 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-1/</guid>
      <dc:creator>Veerle Eeftink - Van Leemput</dc:creator><description><![CDATA[<h1 id="welcome-to-the-shiny-side-of-llms">Welcome to the Shiny Side of LLMs!
</h1>
<p>Large language models (LLMs) are everywhere, and we&rsquo;re way beyond denying their existence.
New use cases pop up every single day, from autocompleting code to customer support bots, summarisers, and tutors.
And everybody feels the urge to jump in.
Tools like ChatGPT, Claude, and GitHub Copilot have made us all comfortable using AI.
But when it comes to building something that is tailored, interactive, and actually useful for others, it all starts to feel&hellip; muddy.
Buzzwords start flying: embedding, encoding, RAG, vector databases, streaming tokens&hellip; and suddenly you&rsquo;re wondering if this is still for you.</p>
<p>Spoiler alert: it is, and it&rsquo;s all more accessible than it seems.
Yes, the underlying technology is complex, but that doesn&rsquo;t mean it&rsquo;s out of reach.
For the first time, you have easy access to something that mimics human reasoning.
As a programmer, that means you can describe a problem in plain language and get useful output: code, explanations, ideas, or even full analyses.
And you don&rsquo;t need to train your own models or master deep learning theory to use it.
You just need to understand what&rsquo;s happening under the hood well enough to start building.
In this blog series, we&rsquo;ll demystify all the buzzwords and walk you through:</p>
<ul>
<li>What LLMs really do, conceptually and technically, but explained super simply</li>
<li>How to interact with them while staying in your comfort zone (Python or R)</li>
<li>How to turn those interactions into real, usable tools using Shiny (again: Python or R)</li>
<li>The magic of mixing in your own data such as documents, notes, and anything else you use</li>
</ul>
<p>This isn&rsquo;t just about building a chatbot.
It&rsquo;s about understanding how things work and how you can use an LLM to build your own tools.
A document summariser, a smart search assistant, or a decision helper for your team: the choice is yours.
We&rsquo;ll go step by step.
From &ldquo;zero&rdquo; (I&rsquo;ve only ever typed things into ChatGPT) to &ldquo;hero&rdquo; (I&rsquo;ve built my own LLM-powered app and I understand how it works).</p>
<p>No need to know anything about model hosting, server clusters, or fancy acronyms.
Just you, some Python or R, and a fun idea!</p>
<blockquote>
<p><strong>What can you expect from this first part?</strong></p>
<p>This is Part 1 of &ldquo;The Shiny Side of LLMs&rdquo; series: <em>What LLMs Actually Do (and What They Don&rsquo;t).</em> It is intended to give you some insight into how LLMs work under the hood.</p>
<p>You don&rsquo;t <em>need</em> to know all this to start building things.
But having a basic understanding of the mechanics can help you reason more clearly about what LLMs are (and aren&rsquo;t) good at and avoid getting lost in the hype.</p>
</blockquote>
<h1 id="the-large-the-language-and-the-model">The &ldquo;Large&rdquo;, the &ldquo;Language&rdquo;, and the &ldquo;Model&rdquo;
</h1>
<blockquote>
<p><strong>TL;DR</strong></p>
<p>When we talk about a &ldquo;Large Language Model&rdquo;, we&rsquo;re really talking about a system trained on huge amounts of text, designed to predict and generate human-like language, powered by a model that recognises patterns at scale.</p>
</blockquote>
<p>First things first, what is a Large Language Model?
A good first step is breaking down the term &ldquo;Large Language Model&rdquo;.
Let&rsquo;s do it backwards, starting with the last word: &ldquo;Model&rdquo;.
In this context, a model is a statistical system that learns patterns from data and uses them to make predictions.
Yes, that&rsquo;s right: plain old statistics.</p>
<p>When we&rsquo;re talking about a &ldquo;Language Model&rdquo; we make predictions about a language, consisting of sentences and its words.
It all boils down to the likelihood of a next word, or sequence of words.
A language model is trained to understand the kinds of words that tend to appear together, how sentences flow, and how meaning is built from context.
Early models could only guess a single word at a time and struggled with anything longer or more complex, but modern ones can generate entire paragraphs or documents that stay on topic and sound surprisingly coherent.
Almost like a human.</p>
<p>Bigger is better.
The &ldquo;Large&rdquo; in &ldquo;Large Language Model&rdquo; refers to both the amount of data the model is trained on and the number of parameters it has (the internal settings the model adjusts as it learns).
The &ldquo;larger&rdquo; a model is, the more patterns it can recognise and the more fluent its outputs tend to be.</p>
<p>So what does that mean when you interact with one?
When you ask an LLM something, you prompt it to do something (that&rsquo;s why the input or text you type is also called a prompt).
That something is to predict the most likely sequence of words based on the sequence you have given.</p>
<blockquote>
<p><strong>LLMs and AI</strong></p>
<p>You didn&rsquo;t see much mention of Artificial Intelligence (AI) yet, and that&rsquo;s on purpose.
While LLMs are a specific kind of AI, they&rsquo;re just one part of a much broader field.
But since LLMs are the most visible (and frankly, the most hyped) examples of AI right now, people often use the terms interchangeably.
To keep things focussed, we&rsquo;ll only use the term LLM here.</p>
</blockquote>
<h1 id="how-llms-learn">How LLMs learn
</h1>
<p>We can talk about LLMs like they&rsquo;re the newest thing, but the foundation is still as &ldquo;ancient&rdquo; as statistics.
It&rsquo;s about understanding data: making inferences, finding patterns, estimating (un)certainty.
Machine Learning (ML) is statistics, and ML models learn patterns from data.
Think about algorithms like decision trees, support vector machines, and linear regression that all rely on the same idea: learning from examples.</p>
<p>LLMs learn through a technique called deep learning, which is a subfield of ML.
At the core of deep learning are neural networks: mathematical structures loosely inspired by biological neurons within the brain.
You can think of them as layers of tiny decision-makers, where each layer takes in numbers, transforms them, and passes them on to the next.</p>
<p>Each connection between layers has a weight, aka a number that determines how strongly one &ldquo;neuron&rdquo; influences another.
During training, the model adjusts these weights millions (or billions) of times to reduce the gap between its predictions and the actual data.
This process is called back propagation, and it&rsquo;s basically a giant optimisation loop: the model makes a guess, sees how wrong it was, tweaks the weights, and tries again.
Over time, it gets surprisingly good at spotting patterns in language.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-1/shiny-side-of-llms-back-propagation-1.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-1/shiny-side-of-llms-back-propagation-2.png" alt="A simplified explanation of back propagation in neural networks" />
<figcaption aria-hidden="true">A simplified explanation of back propagation in neural networks</figcaption>
</figure>
<p>The bigger the model, the more opportunities it has to learn how to make good guesses.
That&rsquo;s why the &ldquo;Large&rdquo; in &ldquo;Large Language Model&rdquo; is so important.
In the last few years, model size and model capability has exploded.
This is due to hardware improvements (something has to crunch all that data), better training techniques and something called transformers.
Ah, jargon.</p>
<p>A transformer is a kind of model architecture.
&ldquo;Model architecture&rdquo; sounds difficult, but in simple terms this is a blueprint for how a model processes and learns from data.
It was introduced by <a href="https://arxiv.org/pdf/1706.03762" target="_blank" rel="noopener">Vaswani et al. (2017)</a>
.
Transformers are part of the neural network family, and they&rsquo;re designed to handle huge amounts of data quickly and efficiently, which makes them perfect for crunching lots of text.</p>
<p>What made transformers such a game-changer is a clever idea called attention.
Instead of reading text word by word like older models, transformers look at the whole sentence (or paragraph) at once and figure out which words matter most to each other.
That&rsquo;s how large language models can generate surprisingly accurate, context-aware text.</p>
<p>They also made it possible to train on much bigger chunks of data and pick up more subtle patterns in language.
This is combined with a technique called self-supervised learning, where the model learns to predict missing words in sentences.
If you show it &ldquo;I lifted some ___&rdquo; it learns that &ldquo;weights&rdquo; is more likely than &ldquo;horses&rdquo;.
Because the data itself provides the training signals, we don&rsquo;t need humans to label anything.
That makes training scalable.
It doesn&rsquo;t mean it&rsquo;s totally label-free, though.
The &ldquo;labels&rdquo; (like missing words) just come naturally from the text.</p>
<p>The combo of smart architecture and massive training data is what unlocked the big leap in quality of today&rsquo;s LLMs and made models like ChatGPT possible.
Fun fact: the &ldquo;T&rdquo; in GPT stands for Transformer.
Now you know why that is!</p>
<p>From statistics to transformers, to models we can chat with and help us to get things done faster.
How&rsquo;s that for the &ldquo;ancient&rdquo; statistical techniques?!</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-1/shiny-side-of-llms-stats.png" alt="It all starts with statistics" />
<figcaption aria-hidden="true">It all starts with statistics</figcaption>
</figure>
<p>It all sounds too good to be true: a model that learns on its own, getting &ldquo;smarter&rdquo; with every chunk of input you provide.
And it indeed is, because with this self-supervised learning come challenges too.
As more online text is generated by LLMs, there&rsquo;s a risk the model ends up learning from itself.
It&rsquo;s like copying a copy: input gets blurry.
And in the end LLM are just models that predict something based on the data it has been given.
If that data is garbage, so is the outcome.
If we don&rsquo;t want to end up with boring, soulless, similar, and even incorrect content, we still have to write content ourselves.</p>
<p>Still, one thing is clear: what makes LLMs so powerful is the amount of data they learn from.
And that leads us straight into the magic of scale.</p>
<h1 id="the-magic-of-scale">The magic of scale
</h1>
<p>As LLMs are trained on more and more data, something cool happens: they begin to show emergent abilities (<a href="https://doi.org/10.48550/arXiv.2206.07682" target="_blank" rel="noopener">Wei et al., 2022</a>
).
These are skills the model wasn&rsquo;t directly trained for, yet still manages to do.
Nobody explicitly programmed an LLM to summarise text, translate between languages, or generate code.
But once the model is large enough and has seen enough examples, these abilities just&hellip; emerge.
Magic!</p>
<p>This is different from what we call generative abilities.
&ldquo;Generative&rdquo; simply means the model can generate new text.
All LLMs are generative and can produce new text.
You give them a prompt, and they predict what comes next.
That&rsquo;s their basic job and it&rsquo;s expected behaviour.
Emergent abilities were not in the original job description and the model just suddenly gets good at tasks nobody told it to do.
It&rsquo;s surprising behaviour.</p>
<p>For example, you can ask it to convert a sentence into pirate-speak, even if it was never explicitly trained to do that.
It uses general language patterns it has learned to give it a try, which is known as zero-shot learning.
And if the model struggles to understand what you want, you can help it by giving a few examples in your prompt: few-shot learning.
If you want a list reformatted in a certain way, you can provide some examples and let the LLM finish the rest.</p>
<p>All of this is possible not because the model is &ldquo;intelligent&rdquo; in a human sense, but because it has seen so much text and learned such general patterns that it can apply them in all kinds of situations.
This ability to generalise, or doing more with less instruction, is one of the most powerful outcomes of large-scale learning.</p>
<h1 id="what-llms-arent">What LLMs aren&rsquo;t
</h1>
<p>So you might think LLMs are pretty smart.
After all, they can write essays, answer questions, and even debug your Python and R code.
It&rsquo;s the shiny side of LLMs.
But it isn&rsquo;t all rainbows and unicorns: LLMs aren&rsquo;t smart and they aren&rsquo;t thinking.
LLMs don&rsquo;t reason like humans.
They don&rsquo;t understand what they&rsquo;re saying.
They don&rsquo;t &ldquo;know&rdquo; facts in the way we do.
What they do is predict the next word in a sentence, based on patterns they&rsquo;ve seen in massive amounts of text.</p>
<p>That means an LLM can be confidently wrong.
They might &ldquo;hallucinate&rdquo; an answer that sounds plausible but is completely made up.
The code it generates might look solid, but when you actually run it you notice that it contains packages that don&rsquo;t exist or variable names that were never defined.
They might reflect or even amplify biases in their training data.
They might sound accurate, but miss the mark entirely.
And crucially, you don&rsquo;t really control them.
You can&rsquo;t force an LLM to always give a safe or correct answer.
What you can do is guide it with better prompts, build tools around it that check its output, and design systems that use the model responsibly.</p>
<p>So while LLMs are great at generalising across tasks, that power doesn&rsquo;t come from intelligence.
It comes from scale.
And that is important to remember when building with LLMs.</p>
<h1 id="lets-talk-about-context">Let&rsquo;s talk about context
</h1>
<p>As mentioned earlier, LLMs can understand and generate surprisingly accurate, context-aware text.
And that context deserves some attention (literally: remember transformers!).
It&rsquo;s the context that makes sure an LLM doesn&rsquo;t blurt out random sentences (or, ok, only occasionally do).
But what does &ldquo;context&rdquo; actually mean for a Large Language Model?</p>
<p>It all starts with tokens, which are tiny chunks of text.
A token can be a word, a part of a word, or even just characters or punctuation.
Every time you type something into an LLM, your input is split into these tokens.
Depending on the model, an LLM can &ldquo;see&rdquo; a certain number of tokens at once.
This is called the context window.
Think of it like a sliding window over a long scroll of text.
The model can only see what fits inside that window.
If your input is too long and tokens fall outside of that window: too bad.
An LLM might trims the beginning or the end and hopes you didn&rsquo;t put anything important there.
The exact behaviour varies by provider and how the system around the model is designed.
Some providers implement strategies like summarising or condensing previous conversation history, while others simply drop older messages to make room for new ones.
And in some cases, the system might reject the request outright rather than attempt to process an incomplete prompt.</p>
<p>But wait&hellip; We previously learned LLMs were trained on all of language right?
Yes.
But at generation time, it doesn&rsquo;t remember everything it&rsquo;s ever seen.
It only has this input, right now, and the tokens inside the window.
That&rsquo;s its entire world.
There&rsquo;s no long-term memory (unless you&rsquo;ve built that in, we&rsquo;ll get to that later).
So if you want the model to give you a good answer, what you say really matters.
And that&rsquo;s not the only thing: it also matters how you say it.</p>
<p>Imagine these two prompts:</p>
<pre><code>1. “Create a Shiny for Python app that plots a histogram of random numbers”
2. “Plot a histogram of random numbers. Use Shiny for Python.”
</code></pre>
<p>Same words.
Different order.
Potentially different behaviour.</p>
<p>Language models are super sensitive to the flow of a sentence.
If you put instructions at the start, the model is more likely to follow them.
Bury them at the end, and it might treat it more like a side-note.
Clarity is an LLMs best friend.</p>
<p>So context isn&rsquo;t just about what&rsquo;s in the window.
It&rsquo;s also about where words are and how they&rsquo;re phrased.
Order and surrounding words matter.
In the model&rsquo;s brain, each token looks around (thanks to that attention trick you met earlier) and asks, &ldquo;who&rsquo;s standing next to me, and what story are we telling?&rdquo;
Those neighbouring words:</p>
<ol>
<li><strong>Disambiguate meaning:</strong> words on their own only say so much. &ldquo;Bass&rdquo; is a fish next to &ldquo;river&rdquo;, and a guitar next to &ldquo;band.&rdquo;</li>
<li><strong>Signal tone:</strong> a single word can flip the sentiment. For example, &ldquo;nice&rdquo; after &ldquo;not&rdquo;. On its own, the word &ldquo;nice&rdquo; would be positive.</li>
<li><strong>Set expectations:</strong> since a model is trained on sequences of tokens actually written by humans, some sequences are familiar. So after &ldquo;Once upon a&rdquo; the model is primed for &ldquo;time,&rdquo; not &ldquo;ship&rdquo;.</li>
</ol>
<p>Because the model predicts the next word by weighing everything it can see in the window, every surrounding word tweaks the odds.
Change one word, and the whole probability landscape shifts.
This becomes clear with these different prompts:</p>
<pre><code>1. “Write a short horror story about a clown”
2. “Write a funny short horror story about a clown”
</code></pre>
<p>Can you already sense why &ldquo;prompt engineering&rdquo; became so hot?</p>
<p>Now back to the memory thing.
Out of the box, the model doesn&rsquo;t &ldquo;remember&rdquo; what you said three chats ago.
It doesn&rsquo;t even remember what you said two messages ago, unless you send the whole chat history in the current prompt.
So how big a prompt can get, determines how much an LLM can remember.
This brings us back to the context window.
The maximum number of tokens they can handle at once matter.
It could be 2000 or 128000 tokens.
That all depends on the model.
If your conversation gets longer than the model can hold, the oldest messages get chopped off.
That&rsquo;s why in very long chats, you might notice an LLM starts &ldquo;forgetting&rdquo; early facts.
These facts are not in the context window anymore, so the model can&rsquo;t &ldquo;see&rdquo; them anymore either.</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-1/shiny-side-of-llms-chat.png" alt="Ok, this isn’t a “very long” chat, but you get the idea: whatever is outside the context window is “lost” information" />
<figcaption aria-hidden="true">Ok, this isn’t a “very long” chat, but you get the idea: whatever is outside the context window is “lost” information</figcaption>
</figure>
<p>It is possible for models to have access to persistent memory across conversations, but in that case facts and knowledge have to be explicitly stored somewhere.
This memory is separate from the context window.
Whether and how that memory gets updated depends on the broader system designed by the LLM provider.
This system, the surrounding infrastructure and logic around the model, determines what&rsquo;s worth saving.
Only if something seems important enough the memory will be updated.</p>
<h1 id="from-tokens-to-embedding-to-encoding">From tokens to embedding to encoding
</h1>
<p>Your prompt is divided into tokens, those tiny chunks of text.
But computers don&rsquo;t &ldquo;understand&rdquo; words like we do.
Computers work with numbers.
That&rsquo;s where something fancy called &ldquo;embedding&rdquo; comes in: each token is turned into numbers that represents its meaning.
And these numbers are not a plain &ldquo;4982&rdquo; or &ldquo;2974&rdquo;, but rather a pattern of numbers.
You can compare it with an unique barcode.
To &ldquo;understand&rdquo; your prompt, the model looks at all these number patterns together and uses the earlier mentioned attention to figure out how they relate to each other and what the relationship between the words is.
This process is called encoding.
A well-known model that does this is BERT, which was introduced by Google in 2018 (<a href="https://doi.org/10.48550/arXiv.1810.04805" target="_blank" rel="noopener">Devlin et al., 2019</a>
).</p>
<p>Because of word embeddings and encoding, a prompt like &ldquo;Create a Shiny for Python app that plots a histogram&rdquo; is not just a set of random words.
The model knows that &ldquo;Shiny&rdquo; and &ldquo;Python&rdquo; relate to things like &ldquo;web app&rdquo; or &ldquo;dashboard&rdquo; because their embeddings are close together.
It gives the model directions on the tech stack to use.
It also sees that &ldquo;plot&rdquo; and &ldquo;histogram&rdquo; are both in the prompt, so it understands you want a chart.
And the word &ldquo;create&rdquo; signals that you&rsquo;re asking for code, not just an explanation.
All these relationships, links and signals are taken into account when generating the output token by token.</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-1/shiny-side-of-llms-code-gen.png" alt="From input to output: how an LLM generates code" />
<figcaption aria-hidden="true">From input to output: how an LLM generates code</figcaption>
</figure>
<p>So far, we only talked about generating language, like plain text or some code.
But what about images?
The latest models can take images as input and they spit out a polished (perhaps fake-looking) image when being asked too.
These multimodal models (like GPT-4o or Claude 3) combine both vision and language.
How?
By treating images like language!</p>
<blockquote>
<p><strong>Images and multimodal models</strong></p>
<p>When talking about images, we&rsquo;re taking a small side-step in our LLM journey.
Image tasks are typically handled by specialised models (like diffusion models for image generation) or by multimodal LLMs (like GPT-4o or Gemini) that can process both text and images.
So when we talk about &ldquo;LLMs&rdquo; generating or understanding images, we&rsquo;re really referring to LLMs with extra capabilities.</p>
</blockquote>
<p>Just like text is broken down into tokens, images are also converted into a form the model can understand.
Instead of words, an image is split into patches (tiny chunks of pixels).
You can think of this as &ldquo;tokenizing an image.&rdquo; And just like tokens, patches are turned into numbers that capture the meaning of those pixels, which brings us back to the fancy word: &ldquo;embedding&rdquo;.
These image embeddings are then processed by the model just like text embeddings.
That&rsquo;s how a model can look at an image and describe it or answer questions about it.</p>
<p>Generating new images is also not a problem: in that case embeddings are translated to a visual representation (pixels) and a full image is being built from it.
This is exactly what well-known generative vision models like DALL·E, Midjourney, or Stable Diffusion do: they&rsquo;re trained to turn a sequence of text tokens into a sequence of image tokens, gradually &ldquo;painting&rdquo; a picture that matches the prompt.
If we ask Stable Diffusion for &ldquo;A weightlifting Dalmatian holding a barbell with red plates&rdquo;, it has no problem doing so.
It&rsquo;s all about processing the embeddings together and figure out how every word relates to each other.
In this case, &ldquo;weightlifting&rdquo; modifies what the &ldquo;Dalmatian&rdquo; should look like, and &ldquo;holding&rdquo; links &ldquo;Dalmatian&rdquo; and &ldquo;barbell&rdquo;.</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-1/shiny-side-of-llms-image-gen.png" alt="Generating images works pretty similar to generating language for LLMs with those extra capabilities" />
<figcaption aria-hidden="true">Generating images works pretty similar to generating language for LLMs with those extra capabilities</figcaption>
</figure>
<p>Note that depending on the model you&rsquo;re using, you might need to provide more context in your prompt by saying something like &ldquo;Generate an image that shows&hellip;&rdquo;.
This is especially important for multimodal models, like GPT-4o or Gemini, which can generate text, images, audio, and more.
A prompt like &ldquo;A weightlifting Dalmatian holding a barbell with red plates&rdquo; might seem clear to you and me, but to a multimodal model, it could mean several things: perhaps you want an image, or a description, or a funny meme.
This is different for a model like Stable Diffusion, a text-to-image model, which is trained specifically for image generation.
Naturally it will assume you want an image when you give it a prompt.</p>
<h1 id="more-jargon-rag-and-vector-databases">More jargon: RAG and vector databases
</h1>
<p>LLMs can only generate such &ldquo;human-like&rdquo; language because they are good at embeddings, encodings, and understanding context.
Context is everything.
This context comes from the &ldquo;knowledge&rdquo; an LLM has, or from you.
On their own, LLMs can only use data that has been used during training to generate output.
LLMs only &ldquo;know&rdquo; facts that happened during their training process (and not after), and proprietary information is off limits.
That doesn&rsquo;t mean you can&rsquo;t let an LLM use new facts, or private company info.
You can add it to the context, taking the context window into account.
So what do you do when you want to add so much information that it doesn&rsquo;t fit that window?
Hello, RAG!</p>
<p>It&rsquo;s probably not the first time you encountered the term &ldquo;RAG&rdquo;.
RAG stands for Retrieval-Augmented Generation (<a href="https://doi.org/10.48550/arXiv.2005.11401" target="_blank" rel="noopener">Lewis et al., 2020</a>
).
It means that instead of relying only on what the language model was trained on, it first retrieves relevant information (like from a document or database) and then generates a response using both your prompt and that retrieved info.
This helps the model give more accurate and up-to-date answers.</p>
<p>&ldquo;Retrieving information&rdquo; sounds simple when you just have one document, but imagine you have a big pile of documents.
That&rsquo;s a bit more complicated.
To make retrieval easier, RAG systems usually break documents into smaller chunks, like paragraphs or even sentences.
Each chunk gets turned into a vector and stored in a vector database.
When you ask a question, the system searches for the chunks whose vectors are closest in meaning to your query and uses those to be added to the context.
It&rsquo;s all about making the most of the context window.</p>
<h1 id="so-many-models">So many models
</h1>
<p>We&rsquo;re not talking about one big giant LLM in this blog series.
It&rsquo;s plural: LLMs.
That&rsquo;s because there are a lot of different LLMs out there.
All these models are trained a little differently.
That&rsquo;s important, because how a model is trained (what and how many data it saw, what tasks it practiced, what architecture it uses) shapes what it&rsquo;s good at.
Some models are trained mostly on text (think books, articles, web pages), while others are trained more heavily on code (like GitHub repos).
That&rsquo;s why some models might be better suited as your coding-companion, and others may serve you well as your ghostwriter.</p>
<p>There are many ways to compare LLMs, but here are few things you might want to consider:</p>
<ul>
<li><strong>Context window</strong>: larger windows help with long documents or multi-turn conversations.</li>
<li><strong>Training data</strong>: what kind of data the model was exposed to (e.g. text, code, math problems, dialogue, or some mix).</li>
<li><strong>Instruction tuning</strong>: some models are trained to follow prompts more precisely, often using human feedback (this has a very lengthy name: Reinforcement Learning from Human Feedback, aka RLHF).</li>
<li><strong>Output quality</strong>: ah, there it is! Probably the the most important thing. How good are the model&rsquo;s answers?! This depends on everything above, plus things like formatting ability, reasoning steps, or hallucination rates.</li>
<li><strong>Cost and speed</strong>: we&rsquo;ll come back when actually talking to LLMs, but not all models are priced equally, and speed can vary a lot.</li>
</ul>
<p>Since output quality is probably one of your top priorities, you might wonder how you can quickly get a sense of LLM performance.
You can try out different models, sure.
But as a data-savvy person you&rsquo;re probably keen to turn towards a more objective method.
Luckily, there are tools like <a href="https://llm-stats.com/" target="_blank" rel="noopener">llm-stats.com</a>
, <a href="https://www.vellum.ai/open-llm-leaderboard" target="_blank" rel="noopener">Vellum&rsquo;s leaderboard</a>
, and <a href="https://lmarena.ai/leaderboard" target="_blank" rel="noopener">LM Arena</a>
 that help you with a comparison.
The latter is based on subjective human ratings of output quality across tasks.</p>
<p>Looking at these leaderboards you see all the popular models like GPT (OpenAI), Claude (Anthropic), Gemini (Google), Llama (Meta), DeepSeek, Mistral and Nemotron (NVIDIA).
And all of these models have different versions too!
It might spin your head.
Each model has strengths and weaknesses.
Some are better with long input, some are cheaper, some are open source&hellip; But honestly, don&rsquo;t stress too much about picking the &ldquo;perfect&rdquo; one.
Once you start integrating an LLM into your Shiny app, it&rsquo;s easy to change models later if you need to.
At this stage, it&rsquo;s more important to just get started.</p>
<p>When it comes to personal experience and &ldquo;word on the street&rdquo; from Python and R developers: with Claude you&rsquo;re generally good when it comes to writing code (e.g. as a copilot) and summarising text.
If you&rsquo;re looking for a model with a large context window, Gemini might be what you would be after.
And your friendly neighbourhood LLM could potentially be found in GPT models (like GPT-4o) which generally have a good balance between reasoning, code, and creative tasks.</p>
<h1 id="building-with-llms">Building with LLMs
</h1>
<p>LLMs can be applied in a wide range of natural language tasks.
They&rsquo;re perfect for building chatbots or virtual assistants.
They can generate content, which can be blog posts, emails, or code snippets.
They can handle machine translation across languages, summarise long reports into key points, and perform sentiment analysis to extract opinions or emotional tone from text.
They even can do data analysis for you.</p>
<p>It&rsquo;s worth recognising that the examples above can have multiple use cases: you can use LLMs to increase your own productivity, or you can move from using an LLM to building with one.
As a copilot, an LLM can help you write code, draft content, summarise documents, or generate ideas, which (hopefully) makes you faster and more efficient in your day-to-day work.
But when you start building tools for others, the focus shifts.
Now it&rsquo;s not just about what the model can do, but how you wrap it in a helpful interface.
You need to think about the user&rsquo;s problem, how they interact with your tool, and how to guide their input to get meaningful results.
That&rsquo;s a different kind of challenge.
And this is exactly where something like Shiny shines.
Whether you&rsquo;re using Python or R, Shiny makes it easy to build interactive, user-friendly applications that connect to an LLM behind the scenes.
You can quickly prototype ideas, test interfaces, and deliver real value.
It&rsquo;s the perfect bridge between your data science skills and creating practical tools powered by LLMs.
The only thing you need is a bit of creativity, some coding skills, and a foundational understanding of LLMs.
The latter we tackled already!</p>
<p>So what are we going to build in this blog series?
Imagine you need to present something to your colleagues in the development team, you&rsquo;re a little nervous and would like to have some feedback on the presentation first.
Since you&rsquo;re a data scientist who loves reproducible slides, you&rsquo;ve chosen to build a <a href="https://quarto.org" target="_blank" rel="noopener">Quarto presentation</a>
.
Wouldn&rsquo;t it be wonderful if you could upload your slides in an app and get instant feedback?
Like the perfect &ldquo;Presentation Rehearsal Buddy&rdquo; that analyses your slides and gives you tailored suggestions?
And wouldn&rsquo;t it be great if you can help other data scientists with the same problem?
You can even prevent your colleagues from giving too lengthly and unfocussed presentations.
Also, it&rsquo;s great for polishing your talks at conferences, like <a href="https://posit.co/conference/" target="_blank" rel="noopener">posit::conf(2025)</a>
.
Now that&rsquo;s a cool app!</p>
<p>And it&rsquo;s the kind of thing you can actually start building now you understand what LLMs can and can&rsquo;t do.
But wait&hellip; I can feel your creativity flowing, the app idea growing on you, and I understand you might be dreaming bigger.
Maybe a model trained on your own (sensitive) company data to really tailor presentations to your work?
Amazing!
You probably heard it&rsquo;s not the best idea to send sensitive data to just any public API, so your mind jumps to a local LLM: one that runs on your own machine(s), just for you.
A private model that knows everything about your org&hellip; That&rsquo;s tempting!
And while it sounds wonderful, it&rsquo;s probably not the best place to start.</p>
<p>Running your own LLM comes with lots of complexity: infrastructure, cost, fine-tuning, and security.
It&rsquo;s a bit like buying a race car before learning to drive, then trying to build the track and engine from scratch.
A better place to begin?
Use the best pre-trained models that are already available.</p>
<p>And remember the magic of scale: LLMs show their real power only when trained on massive amounts of data and compute.
That&rsquo;s where those emergent abilities come from.
Why reinvent the wheel when you can just take it for a spin?
There&rsquo;s a reason why the best performing models can&rsquo;t be run on a laptop!</p>
<h1 id="responsible-use-and-regulations">Responsible use and regulations
</h1>
<p>Ready, set, &hellip; disclaimers.
Damn.
This is not an ethics class.
We&rsquo;re here to code.
But we can&rsquo;t get around some disclaimers, and if you&rsquo;re using an LLM, you should take some things into account when it comes to responsible use:</p>
<ul>
<li>Perhaps this goes without saying, but you can&rsquo;t bluntly copy and paste data or (sensitive) information into any LLM. That means your own data, and somebody else&rsquo;s data. Generated or uploaded input (text, data, prompts, images, etc.) could (and will) be used for other purposes, such as the training of AI models. If you don&rsquo;t want things to be out in the open, you shouldn&rsquo;t input it unless you&rsquo;re really sure that data will not be re-used.</li>
<li>You can&rsquo;t trust an LLM completely either. You must always take a critical approach to using the output produced and be aware of the limitations, such as bias, hallucinations and inaccuracies.</li>
<li>You&rsquo;re accountable for the integrity of the content generated by or with the support of an LLM. There&rsquo;s also an important difference between <em>using</em> an LLM for your own tasks and <em>building</em> tools on top of LLMs that others will rely on. In the first case, you&rsquo;re the human in the loop, judging what to trust and what to ignore. But when others interact with your app, you&rsquo;re responsible for the entire experience: how the model is prompted, how outputs are presented, and whether users are warned of uncertainty. In any case, if you use an LLM to make decisions or build an app that others use to act upon, you can&rsquo;t blame the LLM for giving false directions.</li>
</ul>
<p>Many organisations know this too, and it makes them wary of using LLM-based tools.
Especially in regulated environments, there&rsquo;s a natural hesitation to adopt these tools without strong safeguards in place.
If you want your organisation to be open to the idea, you need to think about clear data handling policies, usage boundaries, a transparent explanation of how the model is prompted, what kind of outputs are produced, and where the limitations lie.
The more control and clarity you can offer, the more trust you&rsquo;ll build.</p>
<p>If your organisation already trusts a cloud vendor like AWS, Azure, or Google Cloud with your data, then using an LLM hosted by that same vendor often fits within existing security and compliance frameworks, which might make it easier to justify adoption.</p>
<p>It&rsquo;s also good to be aware of applicable regulations and guidelines.
Know what you can and can&rsquo;t use an LLM for when building your application.
For example, in Europe, there&rsquo;s the <a href="https://digital-strategy.ec.europa.eu/en/policies/regulatory-framework-ai" target="_blank" rel="noopener">AI Act</a>
 (which is the first-ever legal framework on AI).
Generally speaking, legislation is not that fast, but since AI capabilities are quickly expanding it&rsquo;s only a matter of time before rules and guidelines do catch up.
Stay informed!</p>
<p>And with those less fun but necessary warnings we wrap up this introduction to LLMs.
It&rsquo;s not a magic all-mighty black box anymore: you now know exactly what LLMs are and what they are not.</p>
<h1 id="coming-up-talking-to-llms">Coming up: talking to LLMs
</h1>
<p>Our app for polished Quarto presentations is just around the corner, but before we can build it, we need to learn how to actually talk to an LLM.
Programmatically that is.
After all, the magic only happens once you know how to send the right prompts, pass along the right content, and make sense of what comes back.
In our case, that means: extracting a presentation&rsquo;s content, feeding it to the model with smart prompts, and formatting the output into something useful.
Think about value boxes, improvement tips, or even edits to your Quarto file directly.
That&rsquo;s where we&rsquo;re headed.
But first, the next step in our app-building journey: talking to an LLM.</p>
<h1 id="references">References
</h1>
<p>Devlin, J., Chang, M. W., Lee, K., &amp; Toutanova, K.
(2019).
BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding.
<em>arXiv.</em> <a href="https://doi.org/10.48550/arXiv.1810.04805" target="_blank" rel="noopener">https://doi.org/10.48550/arXiv.1810.04805</a>
</p>
<p>Lewis, P., Perez, E., Piktus, A., Petroni, F., Karpukhin, V., Goyal, N., Küttler, H., Lewis, M., Yih, W., Rocktäschel, T., Riedel, S., &amp; Kiela, D.
(2020).
Retrieval‑augmented generation for knowledge‑intensive NLP tasks.
<em>arXiv</em>.
<a href="https://doi.org/10.48550/arXiv.2005.11401" target="_blank" rel="noopener">https://doi.org/10.48550/arXiv.2005.11401</a>
</p>
<p>Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, Ł., &amp; Polosukhin, I.
(2017).
Attention is all you need.
<em>arXiv</em>.
<a href="https://doi.org/10.48550/arXiv.1706.03762" target="_blank" rel="noopener">https://doi.org/10.48550/arXiv.1706.03762</a>
</p>
<p>Wei, J., Tay, Y., Bommasani, R., Raffel, C., Zoph, B., Borgeaud, S., Yogatama, D., Bosma, M., Zhou, D., Metzler, D., Chi, E. H., Hashimoto, T., Vinyals, O., Liang, P., Dean, J., &amp; Fedus, W.
(2022).
Emergent Abilities of Large Language Models (Version 2).
<em>arXiv.</em> <a href="https://doi.org/10.48550/arXiv.2206.07682" target="_blank" rel="noopener">https://doi.org/10.48550/arXiv.2206.07682</a>
</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/shiny-side-of-llms-part-1/shiny-side-of-llms-header.png" length="493133" type="image/png" />
    </item>
    <item>
      <title>Posit at SciPy 2025</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/shiny-at-scipy-2025/</link>
      <pubDate>Mon, 14 Jul 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/shiny-at-scipy-2025/</guid>
      <dc:creator>Shiny Team</dc:creator><description><![CDATA[<p><strong>The Shiny Team was at SciPy 2025!</strong>
If you missed us at <a href="https://www.scipy2025.scipy.org/" target="_blank" rel="noopener">SciPy 2025</a>
 here&rsquo;s what we were up to.</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/shiny-at-scipy-2025/scipy-posit-shiny-team.jpg" alt="posit shiny team at our scipy booth" />
<figcaption aria-hidden="true">posit shiny team at our scipy booth</figcaption>
</figure>
<p>We just got back from a week at <a href="https://www.scipy2025.scipy.org/" target="_blank" rel="noopener">SciPy 2025</a>
 and had a great time meeting and talking with folks at the conference.
If you didn&rsquo;t get a chance to say hi to us,
you can also find us online on <a href="https://github.com/posit-dev/py-shiny" target="_blank" rel="noopener"><i></i> GitHub</a>
 or
<a href="https://discord.com/invite/yMGCamUMnS" target="_blank" rel="noopener"><i></i> Discord</a>
.
You can also learn more about Posit at <a href="https://posit.co" target="_blank" rel="noopener"><i></i> posit.co</a>
.</p>
<p>If you missed a tutorial or talk we have all the information below.
You&rsquo;ll have to wait a bit for the SciPy folks to get all the tutorials and talks uploaded to <a href="https://www.youtube.com/@SciPy-Conf/videos" target="_blank" rel="noopener"><i></i> Youtube</a>
.</p>
<h2 id="talks-by-posit-folks">Talks by Posit Folks
</h2>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/shiny-at-scipy-2025/scipy-og.jpg" length="81799" type="image/jpeg" />
    </item>
    <item>
      <title>webR 0.5.4</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2025/webr-0-5-4/</link>
      <pubDate>Fri, 11 Jul 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2025/webr-0-5-4/</guid>
      <dc:creator>George Stagg</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [x] Add intro sentence, e.g. the standard tagline for the package
* [x] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<p>We&rsquo;re indubitably delighted to announce the release of <a href="https://docs.r-wasm.org/webr/v0.5.4/" target="_blank" rel="noopener">webR</a>
 0.5.4. WebR brings R to the web browser using WebAssembly, powering <a href="https://r-wasm.github.io/quarto-live/" target="_blank" rel="noopener">Quarto Live</a>
, <a href="https://shinylive.io/r/examples/" target="_blank" rel="noopener">Shinylive</a>
, and other interactive websites with client-side R sessions.</p>
<p>This post highlights some key changes in recent webR releases. We&rsquo;ve updated to R 4.5.1, upgraded the Emscripten version, implemented shareable URLs in the webR application, and added filesystem support for JupyterLite. For a complete list of changes, see the <a href="https://github.com/r-wasm/webr/releases" target="_blank" rel="noopener">GitHub release notes</a>
.</p>
<h2 id="r-and-emscripten-upgrades">R and Emscripten upgrades
</h2>
<p>Our WebAssembly compiled version of R is now up to date with R 4.5.1, ensuring that we have access to all the latest R core improvements and bug fixes. Under the hood, we&rsquo;ve also upgraded Emscripten to version 4.0.8 and updated the version of LLVM we use to compile Fortran sources to 20.1.4.</p>
<p>Emscripten serves as the crucial layer between the web browser and R&rsquo;s source code, playing a role similar to an operating system. Upgrading Emscripten and LLVM ensures we&rsquo;re taking advantage of the latest WebAssembly features provided by both projects.</p>
<h2 id="jupyterlite-filesystem-support">JupyterLite filesystem support
</h2>
<p>The <a href="https://pypi.org/project/jupyterlite-webr/" target="_blank" rel="noopener">webR JupyterLite kernel</a>
 now targets the latest JupyterLite 0.6 series. An exciting improvement is the addition of a virtual filesystem driver designed specifically for use with JupyterLite. Files uploaded to JupyterLite&rsquo;s filesystem are now accessible to webR, and conversely, files created by webR can now be downloaded through the JupyterLite web interface.</p>
<img src="https://posit-open-source.netlify.app/blog/tidyverse/2025/webr-0-5-4/images/jupyterlite.png" alt="Screenshot of JupyterLite running the webR kernel. A data file is shown uploaded to the JupyterLite UI and loaded into the webR session."/>
<p>To try out these new features, visit <a href="https://jupyter.r-wasm.org" target="_blank" rel="noopener">https://jupyter.r-wasm.org</a>
, which has been upgraded to include both the latest version of JupyterLite and our webR kernel.</p>
<h2 id="sharing-urls-in-the-webr-application">Sharing URLs in the webR application
</h2>
<p>The webR application has been updated to support URL-based sharing. When you edit and save your files using the online editor, the application automatically updates the page URL to encode the current state of all open files.</p>
<p>Copy and share the URL with colleagues and they&rsquo;ll be able to see exactly what you&rsquo;re working on, providing an effortless way to share R code examples.</p>
<img src="https://posit-open-source.netlify.app/blog/tidyverse/2025/webr-0-5-4/images/share.png" alt="Screenshot of the webR application. A sharing URL is shown in a modal display."/>
<p>Try it out for yourself! <a href="https://webr.r-wasm.org/latest/#code=eJxNUDtOw0AQFa1PMVqatbCSADUFihIaaKCgjMabsb1i12vtTnBMywm4QxpukAtwL9bx8qlGmveZee%2Fj%2FdCipU%2Fao%2B0MzR4PHXJznDfO0ryncrML5Od%2F6BYZv86Oug2Mxsw6VC9YU5Cirjvj%2BErkmdGlRz%2FItMmz7ByWnpAJEIJCZvIwQuAqsF0Nr2EGPem64QJ6zQ0oZ5yHcgA1GN1uyWeTl7Ss0IcCMF7cww30UTHEGV2KpLqBChU7L6M2z3O4yABqcnbTOd2yDPqNIuf6HxCsc9xIS9y4bcSEsaKAMNLWt%2FdPq4lqsAwyTgDWbEZQLNHD8%2BnvU4T1jgysqkorTa0aRHFih135K1h7Z4EbgikHjGUG4sQcA4nkJy8XiwWYMuQJHFOKB20oQBfru4vluzZhP8HFMtUVRNxPb8drljZWt9qikfk37j%2Bfgw%3D%3D&a" target="_blank">This link</a> will open the webR application with a pre-loaded example R script.</p>
<h2 id="changes-for-web-developers">Changes for web developers
</h2>
<h3 id="returning-javascript-objects-to-r">Returning JavaScript objects to R
</h3>
<p>The <code>eval_js()</code> function has been enhanced and can now return a wider variety of R object types beyond scalar integers.</p>
<!-- This output is pre-rendered because webr::eval_js() runs only under Emscripten -->
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'># Previously, only integers could be returned</span></span>
<span><span class='nf'>webr</span><span class='nf'>::</span><span class='nf'>eval_js</span><span class='o'>(</span><span class='s'>"1729"</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] 1729</span></span>
<span><span class='c'></span></span>
<span><span class='c'># Now, structured outputs can be returned </span></span>
<span><span class='nv'>df</span> <span class='o'>&lt;-</span> <span class='nf'>webr</span><span class='nf'>::</span><span class='nf'>eval_js</span><span class='o'>(</span><span class='s'>"(&#123;foo: [1,2,3], bar: [4,5,6], baz: ['a', 'b', 'c']&#125;)"</span><span class='o'>)</span></span>
<span><span class='nf'><a href='https://rdrr.io/r/base/class.html'>class</a></span><span class='o'>(</span><span class='nv'>df</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] "data.frame"</span></span>
<span></span><span><span class='nv'>df</span></span>
<span><span class='c'>#&gt;   foo bar baz</span></span>
<span><span class='c'>#&gt; 1   1   4   a</span></span>
<span><span class='c'>#&gt; 2   2   5   b</span></span>
<span><span class='c'>#&gt; 3   3   6   c</span></span>
<span></span></code></pre>
</div>
<p>By default JavaScript objects are converted to R data frames, matching the existing behaviour of <a href="https://r-wasm.github.io/quarto-live/" target="_blank" rel="noopener">Quarto Live</a>
 when sharing data between OJS and R, but you can also use <a href="https://docs.r-wasm.org/webr/latest/api/js/modules/RWorker.html#classes" target="_blank" rel="noopener">R object constructors</a>
 for more control over the conversion process.</p>
<p>In addition, the <code>RList</code> constructor now recursively converts JavaScript objects into R lists, rather than just the outer object only. This is usually what we want when working with complex nested structures.</p>
<!-- This output is pre-rendered because webr::eval_js() runs only under Emscripten -->
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'># The inner object `baz` is now converted into a nested R list</span></span>
<span><span class='nf'>webr</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/pkg/webr/man/eval_js.html'>eval_js</a></span><span class='o'>(</span><span class='s'>"new RList(&#123;foo: 123, bar: ['a', 'z'], baz: &#123;x: 2, y: [7, 1, 8]&#125;&#125;)"</span><span class='o'>)</span></span>
<span></span><span><span class='c'>#&gt; $foo</span></span>
<span><span class='c'>#&gt; [1] 123</span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; $bar</span></span>
<span><span class='c'>#&gt; [1] "a" "z"</span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; $baz</span></span>
<span><span class='c'>#&gt; $baz$x</span></span>
<span><span class='c'>#&gt; [1] 2</span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; $baz$y</span></span>
<span><span class='c'>#&gt; [1] 7 1 8</span></span>
<span></span></code></pre>
</div>
<h3 id="filesystem-api">Filesystem API
</h3>
<p>Filesystem errors now provide more specific information. Instead of a simple generic <code>&quot;FS Error&quot;</code> message, you&rsquo;ll also see <code>&quot;ErrnoError: n&quot;</code> where <code>n</code> is the actual error number as returned by the <a href="https://emscripten.org/docs/api_reference/Filesystem-API.html" target="_blank" rel="noopener">Emscripten Filesystem API</a>
, making debugging easier.</p>
<p>We&rsquo;ve also exposed some additional Emscripten filesystem API functions on the <a href="https://docs.r-wasm.org/webr/latest/api/js/classes/WebR.WebR.html#fs" target="_blank" rel="noopener"><code>WebR.FS</code> JavaScript interface</a>
, so that they can be accessed from the webR main thread.</p>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>Thank you, as always, to the users and developers contributing to webR in the form of discussion in issues, bug reports, and pull requests.</p>
<p><a href="https://github.com/agmath" target="_blank" rel="noopener">@agmath</a>
, <a href="https://github.com/aixnr" target="_blank" rel="noopener">@aixnr</a>
, <a href="https://github.com/ajostel" target="_blank" rel="noopener">@ajostel</a>
, <a href="https://github.com/allefeld" target="_blank" rel="noopener">@allefeld</a>
, <a href="https://github.com/baogorek" target="_blank" rel="noopener">@baogorek</a>
, <a href="https://github.com/christianp" target="_blank" rel="noopener">@christianp</a>
, <a href="https://github.com/dipterix" target="_blank" rel="noopener">@dipterix</a>
, <a href="https://github.com/Dual-Ice" target="_blank" rel="noopener">@Dual-Ice</a>
, <a href="https://github.com/durraniu" target="_blank" rel="noopener">@durraniu</a>
, <a href="https://github.com/dusadrian" target="_blank" rel="noopener">@dusadrian</a>
, <a href="https://github.com/econstar" target="_blank" rel="noopener">@econstar</a>
, <a href="https://github.com/EduardBel" target="_blank" rel="noopener">@EduardBel</a>
, <a href="https://github.com/eitsupi" target="_blank" rel="noopener">@eitsupi</a>
, <a href="https://github.com/gergness" target="_blank" rel="noopener">@gergness</a>
, <a href="https://github.com/gregvolny" target="_blank" rel="noopener">@gregvolny</a>
, <a href="https://github.com/H4x0rcr4x" target="_blank" rel="noopener">@H4x0rcr4x</a>
, <a href="https://github.com/Hasnep" target="_blank" rel="noopener">@Hasnep</a>
, <a href="https://github.com/holtzy" target="_blank" rel="noopener">@holtzy</a>
, <a href="https://github.com/JeremyPasco" target="_blank" rel="noopener">@JeremyPasco</a>
, <a href="https://github.com/jeroen" target="_blank" rel="noopener">@jeroen</a>
, <a href="https://github.com/jgf5013" target="_blank" rel="noopener">@jgf5013</a>
, <a href="https://github.com/jmbo1190" target="_blank" rel="noopener">@jmbo1190</a>
, <a href="https://github.com/Josselyn142" target="_blank" rel="noopener">@Josselyn142</a>
, <a href="https://github.com/jrosell" target="_blank" rel="noopener">@jrosell</a>
, <a href="https://github.com/kwgish" target="_blank" rel="noopener">@kwgish</a>
, <a href="https://github.com/lauralambert99" target="_blank" rel="noopener">@lauralambert99</a>
, <a href="https://github.com/madhur-tandon" target="_blank" rel="noopener">@madhur-tandon</a>
, <a href="https://github.com/manojkumaryejjala" target="_blank" rel="noopener">@manojkumaryejjala</a>
, <a href="https://github.com/manuelgirbal" target="_blank" rel="noopener">@manuelgirbal</a>
, <a href="https://github.com/perhurt" target="_blank" rel="noopener">@perhurt</a>
, <a href="https://github.com/psychemedia" target="_blank" rel="noopener">@psychemedia</a>
, <a href="https://github.com/Quantilogy" target="_blank" rel="noopener">@Quantilogy</a>
, <a href="https://github.com/richarddmorey" target="_blank" rel="noopener">@richarddmorey</a>
, <a href="https://github.com/schloerke" target="_blank" rel="noopener">@schloerke</a>
, <a href="https://github.com/sn248" target="_blank" rel="noopener">@sn248</a>
, <a href="https://github.com/vsk-1167" target="_blank" rel="noopener">@vsk-1167</a>
, <a href="https://github.com/wch" target="_blank" rel="noopener">@wch</a>
, <a href="https://github.com/yhm-amber" target="_blank" rel="noopener">@yhm-amber</a>
, and <a href="https://github.com/zpinocchio" target="_blank" rel="noopener">@zpinocchio</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2025/webr-0-5-4/thumbnail-wd.jpg" length="90333" type="image/jpeg" />
    </item>
    <item>
      <title>Shiny 1.4 brings bookmarking and Generative AI docs</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.4/</link>
      <pubDate>Tue, 15 Apr 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.4/</guid>
      <dc:creator>Shiny Team</dc:creator><description><![CDATA[<style>
  .panel-tabset .tab-content, .nav {
    border: none;
  }
  .panel-tabset.nav-centered .nav {
    justify-content: center;
  }
</style>
<p>We&rsquo;re pleased to announce that Shiny <code>v1.4</code> is now <a href="https://pypi.org/project/shiny/" target="_blank" rel="noopener">available on PyPI</a>
! 🎉</p>
<p>Install it now with <code>pip install -U shiny</code>.</p>
<p>To celebrate, let&rsquo;s highlight two big additions from this release: <a href="#bookmarking">bookmarking</a>
 and <a href="#genai-docs">new Generative AI docs</a>
. A full list of changes is available in the <a href="https://github.com/posit-dev/py-shiny/blob/main/CHANGELOG.md#140---2025-04-08" target="_blank" rel="noopener">CHANGELOG</a>
.</p>
<h2 id="bookmarking-">Bookmarking 🔖
</h2>
<p>Shiny now supports bookmarking!
This means you can save the current state of your app and return to it later, or share it with others as a URL.
This is a great way to improve user experience, especially when it&rsquo;s difficult to return to a particular state &ndash; which is often true for <a href="#genai-docs">Generative AI apps</a>
.</p>
<blockquote>
<p><strong>Bookmarking <code>Chat</code></strong></p>
<p>Bookmarking is crucial for <a href="https://shiny.posit.co/py/docs/genai-chatbots.html" target="_blank" rel="noopener">AI chatbots</a>
, where returning to a previous conversation is often desired.
<a href="https://shiny.posit.co/py/docs/genai-chatbots.html#bookmark-messages" target="_blank" rel="noopener">See here</a>
 to learn how to add bookmarking to your <code>Chat</code> app.</p>
</blockquote>
<p>Adding bookmark support to an app generally requires some additional effort.
At the very least, Shiny needs to know where and when to save state, and in some more advanced cases, how to save/restore it as well:<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>To help you get started, here&rsquo;s a basic example of how to add bookmarking to an app.
Notice how it:</p>
<ol>
<li>Stores state in the URL (i.e., <code>bookmark_store=&quot;url&quot;</code>).
<ul>
<li>Server-side storage is also available.</li>
</ul>
</li>
<li>Provides a <code>ui.input_bookmark_button()</code> to allow the user to trigger a bookmark.
<ul>
<li>Bookmarks can also be triggered programmatically via <code>session.bookmark()</code>.</li>
</ul>
</li>
<li>When a bookmark happens, the URL is updated with the current state of the app (via <code>session.bookmark.update_query_string()</code>).
<ul>
<li>Updating the URL is optional, but almost always desirable (alternatively, you could open a modal with the bookmark URL).</li>
</ul>
</li>
</ol>
<div class="panel-tabset">
<ul id="tabset-1" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-1-1">Express</a></li>
<li><a href="#tabset-1-2">Core</a></li>
</ul>
<div id="tabset-1-1">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="n">app_opts</span><span class="p">,</span> <span class="nb">input</span><span class="p">,</span> <span class="n">render</span><span class="p">,</span> <span class="n">session</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app_opts</span><span class="p">(</span><span class="n">bookmark_store</span><span class="o">=</span><span class="s2">&#34;url&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">sidebar</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">input_slider</span><span class="p">(</span><span class="s2">&#34;slider&#34;</span><span class="p">,</span> <span class="s2">&#34;Choose a number&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="nb">min</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">input_bookmark_button</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">slider_value</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Slider value: </span><span class="si">{</span><span class="nb">input</span><span class="o">.</span><span class="n">slider</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@session.bookmark.on_bookmarked</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">_</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">session</span><span class="o">.</span><span class="n">bookmark</span><span class="o">.</span><span class="n">update_query_string</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-1-2">
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">starlette.requests</span> <span class="kn">import</span> <span class="n">Request</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">App</span><span class="p">,</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">Session</span><span class="p">,</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">app_ui</span><span class="p">(</span><span class="n">request</span><span class="p">:</span> <span class="n">Request</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_slider</span><span class="p">(</span><span class="s2">&#34;slider&#34;</span><span class="p">,</span> <span class="s2">&#34;Choose a number&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="nb">min</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="mi">100</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ui</span><span class="o">.</span><span class="n">input_bookmark_button</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">output_text</span><span class="p">(</span><span class="s2">&#34;slider_value&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="nb">input</span><span class="p">:</span> <span class="n">Inputs</span><span class="p">,</span> <span class="n">output</span><span class="p">:</span> <span class="n">Outputs</span><span class="p">,</span> <span class="n">session</span><span class="p">:</span> <span class="n">Session</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@render.text</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">slider_value</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Slider value: </span><span class="si">{</span><span class="nb">input</span><span class="o">.</span><span class="n">slider</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@session.bookmark.on_bookmarked</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">_</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="n">session</span><span class="o">.</span><span class="n">bookmark</span><span class="o">.</span><span class="n">update_query_string</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">App</span><span class="p">(</span><span class="n">app_ui</span><span class="p">,</span> <span class="n">server</span><span class="p">,</span> <span class="n">bookmark_store</span><span class="o">=</span><span class="s2">&#34;url&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>More generally, some of the key things to keep in mind when bookmarking:</p>
<ul>
<li><strong>Where to save</strong>: For basic apps, state can be likely encoded entirely within the URL, in which case setting <code>bookmark_store=&quot;url&quot;</code> is appropriate. However, if you have state that can&rsquo;t be JSON-encoded or is too large to fit in a URL, use <code>bookmark_store=&quot;server&quot;</code> instead.</li>
<li><strong>When to bookmark</strong>: Since a bookmark is potentially expensive to perform, Shiny won&rsquo;t automatically bookmark for you every time the state changes. Instead, either the user or you (the developer) will need to trigger a bookmark somehow. If bookmarking is a relatively cheap operation in your case, you might consider programmatically triggering a bookmark every time &ldquo;important&rdquo; state changes happen. This could be done by calling <code>session.bookmark()</code> in a <code>reactive.effect</code> that watches the relevant inputs.</li>
<li><strong>How to save/restore</strong>: For basic apps, Shiny can automatically figure out how to save and restore it. However, if you have server-side state that can&rsquo;t be fully determined by the UI&rsquo;s input values alone, you&rsquo;ll need to register <code>on_bookmark</code> and <code>on_restore</code> callbacks to save and restore that server-state. For brevity sake, we won&rsquo;t dig into this topic here, but you can find more information <a href="https://github.com/posit-dev/py-shiny/tree/main/shiny/bookmark" target="_blank" rel="noopener">over here</a>
.</li>
</ul>
<h3 id="coming-soon">Coming soon
</h3>
<p>Over the coming weeks, be on the lookout for:</p>
<ul>
<li>More bookmarking documentation
<ul>
<li>In the meantime, refer to <a href="https://github.com/posit-dev/py-shiny/tree/main/shiny/bookmark" target="_blank" rel="noopener">this README</a>
 and <a href="https://shiny.posit.co/py/api/core/bookmark.Bookmark.html" target="_blank" rel="noopener">the API reference</a>
.</li>
</ul>
</li>
<li>Bookmarking integration into various platforms such as <a href="https://shinylive.io/" target="_blank" rel="noopener">shinylive</a>
, <a href="https://posit.co/products/enterprise/connect/" target="_blank" rel="noopener">Posit Connect</a>
, and <a href="https://connect.posit.cloud/" target="_blank" rel="noopener">Posit Connect Cloud</a>
.</li>
</ul>
<h2 id="generative-ai-docs-">Generative AI docs 🤖
</h2>
<p>For a while now, Shiny has had both a <code>Chat()</code> and <code>MarkdownStream()</code> component for building chatbots and other Generative AI applications.
While this release includes improvements and fixes for these components, the real highlight is the new documentation that accompanies them.
This includes both new articles as well as <a href="#templates">templates</a>
.</p>
<h3 id="new-articles">New articles
</h3>
<p>Now, when you visit the &ldquo;Learn Shiny&rdquo; portion of the documentation, you&rsquo;ll be met with a new section dedicated to Generative AI in the sidebar.
Here&rsquo;s a screenshot of the new <a href="https://shiny.posit.co/py/docs/genai-chatbots.html" target="_blank" rel="noopener">getting started with chatbots</a>
 page:</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.4/genai-docs.png" class="rounded" alt="A screenshot of the new Generative AI docs" />
<figcaption aria-hidden="true">A screenshot of the <a href="https://shiny.posit.co/py/docs/genai-inspiration.html">new Generative AI docs</a></figcaption>
</figure>
<p>In addition to an article dedicated to chatbots, which there&rsquo;s also a dedicated <a href="https://shiny.posit.co/py/docs/genai-stream.html" target="_blank" rel="noopener">article on streaming</a>
, which is all about <code>MarkdownStream()</code>.
Think of this as your go-to component if you want to leverage generative AI, but don&rsquo;t need a full chat interface.
For example, maybe you want to create an experience like the <a href="https://shiny.posit.co/py/templates/workout-plan/" target="_blank" rel="noopener">workout plan generator template</a>
 where a set of input controls are used to user information and fill in a prompt.</p>
<h3 id="new-templates">New templates
</h3>
<p>We&rsquo;ve also added a new &ldquo;Generative AI&rdquo; section to the <a href="https://shiny.posit.co/py/templates/" target="_blank" rel="noopener">templates page</a>
.
These provide a great starting point for building your own Generative AI application.</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.4/genai-templates.png" class="rounded" alt="A screenshot of the new Generative AI templates" />
<figcaption aria-hidden="true">A screenshot of the <a href="https://shiny.posit.co/py/templates/">new Generative AI templates</a></figcaption>
</figure>
<h3 id="coming-to-r-soon">Coming to R soon
</h3>
<p>Be on the lookout for similar articles and templates making their way to <a href="https://posit-dev.github.io/shinychat/" target="_blank" rel="noopener"><code>shinychat</code>&rsquo;s website</a>
 soon (i.e., the R equivalent of <code>Chat</code>).</p>
<h2 id="in-closing">In closing
</h2>
<p>We&rsquo;re thrilled to bring you these new features and improvements in Shiny <code>v1.4</code>. As always, if you have any questions or feedback, please <a href="https://discord.gg/yMGCamUMnS" target="_blank" rel="noopener">join us on Discord</a>
 or <a href="https://github.com/posit-dev/py-shiny/issues/new" target="_blank" rel="noopener">open an issue on GitHub</a>
. Happy Shiny-ing!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>When server-side state can&rsquo;t be fully determined by the UI&rsquo;s input values alone, you&rsquo;ll need to register <code>on_bookmark</code> and <code>on_restore</code> callbacks to save and restore that server-state.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.4/shiny-bookmark.png" length="2530013" type="image/png" />
    </item>
    <item>
      <title>chromote v0.5.0</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/chromote-0.5.0/</link>
      <pubDate>Fri, 21 Mar 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/chromote-0.5.0/</guid>
      <dc:creator>Garrick Aden-Buie</dc:creator><description><![CDATA[<script  src="https://posit-open-source.netlify.app/blog/shiny/chromote-0.5.0/index_files/libs/quarto-diagram/mermaid.min.js"></script>
<script  src="https://posit-open-source.netlify.app/blog/shiny/chromote-0.5.0/index_files/libs/quarto-diagram/mermaid-init.js"></script>
<link  href="index_files/libs/quarto-diagram/mermaid.css" rel="stylesheet" />
<p>We are excited to announce the latest release of <a href="https://rstudio.github.io/chromote" target="_blank" rel="noopener">chromote</a>
 v0.5.0, which brings new functions that make it much easier to download and use any version of Chrome.</p>
<h2 id="getting-started">Getting Started
</h2>
<p><a href="https://rstudio.github.io/chromote" target="_blank" rel="noopener">chromote</a>
 lets you drive and access the Chrome web browser<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> programmatically from R using <a href="#what-is-headless-mode">Chrome&rsquo;s headless mode</a>
, making it useful for tasks ranging from screenshots and web scraping to full automated browser testing.
chromote is a powerful, but low-level, package that powers other easier-to-use packages:</p>
<ul>
<li>Take screenshots of web pages with <a href="https://rstudio.github.io/webshot2" target="_blank" rel="noopener">webshot2</a>
</li>
<li>Test Shiny apps with <a href="https://rstudio.github.io/shinytest2" target="_blank" rel="noopener">shinytest2</a>
</li>
<li>Scrape otherwise hard-to-access content from websites with <code>rvest::read_html_live()</code></li>
</ul>
<p>To get started, make sure you&rsquo;ve installed the latest version of <a href="https://rstudio.github.io/chromote" target="_blank" rel="noopener">chromote</a>
:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;chromote&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="what-is-headless-mode">What is headless mode?
</h2>
<p>Chrome&rsquo;s headless mode is a special browsing mode without a visible interface, which is ideal for automated testing and server environments.
In headless mode, developers can run and drive Chrome remotely<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, without the overhead of having to click, type, and interact with the browser manually.</p>
<p>You could think of headless mode as using Chrome on a computer without a monitor.
This analogy is now mostly correct, but it wasn&rsquo;t always this way.</p>
<p>Initially, headless mode wasn&rsquo;t just Chrome without a UI; it was a entirely separate browser designed for programmatic use, independent of the standard Chrome browser.
Although differences between headless and regular Chrome were small, they mattered for situations in which consistency with the user experience is crucial.</p>
<h2 id="a-year-of-big-changes">A year of big changes
</h2>
<pre class="mermaid mermaid-js" data-label="timeline">%% Custom CSS to style the chart
%%{init: {
    &#39;themeVariables&#39;: {
        &#39;gridLineColor&#39;: &#39;#e0e0e0&#39;,
        &#39;todayLineColor&#39;: &#39;#00000000&#39;
    }
}}%%

gantt
    title Chrome Headless Mode Timeline
    dateFormat YYYY-MM
    axisFormat %b %Y
    
    section Chrome
    v112 (New headless introduced)    :milestone, m1, 2023-04, 0d
    v128 (New headless default)       :milestone, m2, 2024-08, 0d
    v132 (Old headless removed)       :milestone, m3, 2025-01, 0d
    
    section Headless Modes
    Old Headless Default    :active, h1, 2023-03, 2024-08
    New Headless Optional   :done, h2, 2023-03, 2024-08
    Old Headless Optional   :done, h4, 2024-08, 2025-01
    New Headless Default    :active, h3, 2024-08, 2025-01
    New Headless Only      :active, h5, 2025-01, 2025-03
    
    section chromote
    v0.3.1        :milestone, c1, 2024-08, 0d
    v0.4.0        :milestone, c2, 2025-01, 0d

</pre>
<style>
#timeline .grid .tick {
  stroke: lightgrey;
  opacity: 0.3;
  shape-rendering: crispEdges;
}
#timeline .grid path {
  stroke-width: 0;
}
</style>
<p>When Chrome released version 112 in early 2023, they announced a <a href="https://developer.chrome.com/docs/chromium/headless" target="_blank" rel="noopener">new, unified headless mode</a>
 that uses the same browser engine as regular, headful Chrome, ensuring consistency in automated testing.
chromote automatically launches Chrome for you, activating headless mode by including the <code>--headless</code> flag in the process.
Old headless mode remained the default <code>--headless</code> mode, but you&rsquo;d have to opt into this mode by using <code>--headless=new</code> when launching Chrome, which wasn&rsquo;t possible at the time with chromote.</p>
<p>Then, with Chrome v128 (August 2024), the meaning of <code>--headless</code> changed from <code>old</code> to <code>new</code>, making <em>new headless mode</em> the default headless version.
To minimize disruption to chromote users, we quickly <a href="https://rstudio.github.io/chromote/news/index.html#chromote-031" target="_blank" rel="noopener">patched chromote and released v0.3.1</a>
, to continue to use the old headless mode with <code>--headless=old</code>, while offering adventurous users a way to try the new headless mode via an R option.</p>
<p>Finally, in <a href="https://developer.chrome.com/blog/removing-headless-old-from-chrome?hl=en" target="_blank" rel="noopener">Chrome v132 (January 2025)</a>
, old headless mode was completely removed from Chrome and made available as a new, separate binary, <a href="https://developer.chrome.com/blog/chrome-headless-shell" target="_blank" rel="noopener">chrome-headless-shell</a>
.
Again, we <a href="https://rstudio.github.io/chromote/news/index.html#chromote-040" target="_blank" rel="noopener">patched chromote and released v0.4.0</a>
 to use <code>--headless</code> instead of <code>--headless=old</code>, which will now cause an error when used with Chrome v132 and later.</p>
<h2 id="which-version-of-chrome-is-best-for-chromote-users">Which version of chrome is best for chromote users?
</h2>
<p>The Chrome developers helpfully shared this diagram comparing old and new headless modes.</p>
<img src="https://posit-open-source.netlify.app/blog/shiny/chromote-0.5.0/old-new-headless.svg" alt="A chart comparing Old Headless and New Headless Chrome features. The horizontal axis ranges from &quot;performant&quot; to &quot;authentic,&quot; while the vertical axis ranges from &quot;lightweight&quot; to &quot;fully featured.&quot; Old Headless is positioned in the lower left quadrant, labeled with &quot;screenshotting, PDF printing, and web scraping/crawling.&quot; In contrast, New Headless is in the upper right quadrant, labeled with &quot;reliable web app testing and extension testing.&quot;" />
<p>Practically speaking, most chromote users will be happiest using <code>chrome-headless-shell</code>, i.e. the specialized binary that continues the functionality of the old headless mode.
As highlighted by the Chrome team, <code>chrome-headless-shell</code> starts up faster and is well-suited to screenshots, printing to PDF and for web scraping&mdash;all common use cases for chromote.</p>
<h2 id="keeping-chromote-up-to-date">Keeping chromote up-to-date
</h2>
<p>One of chromote&rsquo;s greatest design strengths is that it works with practically <em>any</em> version of Chrome.
Rather than implement static bindings to specific versions of Chrome in a way that requires manual updates with each new version of Chrome, chromote asks Chrome for the schema of the Chrome DevTools Protocol &mdash; the commands supported by headless Chrome &mdash; and then it uses this schema to hydrate the package with the exact methods matching the version of Chrome you&rsquo;re using.</p>
<p>Most Chrome updates work seamlessly with chromote, but the major shift from old to new headless mode &mdash; a huge undertaking on a very large codebase &mdash; created some inevitable complications for users.</p>
<p>Modern browsers, like Chrome, operate with rolling updates on a regular schedule.
This is great for users and internet browsers, but not great for the reproducibility of automated scripts that use chromote.
It&rsquo;s a frustrating experience to have a script break from one day to the next because Chrome updated overnight.</p>
<p>Fortunately, chromote v0.5. includes new features that make it easier to use the exact version of Chrome that you want.</p>
<h2 id="managing-chrome-versions-with-chromote-v050">Managing Chrome versions with chromote v0.5.0
</h2>
<p>chromote v0.5.0 includes new features that let you download any version of Chrome or <code>chrome-headless-shell</code> from the <a href="https://googlechromelabs.github.io/chrome-for-testing" target="_blank" rel="noopener">Google Chrome for Testing service</a>
, drawing inspiration from similar features available in the JavaScript and Python browser-testing package <a href="https://playwright.dev/docs/browsers#managing-browser-binaries" target="_blank" rel="noopener">playwright</a>
.</p>
<img src="https://posit-open-source.netlify.app/blog/shiny/chromote-0.5.0/chromote-demo.svg" alt="An animation showing local_chrome_version() in use in the R console. The code in the animation is described in the post text." />
<p>To get started, call <a href="https://rstudio.github.io/chromote/reference/with_chrome_version.html" target="_blank" rel="noopener"><code>local_chrome_version()</code></a>
 with a specific <code>version</code> and <code>binary</code> choice at the start of your script, before you create a new <code>ChromoteSession</code> or use another package that relies on chromote.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">chromote</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">local_chrome_version</span><span class="p">(</span><span class="s">&#34;latest-stable&#34;</span><span class="p">,</span> <span class="n">binary</span> <span class="o">=</span> <span class="s">&#34;chrome&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ℹ Downloading `chrome` version 134.0.6998.88 for mac-arm64</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; trying URL &#39;https://storage.googleapis.com/chrome-for-testing-public/134.0.6998.88/mac-arm64/chrome-mac-arm64.zip&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Content type &#39;application/zip&#39; length 158060459 bytes (150.7 MB)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ==================================================</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; downloaded 150.7 MB</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ✔ Downloading `chrome` version 134.0.6998.88 for mac-arm64 [5.3s]</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; chromote will now use version 134.0.6998.88 of `chrome` for mac-arm64.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">b</span> <span class="o">&lt;-</span> <span class="n">ChromoteSession</span><span class="o">$</span><span class="nf">new</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>By default, <code>local_chrome_version()</code> uses the latest stable version of Chrome, matching the arguments shown in the code example above.</p>
<p>For scripts with a longer life span and to ensure reproducibility, you can choose a specific version of Chrome or <code>chrome-headless-shell</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">local_chrome_version</span><span class="p">(</span><span class="s">&#34;134.0.6998.88&#34;</span><span class="p">,</span> <span class="n">binary</span> <span class="o">=</span> <span class="s">&#34;chrome-headless-shell&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; chromote will now use version 134.0.6998.88 of `chrome-headless-shell` for mac-arm64.</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If you don&rsquo;t already have a copy of the requested version of the binary, <code>local_chrome_version()</code> will download it for you so you&rsquo;ll only need to download the binary once.
You can list all of the versions and binaries you&rsquo;ve installed with <code>chrome_versions_list()</code>, or all available versions and binaries with <code>chrome_versions_list(&quot;all&quot;)</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">chrome_versions_list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; # A tibble: 2 × 6</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   version       revision binary                platform  url                        path </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &lt;chr&gt;         &lt;chr&gt;    &lt;chr&gt;                 &lt;chr&gt;     &lt;chr&gt;                      &lt;chr&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1 134.0.6998.88 1415337  chrome                mac-arm64 https://storage.googleapi… /Use…</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 2 134.0.6998.88 1415337  chrome-headless-shell mac-arm64 https://storage.googleapi… /Use…</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>local_chrome_version()</code> sets the version of Chrome for the current session or within the context of a function.
For small tasks where you want to use a specific version of Chrome for a few lines of code, chromote provides a <code>with_chrome_version()</code> variant:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">with_chrome_version</span><span class="p">(</span><span class="s">&#34;132&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Take a screenshot with Chrome v132</span>
</span></span><span class="line"><span class="cl">  <span class="n">webshot2</span><span class="o">::</span><span class="nf">webshot</span><span class="p">(</span><span class="s">&#34;https://r-project.org&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Finally, you can manage Chrome binaries directly with three additional helper functions:</p>
<ol>
<li>
<p><code>chrome_versions_add()</code> can be used to add a new Chrome version to the cache, without explicitly configuring chromote to use that version.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">chrome_versions_add</span><span class="p">(</span><span class="m">135</span><span class="p">,</span> <span class="s">&#34;chrome-headless-shell&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ℹ Downloading `chrome-headless-shell` version 135.0.7049.17 for mac-arm64</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; trying URL &#39;https://storage.googleapis.com/chrome-for-testing-public/135.0.7049.17/mac-arm64/chrome-headless-shell-mac-arm64.zip&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Content type &#39;application/zip&#39; length 90601586 bytes (86.4 MB)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ==================================================</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; downloaded 86.4 MB</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ✔ Downloading `chrome-headless-shell` version 135.0.7049.17 for mac-arm64 [3.2s]</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;/Users/garrick/Library/Caches/org.R-project.R/R/chromote/chrome/135.0.7049.17/chrome-headless-shell-mac-arm64/chrome-headless-shell&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p><code>chrome_versions_path()</code> returns the path to the Chrome binary for a given version and binary type.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">chrome_versions_path</span><span class="p">(</span><span class="m">135</span><span class="p">,</span> <span class="s">&#34;chrome-headless-shell&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;/Users/garrick/Library/Caches/org.R-project.R/R/chromote/chrome/135.0.7049.17/chrome-headless-shell-mac-arm64/chrome-headless-shell&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p><code>chrome_versions_remove()</code> can be used to delete copies of Chrome from the local cache.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">chrome_versions_remove</span><span class="p">(</span><span class="m">135</span><span class="p">,</span> <span class="s">&#34;chrome-headless-shell&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Will remove 1 cached version of chrome:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; /Users/garrick/Library/Caches/org.R-project.R/R/chromote/chrome/135.0.7049.17/chrome-headless-shell-mac-arm64</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Delete from cache?</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 1: Yes</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 2: No</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 3: Cancel</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Selection: 1</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
</ol>
<h2 id="other-new-features-in-chromote-v050">Other new features in chromote v0.5.0
</h2>
<img src="https://posit-open-source.netlify.app/blog/shiny/chromote-0.5.0/chromote.png" class="float-sm-end" style="max-width: min(33%, 180px)" alt="The chromote logo, featuring a minimalist design inspired by the Chrome browser logo. The text &#39;chromote&#39; appears in white lowercase letters above a white circular ring reminiscent of Chrome&#39;s central element. The entire design is set within a hexagonal shape composed of geometric sections in varying shades of blue, from deep navy to lighter blue." />
<p>chromote now has a hex sticker!
Sorry for burying the lede, we know this is probably the most exciting update in this release.
Thank you to <a href="https://github.com/davidrsch" target="_blank" rel="noopener">David Díaz Rodríguez</a>
 for proposing a design that inspired the final hex sticker.</p>
<p>One more interesting feature from this release is a new <code>$set_viewport_size()</code> method that makes it easier to adjust the virtual window size of a chromote tab:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">b</span> <span class="o">&lt;-</span> <span class="n">ChromoteSession</span><span class="o">$</span><span class="nf">new</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Common laptop resolution</span>
</span></span><span class="line"><span class="cl"><span class="n">b</span><span class="o">$</span><span class="nf">set_viewport_size</span><span class="p">(</span><span class="n">width</span> <span class="o">=</span> <span class="m">1366</span><span class="p">,</span> <span class="n">height</span> <span class="o">=</span> <span class="m">768</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># iPhone 13, with mobile device emulation</span>
</span></span><span class="line"><span class="cl"><span class="n">b</span><span class="o">$</span><span class="nf">set_viewport_size</span><span class="p">(</span><span class="n">width</span> <span class="o">=</span> <span class="m">390</span><span class="p">,</span> <span class="n">height</span> <span class="o">=</span> <span class="m">844</span><span class="p">,</span> <span class="n">mobile</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You can learn more in the <a href="https://rstudio.github.io/chromote/reference/ChromoteSession.html#method-set-viewport-size-" target="_blank" rel="noopener"><code>$set_viewport_size()</code> method documentation</a>
.</p>
<h2 id="thank-you-">Thank you 💙
</h2>
<p>This post covered the biggest changes to chromote in this release, but there&rsquo;s even more
in the <a href="https://rstudio.github.io/chromote/news/index.html#chromote-050" target="_blank" rel="noopener">chromote v0.5.0 release notes</a>
!</p>
<p><strong>A huge thank you</strong> to everyone who contributed pull requests, bug reports and feature requests.</p>
<p><a href="https://github.com/aaelony-fb" target="_blank" rel="noopener">@aaelony-fb</a>
, <a href="https://github.com/amurtha80" target="_blank" rel="noopener">@amurtha80</a>
, <a href="https://github.com/ashbythorpe" target="_blank" rel="noopener">@ashbythorpe</a>
, <a href="https://github.com/bradvf" target="_blank" rel="noopener">@bradvf</a>
, <a href="https://github.com/braverock" target="_blank" rel="noopener">@braverock</a>
, <a href="https://github.com/cellocgw" target="_blank" rel="noopener">@cellocgw</a>
, <a href="https://github.com/chlebowa" target="_blank" rel="noopener">@chlebowa</a>
, <a href="https://github.com/daryabusen" target="_blank" rel="noopener">@daryabusen</a>
, <a href="https://github.com/davidrsch" target="_blank" rel="noopener">@davidrsch</a>
, <a href="https://github.com/DivadNojnarg" target="_blank" rel="noopener">@DivadNojnarg</a>
, <a href="https://github.com/fh-mthomson" target="_blank" rel="noopener">@fh-mthomson</a>
, <a href="https://github.com/gadenbuie" target="_blank" rel="noopener">@gadenbuie</a>
, <a href="https://github.com/ISumaneev" target="_blank" rel="noopener">@ISumaneev</a>
, <a href="https://github.com/maelle" target="_blank" rel="noopener">@maelle</a>
, <a href="https://github.com/Manestricker" target="_blank" rel="noopener">@Manestricker</a>
, <a href="https://github.com/marvin3FF" target="_blank" rel="noopener">@marvin3FF</a>
, <a href="https://github.com/nclsbarreto" target="_blank" rel="noopener">@nclsbarreto</a>
, <a href="https://github.com/oozbeker-onemagnify" target="_blank" rel="noopener">@oozbeker-onemagnify</a>
, <a href="https://github.com/PaulC91" target="_blank" rel="noopener">@PaulC91</a>
, <a href="https://github.com/r2evans" target="_blank" rel="noopener">@r2evans</a>
, <a href="https://github.com/rcepka" target="_blank" rel="noopener">@rcepka</a>
, <a href="https://github.com/sale4cast" target="_blank" rel="noopener">@sale4cast</a>
, <a href="https://github.com/saleforecast1" target="_blank" rel="noopener">@saleforecast1</a>
, <a href="https://github.com/schloerke" target="_blank" rel="noopener">@schloerke</a>
, <a href="https://github.com/simon-smart88" target="_blank" rel="noopener">@simon-smart88</a>
, <a href="https://github.com/skyeturriff" target="_blank" rel="noopener">@skyeturriff</a>
, <a href="https://github.com/stadbern" target="_blank" rel="noopener">@stadbern</a>
, <a href="https://github.com/sybrohee" target="_blank" rel="noopener">@sybrohee</a>
, and <a href="https://github.com/zeloff" target="_blank" rel="noopener">@zeloff</a>
.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Or any browser based on <a href="https://www.google.com/chrome/index.html" target="_blank" rel="noopener">Chrome</a>
 or <a href="https://www.chromium.org/chromium-projects/" target="_blank" rel="noopener">chromium</a>
, of which <a href="https://en.wikipedia.org/wiki/Chromium_%28web_browser%29#Browsers_based_on_Chromium" target="_blank" rel="noopener">there are many</a>
.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Run chrome remotely: <em>chromote</em>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/chromote-0.5.0/feature.png" length="233947" type="image/png" />
    </item>
    <item>
      <title>Creating Responsive Layouts in Shiny with `layout_columns()`</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/responsive-shiny-layouts/</link>
      <pubDate>Sat, 08 Feb 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/responsive-shiny-layouts/</guid>
      <dc:creator>Shiny Team</dc:creator><description><![CDATA[<h2 id="shiny-layouts-for-different-screen-sizes">Shiny Layouts for Different Screen Sizes
</h2>
<p>Creating web applications that look good on any device is crucial. Shiny offers tools to make your dashboards responsive. In this post, we&rsquo;ll explore how to use <code>layout_columns()</code> to tailor your app&rsquo;s layout for desktops, tablets, and smartphones.</p>
<h3 id="the-problem-default-layout-behavior">The Problem: Default Layout Behavior
</h3>
<p>When first starting with Shiny, developers might create UI elements without explicitly defining columns. Shiny&rsquo;s default behavior is responsive in that it will stretch elements to fill the available space. However, this can lead to layouts that look great on a large monitor but become squashed or unwieldy on smaller screens.</p>
<h3 id="the-solution-uilayout_columns">The Solution: ui.layout_columns()
</h3>
<p><code>ui.layout_columns()</code> is a powerful function that lets you define column-based layouts that adjust based on screen width. It leverages the Bootstrap grid system, dividing the screen into 12 virtual columns. You can then specify how many of these columns each element should occupy at different breakpoints.</p>
<h3 id="key-concepts">Key Concepts
</h3>
<ul>
<li><strong>Breakpoints:</strong> These define the screen widths at which the layout changes. Shiny provides common breakpoints:
<ul>
<li><code>lg</code>: Large (desktop, typically 992px and wider)</li>
<li><code>md</code>: Medium (tablets, typically 768px and wider)</li>
<li><code>sm</code>: Small (smartphones)</li>
<li><code>xs</code>: Extra Small (smaller smartphones)</li>
</ul>
</li>
<li><strong><code>col_widths</code>:</strong> This argument within <code>ui.layout_columns()</code> is where the magic happens. You provide a list or dictionary that specifies the number of columns for each UI element at each breakpoint.</li>
</ul>
<h3 id="example-two-column-layout-on-desktop-single-column-on-mobile">Example: Two-Column Layout on Desktop, Single-Column on Mobile
</h3>
<p>Let&rsquo;s say we want two cards to appear side-by-side on a desktop, but stacked vertically on a mobile device. Here&rsquo;s how we can achieve that using Shiny Express mode (the same principle applies to core Shiny):</p>















  

  
  
  
    
    
  

  
  










  
  
    <div class="w-full aspect-video">
      <iframe
        src="https://www.youtube.com/embed/KkUpgeUIVvM"
        class="w-full h-full"
        
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowfullscreen></iframe>
    </div>
  




<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">layout_columns</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Responsive widths</span>
</span></span><span class="line"><span class="cl">    <span class="n">col_widths</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;lg&#34;</span><span class="p">:</span> <span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">12</span><span class="p">),</span> <span class="c1"># Large screens</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;md&#34;</span><span class="p">:</span> <span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">12</span><span class="p">),</span> <span class="c1"># Medium screens</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;sm&#34;</span><span class="p">:</span> <span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">12</span><span class="p">),</span> <span class="c1"># Small screens</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;xs&#34;</span><span class="p">:</span> <span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">12</span><span class="p">),</span> <span class="c1"># Extra small screens</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">card1</span>
</span></span><span class="line"><span class="cl">    <span class="n">card2</span>
</span></span><span class="line"><span class="cl">    <span class="n">card3</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>Explanation:</strong></p>
<ul>
<li>
<p><code>ui.layout_columns()</code>: We use this function to wrap our three cards.</p>
</li>
<li>
<p><code>col_widths</code>:</p>
<ul>
<li><code>&quot;lg&quot;: (6, 6, 12)</code>: On large screens, the first two cards each take up 6 out of 12 columns (half the width), and the third card takes up all 12 columns (full width). This positions the first two cards side-by-side and the third card below them.</li>
<li><code>&quot;md&quot;: (12, 12, 12)</code>, <code>&quot;sm&quot;: (12, 12, 12)</code>, <code>&quot;xs&quot;: (12, 12, 12)</code>: On medium, small, and extra-small screens, <em>all</em> cards take up the full 12 columns. This stacks them vertically.</li>
</ul>
</li>
</ul>
<h3 id="going-further">Going Further
</h3>
<p>The <code>ui.layout_columns()</code> function is quite flexible. You can:</p>
<ul>
<li>Use a list instead of a dictionary for <code>col_widths</code> if you want the same layout for all breakpoints.</li>
<li>Use a dictionary of column widths at different breakpoints. The keys should be one of &ldquo;xs&rdquo;, &ldquo;sm&rdquo;, &ldquo;md&rdquo;, &ldquo;lg&rdquo;, or &ldquo;xl&rdquo;, or &ldquo;xxl&rdquo;, and the values are either of the above. For example, <code>col_widths={&quot;lg&quot;: (4, 8, 12)}</code></li>
</ul>
<p>This technique allows you to create sophisticated, responsive layouts that adapt gracefully to different screen sizes, providing an optimal user experience across all devices. Check out the <a href="https://shiny.posit.co/py/api/express.ui.layout_columns.html" target="_blank" rel="noopener">Shiny documentation</a>
 for more details and advanced options.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/responsive-shiny-layouts/shiny-layouts.jpg" length="84950" type="image/jpeg" />
    </item>
    <item>
      <title>Level Up Your Shiny Forms: Accordions &#43; Dynamic Goodness</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/dynamic-accordion-panels/</link>
      <pubDate>Wed, 05 Feb 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/dynamic-accordion-panels/</guid>
      <dc:creator>Shiny Team</dc:creator><description><![CDATA[<p>Shiny for Python makes building web apps easy. This framework simplifies creating interactive applications with Python&mdash;no need for JavaScript or HTML. Its secret lies in reactive programming, which keeps your UI dynamic with minimal effort. Just focus on your Python code, and let Shiny take care of the rest!</p>
<details class="callout callout-note" role="note" aria-label="Note">
<summary class="callout-header">
<span class="callout-title">Note</span>
</summary>
<div class="callout-body">
<p>To install the Shiny for Python package, users can use the following command in their terminal or command prompt:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pip install <span class="s2">&#34;shiny&gt;=1.0&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</details>
<p>Basic Shiny forms can feel&hellip; well, basic. But what if you&rsquo;ve got a form with a lot of info? Accordion panels to the rescue! They organize things nicely, but we can make them even cooler with dynamic updates.</p>
<h3 id="accordions-form-organization-made-easy">Accordions: Form Organization made easy
</h3>
<p>Instead of one long form, think sections! Accordions let you group related inputs and collapse them. Cleaner, right?</p>
<p><strong>Why Accordions are Awesome:</strong></p>
<ul>
<li><strong>Neat &amp; Tidy:</strong> Sections keep things organized.</li>
<li><strong>Less Overwhelming:</strong> Users focus on one part at a time.</li>
<li><strong>Easy Navigation:</strong> Clear section titles guide the way.</li>
</ul>
<h4 id="forms-without-and-with-the-use-of-accordions"><strong>Forms without and with the use of Accordions</strong>
</h4>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Basic form</span>
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">h2</span><span class="p">(</span><span class="s2">&#34;Basic form&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">input_text</span><span class="p">(</span><span class="s2">&#34;name&#34;</span><span class="p">,</span> <span class="s2">&#34;Name&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">input_text</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">,</span> <span class="s2">&#34;Email&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">input_text</span><span class="p">(</span><span class="s2">&#34;phone&#34;</span><span class="p">,</span> <span class="s2">&#34;Phone&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">input_action_button</span><span class="p">(</span><span class="s2">&#34;submit_personal&#34;</span><span class="p">,</span> <span class="s2">&#34;Submit&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Accordion form</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">accordion</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="s2">&#34;info_accordion&#34;</span><span class="p">,</span> <span class="n">multiple</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">ui</span><span class="o">.</span><span class="n">accordion_panel</span><span class="p">(</span><span class="s2">&#34;Personal Information&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="s2">&#34;personal&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">input_text</span><span class="p">(</span><span class="s2">&#34;name&#34;</span><span class="p">,</span> <span class="s2">&#34;Name&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">input_text</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">,</span> <span class="s2">&#34;Email&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">input_text</span><span class="p">(</span><span class="s2">&#34;phone&#34;</span><span class="p">,</span> <span class="s2">&#34;Phone&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">ui</span><span class="o">.</span><span class="n">input_action_button</span><span class="p">(</span><span class="s2">&#34;submit_personal&#34;</span><span class="p">,</span> <span class="s2">&#34;Submit&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/dynamic-accordion-panels/comparison.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
<h3 id="making-accordions-dynamic">Making Accordions dynamic
</h3>
<p>The video below demonstrates a powerful technique to further enhance accordion forms through <strong>dynamic updates</strong>. This involves programmatically modifying the appearance of accordion panels in response to user actions, creating a more interactive and informative interface.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@reactive.effect</span>
</span></span><span class="line"><span class="cl"><span class="nd">@reactive.event</span><span class="p">(</span><span class="nb">input</span><span class="o">.</span><span class="n">submit_personal</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">update_accordion_1</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Get input values, defaulting to &#34;N/A&#34; if empty</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="nb">input</span><span class="o">.</span><span class="n">name</span><span class="p">()</span> <span class="ow">or</span> <span class="s2">&#34;N/A&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">email</span> <span class="o">=</span> <span class="nb">input</span><span class="o">.</span><span class="n">email</span><span class="p">()</span> <span class="ow">or</span> <span class="s2">&#34;N/A&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">phone</span> <span class="o">=</span> <span class="nb">input</span><span class="o">.</span><span class="n">phone</span><span class="p">()</span> <span class="ow">or</span> <span class="s2">&#34;N/A&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Create new title string with user input values</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_title</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Name: </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">, Email: </span><span class="si">{</span><span class="n">email</span><span class="si">}</span><span class="s2">, Phone: </span><span class="si">{</span><span class="n">phone</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Updates the appearance and state of an accordion panel dynamically</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">update_accordion_panel</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;info_accordion&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;personal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span><span class="o">=</span><span class="n">new_title</span><span class="p">,</span>     <span class="c1"># Set new title with user info</span>
</span></span><span class="line"><span class="cl">        <span class="n">show</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>          <span class="c1"># Collapse this panel</span>
</span></span><span class="line"><span class="cl">        <span class="n">icon</span><span class="o">=</span><span class="n">icon_svg</span><span class="p">(</span><span class="s2">&#34;check&#34;</span><span class="p">,</span> <span class="n">fill</span><span class="o">=</span><span class="s2">&#34;green&#34;</span><span class="p">),</span>  <span class="c1"># Show green checkmark icon</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Open the next panel automatically</span>
</span></span><span class="line"><span class="cl">    <span class="n">ui</span><span class="o">.</span><span class="n">update_accordion_panel</span><span class="p">(</span><span class="s2">&#34;info_accordion&#34;</span><span class="p">,</span> <span class="s2">&#34;professional&#34;</span><span class="p">,</span> <span class="n">show</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div>














  

  
  
  
    
    
  

  
  










  
  
    <div class="w-full aspect-video">
      <iframe
        src="https://www.youtube.com/embed/v26E2_1cSa8"
        class="w-full h-full"
        
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowfullscreen></iframe>
    </div>
  




<p>Specifically, the video showcases how to:</p>
<ol>
<li>
<p><strong>Dynamically Change Panel Titles:</strong> Upon completing a section of the form and clicking a <em>&ldquo;Submit&rdquo;</em> button, the accordion panel title is updated to reflect the entered information. This provides immediate visual confirmation and a clear summary of the data provided within each section.</p>
</li>
<li>
<p><strong>Incorporate Dynamic Icons for Status Indication:</strong> Beyond textual updates, the example effectively uses icons to visually communicate the status of each accordion panel. Initially, an exclamation icon in red may indicate sections requiring user attention. Upon successful submission of a section, this icon can be dynamically updated to a green checkmark. This visual cue provides instant feedback, allowing users to easily identify completed sections and those still requiring input.</p>
</li>
</ol>
<p>The implementation leverages the <a href="https://shiny.posit.co/py/api/express/express.ui.update_accordion_panel.html" target="_blank" rel="noopener">ui.update_accordion_panel</a>
 function (in the context of Shiny for Python&rsquo;s Express mode), which allows developers to programmatically modify various aspects of an existing accordion panel, including its title and icon.</p>
<p>Moving beyond basic form structures is crucial for creating sophisticated and user-centric Shiny applications. <strong>Accordion panels</strong> offer a significant step forward in form organization and user experience. By incorporating dynamic updates, as demonstrated in the video, developers can further elevate their Shiny forms, creating interfaces that are not only functional but also visually informative and engaging. This approach contributes to a more polished and professional application, ultimately enhancing user satisfaction and data interaction efficiency.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/dynamic-accordion-panels/forms.jpg" length="67997" type="image/jpeg" />
    </item>
    <item>
      <title>bslib v0.9.0 brings branded theming to Shiny for R</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/bslib-0.9.0/</link>
      <pubDate>Fri, 31 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/bslib-0.9.0/</guid>
      <dc:creator>Garrick Aden-Buie</dc:creator><description><![CDATA[<p>We are excited to share that with <a href="https://rstudio.github.io/bslib" target="_blank" rel="noopener">bslib</a>
 v0.9.0, Shiny for R now supports <a href="https://posit-dev.github.io/brand-yml" target="_blank" rel="noopener">brand.yml</a>
, providing a simple and unified theming experience through a single YAML file!</p>
<h2 id="what-is-brandyml">What is brand.yml?
</h2>
<p><a href="https://posit-dev.github.io/brand-yml" target="_blank" rel="noopener">brand.yml</a>
 simplifies brand management by consolidating your visual identity&mdash;colors, typography, and styling&mdash;into a single, easy-to-maintain YAML file.
Last year, we launched brand.yml with initial support in <a href="https://quarto.org/docs/authoring/brand.html" target="_blank" rel="noopener">Quarto</a>
 and <a href="https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.2-brand-yml/">Shiny for Python</a>
, and we&rsquo;re happy to be bringing brand.yml to R as well.</p>
<p>If you haven&rsquo;t seen <a href="https://posit-dev.github.io/brand-yml" target="_blank" rel="noopener">brand.yml</a>
 in action yet, here&rsquo;s an example <code>_brand.yml</code> file that includes metadata about the company, its logos, color palette, theme, and the fonts and typographic settings used by the brand.</p>
<p><strong>_brand.yml</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">meta</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">brand.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">link</span><span class="p">:</span><span class="w"> </span><span class="l">https://posit-dev.github.io/brand-yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">logo</span><span class="p">:</span><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">small</span><span class="p">:</span><span class="w"> </span><span class="l">brand-yml-icon.svg</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">medium</span><span class="p">:</span><span class="w"> </span><span class="l">brand-yml-tall.svg</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">large</span><span class="p">:</span><span class="w"> </span><span class="l">brand-yml-wide.svg</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">color</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">palette</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">orange</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#FF6F20&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">pink</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#FF3D7F&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">primary</span><span class="p">:</span><span class="w"> </span><span class="l">orange</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">danger</span><span class="p">:</span><span class="w"> </span><span class="l">pink</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">typography</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">fonts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">family</span><span class="p">:</span><span class="w"> </span><span class="l">Open Sans</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">source</span><span class="p">:</span><span class="w"> </span><span class="l">google</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">base</span><span class="p">:</span><span class="w"> </span><span class="l">Open Sans</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>This one file can be used to maintain consistent branding across your Shiny apps, Quarto projects, and now any R-based project that uses <a href="https://rstudio.github.io/bslib/articles/theming/index.html" target="_blank" rel="noopener">bslib for theming</a>
, including <a href="https://shiny.posit.co" target="_blank" rel="noopener">shiny</a>
, <a href="https://rmarkdown.rstudio.com" target="_blank" rel="noopener">R Markdown</a>
, <a href="https://pkgdown.r-lib.org/" target="_blank" rel="noopener">pkgdown</a>
, <a href="https://pkgs.rstudio.com/flexdashboard/" target="_blank" rel="noopener">flexdashboard</a>
, and more!</p>
<h2 id="getting-started">Getting Started
</h2>
<p>bslib is a package maintained by the Shiny team to provide [Bootstrap] styles and components for the R ecosystem.
With bslib, using brand.yml to theme your Shiny app is straightforward.
To get started, make sure you&rsquo;ve installed the latest version of bslib:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;bslib&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Then, create a <code>_brand.yml</code> file in your project directory, either alongside your <code>app.R</code> or in a parent folder of the project containing your app.</p>
<p>If your app uses any of the <a href="https://rstudio.github.io/bslib/reference/index.html#page-layouts" target="_blank" rel="noopener">page functions from bslib</a>
 like <code>page_sidebar()</code> or <code>page_navbar()</code>, then you&rsquo;re all set!
bslib will automatically find the <code>_brand.yml</code> file and apply it to the app&rsquo;s theme.
For page functions from Shiny&mdash;like <code>fluidPage()</code> or <code>navbarPage()</code>&mdash;set <code>theme = bs_theme()</code> in the page function.</p>
<p>If you want to use another file name, like <code>acme-brand.yml</code>, or you want to be explicit, you can call <code>bs_theme()</code> and provide the path to the brand.yml file to the <code>brand</code> argument:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_sidebar</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">title</span> <span class="o">=</span> <span class="s">&#34;Acme Sales Dashboard&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">theme</span> <span class="o">=</span> <span class="nf">bs_theme</span><span class="p">(</span><span class="n">brand</span> <span class="o">=</span> <span class="s">&#34;acme-brand.yml&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ... the rest of your app ...</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/bslib-0.9.0/shiny-no-branding.png" alt="Shiny without branding" />
<figcaption aria-hidden="true"><span class="text-white">Shiny without branding</span></figcaption>
</figure>
<figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/bslib-0.9.0/shiny-branding.png" alt="Brand-themed Shiny" />
<figcaption aria-hidden="true"><span class="text-white">Brand-themed Shiny</span></figcaption>
</figure>
<h2 id="try-brandyml-in-shiny-now">Try brand.yml in Shiny now!
</h2>
<p>Try brand.yml now with the <a href="https://bslib.shinyapps.io/brand-yml/" target="_blank" rel="noopener">brand.yml Demo App</a>
.
You can create and preview your own brand.yml files with this app, and it&rsquo;s included with bslib so that you can try it locally, too.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># requires shiny v1.8.1 or later</span>
</span></span><span class="line"><span class="cl"><span class="n">shiny</span><span class="o">::</span><span class="nf">runExample</span><span class="p">(</span><span class="s">&#34;brand.yml&#34;</span><span class="p">,</span> <span class="n">package</span> <span class="o">=</span> <span class="s">&#34;bslib&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This app can also be used as a template; see <a href="https://rstudio.github.io/bslib/articles/brand-yml/index.html#try-brand-yml" target="_blank" rel="noopener">Unified theming with brand.yml</a>
 on the bslib website for instructions.</p>
<h2 id="brandyml-in-r-markdown">brand.yml in R Markdown
</h2>
<p>The R Markdown ecosystem widely <a href="https://rstudio.github.io/bslib/articles/any-project/index.html#r-markdown" target="_blank" rel="noopener">use bslib for theming</a>
.
Anywhere that <code>theme</code> is passed to bslib, you can use brand.yml, either by placing a file named <code>_brand.yml</code> in the project or by passing a path to your brand.yml file to <code>brand</code>.
Note that brand.yml works best with Bootstrap version 5.</p>
<p>For R Markdown, use <code>brand</code> under <code>output.html_document.theme</code>:</p>
<p><strong>report.Rmd</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">output</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">html_document</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">theme</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">brand</span><span class="p">:</span><span class="w"> </span><span class="l">acme-brand.yml</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Similarly, in pkgdown, you can use <code>brand</code> under <code>template.bslib</code>:</p>
<p><strong>_pkgdown.yml</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">bslib</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">brand</span><span class="p">:</span><span class="w"> </span><span class="l">acme-brand.yml</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Note that <code>brand</code> isn&rsquo;t strictly necessary: if you name your brand.yml file <code>_brand.yml</code>, bslib will automatically find it in your project<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<h2 id="looking-forward">Looking Forward
</h2>
<p>We look forward to seeing how the community uses this feature and welcome your feedback!
You can learn more about brand.yml and its features at the <a href="https://posit-dev.github.io/brand-yml" target="_blank" rel="noopener">official brand.yml website</a>
.
Find more specific information about <a href="https://rstudio.github.io/bslib/articles/brand-yml/index.html" target="_blank" rel="noopener">branded and custom theming with bslib</a>
 at the bslib website, or learn about <a href="https://posit.co/blog/unified-branding-across-posit-tools-with-brand-yml/" target="_blank" rel="noopener">unified branding across Posit tools with brand.yml</a>
 on the Posit blog.</p>
<p>As a final tip,
we know that writing YAML isn&rsquo;t everyone&rsquo;s cup of tea!
If you want to enlist the help of a friendly large language model (LLM), we&rsquo;ve written up <a href="https://posit-dev.github.io/brand-yml/articles/llm-brand-yml-prompt/" target="_blank" rel="noopener">a guide to using LLMs to write brand.yml</a>
.</p>
<h2 id="thank-you-">Thank you 💙
</h2>
<p>This post doesn&rsquo;t cover all of the changes and updates that happened in bslib in this release.
To learn more about specific changes in each package, dive into the release notes linked below!</p>
<p><strong>A huge thank you</strong> to everyone who contributed pull requests, bug reports and feature requests.</p>
<h4 id="bslib-v090">bslib <a href="https://rstudio.github.io/bslib/news/index.html#bslib-090" target="_blank" rel="noopener">v0.9.0</a>

</h4>
<p><a href="https://github.com/al-obrien" target="_blank" rel="noopener">@al-obrien</a>
, <a href="https://github.com/AlbertRapp" target="_blank" rel="noopener">@AlbertRapp</a>
, <a href="https://github.com/CharlesBordet" target="_blank" rel="noopener">@CharlesBordet</a>
, <a href="https://github.com/cpsievert" target="_blank" rel="noopener">@cpsievert</a>
, <a href="https://github.com/cscheid" target="_blank" rel="noopener">@cscheid</a>
, <a href="https://github.com/daattali" target="_blank" rel="noopener">@daattali</a>
, <a href="https://github.com/danielloader" target="_blank" rel="noopener">@danielloader</a>
, <a href="https://github.com/DavZim" target="_blank" rel="noopener">@DavZim</a>
, <a href="https://github.com/DeepanshKhurana" target="_blank" rel="noopener">@DeepanshKhurana</a>
, <a href="https://github.com/dsen6644" target="_blank" rel="noopener">@dsen6644</a>
, <a href="https://github.com/dvg-p4" target="_blank" rel="noopener">@dvg-p4</a>
, <a href="https://github.com/eheinzen" target="_blank" rel="noopener">@eheinzen</a>
, <a href="https://github.com/furrrpanda" target="_blank" rel="noopener">@furrrpanda</a>
, <a href="https://github.com/gadenbuie" target="_blank" rel="noopener">@gadenbuie</a>
, <a href="https://github.com/grcatlin" target="_blank" rel="noopener">@grcatlin</a>
, <a href="https://github.com/ismirsehregal" target="_blank" rel="noopener">@ismirsehregal</a>
, <a href="https://github.com/jack-davison" target="_blank" rel="noopener">@jack-davison</a>
, <a href="https://github.com/LDSamson" target="_blank" rel="noopener">@LDSamson</a>
, <a href="https://github.com/lmullany" target="_blank" rel="noopener">@lmullany</a>
, <a href="https://github.com/luisDVA" target="_blank" rel="noopener">@luisDVA</a>
, <a href="https://github.com/lukebandy" target="_blank" rel="noopener">@lukebandy</a>
, <a href="https://github.com/matt-dray" target="_blank" rel="noopener">@matt-dray</a>
, <a href="https://github.com/meztez" target="_blank" rel="noopener">@meztez</a>
, <a href="https://github.com/natashanath" target="_blank" rel="noopener">@natashanath</a>
, <a href="https://github.com/olivroy" target="_blank" rel="noopener">@olivroy</a>
, <a href="https://github.com/RealKai42" target="_blank" rel="noopener">@RealKai42</a>
, <a href="https://github.com/royfrancis" target="_blank" rel="noopener">@royfrancis</a>
, <a href="https://github.com/see24" target="_blank" rel="noopener">@see24</a>
, <a href="https://github.com/SokolovAnatoliy" target="_blank" rel="noopener">@SokolovAnatoliy</a>
, <a href="https://github.com/Teebusch" target="_blank" rel="noopener">@Teebusch</a>
, <a href="https://github.com/udurraniAtPresage" target="_blank" rel="noopener">@udurraniAtPresage</a>
, and <a href="https://github.com/wulj5" target="_blank" rel="noopener">@wulj5</a>
.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>If you don&rsquo;t want bslib to use a project-level <code>_brand.yml</code> file, you can use <code>brand: false</code> to disable automatic discovery. Or you can use <code>brand: true</code> to ensure that a project <code>_brand.yml</code> is found. Finally, you could also use <code>brand</code> to provide an entire brand.yml definition in-line!&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/bslib-0.9.0/feature.jpg" length="112028" type="image/jpeg" />
    </item>
    <item>
      <title>Branded theming for Shiny for Python apps</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.2-brand-yml/</link>
      <pubDate>Mon, 25 Nov 2024 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.2-brand-yml/</guid>
      <dc:creator>Garrick Aden-Buie</dc:creator><description><![CDATA[<img src="https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.2-brand-yml/brand-yml-wide.svg" alt="brand.yml logo" />
<p>We are bursting to announce that Shiny for Python now supports <a href="https://posit-dev.github.io/brand-yml" target="_blank" rel="noopener">brand.yml</a>
, a simple, unified theming experience through a single YAML file.</p>
<h2 id="what-is-brandyml">What is brand.yml?
</h2>
<p><a href="https://posit-dev.github.io/brand-yml" target="_blank" rel="noopener">brand.yml</a>
 simplifies brand management by consolidating your visual identity&mdash;colors, typography, and styling&mdash;into a single, easy-to-maintain YAML file.</p>
<p>For you: it&rsquo;s a centralized source of truth for your brand or company&rsquo;s visual identity.
For the tools you use: it&rsquo;s simple, structured data to coordinate theming of any output.</p>
<p>Here&rsquo;s an example <code>_brand.yml</code> file that includes metadata about the company, its logos, color palette, theme, and the fonts and typographic settings used by the brand.</p>
<p><strong>_brand.yml</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">meta</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">brand.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">link</span><span class="p">:</span><span class="w"> </span><span class="l">https://posit-dev.github.io/brand-yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">logo</span><span class="p">:</span><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">small</span><span class="p">:</span><span class="w"> </span><span class="l">brand-yml-icon.svg</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">medium</span><span class="p">:</span><span class="w"> </span><span class="l">brand-yml-tall.svg</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">large</span><span class="p">:</span><span class="w"> </span><span class="l">brand-yml-wide.svg</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">color</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">palette</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">orange</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#FF6F20&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">pink</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#FF3D7F&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">primary</span><span class="p">:</span><span class="w"> </span><span class="l">orange</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">danger</span><span class="p">:</span><span class="w"> </span><span class="l">pink</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">typography</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">fonts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">family</span><span class="p">:</span><span class="w"> </span><span class="l">Open Sans</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">source</span><span class="p">:</span><span class="w"> </span><span class="l">google</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">base</span><span class="p">:</span><span class="w"> </span><span class="l">Open Sans</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>A core goal of brand.yml is to introduce a common syntax for theming across various data science outputs created with open-source tools.
With brand.yml you won&rsquo;t need to write multiple CSS files to maintain a consistent look across your apps, websites, and presentations.</p>
<p>And because all fields are optional, brand.yml also serves as a simple interface to theming across many outputs.
For example, you could use brand.yml to set the base font to <a href="https://fonts.google.com/specimen/Roboto" target="_blank" rel="noopener">Roboto</a>
 and use <span style="padding-inline:0.25em; background-color: #f96302; color: white;">orange</span> as the primary accent color.</p>
<p><strong>A minimal _brand.yml</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">color</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">primary</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#F96302&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">typography</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">fonts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">family</span><span class="p">:</span><span class="w"> </span><span class="l">Roboto</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">source</span><span class="p">:</span><span class="w"> </span><span class="l">google</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">base</span><span class="p">:</span><span class="w"> </span><span class="l">Roboto</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>This same file can be used for your Shiny apps as well as your Quarto projects.
We&rsquo;re starting with Posit-sponsored projects like <a href="https://shiny.posit.co" target="_blank" rel="noopener">Shiny</a>
, <a href="https://quarto.org" target="_blank" rel="noopener">Quarto</a>
, and <a href="https://rmarkdown.rstudio.com" target="_blank" rel="noopener">R Markdown</a>
, but we&rsquo;re hoping to see brand.yml adopted more widely in the open-source community.
To encourage this, we&rsquo;ve published the <a href="https://posit-dev.github.io/brand-yml/pkg/py" target="_blank" rel="noopener">brand-yml Python package</a>
, which you can use to integrate brand.yml into your project or other packages.</p>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Writing brand.yml with the help of an LLM</span>
</div>
<div class="callout-body">
<p>We know that writing YAML isn&rsquo;t everyone&rsquo;s cup of tea!
If you want to enlist the help of a friendly large language model (LLM), we&rsquo;ve written up <a href="https://posit-dev.github.io/brand-yml/articles/llm-brand-yml-prompt/" target="_blank" rel="noopener">a guide to using LLMs to write brand.yml</a>
.</p>
<p>The article includes a prompt you can copy and use to teach an LLM about the brand.yml syntax, as well as a few tips and follow-up prompts you can use to get the best results.</p>
</div>
</div>
<h2 id="getting-started">Getting Started
</h2>
<p>With Shiny for Python v1.2.0, using brand.yml to theme your Shiny app is straightforward.
To get started, make sure you&rsquo;ve installed the latest version of shiny with the <code>theme</code> extra:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install <span class="s2">&#34;shiny[theme]&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Then, create a <code>_brand.yml</code> file in your project directory (either alongside your <code>app.py</code> or in a parent folder).
Finally, use the <a href="https://shiny.posit.co/py/api/core/ui.Theme.html#shiny.ui.Theme.from_brand" target="_blank" rel="noopener">new <code>ui.Theme.from_brand()</code> function</a>
 to set your page <code>theme</code>.</p>
<div class="panel-tabset" data-tabset-group="shiny-app-mode">
<ul id="tabset-1" class="panel-tabset-tabby">
<li><a data-tabby-default href="#tabset-1-1">Express</a></li>
<li><a href="#tabset-1-2">Core</a></li>
</ul>
<div id="tabset-1-1">
<p><strong>app.py</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="nb">input</span><span class="p">,</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">page_opts</span><span class="p">(</span><span class="n">theme</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">Theme</span><span class="o">.</span><span class="n">from_brand</span><span class="p">(</span><span class="vm">__file__</span><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
<div id="tabset-1-2">
<p><strong>app.py</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny</span> <span class="kn">import</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app_ui</span> <span class="o">=</span> <span class="n">ui</span><span class="o">.</span><span class="n">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># App UI code...</span>
</span></span><span class="line"><span class="cl">    <span class="n">theme</span><span class="o">=</span><span class="n">ui</span><span class="o">.</span><span class="n">Theme</span><span class="o">.</span><span class="n">from_brand</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div>
</div>
<p>We&rsquo;ve added a <a href="https://shinylive.io/py/examples/#branded-theming" target="_blank" rel="noopener">complete branded theming example</a>
 to <a href="https://shinylive.io/py/examples/#branded-theming" target="_blank" rel="noopener">shinylive.io</a>
 that you can run directly in your browser.
Use it to explore brand.yml features or to preview your own company&rsquo;s <code>_brand.yml</code> look in Shiny.</p>
<h2 id="looking-forward">Looking Forward
</h2>
<p>We&rsquo;re excited to see how the community uses this feature and look forward to your feedback!
You can learn more about brand.yml and its features at the <a href="https://posit-dev.github.io/brand-yml" target="_blank" rel="noopener">official brand.yml website</a>
.
Or read more about <a href="https://shiny.posit.co/py/api/core/ui.Theme.html#shiny.ui.Theme.from_brand" target="_blank" rel="noopener">branded and custom theming in Shiny for Python</a>
.</p>
<p>For Python developers, we encourage you to check out the <a href="https://posit-dev.github.io/brand-yml/pkg/py/" target="_blank" rel="noopener">brand_yml Python package</a>
, which provides an easy-to-use interface for parsing and making use of brand.yml files.</p>
<p>For R and Shiny for R developers, hang tight!
Shiny for R support is next on our roadmap, followed by additional tooling to support reading and using brand.yml files.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.2-brand-yml/brand-yml-feature.svg" length="7365" type="image/svg&#43;xml" />
    </item>
    <item>
      <title>posit::conf(2024) Shiny talks</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/conf-2024-shinytalks/</link>
      <pubDate>Mon, 11 Nov 2024 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/conf-2024-shinytalks/</guid>
      <dc:creator>Andrew Holz</dc:creator><description><![CDATA[<p>Videos of posit::conf(2024) talks are now posted <a href="https://www.youtube.com/watch?v=s_Vh9HIeLVg&amp;list=PL9HYL-VRX0oSFkdF4fJeY63eGDvgofcbn" target="_blank" rel="noopener">on YouTube</a>
. We have also made <a href="https://youtube.com/playlist?list=PLitrm9UndxcsoP0pqVvteJWtAQ9TKT-mf" target="_blank" rel="noopener">playlist of talks</a>
 that are about building data driven applications with Shiny.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/videoseries?si=b5ZCT7LMUOXamjoR&amp;list=PLitrm9UndxcsoP0pqVvteJWtAQ9TKT-mf" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen>
</iframe>
<p>Talks included in this playlist are as follows:</p>
<ul>
<li><a href="https://youtu.be/s_cBi155ZIQ?si=RCXLXY0XHeVLFc4k" target="_blank" rel="noopener"><strong>Charlie Gao and Will Landau</strong> - {mirai} and {crew}: next-generation async to supercharge {promises}, Plumber, Shiny, and {targets}</a>
</li>
<li><a href="https://youtu.be/iC78WbnwnIs?si=2GDss6vGoWL-BSRm" target="_blank" rel="noopener"><strong>Eric Nantz</strong> - A New Era for Shiny-based Clinical Submissions using WebAssembly</a>
</li>
<li><a href="https://youtu.be/G2GNZZ8GQPY?si=m7WIT4-JJaG39Gtw" target="_blank" rel="noopener"><strong>Marcin Dubel</strong> - Shiny in Action: Transforming Film Production with TARS</a>
</li>
<li><a href="https://youtu.be/SAbV6d7Pn0U?si=AVd2M9a7yeZ67dIF" target="_blank" rel="noopener"><strong>Kiegan Rice</strong> - Wait, that&rsquo;s Shiny? Building feature-full, user-friendly interactive data explorers with Shiny and friends</a>
</li>
<li><a href="https://youtu.be/FPc5PJRWHsk?si=W484jzz4EyouBCI8" target="_blank" rel="noopener"><strong>Greg Swinehart</strong> - We CAN Have Nice Shiny Apps</a>
</li>
<li><a href="https://youtu.be/zgcyGs9q06g?si=4iNmTxsoYy5FmWK-" target="_blank" rel="noopener"><strong>Andrew Bates</strong> - Making an App a System</a>
</li>
<li><a href="https://youtu.be/59ztetVfmjg?si=uFD7nn6qEl38WtgW" target="_blank" rel="noopener"><strong>Lovekumar Patel</strong> - Empowering Decisions: Advanced Portfolio Analysis and Management through Shiny</a>
</li>
<li><a href="https://youtu.be/TYHiwWlpGOM?si=72zm3YKjncZWE9xg" target="_blank" rel="noopener"><strong>Winston Chang</strong> - Building ML and AI apps with Shiny for Python</a>
</li>
<li><a href="https://youtu.be/pL44iKfcECU?si=_iD8mvy76Vf6Kosy" target="_blank" rel="noopener"><strong>Carson Sievert</strong> - Supercharge Your Shiny (for Python) App: Unleashing Interactive Jupyter Widgets</a>
</li>
<li><a href="https://youtu.be/AP8BWGhCRZc?si=otdkZ1N1H0VD707k" target="_blank" rel="noopener"><strong>Joe Cheng</strong> - Shiny x AI</a>
</li>
<li><a href="https://youtu.be/AXH52WNOErc?si=dCrLS3pJFd2wVTW-" target="_blank" rel="noopener"><strong>Barret Schloerke</strong> - Editable data frames in Py-Shiny: Updating original data in real-time</a>
</li>
<li><a href="https://youtu.be/kYnqxQvIFNQ?si=Ai6bAjxhSxWt2c5h" target="_blank" rel="noopener"><strong>Joseph Richey</strong> - Leveraging Data in a Volunteer Fire Department</a>
</li>
<li><a href="https://youtu.be/_AFa4DIGDJk?si=wdFmHswXNIVHYA3f" target="_blank" rel="noopener"><strong>Mark Wang</strong> - Using GitHub Copilot in R Shiny Development</a>
</li>
</ul>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/conf-2024-shinytalks/shinytalks.jpg" length="157666" type="image/jpeg" />
    </item>
    <item>
      <title>Shiny for Python 1.2.0</title>
      <link>https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.2/</link>
      <pubDate>Thu, 31 Oct 2024 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.2/</guid>
      <dc:creator>Shiny Team</dc:creator><description><![CDATA[<p>Shiny for Python <code>v1.2.0</code> is here! To celebrate, let&rsquo;s highlight a big new feature of this release: integration with <a href="https://narwhals-dev.github.io/narwhals/" target="_blank" rel="noopener"><code>narwhals</code></a>
 for <code>@render.data_frame</code>! 🐳🦄</p>
<p>In a follow up post, we&rsquo;ll also highlight another new exciting feature: integration with brand new <a href="https://github.com/posit-dev/brand-yml" target="_blank" rel="noopener"><code>brand-yml</code></a>
 package for theming.</p>
<p>For a full list of all the changes in this release, check out the <a href="https://github.com/posit-dev/py-shiny/blob/main/CHANGELOG.md#120---2024-10-29" target="_blank" rel="noopener">CHANGELOG</a>
.</p>
<blockquote>
<p><strong>What is Shiny for Python?</strong></p>
<p>Shiny for Python is a framework that makes it easy to build interactive web applications using Python (<a href="https://shiny.posit.co/py/docs/" target="_blank" rel="noopener">Quick Start</a>
). You can create web applications directly from your Python code without needing to know any Javascript. Shiny is built on a <a href="https://shiny.posit.co/py/docs/reactive-foundations.html" target="_blank" rel="noopener">reactive programming model</a>
, meaning that Shiny automatically figures out the relationships between different components in your application. When a user interacts with your application, Shiny will only update the necessary parts. This ensures that your applications stay fast and responsive even as they grow in size and complexity.</p>
</blockquote>
<hr>
<h2 id="narwhals-integration"><code>narwhals</code> integration
</h2>
<p>In the Shiny <code>v1.0.0</code> release, <code>@render.data_frame</code> added support for <a href="https://docs.pola.rs/" target="_blank" rel="noopener">Polars</a>
 data frames (in addition to the existing support for <a href="https://pandas.pydata.org/" target="_blank" rel="noopener">pandas</a>
 data frames). This was performed through custom inspection and handling for the two supported data frame types. While this worked, it was not ideal for users who wanted use a <em>new</em> data frame type. This would require Shiny to implement custom code for each new data frame type of which we are not experts. This approach does not scale well!</p>
<p>In Shiny <code>v1.2.0</code>, <code>@render.data_frame</code> integrated with <a href="https://narwhals-dev.github.io/narwhals/" target="_blank" rel="noopener">narwhals</a>
 better data frame support! 🥳🥳</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.2/narwhals_w_shiny.jpeg"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
<p><code>narwhals</code> describes itself as an <em>&ldquo;Extremely lightweight and extensible compatibility layer between dataframe libraries!&rdquo;</em> that can <em>&ldquo;Seamlessly support all, without depending on any!&rdquo;</em>. <code>narwhals</code> (as of this blog post) has full API support for eager data frames such as <a href="https://docs.rapids.ai/api/cudf/stable/" target="_blank" rel="noopener">cuDF</a>
, <a href="https://modin.readthedocs.io/en/stable/" target="_blank" rel="noopener">Modin</a>
, <a href="https://pandas.pydata.org/" target="_blank" rel="noopener">pandas</a>
, <a href="https://docs.pola.rs/" target="_blank" rel="noopener">Polars</a>
, and <a href="https://arrow.apache.org/docs/python/" target="_blank" rel="noopener">PyArrow</a>
. This means that you can immediately use any of these data frame libraries (even <code>narwhals</code> itself) within Shiny&rsquo;s <code>@render.data_frame</code> without any updates to Shiny or forcing you to export your data to pandas or Polars. Additionally, as <code>narwhals</code> (who is rapidly improving by the day) adds support for new data frame libraries, Shiny will automatically support them as well! As <code>narwhals</code> improves, so does Shiny! 🚀</p>
<blockquote>
<p><strong>Additional Narwhals data frame types</strong></p>
<p>Narwhals also supports other styles of data frames such as <a href="https://docs.dask.org/en/stable/" target="_blank" rel="noopener">Dask</a>
 (Lazy-only) and <a href="https://docs.ibis-project.org/" target="_blank" rel="noopener">Ibis</a>
 and <a href="https://vaex.io/docs/index.html" target="_blank" rel="noopener">Vaex</a>
 through the DataFrame Interchange Protocol. However, these data frame types are not directly supported within Shiny as they are not <em>eager</em> data frames.</p>
<p>Please convert your lazy or interchange data to an eager data frame before returning it within <code>@render.data_frame</code>.</p>
</blockquote>
<p>Let&rsquo;s look at an example that uses a PyArrow Table (which has never been directly implemented by Shiny) displaying <code>great_tables</code>&rsquo;s S&amp;P 500 data:</p>
<p><strong>app.py</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">great_tables</span> <span class="k">as</span> <span class="nn">gt</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pyarrow</span> <span class="k">as</span> <span class="nn">pa</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">shiny.express</span> <span class="kn">import</span> <span class="n">render</span><span class="p">,</span> <span class="n">ui</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">pa_data</span> <span class="o">=</span> <span class="n">pa</span><span class="o">.</span><span class="n">Table</span><span class="o">.</span><span class="n">from_pandas</span><span class="p">(</span><span class="n">gt</span><span class="o">.</span><span class="n">data</span><span class="o">.</span><span class="n">sp500</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="o">.</span><span class="n">h3</span><span class="p">(</span><span class="s2">&#34;S&amp;P 500 (PyArrow)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@render.data_frame</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pa_df</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">pa_data</span>
</span></span></code></pre></td></tr></table>
</div>
</div><figure>
<img src="https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.2/pa-sp500.gif" class="w-100" alt="PyArrow Table; 16,607 rows" />
<figcaption aria-hidden="true">PyArrow Table; 16,607 rows</figcaption>
</figure>
<p>For a quick demo, Barret Schloerke will talk about the example above and more!</p>















  

  
  
  
    
    
  

  
  










  
  
    <div class="w-full aspect-video">
      <iframe
        src="https://www.youtube.com/embed/W-_0rkcuB_8"
        class="w-full h-full"
        
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowfullscreen></iframe>
    </div>
  




<hr>
<h2 id="new-methods-for-renderdata_frame">New methods for <code>@render.data_frame</code>
</h2>
<p>In addition to the integration of <code>narwhals</code>, we have added new instance methods to <code>@render.data_frame</code> objects to make it easier to work with data frames.</p>
<ul>
<li><code>.update_data(data)</code>
<ul>
<li>Updates the <code>.data()</code> data frame with a new value.</li>
<li>The user&rsquo;s sorting and filtering will not be reset. However, all <code>.cell_patches()</code> patches (user edits) will be removed.</li>
</ul>
</li>
<li><code>.update_cell_value(value, *, row, col)</code>
<ul>
<li>Updates a single value of a cell in the data frame.</li>
<li><code>.cell_patches()</code> patches which are used to create <code>.data_patched()</code> from <code>.data()</code>.</li>
</ul>
</li>
<li><code>.data_patched()</code>
<ul>
<li>Reactive calculation of the <code>.data()</code> data frame value with all <code>.cell_patches()</code> patches applied.</li>
<li>Unlike <code>.data_view()</code>, <code>.data_patched()</code> will not be affected by user sorting, filtering, or row selections.</li>
<li>The new data set can be of completely different types and shape. For typing purposes, it is strongly recommended to use the same type as the original data set.</li>
</ul>
</li>
</ul>
<h3 id="learn-more">Learn more
</h3>
<p>For a comprehensive overview of new and old data frame features, see the updated articles on <a href="https://shiny.posit.co/py/components/outputs/data-grid/" target="_blank" rel="noopener">DataGrid</a>
 and <a href="https://shiny.posit.co/py/components/outputs/data-table/" target="_blank" rel="noopener">DataTable</a>
.</p>
<hr>
<p>We&rsquo;re thrilled to bring you these new features and improvements in Shiny for Python 1.2.0. As always, if you have any questions or feedback, please <a href="https://discord.gg/yMGCamUMnS" target="_blank" rel="noopener">join us on Discord</a>
 or <a href="https://github.com/posit-dev/py-shiny/issues/new" target="_blank" rel="noopener">open an issue on GitHub</a>
. Happy Shiny-ing!</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/shiny/shiny-python-1.2/shinyforpython-120.jpg" length="44254" type="image/jpeg" />
    </item>
  </channel>
</rss>
