<?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: Yoshifumi Tamoto</title>
    <description>The latest articles on DEV Community by Yoshifumi Tamoto (@tdual).</description>
    <link>https://dev.to/tdual</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%2F4133574%2F9f68ba3a-f3d3-41b8-b6ff-b2caf12a7e47.jpg</url>
      <title>DEV Community: Yoshifumi Tamoto</title>
      <link>https://dev.to/tdual</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tdual"/>
    <language>en</language>
    <item>
      <title>I let an LLM judge inside Python's if statements, then ran Ansible's own tests on it</title>
      <dc:creator>Yoshifumi Tamoto</dc:creator>
      <pubDate>Sun, 20 Sep 2026 02:34:02 +0000</pubDate>
      <link>https://dev.to/tdual/i-let-an-llm-judge-inside-pythons-if-statements-then-ran-ansibles-own-tests-on-it-43fo</link>
      <guid>https://dev.to/tdual/i-let-an-llm-judge-inside-pythons-if-statements-then-ran-ansibles-own-tests-on-it-43fo</guid>
      <description>&lt;p&gt;I wrote a small library that lets you put a question, in plain language, where a Python &lt;code&gt;if&lt;/code&gt; condition goes.&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fuzzyif&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;fuzzy&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;fuzzy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Is this message urgent?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;notify_oncall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is called fuzzyif. &lt;code&gt;pip install fuzzyif&lt;/code&gt;. The code is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Tdual/fuzzyif" rel="noopener noreferrer"&gt;https://github.com/Tdual/fuzzyif&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post is about two things: what the library actually does, and whether it can replace a real pile of &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;elif&lt;/code&gt; in a project everyone knows. I patched Ansible's distribution detection and ran Ansible's own test fixtures against the result.&lt;/p&gt;

&lt;p&gt;The short version: the &lt;em&gt;judgement&lt;/em&gt; part of the pile was replaceable. The &lt;em&gt;extraction&lt;/em&gt; part was not. Finding exactly where that line falls was the most useful outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why would anyone want this
&lt;/h2&gt;

&lt;p&gt;If you have ever routed support emails into "bug report", "how-to question" and "billing", you have written this:&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;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;doesn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t work&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;how do I&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;how to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;howto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;charge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refund&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;billing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code loses the moment you write it. "The screen goes blank after login" is a bug report with none of the bug keywords. "What does this error mean?" is a how-to question that contains "error". Every keyword you add fixes one case and breaks another, and the ladder never stops growing.&lt;/p&gt;

&lt;p&gt;What you wanted to write was the question itself: &lt;em&gt;is this a bug report?&lt;/em&gt; fuzzyif lets you write that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is doing the judging
&lt;/h2&gt;

&lt;p&gt;Behind fuzzyif is Jev, a model TypeSafe AI released in September 2026. They call it a "System One" model: it does not generate text. You give it a text and a question, and it returns a probability, a choice among labels, or a position on a scale. Values a program can use directly.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzlu6fjm19wv9oxfwycxu.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzlu6fjm19wv9oxfwycxu.png" alt="How fuzzyif works: if statement, fuzzyif, Jev, probability, threshold, True or False" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fuzzy("Is this urgent?", msg)&lt;/code&gt; sends the question and the text to Jev, gets back a probability (say 0.93), and applies a 0.5 threshold. Because nothing is generated, a call on a warm connection takes about 0.25 s and produces about 20 output tokens. Identical question and text pairs are cached, and the HTTPS connection is reused.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four kinds of question
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fuzzy(question, text)&lt;/code&gt; returns a &lt;code&gt;bool&lt;/code&gt; for a yes/no question.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fuzzy_match(text, {label: description})&lt;/code&gt; picks one label. Use this when you want exactly one of several. Stacking &lt;code&gt;fuzzy()&lt;/code&gt; calls in &lt;code&gt;if / elif&lt;/code&gt; is not exclusive: if two questions both cross the threshold, the first branch wins even when the second was more likely.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fuzzy_batch(text, [q1, q2])&lt;/code&gt; answers several yes/no questions in one request.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fuzzy_score(text, question, [level0, level1, ...])&lt;/code&gt; returns a position on an ordered scale, for things like "how angry is the writer".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The support-email router becomes one call:&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="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fuzzy_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a bug report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;howto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a how-to question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;billing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a question about invoices or charges&lt;/span&gt;&lt;span class="sh"&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;h2&gt;
  
  
  Can it really delete a pile of if statements?
&lt;/h2&gt;

&lt;p&gt;Toy examples prove nothing, so I set three conditions: a library everyone knows, an official test suite, and a target whose purpose anyone can understand.&lt;/p&gt;

&lt;p&gt;I picked Ansible. The first thing Ansible does on a host is work out &lt;code&gt;ansible_distribution&lt;/code&gt; (Ubuntu? RHEL?) and &lt;code&gt;ansible_os_family&lt;/code&gt; (Debian-like? RedHat-like?). That decision lives in &lt;code&gt;distribution.py&lt;/code&gt;, 786 lines, structured like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;walk a list of release files: &lt;code&gt;/etc/os-release&lt;/code&gt;, &lt;code&gt;/etc/redhat-release&lt;/code&gt;, &lt;code&gt;/etc/lsb-release&lt;/code&gt;, &lt;code&gt;/etc/SuSE-release&lt;/code&gt;, and so on&lt;/li&gt;
&lt;li&gt;match search strings in their contents ("Red Hat", "Amazon", ...)&lt;/li&gt;
&lt;li&gt;dispatch to one of thirteen &lt;code&gt;parse_distribution_file_*&lt;/code&gt; methods, each a ladder of &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;elif&lt;/code&gt; over the file text&lt;/li&gt;
&lt;li&gt;finally map the name to a family through &lt;code&gt;OS_FAMILY_MAP&lt;/code&gt;, a hand-maintained table of about 70 entries&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;

&lt;p&gt;Everything I deleted, in one picture. 418 lines, 84 &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;elif&lt;/code&gt;. It is unreadable at this size on purpose: this is what a pile of &lt;code&gt;if&lt;/code&gt; looks like.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwiiw04pva3gj9cx0e7uk.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwiiw04pva3gj9cx0e7uk.png" alt="Ansible distribution.py before the change: the 13 parser methods and OS_FAMILY_MAP" width="800" height="848"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the thirteen at readable size. This is the SUSE parser, 67 lines.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7dt62bn3uf99w83shktb.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7dt62bn3uf99w83shktb.png" alt="The SUSE parser alone, 67 lines" width="800" height="1285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  After
&lt;/h3&gt;

&lt;p&gt;I rewrote &lt;code&gt;process_dist_files&lt;/code&gt; and deleted the thirteen parsers and &lt;code&gt;OS_FAMILY_MAP&lt;/code&gt;. This is what is left.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy1toyv7g1couhkgsi29c.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy1toyv7g1couhkgsi29c.png" alt="After: process_dist_files builds evidence from the release files and asks fuzzy_match, 29 lines" width="800" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It concatenates whatever release files exist into one block of evidence and asks &lt;code&gt;fuzzy_match&lt;/code&gt; "which distribution is this". A second &lt;code&gt;fuzzy_match&lt;/code&gt; asks "which family". The label sets are the distribution names Ansible already documents, with a one-line description each.&lt;/p&gt;

&lt;p&gt;The file went from 786 lines to 450.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ansible's own tests: 90 fixtures
&lt;/h2&gt;

&lt;p&gt;Ansible ships 90 recorded fixtures: real &lt;code&gt;/etc/*-release&lt;/code&gt; contents captured from machines, paired with the facts the collector must report. They cover 52 distributions. I ran that test unchanged against the patched code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;65 of 90 fixtures matched on every key. 25 differed on at least one key.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Per key, the picture is much sharper:&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftosof30guko8cb6foeeu.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftosof30guko8cb6foeeu.png" alt="Per-key agreement across the 90 fixtures" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;distribution&lt;/code&gt;: 90 of 90&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;os_family&lt;/code&gt;: 87 of 88&lt;/li&gt;
&lt;li&gt;version and major version: 88 of 90 and 84 of 84&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;distribution_release&lt;/code&gt;: 68 of 88, and this is where it fell apart&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the 25 differences were
&lt;/h2&gt;

&lt;p&gt;I went through all 25. Almost none are wrong judgements. They are Ansible's house conventions for cutting substrings out of files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SUSE puts only the service-pack number in &lt;code&gt;release&lt;/code&gt;: &lt;code&gt;VERSION="15-SP6"&lt;/code&gt; becomes &lt;code&gt;6&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;openSUSE Leap puts the minor digit there: &lt;code&gt;15.1&lt;/code&gt; becomes &lt;code&gt;1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Clear Linux uses the literal string &lt;code&gt;clear-linux-os&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;CentOS 8 reports &lt;code&gt;Stream&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;only the Debian and Amazon parsers add a &lt;code&gt;minor_version&lt;/code&gt; key&lt;/li&gt;
&lt;li&gt;OSMC reads the string &lt;code&gt;March 2022&lt;/code&gt; from a custom file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these is a question of &lt;em&gt;what something is&lt;/em&gt;. They are questions of &lt;em&gt;which slice of the string to take&lt;/em&gt;. My patch left version and codename to the &lt;code&gt;distro&lt;/code&gt; library that Ansible already uses as a baseline, so it does not reproduce those conventions.&lt;/p&gt;

&lt;p&gt;The one real judgement miss: Ansible has two labels for the same UnionTech OS, &lt;code&gt;Uos&lt;/code&gt; (Debian family) and &lt;code&gt;UnionTech&lt;/code&gt; (RedHat family), chosen by which release files happen to exist. The judge picked the other one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson: you can delete judgement, you cannot delete extraction
&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqlnei6jhnl5sykadyv05.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqlnei6jhnl5sykadyv05.png" alt="Judgement suits fuzzy; extraction belongs to regex" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look closely at a pile of &lt;code&gt;if&lt;/code&gt; and you find two different jobs mixed together.&lt;/p&gt;

&lt;p&gt;One is judgement. "What distribution do these files describe?" "Is this database error a disconnect?" "Is this ticket a bug report?" These are about meaning. Written as keyword matches they grow without bound. They suit fuzzy.&lt;/p&gt;

&lt;p&gt;The other is extraction. "Take the value of &lt;code&gt;VERSION_ID&lt;/code&gt;." "Keep only the service-pack number." "Pull the codename out of the parentheses." These are about position, not meaning. A regex does them in one line, deterministically. There is no reason to hand them to a model.&lt;/p&gt;

&lt;p&gt;fuzzyif replaces the judgement. Keep the extraction. Once you can see which lines are which, you know which part of the pile is safe to delete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you should not use it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anything you would not send to a third party.&lt;/strong&gt; The text goes to an API. Passwords and personal data do not belong in a &lt;code&gt;fuzzy()&lt;/code&gt; call. At one point I considered fuzzifying Django's password validators and dropped the idea for exactly this reason.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extraction.&lt;/strong&gt; See above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security decisions.&lt;/strong&gt; A case near 0.5 can flip between runs. Do not put authorization behind a threshold.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tight loops over large data.&lt;/strong&gt; Every distinct text is a network call. Batch what you can and let the cache do the rest.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementation notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Zero dependencies. The HTTP client is &lt;code&gt;http.client&lt;/code&gt; from the standard library, one keep-alive connection per thread. First call about 0.6 s, later calls about 0.25 s.&lt;/li&gt;
&lt;li&gt;LRU cache in front of every call. The same question and text never hit the API twice.&lt;/li&gt;
&lt;li&gt;Retries with backoff on 429 and 5xx, honouring &lt;code&gt;Retry-After&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mock()&lt;/code&gt; for tests: answer from a mapping without touching the API, so code that uses &lt;code&gt;fuzzy&lt;/code&gt; stays unit-testable.&lt;/li&gt;
&lt;li&gt;A bug I hit myself: &lt;code&gt;http.client&lt;/code&gt; encodes &lt;code&gt;str&lt;/code&gt; bodies as latin-1, so any non-Latin text failed. Bodies are now sent as UTF-8 bytes. Found while judging Japanese text; glad it made the first release.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;fuzzyif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put a TypeSafe API key in &lt;code&gt;TYPESAFE_API_KEY&lt;/code&gt; or &lt;code&gt;~/.config/typesafe/api_key&lt;/code&gt;. The Ansible patch script and everything needed to reproduce the numbers above are in &lt;code&gt;examples/ansible_distribution/&lt;/code&gt; in the repository linked at the top.&lt;/p&gt;

&lt;p&gt;Next I want to build the tool that reads an existing pile of &lt;code&gt;if&lt;/code&gt;, separates judgement from extraction, and proposes the fuzzy rewrite for just the judgement part.&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>jev</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
