<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: corridor project</title>
    <description>The latest articles on DEV Community by corridor project (@pj-corridor).</description>
    <link>https://dev.to/pj-corridor</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4136824%2F823de776-b646-4464-813f-1e6b5ebaea4b.png</url>
      <title>DEV Community: corridor project</title>
      <link>https://dev.to/pj-corridor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pj-corridor"/>
    <language>en</language>
    <item>
      <title>How to Handle Challenges in Three.js App Development</title>
      <dc:creator>corridor project</dc:creator>
      <pubDate>Sun, 27 Sep 2026 21:06:30 +0000</pubDate>
      <link>https://dev.to/pj-corridor/how-to-handle-challenges-in-threejs-app-development-41f</link>
      <guid>https://dev.to/pj-corridor/how-to-handle-challenges-in-threejs-app-development-41f</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2Ftitle.png" class="article-body-image-wrapper"&gt;&lt;img width="799" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2Ftitle.png" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hello, my name is pj-corridor, previously an advertising engineer and currently a data platform engineer. In this article, I will share insights gained through my hobby of developing Three.js applications, such as common beginner pitfalls, browser-compatibility issues, and their solutions.&lt;/p&gt;

&lt;p&gt;Let's start by introducing some of my Three.js applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  A New Concept for Reversi
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpj-corridor.net%2Fimages%2Fix-side6-reversi-4-loop.png" class="article-body-image-wrapper"&gt;&lt;img width="784" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpj-corridor.net%2Fimages%2Fix-side6-reversi-4-loop.png" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You might wonder, Is Three.js necessary for Reversi ? But with a 3D board that loops in a cylindrical shape, you can enjoy strategies that were not possible before. Additionally, the introduction of the DMZ area and a variety of NPC options (1 to 3) are available. In the case of the loop board, you can also opt for rotation tactics. The rule-based NPC implementation (as of June 2025) might feel lacking in one-on-one battles, but even experienced players will find the chaotic four-player matches (NPC x3 + human) challenging. Feel free to try it while waiting for the train.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/side-six/side-six-reversi.html?type=traditional" rel="noopener noreferrer"&gt;Standard Board Reversi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/side-six/side-six-reversi.html?type=hexmap&amp;amp;level=3" rel="noopener noreferrer"&gt;Hex Board Reversi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/side-six/side-six-reversi.html?type=variety&amp;amp;level=2" rel="noopener noreferrer"&gt;Variety Board Reversi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/side-six/side-six-reversi.html?type=loop&amp;amp;level=5" rel="noopener noreferrer"&gt;Loop Board Reversi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Diverse Puzzles
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpj-corridor.net%2Fimages%2Fix-cube1.png" class="article-body-image-wrapper"&gt;&lt;img width="784" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpj-corridor.net%2Fimages%2Fix-cube1.png" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I developed various puzzles by expanding on the classic Three.js study subject, the Rubik's Cube. However, my collection still pales in comparison to &lt;a href="https://www.youtube.com/@Z3Cubing" rel="noopener noreferrer"&gt;Z3Cubing&lt;/a&gt;. In the future, I aim to explore directions that cannot be realized with physical gadgets (such as &lt;a href="https://pj-corridor.net/cube3d/caterpillar.html" rel="noopener noreferrer"&gt;Cube with Anomalous Rotations&lt;/a&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/cube3d/cube3d.html" rel="noopener noreferrer"&gt;Standard Rubik's Cube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/cube3d/cube3d.html?level=3" rel="noopener noreferrer"&gt;Cube with Anomalous Piece Shapes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/cube3d/caterpillar.html" rel="noopener noreferrer"&gt;Cube with Anomalous Rotations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/cube3d/diamond.html" rel="noopener noreferrer"&gt;Diamond-Shaped Puzzle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/cube3d/gemini.html" rel="noopener noreferrer"&gt;Twin Cube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/side-six/side-six.html" rel="noopener noreferrer"&gt;Cylinder-Shaped Puzzle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stick Figures
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpj-corridor.net%2Fimages%2Fix-figure.png" class="article-body-image-wrapper"&gt;&lt;img width="784" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpj-corridor.net%2Fimages%2Fix-figure.png" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I often create document using a lot of simple illustrations in PowerPoint slides (&lt;a href="https://lydesign.jp/n/n3aa55611b347" rel="noopener noreferrer"&gt;This is a blog article&lt;/a&gt; written in Japanese about simple illustration), but finding copyright-free stick figure materials to paste on slides can be a hassle. So, I decided to develop my own. Feel free to use them in your slides as well.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/stick-figure/stick-figure.html" rel="noopener noreferrer"&gt;Stick Figure (Joint Operation Posing)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/stick-figure/rubber-figure.html" rel="noopener noreferrer"&gt;Rubber Figure (Bend and Stretch Posing)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/stick-figure/hand.html" rel="noopener noreferrer"&gt;Hand (Bend and Stretch Posing)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stick Figure Gallery
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpj-corridor.net%2Fimages%2Ffigure-gallery.png" class="article-body-image-wrapper"&gt;&lt;img width="710" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpj-corridor.net%2Fimages%2Ffigure-gallery.png" height="1155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While the stick figures solved the issue of finding materials, even posing them became cumbersome :-p. So, I prepared structured pose data input/output and a gallery. You can find something close to your image and make slight adjustments to obtain the desired stick figure material.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/stick-figure/gallery/index.html" rel="noopener noreferrer"&gt;Stick Figure Gallery&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As the next step, I'm envisioning a stick figure that generates appropriate poses from natural language (e.g., words expressing emotions or postures) using machine learning by labeling pose data.&lt;/p&gt;

&lt;p&gt;Now, let's delve into the insights gained through Three.js app development.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Empty Draw Buffer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2Fscreenshot.gif" class="article-body-image-wrapper"&gt;&lt;img width="799" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2Fscreenshot.gif" height="685"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://pj-corridor.net/stick-figure/stick-figure.html" rel="noopener noreferrer"&gt;Stick Figure&lt;/a&gt;, &lt;a href="https://pj-corridor.net/stick-figure/rubber-figure.html" rel="noopener noreferrer"&gt;Rubber Figure&lt;/a&gt;, and &lt;a href="https://pj-corridor.net/stick-figure/hand.html" rel="noopener noreferrer"&gt;Hand&lt;/a&gt;, I implemented a screenshot feature that copies the image of the determined pose to the clipboard. This feature uses &lt;code&gt;toDataURL()&lt;/code&gt; on &lt;code&gt;WebGLRenderer.domElement&lt;/code&gt; (&lt;a href="https://threejs.org/docs/#api/en/renderers/WebGLRenderer.domElement" rel="noopener noreferrer"&gt;reference&lt;/a&gt;), but initially, I struggled to capture the rendered image.&lt;/p&gt;

&lt;p&gt;For example, in the following code :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebGLRenderer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPixelRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devicePixelRatio&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PerspectiveCamera&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Scene&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geometry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BoxGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;material&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MeshNormalMaterial&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Mesh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// (1) capture the canvas image right after WebGLRenderer.render()&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// (2) capture the canvas image in a timer event handler&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;in_ev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// (3) capture the canvas image in a click event handler&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the timing of (1), &lt;code&gt;toDataURL()&lt;/code&gt; provides the expected output, but at (2) and (3), it doesn't work. This is because the &lt;code&gt;WebGLRenderer&lt;/code&gt; automatically clears the draw buffer after rendering each frame. By trying to set &lt;code&gt;WebGLRenderer.preserveDrawingBuffer&lt;/code&gt; (&lt;a href="https://threejs.org/docs/#api/en/renderers/WebGLRenderer.preserveDrawingBuffer" rel="noopener noreferrer"&gt;reference&lt;/a&gt;) to retain the draw buffer :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebGLRenderer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;preserveDrawingBuffer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can obtain the expected output even at the asynchronous timings of (2) and (3). However, according to the &lt;a href="https://registry.khronos.org/webgl/specs/latest/1.0/" rel="noopener noreferrer"&gt;WebGL Specification&lt;/a&gt; :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While it is sometimes desirable to preserve the drawing buffer, it can cause significant performance loss on some platforms. Whenever possible this flag should remain false and other techniques used.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Given the non-normative note and past related bugs reported in WebKit, I decided not to change the default &lt;code&gt;false&lt;/code&gt; for the drawing buffer setting and instead re-render just before &lt;code&gt;toDataURL()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// (2) capture the canvas image in a timer event handler&lt;/span&gt;
    &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;in_ev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// (3) capture the canvas image in a click event handler&lt;/span&gt;
    &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, I successfully implemented the screenshot feature. Please try pasting it into PowerPoint slides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Three.js Raycasting Traps
&lt;/h2&gt;

&lt;p&gt;In Three.js applications, &lt;a href="https://threejs.org/docs/#api/en/core/Raycaster" rel="noopener noreferrer"&gt;raycasting&lt;/a&gt; is used to determine the intersection between the coordinates where touch or mouse events occur and objects.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Raycasting is used for mouse picking (working out what objects in the 3D space the mouse is over) amongst other things.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here, I will introduce three traps (or failures) related to raycasting.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Intrusive AxesHelper
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://pj-corridor.net/stick-figure/stick-figure.html" rel="noopener noreferrer"&gt;Stick Figure&lt;/a&gt; and &lt;a href="https://pj-corridor.net/cube3d/cube3d.html" rel="noopener noreferrer"&gt;Rubik's Cube&lt;/a&gt;, raycasting from the coordinates where &lt;code&gt;touchstart&lt;/code&gt; or &lt;code&gt;mousedown&lt;/code&gt; events occur is used to determine if :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There is an intersection with an object in the scene :

&lt;ul&gt;
&lt;li&gt;Drag the intersecting part&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;touchmove&lt;/code&gt; or &lt;code&gt;mousemove&lt;/code&gt; to manipulate the part (e.g., change the pose)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;There is no intersection with an object in the scene :

&lt;ul&gt;
&lt;li&gt;Drag from that coordinate&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;touchmove&lt;/code&gt; or &lt;code&gt;mousemove&lt;/code&gt; to rotate the object (actually, the object itself does not rotate; the &lt;code&gt;PerspectiveCamera&lt;/code&gt;, which continuously look at the object, moves in the opposite direction of the &lt;code&gt;touchmove&lt;/code&gt; or &lt;code&gt;mousemove&lt;/code&gt; events)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the common UX. However, when I added an &lt;code&gt;AxesHelper&lt;/code&gt; (three-colored lines representing axes) to the scene for debugging purposes, it sometimes behaved strangely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2FAxesHelper.png" class="article-body-image-wrapper"&gt;&lt;img width="800" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2FAxesHelper.png" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reason was that &lt;code&gt;AxesHelper&lt;/code&gt; itself was a target for &lt;code&gt;Raycaster.intersectObject()&lt;/code&gt; (&lt;a href="https://threejs.org/docs/#api/en/core/Raycaster.intersectObject" rel="noopener noreferrer"&gt;reference&lt;/a&gt;). To address this, either consider intersections with it or exclude its influence before checking intersections, as shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;in_child&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;in_child&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AxesHelper&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intersects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;raycaster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intersectObjects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Ghost CircleGeometry
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://pj-corridor.net/stick-figure/stick-figure.html" rel="noopener noreferrer"&gt;Stick Figure&lt;/a&gt;, part manipulation works as follows :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add an operation object to the scene when a part is dragged :

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SphereGeometry&lt;/code&gt; with the same radius as the height of the target part&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CircleGeometry&lt;/code&gt; that passes through the center of the &lt;code&gt;SphereGeometry&lt;/code&gt; with its normal vector facing the &lt;code&gt;PerspectiveCamera&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use raycasting from the coordinates where &lt;code&gt;touchmove&lt;/code&gt; or &lt;code&gt;mousemove&lt;/code&gt; events occur to determine the intersection direction with the operation object, and have the dragged part &lt;code&gt;lookAt()&lt;/code&gt; (&lt;a href="https://threejs.org/docs/#api/en/core/Object3D.lookAt" rel="noopener noreferrer"&gt;reference&lt;/a&gt;) it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's a demonstration with the operation object colored for debugging, showing how the stick figure's hand moves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2FCircleGeometry.gif" class="article-body-image-wrapper"&gt;&lt;img width="800" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2FCircleGeometry.gif" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes, raycasting occurs from the opposite direction of the object in 3D space, and in that case the hit-test behaves unexpectedly. I found that &lt;code&gt;SphereGeometry&lt;/code&gt; does not intersect with raycasting from the back. Since &lt;code&gt;CircleGeometry&lt;/code&gt; always faces the &lt;code&gt;PerspectiveCamera&lt;/code&gt;, this was a blind spot ! ^^;&lt;/p&gt;

&lt;p&gt;In this case, you can use a material for both sides, for example :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geometry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CircleGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;material&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MeshNormalMaterial&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;side&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DoubleSide&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;circle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Mesh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Why Do Bent SkinnedMeshes Lack Vertices ?
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://pj-corridor.net/stick-figure/rubber-figure.html" rel="noopener noreferrer"&gt;Rubber Figure&lt;/a&gt; and &lt;a href="https://pj-corridor.net/stick-figure/hand.html" rel="noopener noreferrer"&gt;Hand&lt;/a&gt;, &lt;code&gt;SkinnedMesh&lt;/code&gt; is used to smoothly bend parts. That worked nicely, except that bent parts no longer hit in ray-casting. While there was no information about this in the &lt;a href="https://threejs.org/docs/#api/en/objects/SkinnedMesh" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, I wondered why.&lt;/p&gt;

&lt;p&gt;After researching forums and the &lt;a href="https://github.com/mrdoob/three.js/blob/master/src/objects/SkinnedMesh.js" rel="noopener noreferrer"&gt;implementation&lt;/a&gt;, I understood that the actual vertex data is not changed, and the influence of bones is calculated and drawn. So, I created an &lt;code&gt;ExtrudeGeometry&lt;/code&gt;  (&lt;a href="https://threejs.org/docs/#api/en/geometries/ExtrudeGeometry" rel="noopener noreferrer"&gt;reference&lt;/a&gt;) using &lt;code&gt;SkinnedMesh.skeleton.bones&lt;/code&gt; as rough alternative vertex data, and by dragging the intersection with raycasting, I was able to manipulate the rubber figure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2Frubber-figure.gif" class="article-body-image-wrapper"&gt;&lt;img width="800" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2Frubber-figure.gif" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser Compatibility Issues with AdSense
&lt;/h2&gt;

&lt;p&gt;Once the Three.js app was taking shape, I decided to try implementing AdSense. However, I encountered unexpected hurdles in coexisting Three.js apps with AdSense. Here, I will share the journey of resolving browser compatibility issues related to iframes.&lt;/p&gt;

&lt;p&gt;During initialization and window resizing, Three.js apps require appropriate coordinate processing and settings changes for rendering :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change &lt;code&gt;PerspectiveCamera.aspect&lt;/code&gt; (&lt;a href="https://threejs.org/docs/#api/en/cameras/PerspectiveCamera.aspect" rel="noopener noreferrer"&gt;reference&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;PerspectiveCamera.updateProjectionMatrix()&lt;/code&gt; (&lt;a href="https://threejs.org/docs/#api/en/cameras/PerspectiveCamera.updateProjectionMatrix" rel="noopener noreferrer"&gt;reference&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;WebGLRenderer.setSize()&lt;/code&gt; (&lt;a href="https://threejs.org/docs/#api/en/renderers/WebGLRenderer.setSize" rel="noopener noreferrer"&gt;reference&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, since &lt;a href="https://support.google.com/adsense/answer/9190028" rel="noopener noreferrer"&gt;AdSense code&lt;/a&gt; may automatically insert ads, potentially changing the size of other elements, similar processing is needed at that timing. For example, the &lt;code&gt;offsetHeight&lt;/code&gt; of elements is changed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2Fadsense.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2Fadsense.gif" width="300" height="649"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, since there are writes to the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; of &lt;code&gt;WebGLRenderer.domElement&lt;/code&gt; in the &lt;a href="https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLRenderer.js" rel="noopener noreferrer"&gt;implementation&lt;/a&gt;, calling it within the callback of &lt;code&gt;ResizeObserver&lt;/code&gt; feels a bit risky (incidentally, in the &lt;a href="https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/resize_observer/resize_observer.cc" rel="noopener noreferrer"&gt;Chromium implementation&lt;/a&gt;, it checks for changes in element size from the previous observation, so it doesn't fall into an infinite loop).&lt;/p&gt;

&lt;p&gt;Therefore, by placing &lt;code&gt;WebGLRenderer.domElement&lt;/code&gt; inside an &lt;code&gt;iframe&lt;/code&gt; :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Automatic ad insertion by AdSense&lt;/li&gt;
&lt;li&gt;Resizing of the &lt;code&gt;iframe&lt;/code&gt; due to the above&lt;/li&gt;
&lt;li&gt;Event handler processing due to the above

&lt;ul&gt;
&lt;li&gt;Resize &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; of &lt;code&gt;WebGLRenderer.domElement&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Change settings for coordinate processing and rendering&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I tried the following approach.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createOuterWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;in_document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iframe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;in_document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iframe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;in_document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentWindow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outerWin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createOuterWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outerDoc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;outerWin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// myCanvas : WebGLRenderer.domElement&lt;/span&gt;
&lt;span class="nx"&gt;outerDoc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myCanvas&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;outerWin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;in_event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// maintain PerspectiveCamera.aspect etc&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, while it worked as expected in Chrome (137.0), &lt;code&gt;WebGLRenderer.domElement&lt;/code&gt; did not display in Firefox (139.0). So, I tried changing the processing timing. According to the &lt;a href="https://html.spec.whatwg.org/#the-iframe-element" rel="noopener noreferrer"&gt;specification&lt;/a&gt; of &lt;code&gt;iframe&lt;/code&gt;, &lt;code&gt;iframe&lt;/code&gt; without &lt;code&gt;src&lt;/code&gt; or &lt;code&gt;srcdoc&lt;/code&gt; attributes load &lt;code&gt;about:blank&lt;/code&gt; as a default, so :&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;If url matches about:blank and initialInsertion is true, then: Run the iframe load event steps given element.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;How about this timing ?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outerWin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createOuterWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;outerWin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outerDoc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;outerWin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// myCanvas : WebGLRenderer.domElement&lt;/span&gt;
    &lt;span class="nx"&gt;outerDoc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myCanvas&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, it worked as expected in Firefox, but the event did not execute in Chrome. Related discussions can be found in &lt;a href="https://github.com/whatwg/html/issues/6863" rel="noopener noreferrer"&gt;stop triggering navigations to about:blank on iframe insertion&lt;/a&gt;, but by delaying the processing to the next event loop, I achieved the expected behavior in both browsers, so I decided to leave it at that for now and document it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outerWin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createOuterWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// asynchronous process for Firefox&lt;/span&gt;
&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outerDoc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;outerWin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// myCanvas : WebGLRenderer.domElement&lt;/span&gt;
    &lt;span class="nx"&gt;outerDoc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myCanvas&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, I finally managed to keep up with automatic ad insertion for coordinate processing and rendering settings changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Super Special Duper Animation Function
&lt;/h2&gt;

&lt;p&gt;Now that AdSense implementation is settled, I want to brush up on the overall UX. In my Three.js apps, &lt;code&gt;WebGLRenderer&lt;/code&gt; rendering adopts animation expressions overall, but I want to adopt similar UX for rendering regular HTML elements (such as dialog displays) as well. However, I don't want to disperse animation-related descriptions like CSS &lt;code&gt;@keyframes&lt;/code&gt; definitions. Here's an implementation I came up with to manage it simply and centrally with JavaScript code only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;autoTransition1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;in_elem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;in_shorthand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;in_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;in_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;in_resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,,,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;in_shorthand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// convert from CSS to CSSOM&lt;/span&gt;
        &lt;span class="nx"&gt;prop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;])&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;in_match&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;in_letter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;in_letter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;in_elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transition&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;in_shorthand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;in_elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;in_start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// automatically start the transition in the next event loop&lt;/span&gt;
        &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;in_elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;in_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;in_ev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;in_ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;propertyName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;in_elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transitionend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;in_resolve&lt;/span&gt;&lt;span class="p"&gt;)();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nx"&gt;in_elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transitionend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;autoTransition2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;in_elem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;in_shorthand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;in_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;in_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;autoTransition1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;in_elem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;in_shorthand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;in_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;in_end&lt;/span&gt;&lt;span class="p"&gt;))();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;autoTransition2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;color 1.5s ease-out&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By specifying the shorthand of CSS Transitions and the start and end values of transition-property, you can execute animations related to elements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2Fdialog.gif" class="article-body-image-wrapper"&gt;&lt;img width="600" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fthreejs%2Fimg%2Fdialog.gif" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for reading this far. Although I didn't create headings for them, there were various minor failures as well. For example, many classes in Three.js have a &lt;code&gt;clone()&lt;/code&gt; method implemented, but when I implemented a new class by inheriting a class, I struggled with suspicious behavior of cloned instances due to changing the constructor's interface, which is quite embarrassing.&lt;/p&gt;

&lt;p&gt;I hope that the insights (or failures ?) I gained through Three.js app development will be useful information for you.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>webdev</category>
      <category>threejs</category>
    </item>
    <item>
      <title>Improving Online Meetings with Doodles</title>
      <dc:creator>corridor project</dc:creator>
      <pubDate>Sat, 26 Sep 2026 03:03:30 +0000</pubDate>
      <link>https://dev.to/pj-corridor/improving-online-meetings-with-doodles-5b6p</link>
      <guid>https://dev.to/pj-corridor/improving-online-meetings-with-doodles-5b6p</guid>
      <description>&lt;p&gt;Hello, I'm pj-corridor, a platform engineer. This article is a translated version of the &lt;a href="https://lydesign.jp/n/n3aa55611b347" rel="noopener noreferrer"&gt;original Japanese article&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  0. Whiteboard
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fwhite.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fwhite.png" width="675" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the past, when going to the office was the norm, discussions utilizing whiteboards were held throughout the office. In retrospect, discussions using whiteboards were effective in many respects, such as :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organizing one\'s ideas&lt;/li&gt;
&lt;li&gt;Enhancing participants\' creativity&lt;/li&gt;
&lt;li&gt;Aligning participants\' understanding&lt;/li&gt;
&lt;li&gt;Early detection of issues by drawing out participants\' perspectives and questions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For companies in the process of integrating knowledge and culture between former organizations, such as right after an M&amp;amp;A, we would like to bridge the gaps through discussions using whiteboards, as we did before. However, in situations where global collaboration and diverse working styles are prevalent, it is not always possible for all stakeholders to gather in person. Then, it is essential to enhance the quality of online communication in our daily operations.&lt;/p&gt;

&lt;p&gt;Therefore, as an alternative to discussions using whiteboards, I'd like to suggest using doodles extensively, along with discussions that utilize them.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Benefits of Doodles
&lt;/h2&gt;

&lt;p&gt;As Richard Sennett explores in "The Craftsman", the act of making is inherently linked to thinking and problem-solving. In the process of devising doodles while preparing for discussions, you can better organize your thoughts and points of argument. This alone makes creating doodles highly recommended.&lt;/p&gt;

&lt;p&gt;Moreover, since doodles are easy to grasp in concrete form, participants can easily spot inconsistencies and unclear points. The visual stimulation also draws out participants\' creativity, sparking new ideas and solutions. Additionally, the &lt;a href="https://en.wikipedia.org/wiki/Picture_superiority_effect" rel="noopener noreferrer"&gt;picture superiority effect&lt;/a&gt; makes it easier to remember information after online communication.&lt;/p&gt;

&lt;p&gt;While using existing materials is rational from the perspective of reducing preparation time, if you value the benefits mentioned above, I recommend investing in the use of doodles I suggested.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Increase the Number of Pages
&lt;/h2&gt;

&lt;p&gt;First, don\'t hesitate to increase the number of pages. However, increasing the number of pages is not the same as overwhelming participants with voluminous materials. The goal is to enhance the quality of online communication.&lt;/p&gt;

&lt;p&gt;For example, having participants navigate back and forth between slides or transitioning to linked content to reference specific information can hinder understanding and discussion. If you want to repeatedly refer to the same slide, it should be reinserted each time. If you are concerned about maintenance errors caused by copying, you can manage slides that need to be reused as slide-master layouts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fslide-master-1-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fslide-master-1-en.png" width="798" height="119"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's increase the number of pages further. With that mindset, let me introduce :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Where We Are Now&lt;/li&gt;
&lt;li&gt;Pay Attention !&lt;/li&gt;
&lt;li&gt;Insights from Change&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. Where We Are Now
&lt;/h2&gt;

&lt;p&gt;Participants may feel stressed about leaving inconsistencies or unclear points unresolved, even if they don't want to interrupt the conversation with trivial questions. Therefore, insert slides that clearly indicate when the topic changes (in this case, I recommend to use slides with inverted brightness) and visualize the progress at the same time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-001-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-001-en.png" width="600" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By doing this, you can expect effects such as :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Making it easier for participants to determine when to ask questions&lt;/li&gt;
&lt;li&gt;Allowing presenters to create opportunities to ask about understanding based on the situation &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Intentionally creating timing can promote interactivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Pay Attention !
&lt;/h2&gt;

&lt;p&gt;Emphasizing a part of doodles to draw participants\' attention is useful when you want to explain the whole in order or deepen discussions on important parts.&lt;/p&gt;

&lt;p&gt;For example :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Duplicate the doodle and change the overall tone to a grayed-out expression, such as overlaying it with 10% transparency white.&lt;/li&gt;
&lt;li&gt;Add the slide from step 1 as a layout in the slide master.&lt;/li&gt;
&lt;li&gt;Overlay the original doodle on a new slide using the layout from step 2.&lt;/li&gt;
&lt;li&gt;Remove everything except the part you want to highlight.&lt;/li&gt;
&lt;li&gt;As a result, the removed parts will be grayed out.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The slide created using these steps looks like this :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-003.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-003.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By overlaying a dark color, for example, 50% transparency black, it looks like this :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-004.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-004.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also use radial lines or background colors. In this case, having a set of reusable parts is convenient.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-005.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-005.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Insights from Change
&lt;/h2&gt;

&lt;p&gt;Things that are hard to notice only in snapshots can become apparent by demonstrating change, making it easier for participants to gain insights. This is a doodle used in discussions on real projects. With a doodle only Before &amp;amp; After, which omits several changes in process, participants\' thoughts cannot keep up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-006.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-006.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By inserting doodles that express several changes in between, you can draw out more perspectives from participants, leading to the early detection and improvement of issues.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-007.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-007.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, you might gain insights like this at each step :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In this step, presenter uses several changes to appeal to participants\' mental models about the "integration of A and B". Participants will then be concerned about attribute information or state conflicts linked to A and B and will demonstrate creativity to resolve them.&lt;/li&gt;
&lt;li&gt;In this step, presenter explains that C requires special treatment, including the background. Participants will then understand that this state is challenging to express in the UI.&lt;/li&gt;
&lt;li&gt;In this step, presenter explains proposal that aligns the treatment of D and E with C, maintaining consistency overall. Participants will then deepen their understanding of the Before &amp;amp; After based on the background and simultaneously begin exploring better alternatives.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can expect to draw out participants\' creativity in this way.&lt;/p&gt;

&lt;p&gt;On a side note, when someone starts using the pen tool of online meeting system, other participants tend to wait (in a state of suspended thinking) while watching the screen until it's completed. Then, the finished modern art confuses participants anew.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fpen.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fpen.png" width="595" height="42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pen tool promotes interactivity through visual stimulation, but it seems more effective when used simply, such as :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding a few lines&lt;/li&gt;
&lt;li&gt;Marking elements with "o" or "x"&lt;/li&gt;
&lt;li&gt;Drawing one or two additional elements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With doodles that include several changes, you can easily select a base doodle that matches the discussion point, maximizing the effect of the pen tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Avoid Preconceptions
&lt;/h2&gt;

&lt;p&gt;As stimulating participants\' creativity is one goal, be mindful of preconceptions, misunderstandings, and distractions that hinder understanding. For example, if size is a discussion point, the doodle should represent size relationships without causing misunderstandings.&lt;/p&gt;

&lt;p&gt;Also, when expressing connections, it\'s important to minimize misunderstandings. In the following doodle, when connecting boxes labeled with the same letter, using an elbow connector is not recommended. Instead, there is room for improvement by using straight or curved lines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-008.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-008.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, how about visual separation of intersections using masks ?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-009.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-009.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shifting the intersection itself might also work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-010.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-010.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can move connectors to the front or change the order of masks and connectors using "Format Shape" + "Selection Pane".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Flayer-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Flayer-en.png" width="232" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, let's express the relationship of the vertices of an equilateral triangle with doodle. When positioning an equilateral triangle at the center of a slide according to PowerPoint guides, the center and centroid of the triangle do not align. Therefore, a doodle connecting each vertex from the center may give the impression that vertex A is a special entity, which is inappropriate for expressing equal relationships.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-011.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-011.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A doodle connecting each vertex from the centroid of an equilateral triangle can clearly indicate the equal relationship of each vertex.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-012.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-012.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the case of a regular pentagon, a doodle connecting each vertex from the center of the guide reveals the source of discomfort when rotated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-013.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-013.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A doodle connecting each vertex from the centroid maintains a beautiful regular pentagon even when rotated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-014.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-014.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next is a topic specific to concrete examples. A doodle often expresses API offerings or data exchanges. In this case, the direction of the HTTP request calling the API and the direction of data exchange may not only be the same but also opposite. Be careful not to cause preconceptions or misunderstandings about what the arrows in the doodle are intended to represent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-025.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-025.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Which Preset Storage ?
&lt;/h2&gt;

&lt;p&gt;A doodle often represents storage, especially for system use, but PowerPoint has two types of preset storage. Cylinder A cannot adjust the shape of the bottom, resulting in awkward perspective when elongated. Which cylinder should be used ?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-015.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-015.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cylinder B allows for expressing data accumulation or proportions in storage by overlaying cylinders without using gradient fills. In conclusion, using cylinder B seems more flexible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-016.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-016.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Doodle in Tables
&lt;/h2&gt;

&lt;p&gt;There are times when you want to express the behavior for each use case using both tables and doodles. While separating tables and doodles into different slides makes the material tidy, you have to navigate between slides to understand what happens in each case.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-017.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-017.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How about making a doodle into a table ? Although it may look cluttered, it succinctly expresses what happens in each case. It's also possible to embed the doodle part using Slide Zoom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-018.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-018.png" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  9. You Can Draw Anything
&lt;/h2&gt;

&lt;p&gt;Like generative AI improves accuracy with specific examples, specific examples are beneficial when dealing with humans too. By providing specific examples as input, you can set expectations for the direction of arguments and feedback, enabling more targeted discussions. Finally, for your specific examples, I\'ll share how to draw doodles with high flexibility.&lt;/p&gt;

&lt;p&gt;For example, a diamond can be created by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Combining triangles&lt;/li&gt;
&lt;li&gt;Coloring each part with gradient fills&lt;/li&gt;
&lt;li&gt;Grouping them&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-019.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-019.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bags and wallets can be drawn easily. Reuse them for advertisements or EC-specific examples.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-020.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-020.png" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For more flexible doodle preparation, prepare a shape with rounded edges by surrounding a square with curves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-021.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-021.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same applies to shapes other than squares.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-022.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-022.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connect and join these to create a stick figure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-023.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fsec-023.gif" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you'd like to use stick figures more easily, please check out my &lt;a href="https://pj-corridor.net/stick-figure/gallery/index.html" rel="noopener noreferrer"&gt;Stick Figure Gallery&lt;/a&gt; made by Three.js. You can find something close to your image and make slight adjustments to obtain the desired stick figure material.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpj-corridor.net%2Fimages%2Ffigure-gallery.png" class="article-body-image-wrapper"&gt;&lt;img width="710" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpj-corridor.net%2Fimages%2Ffigure-gallery.png" height="1155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a side note, PNG or JPG images can also be color-adjusted or have their backgrounds removed in PowerPoint. For example, when expressing multiple characters with PNG image icons, you can handle it with color adjustments without preparing different images.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fimg-en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fpower-punch%2Fimg%2Fimg-en.png" width="338" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for reading this far. In fact, my method using doodles is still in the process of trial and error and improvement, and it is not yet systematically organized, with ongoing updates planned. The burden of drawing doodles remains a challenge. On the other hand, within the scope summarized in this article, we have been able to experience its effectiveness through actual work, and we hope that readers will try it as a means to improve online communication.&lt;/p&gt;

&lt;p&gt;The PowerPoint file used in this article is available &lt;a href="https://github.com/nakayama-kazuki/2021/blob/master/pptx/power-punch-v2.pptx" rel="noopener noreferrer"&gt;here&lt;/a&gt;, so feel free to use it.&lt;/p&gt;

</description>
      <category>resources</category>
      <category>powerpoint</category>
      <category>presentation</category>
    </item>
    <item>
      <title>Did Generative AI Do a Good Job Today? What About Tomorrow?</title>
      <dc:creator>corridor project</dc:creator>
      <pubDate>Wed, 23 Sep 2026 21:33:11 +0000</pubDate>
      <link>https://dev.to/pj-corridor/did-generative-ai-do-a-good-job-today-what-about-tomorrow-29g3</link>
      <guid>https://dev.to/pj-corridor/did-generative-ai-do-a-good-job-today-what-about-tomorrow-29g3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fogp.png" class="article-body-image-wrapper"&gt;&lt;img width="800" alt="ogp" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fogp.png" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hi, I'm pj-corridor, an engineer.&lt;/p&gt;

&lt;p&gt;A while ago, Google's AI Overviews drew attention after surfacing &lt;a href="https://blog.google/products-and-platforms/products/search/ai-overviews-update-may-2024/" rel="noopener noreferrer"&gt;advice about using glue on pizza&lt;/a&gt;. How do you approach the quality of generative AI output?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I want to refactor an increasingly bloated prompt, but I'm worried about unintended side effects.&lt;/li&gt;
&lt;li&gt;After upgrading the model, will those carefully tuned prompt tricks still work the same way?&lt;/li&gt;
&lt;li&gt;A change to the generation pipeline seems functionally fine, but could it introduce regressions in edge cases?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I imagine many development teams face similar concerns. In the service I work on, we also had a need to automatically generate briefings from news articles and social media posts using generative AI.&lt;/p&gt;

&lt;p&gt;In this article, I'll use automated briefing generation as a case study to explain how we evaluated the quality of generative AI output.&lt;/p&gt;

&lt;h1&gt;
  
  
  Overview of the Evaluation Process
&lt;/h1&gt;

&lt;p&gt;Let's start with an overview of the evaluation process. The main actors are experienced editors and operations staff (green), an evaluation bot (blue), and the generation pipeline (red).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi00.png" class="article-body-image-wrapper"&gt;&lt;img width="800" alt="evaluation roles" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi00.png" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ask experienced editors to create concrete examples of "good briefings" (hereafter, Gold Data).&lt;/li&gt;
&lt;li&gt;Have them abstract the Gold Data into criteria that define what makes a briefing "good" (hereafter, Rubrics), which become the basis of the evaluation bot.&lt;/li&gt;
&lt;li&gt;Run an initial validation. Can the evaluation bot assign high scores to the Gold Data?&lt;/li&gt;
&lt;li&gt;Review either the Gold Data or the Rubrics until the validation produces the expected results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi01.png" class="article-body-image-wrapper"&gt;&lt;img width="800" alt="Gold Data and Rubric validation" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi01.png" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the initial version of the briefing generation pipeline.&lt;/li&gt;
&lt;li&gt;Run the generation and evaluation batch process.

&lt;ul&gt;
&lt;li&gt;Generate briefings from an input dataset with sufficient volume and diversity.&lt;/li&gt;
&lt;li&gt;Have the evaluation bot produce evaluation reports for the generated briefings.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Improve the generation pipeline based on the reports and treat the result as the next-generation version.&lt;/li&gt;
&lt;li&gt;Repeat generation, evaluation, and improvement until the predefined criteria are met.&lt;/li&gt;
&lt;li&gt;Have experienced editors perform the final deployment review.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi02.png" class="article-body-image-wrapper"&gt;&lt;img width="800" alt="generation to deployment" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi02.png" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Artifacts and reports are managed in the repository. If the model changes in the future, or if the briefing specification changes—for example, to support stronger personalization—we can repeat the process and compare the new version against previous ones when necessary.&lt;/p&gt;

&lt;p&gt;The goal of this process is to maximize the cost-effectiveness of evaluation: the evaluation bot provides a certain level of quality at scale, while experienced editors focus on reviewing risks that require human judgment.&lt;/p&gt;

&lt;p&gt;To support this approach, we implemented an evaluation bot and process-support framework using &lt;a href="https://deepeval.com/" rel="noopener noreferrer"&gt;DeepEval&lt;/a&gt;. Through actual use, however, we encountered a number of challenges.&lt;/p&gt;

&lt;p&gt;Let's take a closer look at those challenges and how we addressed them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating Gold Data
&lt;/h1&gt;

&lt;p&gt;Let's start with a fictional article. It's intentionally long, so feel free to skim it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The International Space and Aerospace Development Agency (ISADA) and other members of an international space exploration team announced on the 7th that initial construction of humanity's first permanent lunar base, "Artemis Base," near the Moon's south pole had been completed. The milestone establishes the infrastructure needed for humans to remain on the lunar surface for extended periods and conduct scientific research and development. The base consists of residential modules, solar power systems, and an experimental plant that extracts hydrogen and oxygen from lunar water resources. Up to six astronauts are expected to rotate through the base, conducting research into the effects of low gravity on the human body, astronomical observations, and technology demonstrations for resource extraction. The base is also expected to serve as a staging point for future crewed missions to Mars, since launching rockets from the Moon could require less fuel than traveling directly from Earth to Mars. A representative of the international exploration team described the completion of the base as a historic step toward humanity becoming "a species that lives in space."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now let's generate several types of short-form text from it.&lt;/p&gt;

&lt;p&gt;First, we prompted:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Give this article a title of no more than 30 characters that concisely conveys its content.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And got:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Humanity's First Moon Base&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That seems like a reasonable title.&lt;/p&gt;

&lt;p&gt;Next, we asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Summarize this article as an internet forum thread title.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And got:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[Good News] New house completed: quiet neighborhood, great sunlight, no oxygen&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That certainly has the right vibe.&lt;/p&gt;

&lt;p&gt;Next:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Express this article as an acrostic using the word "MOON".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And got:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;M&lt;/strong&gt;ankind makes a home beyond Earth &lt;br&gt;&lt;br&gt;
&lt;strong&gt;O&lt;/strong&gt;n the Moon, a new chapter begins &lt;br&gt;&lt;br&gt;
&lt;strong&gt;O&lt;/strong&gt;utward lies the journey to Mars &lt;br&gt;&lt;br&gt;
&lt;strong&gt;N&lt;/strong&gt;ow, space becomes a place to live &lt;br&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not bad for an acrostic, right?&lt;/p&gt;

&lt;p&gt;Now suppose we asked, "Which of these short texts is the best?"&lt;/p&gt;

&lt;p&gt;Without defining what "good" means, there is no reproducible way to answer that question.&lt;/p&gt;

&lt;p&gt;The same applies to briefings. Since it is difficult to arrive at an abstract definition from the outset, at this stage of the overall evaluation process...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi03.png" class="article-body-image-wrapper"&gt;&lt;img width="800" alt="Rubric definition" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi03.png" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...we first create multiple examples of "good briefings" as Gold Data, taking the direction of the service into account.&lt;/p&gt;

&lt;p&gt;We then compare those examples and extract common characteristics such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is accuracy the most important quality?&lt;/li&gt;
&lt;li&gt;Is it more important to make readers want to continue reading?&lt;/li&gt;
&lt;li&gt;What kind of care is required when describing sensitive topics?&lt;/li&gt;
&lt;li&gt;Should clarity be prioritized, for example by using rhetorical techniques?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We use these shared characteristics to define the Rubrics.&lt;/p&gt;

&lt;h1&gt;
  
  
  Defining the Rubrics
&lt;/h1&gt;

&lt;p&gt;DeepEval provides built-in Metrics such as &lt;code&gt;Faithfulness&lt;/code&gt;, which evaluates whether output is faithful to the input, and &lt;code&gt;Summarization&lt;/code&gt;, which evaluates the quality of a summary.&lt;/p&gt;

&lt;p&gt;However, the Rubrics that define a "good briefing" do not necessarily map directly to existing Metrics. For this reason, we chose &lt;a href="https://deepeval.com/docs/metrics-llm-evals" rel="noopener noreferrer"&gt;G-Eval&lt;/a&gt;, which allows Rubrics to be defined in natural language.&lt;/p&gt;

&lt;p&gt;For example, an accuracy Rubric might be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The generated text must not contradict the original and must accurately reflect its content. It must not introduce facts, speculation, exaggeration, or definitive claims that are not present in the original, and it must not present uncertain information as established fact. Missing information alone should not reduce the score.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A Rubric for appropriate handling of sensitive information might be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The generated text must handle sensitive topics such as death, accidents, disasters, crime, illness, suicide, discrimination, and human rights issues with appropriate care. It must not include unnecessary condemnation or ridicule of victims, bereaved families, related parties, or people merely suspected of wrongdoing. It must not damage someone's reputation or credibility through speculation or unverified information. It must not use sensational or entertainment-oriented language to excessively attract attention to sensitive topics.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The evaluation bot refers to Rubrics like these when scoring outputs.&lt;/p&gt;

&lt;p&gt;Because evaluations from multiple perspectives can be run in parallel, adding more perspectives does not necessarily have a large impact on processing time. However, increasing the number of Rubrics also creates problems such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instructions may overlap across Rubrics, reducing maintainability.&lt;/li&gt;
&lt;li&gt;Instructions may conflict across Rubrics. For example, improving "conciseness" could worsen "completeness," making it harder to improve the generation pipeline coherently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These issues require care.&lt;/p&gt;

&lt;p&gt;So, at this stage of the overall evaluation process...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi05.png" class="article-body-image-wrapper"&gt;&lt;img width="800" alt="Gold Data and Rubric review" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi05.png" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...we report not only the consistency between the Gold Data and the Rubrics, but also overlaps and conflicts among the Rubrics themselves, helping us improve the evaluation bot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Freview-en.png" class="article-body-image-wrapper"&gt;&lt;img width="799" alt="Gold Data and Rubric review report" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Freview-en.png" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Going through this step helps both humans and the evaluation bot develop a clearer understanding of what constitutes a "good briefing."&lt;/p&gt;

&lt;p&gt;That said, although &lt;a href="https://aclanthology.org/2023.emnlp-main.153.pdf" rel="noopener noreferrer"&gt;the original G-Eval paper&lt;/a&gt; reported improvements over previous approaches for summarization tasks, its Spearman correlation with human evaluation was &lt;code&gt;0.514&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We show that G-EVAL with GPT-4 as the backbone model achieves a Spearman correlation of 0.514 with human on summarization task, outperforming all previous methods by a large margin.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We therefore do not assume that validation against Gold Data alone guarantees agreement with human judgment. As described earlier, it is more appropriate to assign the evaluation bot the role of providing "a certain level of quality at scale."&lt;/p&gt;

&lt;h1&gt;
  
  
  Generation Pipeline
&lt;/h1&gt;

&lt;p&gt;At this stage of the overall evaluation process...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi07.png" class="article-body-image-wrapper"&gt;&lt;img width="800" alt="briefing generation pipeline" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi07.png" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...the resulting pipeline will eventually be implemented in the production product. The prompts and rule-based processing (Python code) managed in the framework can therefore be treated as implementation specifications for the production system.&lt;/p&gt;

&lt;p&gt;You could aim for a highly polished pipeline from the beginning, but I recommend keeping the first version simple so that the impact of each modification remains easy to understand. The framework also includes a tool that automatically generates a simple initial prompt from the Rubrics.&lt;/p&gt;

&lt;p&gt;However, problems such as the following can still occur even if the prompt strongly prohibits them or explicitly asks the model to review its output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mixed-language output&lt;/li&gt;
&lt;li&gt;Violations of character limits&lt;/li&gt;
&lt;li&gt;Use of prohibited words&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For that reason, we separated rule-based processing from the prompt and built a pipeline like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a briefing using the prompt.&lt;/li&gt;
&lt;li&gt;Apply rule-based processing to adjust the result and validate constraints.&lt;/li&gt;
&lt;li&gt;If the output does not satisfy the constraints, regenerate the briefing with additional guidance intended to avoid the previous failure.

&lt;ul&gt;
&lt;li&gt;Add the failed output and its failure reason to the prompt as feedback.&lt;/li&gt;
&lt;li&gt;Temporarily adjust the &lt;code&gt;temperature&lt;/code&gt; parameter to increase output diversity.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We decided not to support conditional branching or multi-stage prompting until an actual need arose. Metadata input, however, was treated as a mandatory requirement.&lt;/p&gt;

&lt;p&gt;For example, a teenage woman and a man in his forties might focus on different aspects of the same news story. Similarly, for a social media post about a restaurant, the most effective angle for a briefing might depend on the weather or time of day when the briefing is displayed.&lt;/p&gt;

&lt;p&gt;When metadata is included in the generation prompt, the evaluation bot also enables G-Eval's &lt;code&gt;SingleTurnParams.CONTEXT&lt;/code&gt;, keeping the generation and evaluation conditions aligned.&lt;/p&gt;

&lt;h1&gt;
  
  
  Reducing Evaluation Variance
&lt;/h1&gt;

&lt;p&gt;Now let's evaluate the output of the generation pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi08.png" class="article-body-image-wrapper"&gt;&lt;img width="800" alt="generation and evaluation" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi08.png" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Suppose an accuracy score changes from &lt;code&gt;0.75&lt;/code&gt; in the previous evaluation to &lt;code&gt;0.91&lt;/code&gt; in the current one.&lt;/p&gt;

&lt;p&gt;Can we say the system improved?&lt;/p&gt;

&lt;p&gt;Now suppose the score for appropriate handling of sensitive information changes from &lt;code&gt;0.93&lt;/code&gt; to &lt;code&gt;0.84&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Is that a regression?&lt;/p&gt;

&lt;p&gt;To make decisions based on scores, we need to reduce evaluation variance where possible and understand its characteristics before interpreting changes.&lt;/p&gt;

&lt;p&gt;So first, let's try to reduce that variance.&lt;/p&gt;

&lt;p&gt;Looking at the &lt;code&gt;temperature&lt;/code&gt; parameter, which controls output diversity, &lt;a href="https://aclanthology.org/2023.emnlp-main.153.pdf" rel="noopener noreferrer"&gt;the original G-Eval paper&lt;/a&gt; used a &lt;code&gt;temperature&lt;/code&gt; of &lt;code&gt;0&lt;/code&gt; when evaluating with GPT-3.5 in order to increase model determinism.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We use OpenAI’s GPT family as our LLMs, including GPT-3.5 (text-davinci-003) and GPT-4. For GPT-3.5, we set decoding temperature to 0 to increase the model’s determinism.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Likewise, the &lt;a href="https://deepeval.com/integrations/models/anthropic#in-code" rel="noopener noreferrer"&gt;default&lt;/a&gt; for &lt;code&gt;AnthropicModel&lt;/code&gt; is &lt;code&gt;0.0&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;temperature: A float specifying the model temperature. Defaults to TEMPERATURE if not passed; falls back to 0.0 if unset and raises if &amp;lt; 0.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Based on this, we also use &lt;code&gt;0&lt;/code&gt; for the evaluation bot. This value may need to be reconsidered for future models.&lt;/p&gt;

&lt;p&gt;G-Eval also includes a mechanism that uses the probabilities of candidate scores produced by the generative model to calculate a weighted average and &lt;a href="https://deepeval.com/docs/metrics-llm-evals#how-is-it-calculated" rel="noopener noreferrer"&gt;reduce scoring bias&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This does not directly eliminate evaluation variance, but we use it because we expect it to contribute to more stable scoring. However, some backends, including the Bedrock backend used in this implementation, cannot provide the score-token probabilities required for this mechanism.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the original G-Eval paper, the authors used the probabilities of the LLM output tokens to normalize the score by calculating a weighted summation.&lt;br&gt;
This step was introduced in the paper because it minimizes bias in LLM scoring. This normalization step is automatically handled by deepeval by default (unless you're using a custom model).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We also paid attention to the fact that G-Eval internally performs evaluation in &lt;a href="https://deepeval.com/docs/metrics-llm-evals#how-is-it-calculated" rel="noopener noreferrer"&gt;two stages&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Since G-Eval is a two-step algorithm that generates chain of thoughts (CoTs) for better evaluation, in deepeval this means first generating a series of evaluation_steps using CoT based on the given criteria, before using the generated steps to determine the final score using the parameters presented in an LLMTestCase.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, when evaluating the accuracy Rubric shown above, it may generate &lt;code&gt;evaluation_steps&lt;/code&gt; like these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"evaluation_steps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Read the original text carefully to identify all stated facts, claims, and the degree of certainty attributed to each piece of information."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Read the generated text and list every factual claim, inference, or assertion it contains."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"For each item in the generated text, check whether it is directly supported by the original text. Flag any item that introduces a fact, detail, or figure not present in the original."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Check whether the generated text adds speculation, predictions, or assumptions that are not present in the original."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Check whether the generated text exaggerates or overstates any claim beyond what the original text supports."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Check whether the generated text presents uncertain or conditional information from the original as definite fact."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Check whether any statement in the generated text directly contradicts a statement in the original text."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Do not penalize the generated text for omitting information that appears in the original, as insufficient information volume is not a scoring criterion."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Assign a score based on the number and severity of violations found: no violations warrants the highest score, and each confirmed addition of unsupported facts, speculation, exaggeration, false certainty, or contradiction lowers the score proportionally."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If different &lt;code&gt;evaluation_steps&lt;/code&gt; are generated when evaluating the previous and current versions, that difference itself could contribute to evaluation variance.&lt;/p&gt;

&lt;p&gt;To avoid this, our implementation reuses the same &lt;code&gt;evaluation_steps&lt;/code&gt; unless the Rubric changes. As a side effect, this also reduces processing cost.&lt;/p&gt;

&lt;h1&gt;
  
  
  Understanding Evaluation Variance
&lt;/h1&gt;

&lt;p&gt;Even after these measures, evaluation variance cannot be eliminated entirely.&lt;/p&gt;

&lt;p&gt;In addition, the characteristics of that variance may differ depending on whether an evaluation dimension is discrete and objective, such as factual consistency, or continuous and subjective, such as how engaging a briefing is.&lt;/p&gt;

&lt;p&gt;To make better decisions based on scores, we therefore need to understand the characteristics of evaluation variance.&lt;/p&gt;

&lt;p&gt;Immediately before entering the generation-pipeline improvement cycle...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi06.png" class="article-body-image-wrapper"&gt;&lt;img width="800" alt="evaluation variance check" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi06.png" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...we use a randomly generated input dataset and repeat the same evaluation multiple times to report the characteristics of "evaluation variance."&lt;/p&gt;

&lt;p&gt;For example, the following statistics summarize the standard deviation observed when evaluating &lt;code&gt;articles&lt;/code&gt; input items &lt;code&gt;iterations&lt;/code&gt; times.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_BACKEND_MODEL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"articles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"iterations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"stddevAvg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Average standard deviation of repeated evaluations for the same test data. Lower values indicate more consistent scoring."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"stddevMax"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Maximum standard deviation among all test data. Lower values indicate the worst-case evaluation inconsistency is smaller."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"testDataInfo.stddev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Standard deviation of the average scores across the test data. Higher values indicate the test data covers a wider range of quality."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rubrics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"accuracy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"stddevAvg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.17848219523165634&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"stddevMax"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.3666060555964672&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"testDataInfo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"avg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.7906666666666667&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"stddev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.09308538493710433&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"sensitivity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"stddevAvg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.09983845129437227&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"stddevMax"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.3006659275674582&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"testDataInfo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.98&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.74&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"avg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8904&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"stddev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.05650817050067478&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From these results, we can see that accuracy tends to show evaluation variance more broadly, while appropriate handling of sensitive information tends to show larger variance only for some articles.&lt;/p&gt;

&lt;p&gt;Note that &lt;code&gt;testDataInfo.stddev&lt;/code&gt; is not a measure of evaluation variance. It indicates how widely the evaluation scores are distributed across the input dataset.&lt;/p&gt;

&lt;p&gt;If that distribution is too narrow, we review the input dataset and the resulting output distribution before collecting the statistics again.&lt;/p&gt;

&lt;p&gt;As an aside, TypeSafe AI recently announced &lt;a href="https://typesafe.ai/blog/introducing-system-one-models-and-jev" rel="noopener noreferrer"&gt;Jev&lt;/a&gt;. Its approach of producing a predefined judgment and confidence score rather than generating free-form text looks promising for addressing some of the challenges related to evaluation variance.&lt;/p&gt;

&lt;h1&gt;
  
  
  Iterating on the Improvement Cycle
&lt;/h1&gt;

&lt;p&gt;The evaluation bot interprets score changes while also taking statistics such as &lt;code&gt;stddevAvg&lt;/code&gt; and &lt;code&gt;stddevMax&lt;/code&gt; into account, and produces reports describing potential improvements and regressions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Freport-en.png" class="article-body-image-wrapper"&gt;&lt;img width="800" alt="briefing evaluation report" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Freport-en.png" height="1232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the overall evaluation, the report lists scores and reasons for individual briefings.&lt;/p&gt;

&lt;p&gt;We then use this report to iterate on the improvement cycle.&lt;/p&gt;

&lt;p&gt;However, if the evaluation bot throws a traceback, the improvement cycle stops as well. Avoiding that required handling some exceptions that occur inside DeepEval.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;GEval._evaluate()&lt;/code&gt; contains logic that interprets the value returned by the evaluation model as JSON. With one model, however, we occasionally received output like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;Hmm,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;me&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;provide&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;valid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JSON:&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This causes JSON parsing to fail and can stop the entire batch process.&lt;/p&gt;

&lt;p&gt;As a somewhat pragmatic workaround, when the model returns output that cannot be interpreted as JSON, we retry the request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_raw_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;in_kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;isDeepEvalSafe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# emulate trimAndLoadJson
&lt;/span&gt;            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;in_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;```&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;```&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;retryCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;chatCompletion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toChatCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isDeepEvalSafe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatCompletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="n"&gt;retryCount&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retryCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DeepEval acceptable response could not be generated.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;untrackedCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatCompletion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;untrackedCost&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we repeated the improvement cycle, we discovered a number of practical issues. Here are a few examples.&lt;/p&gt;

&lt;p&gt;News headlines often reflect the publisher's editorial intent—for example, which aspect of a story they want to emphasize.&lt;/p&gt;

&lt;p&gt;In some cases, the generation pipeline was pulled toward that framing and produced briefings that were factually misleading.&lt;/p&gt;

&lt;p&gt;From the evaluation bot's perspective, however, the input and output were still consistent with each other, so the accuracy Rubric could not detect the problem.&lt;/p&gt;

&lt;p&gt;We ultimately resolved the issue by excluding headlines from the input, but the lesson was that evaluation results are affected not only by the evaluation model and Rubrics, but also by the input data itself.&lt;/p&gt;

&lt;p&gt;We also encountered cases where each individual fact was correct, but incorrect relationships between those facts could not be detected.&lt;/p&gt;

&lt;p&gt;For those cases, we revised the accuracy Rubric so that relationships such as "actor," "target," "event," and "condition" would also be preserved and evaluated.&lt;/p&gt;

&lt;p&gt;SNS posts may also contain typos or factual errors, but we cannot simply correct them during briefing generation based on our own assumptions. In such cases, we need to avoid relying on potentially incorrect information and abstract the content to a level where the briefing can be expressed without using it.&lt;/p&gt;

&lt;p&gt;Finally, a lesson from operations.&lt;/p&gt;

&lt;p&gt;Early in the improvement cycle, even a relatively small input dataset is often enough to expose critical problems.&lt;/p&gt;

&lt;p&gt;For a more efficient improvement process, I recommend starting with a smaller dataset and switching to an input dataset with sufficient volume and diversity only after the generation pipeline reaches a reasonable level of quality.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;After going through this process, we were able to automatically generate "good briefings" from news articles and social media posts, and even received a passing grade 😊 from our experienced editors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi09.png" class="article-body-image-wrapper"&gt;&lt;img width="800" alt="final deployment review" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Ftechblog%2Fllmj%2Fimg%2Fi09.png" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Does that mean we can safely assume tomorrow's automatically generated briefings will also be fine?&lt;/p&gt;

&lt;p&gt;Unfortunately, not necessarily.&lt;/p&gt;

&lt;p&gt;For cases where the risk is difficult to accept—such as reporting involving members of the Imperial Family or the suicide of a public figure—we may still need HITL approval before publication.&lt;/p&gt;

&lt;p&gt;It may also be worth running the evaluation bot in the production system itself and blocking publication when scores fall below a given threshold.&lt;/p&gt;

&lt;p&gt;Using generative AI in production is not exactly effortless 😅&lt;/p&gt;

&lt;p&gt;I hope this article provides some useful ideas for anyone struggling with the quality of generative AI output.&lt;/p&gt;

&lt;p&gt;Although this article focused on automatically generating briefings, the evaluation bot and process-support framework are implemented as a general-purpose system for text-generation use cases. The &lt;a href="https://github.com/nakayama-kazuki/202x/tree/main/tools/llm-as-a-judge" rel="noopener noreferrer"&gt;implementation is available on my personal GitHub repository&lt;/a&gt;, so feel free to use it if you find it useful.&lt;/p&gt;

&lt;p&gt;I also wrote &lt;a href="https://blog.techscore.com/entry/2026/05/13/080000" rel="noopener noreferrer"&gt;an earlier article about a personal AI-integrated application&lt;/a&gt;. That article ended with a small teaser about LLM-as-a-Judge, so I'm glad I finally got to follow up on it here.&lt;/p&gt;

&lt;p&gt;If you're interested, you can think of that earlier article as Episode #1.&lt;/p&gt;

</description>
      <category>qa</category>
      <category>deepeval</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>What to do in such situations: Developing an app integrated with generative AI</title>
      <dc:creator>corridor project</dc:creator>
      <pubDate>Tue, 22 Sep 2026 05:04:43 +0000</pubDate>
      <link>https://dev.to/pj-corridor/what-to-do-in-such-situations-developing-an-app-integrated-with-generative-ai-21f4</link>
      <guid>https://dev.to/pj-corridor/what-to-do-in-such-situations-developing-an-app-integrated-with-generative-ai-21f4</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Fpj-corridor.net%2Fpersonalitytest%2Fblog%2Ftitle.png" class="article-body-image-wrapper"&gt;&lt;img width="800" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Fpj-corridor.net%2Fpersonalitytest%2Fblog%2Ftitle.png" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hello, I’m pj-corridor, an engineer. In this article, I will introduce the challenges I faced while developing an application integrated with generative AI, such as dealing with abuse and navigating trial-and-error and trade-offs in prompt tuning.&lt;/p&gt;

&lt;p&gt;First, please try the personality assessment app I initially developed. The annoying ads 😆 can be hidden by adjusting the window width.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/personalitytest/OpenCAPS.html" rel="noopener noreferrer"&gt;CAPS (= Controller, Analyzer, Promoter, Supporter) assessment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pj-corridor.net/personalitytest/OpenDiSC.html" rel="noopener noreferrer"&gt;DiSC (= Drive, Influence, Steadiness, Compliance) assessment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Like the popular MBTI, CAPS and DiSC are so-called pseudoscientific personality assessments. They should not be used as a basis for decision-making, but for example, having workshop participants share their results can help energize self-introduction sessions and warm up the atmosphere. Please consider using this assessment app on such occasions.&lt;/p&gt;

&lt;p&gt;For reference, here is my CAPS assessment result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Fpj-corridor.net%2Fpersonalitytest%2Fblog%2Fsample.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Fpj-corridor.net%2Fpersonalitytest%2Fblog%2Fsample.png" width="610" height="1424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The advice and user manual are generated by AI, but the sudden appearance of “Respondent” in Japanese sentences feels a bit unnatural. In this way, while generative AI provides rich expressive capabilities, it also introduces quality risks. That said, I wanted to keep the cost of ensuring quality within the scope of a hobby project. Therefore, I kept the rule-based scoring logic deterministic and limited the generative AI output to supplementary text, thereby controlling the impact of hallucinations.&lt;/p&gt;

&lt;p&gt;From here, I will introduce concrete examples of trial and error and how I approached trade-offs, divided into three chapters.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Abuse prevention
&lt;/h2&gt;

&lt;p&gt;The assessment app can be accessed anonymously, but the backend uses a paid generative AI service (Amazon Bedrock). Therefore, being overwhelmed by bot abuse is a plausible scenario. However, rather than aiming to eliminate all threats, I adopted a basic policy of considering the attacker’s incentives: “accept threats with low manifestation risk, and mitigate the rest.”&lt;/p&gt;

&lt;h3&gt;
  
  
  1.1. Blocking common attack traffic with WAF
&lt;/h3&gt;

&lt;p&gt;For WAF, referring to AWS’s standard protection pack, I adopted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GeoRule&lt;/code&gt; (blocking IPs from regions with frequent attacks)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS-AWSManagedRulesAmazonIpReputationList&lt;/code&gt; (blocking known malicious IPs)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS-AWSManagedRulesAnonymousIpList&lt;/code&gt; (blocking anonymized/tunneled IPs)&lt;/li&gt;
&lt;li&gt;A tuned &lt;code&gt;GlobalRateBasedRule&lt;/code&gt; for the assessment app (request rate limiting)&lt;/li&gt;
&lt;li&gt;A tuned &lt;code&gt;RateBasedRulePOST&lt;/code&gt; (limiting POST/PUT/DELETE requests)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and removed unnecessary rules such as &lt;code&gt;AWS-AWSManagedRulesSQLiRuleSet&lt;/code&gt;. However, when using a Lambda Function URL as a public endpoint, there is concern that requests not routed through CloudFront could bypass the WAF. While it is possible to enforce CloudFront (and thus WAF) routing, doing so would require AWS-specific implementations, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changing the Lambda Function URL &lt;code&gt;AuthType&lt;/code&gt; to &lt;code&gt;AWS_IAM&lt;/code&gt; and using CloudFront OAC
👉 This requires support for &lt;code&gt;x-amz-content-sha256&lt;/code&gt; in POST requests&lt;/li&gt;
&lt;li&gt;Adding a secret header in CloudFront and verifying it in Lambda
👉 This requires managing multiple environment variables and verification logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In any case, AWS-dependent implementation would be necessary. Considering the risk that a malicious actor could discover the Lambda Function URL and bypass the WAF, I decided not to implement this measure for now.&lt;/p&gt;

&lt;p&gt;As a side note, settings configured in the AWS console are easy to forget. Forgetting to revert temporary settings can cause issues. To avoid this, I recommend managing managed service configurations in a repository (something close to IaC).&lt;/p&gt;

&lt;p&gt;For example, for a WAF protection pack:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Manage the &lt;a href="https://github.com/nakayama-kazuki/202x/blob/main/.github/workflows/WAFPolicyApiCorridor.json" rel="noopener noreferrer"&gt;JSON&lt;/a&gt; as the Source of Truth in the repository
&lt;/li&gt;
&lt;li&gt;Always apply changes via Source of Truth → AWS console
&lt;/li&gt;
&lt;li&gt;Check consistency between Source of Truth and the applied WAF policy via &lt;a href="https://github.com/nakayama-kazuki/202x/blob/main/.github/workflows/deploy-corridor.yml#L49" rel="noopener noreferrer"&gt;CI&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Also, as a follow-up: with the traffic scale of this app, the fixed cost of WAF ended up exceeding the usage-based cost of Bedrock. A good lesson in estimating costs 😅. Going forward, I am considering suspending WAF until traffic increases and instead implementing POST request limits using DynamoDB.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2. Controlling access from simple bots and browsers bypassing the flow
&lt;/h3&gt;

&lt;p&gt;For access control, I implemented:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When accessing the app, generate a token from “secret information + timestamp” and send it via &lt;code&gt;Set-Cookie&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The browser includes the token in requests using &lt;code&gt;credentials: 'include'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The server validates that the token could have been generated within a human response time window&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Additionally, I applied:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SameSite=Strict&lt;/code&gt; to block cookies in cross-domain POST requests (see &lt;a href="https://blog.techscore.com/entry/2023/10/06/110100" rel="noopener noreferrer"&gt;SameSite explanation&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Restricting &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; to a whitelist to limit browser-based access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These measures can be bypassed by more sophisticated bots, but I decided to start within the scope of the basic policy and consider additional measures while monitoring access patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Technology selection
&lt;/h2&gt;

&lt;p&gt;Here I describe trial and error related to Lambda and API Gateway.&lt;/p&gt;

&lt;p&gt;Lambda was a reasonable choice for the runtime environment, but I initially preferred:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lambda container image (using PHP)&lt;/li&gt;
&lt;li&gt;Lambda zip deployment (using Python/Node)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I leaned toward (1) because I already had a local PHP testing environment, which seemed suitable for agile development and testing. However, considering compatibility with Lambda and concerns about CI complexity, I ultimately chose (2). In hindsight, I spent more time dealing with AWS environment nuances and black-box behavior, so it turned out to be the right choice.&lt;/p&gt;

&lt;p&gt;Speaking of trial and error, repeatedly creating Lambda functions via the AWS console automatically generates new IAM roles each time. Not just these remnants, but leaving unused resources can become technical debt, so they should be cleaned up promptly.&lt;/p&gt;

&lt;p&gt;I resisted the urge to jump into development and prepared a mechanism to handle Lambda and test environments transparently.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/nakayama-kazuki/202x/blob/main/testenv/scripts/template.py" rel="noopener noreferrer"&gt;Shared Python template&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nakayama-kazuki/202x/blob/main/testenv/scripts/restart-python.bat" rel="noopener noreferrer"&gt;Test environment launcher&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This later became the foundation for prompt tuning.&lt;/p&gt;

&lt;p&gt;As for whether to use API Gateway:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Implementation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;Custom access control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing&lt;/td&gt;
&lt;td&gt;Handled within Lambda&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throttling&lt;/td&gt;
&lt;td&gt;Handled by WAF (planned to change)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Given this, I decided not to use API Gateway at this stage, as the cost outweighed the benefits.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Application development
&lt;/h2&gt;

&lt;p&gt;With abuse prevention and technology selection in place, it was time for development. Once completed, I wanted to share it globally via Reddit. That led to the motivation to support multiple languages, so I implemented a simple i18n class to support 9 languages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GREETING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;en&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ja&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;こんにちは&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bonjour&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;de&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hallo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;es&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hola&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pt&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Olá&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hi&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;नमस्ते&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ko&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;안녕하세요&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;zh&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;你好&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To stabilize AI output quality, I used the user’s native language for UI but fixed the AI input language to English.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Fpj-corridor.net%2Fpersonalitytest%2Fblog%2Farch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnakayama-kazuki%2F202x%2Fmain%2Fpj-corridor.net%2Fpersonalitytest%2Fblog%2Farch.png" width="799" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also unified state management, UI components, and interfaces with Lambda/test environments between DiSC and CAPS, enabling reuse for future assessments like MBTI.&lt;/p&gt;

&lt;p&gt;Finally, I focused on improving and stabilizing AI output quality through prompt tuning. Instead of rushing, I first built a foundation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prepare shortcut features for prompt generation

&lt;ul&gt;
&lt;li&gt;For example, “random answers + query execution” automation
&lt;/li&gt;
&lt;li&gt;Abuse prevention via hash-based validation
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Tune in a test environment (without AI connection)

&lt;ol&gt;
&lt;li&gt;Execute shortcut
&lt;/li&gt;
&lt;li&gt;Output prompts to console
&lt;/li&gt;
&lt;li&gt;Simulate production using Gemini/ChatGPT and evaluate
&lt;/li&gt;
&lt;li&gt;Iterate prompt/template improvements
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Tune in production (with AI connection)

&lt;ol&gt;
&lt;li&gt;Partial tuning via shortcut
&lt;/li&gt;
&lt;li&gt;Full app testing and final checks&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;During this process, I discovered missing outputs in &lt;code&gt;hi&lt;/code&gt;, &lt;code&gt;ko&lt;/code&gt;, and &lt;code&gt;zh&lt;/code&gt;, likely due to token differences across languages.&lt;/p&gt;

&lt;p&gt;Also, AI suggestions tend to bloat prompts with redundant instructions, so consolidating and refactoring prompt structure is recommended.&lt;/p&gt;

&lt;p&gt;Finally, about “Respondent”: the requirement was to fix the subject as colleagues/friends while referring to the user consistently. Attempts like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...use the {{lang}} term for "Respondent".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;resulted in “you,” while banning pronouns led to “Respondent.” To stabilize output, I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;always use the fixed keyword "_RESPONDENT_"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and replaced it client-side 😅&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RESPONDENT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;en&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Respondent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ja&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;回答者&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fr&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Répondant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;de&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Befragter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;es&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Encuestado&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pt&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Respondente&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hi&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;उत्तरदाता&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ko&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;응답자&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;zh&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;受访者&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for reading. In this project, I focused on structuring prompt tuning while controlling hallucination impact. Future improvements may include automated output quality checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rule-based validation of format and keywords
&lt;/li&gt;
&lt;li&gt;Using one AI to evaluate another AI’s output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope my trial-and-error and trade-off decisions are helpful to you.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>lambda</category>
      <category>promptengineering</category>
    </item>
  </channel>
</rss>
