<?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: H.Kataoka</title>
    <description>The latest articles on DEV Community by H.Kataoka (@weezer924).</description>
    <link>https://dev.to/weezer924</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%2F155811%2F584aedd0-1d8d-4f80-80bd-d883bf3504f0.jpeg</url>
      <title>DEV Community: H.Kataoka</title>
      <link>https://dev.to/weezer924</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/weezer924"/>
    <language>en</language>
    <item>
      <title>how we evaluate our AI Gen message for apply message</title>
      <dc:creator>H.Kataoka</dc:creator>
      <pubDate>Thu, 01 Oct 2026 19:25:17 +0000</pubDate>
      <link>https://dev.to/weezer924/how-we-evaluate-our-ai-gen-message-for-apply-messsage-4lkl</link>
      <guid>https://dev.to/weezer924/how-we-evaluate-our-ai-gen-message-for-apply-messsage-4lkl</guid>
      <description>&lt;p&gt;we built a feature: when a pro (service provider on our platform) gets a request from a client, LLM writes the first apply message for the pro.&lt;br&gt;
there are 2 routes. fullAi writes the whole letter, insert adds one AI paragraph into the pro's own template.&lt;/p&gt;

&lt;p&gt;the goal is: we want to reduce the pro's wrtting template time and also have high customization content for each client's request.&lt;/p&gt;

&lt;p&gt;after release, the hard question was: is the generated letter good or not ?&lt;br&gt;
this post is how we try to answer it.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. why we need evaluation
&lt;/h2&gt;

&lt;p&gt;we can't tell good or bad by feeling.&lt;br&gt;
one real example: a letter for a water leak request at an apartment asked the client "where on the upper floor is the leak coming from ?"&lt;br&gt;
the upper floor is someone else's home, so the client has no way to answer it.&lt;/p&gt;

&lt;p&gt;at that time I ran all of our rule checks (contact info, placeholders, price source...), none of them caught it.&lt;br&gt;
I found it by reading.&lt;br&gt;
and the same request generated 3 letters, only 1 of them asked this. which is the randomness of the generation itself.&lt;/p&gt;

&lt;p&gt;so we need 2 things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a golden standard for what is a good letter. at the beginning only humans can define it.&lt;/li&gt;
&lt;li&gt;an automated evaluation system. human review is expensive, reading tens of letters every time we change the prompt is not sustainable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;the order matters. humans define the standard first, then let the machine learn it. and the machine judge also needs to be verified.&lt;br&gt;
I learned this after doing it in the wrong order once.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. round 0: the mistakes I made
&lt;/h2&gt;

&lt;p&gt;one month before release, I built a first version of the eval tool.&lt;br&gt;
looking back, almost every step had a problem.&lt;/p&gt;

&lt;p&gt;what it looked like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a checklist of 10 rules. 6 are code checks: link, placeholder left in the text, contact info, length, prompt leak, refusal phrases.
4 go to an LLM judge: can the client answer it, fabrication, commitment, and saying "we can do it" only at the category level.&lt;/li&gt;
&lt;li&gt;one prompt per judge rule. quote the evidence first, then return true / false. if not sure, it fails.&lt;/li&gt;
&lt;li&gt;the data is letters generated from real requests at our QA environment, plus synthetic violation samples: commitment 20, fabrication 20, injection 24, and 20 clean ones.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  mistake 1: the standard came from my head, not from humans
&lt;/h3&gt;

&lt;p&gt;I wrote the rules from my feelings, there were no human labels behind them.&lt;/p&gt;

&lt;p&gt;later when I calibrated with human labels, the fabrication judge had precision 0. none of the issues it flagged was a real issue.&lt;br&gt;
the answerability judge was 0.33, so 2 out of 3 flags were wrong.&lt;/p&gt;
&lt;h3&gt;
  
  
  mistake 2: comparing prompts with an uncalibrated judge
&lt;/h3&gt;

&lt;p&gt;before release, I changed the prompt to stop saying "we can do it" only at the category level.&lt;br&gt;
for the same 51 requests, the old and new prompt generated once each, then the judge scored them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;judge&lt;/th&gt;
&lt;th&gt;old prompt&lt;/th&gt;
&lt;th&gt;new prompt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"we can do it" at category level&lt;/td&gt;
&lt;td&gt;28/51 (55%)&lt;/td&gt;
&lt;td&gt;15/51 (29%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;commitment&lt;/td&gt;
&lt;td&gt;10/51 (20%)&lt;/td&gt;
&lt;td&gt;5/51 (10%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fabrication&lt;/td&gt;
&lt;td&gt;12/51 (24%)&lt;/td&gt;
&lt;td&gt;14/51 (27%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;client can't answer&lt;/td&gt;
&lt;td&gt;8/51 (16%)&lt;/td&gt;
&lt;td&gt;6/51 (12%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;the first 2 rows look great, both cut in half.&lt;br&gt;
but fabrication went up from 24% to 27%, and this judge has precision 0. so this row tells nothing.&lt;/p&gt;

&lt;p&gt;same for the other rows. every number in the table comes from the judge,&lt;br&gt;
and at that time I could not answer whether the judge itself was right.&lt;br&gt;
so the comparison can only be as reliable as the judge behind it.&lt;/p&gt;
&lt;h3&gt;
  
  
  mistake 3: "not sure" counted as fail
&lt;/h3&gt;

&lt;p&gt;this way the violation rate mixed in the judge's own uncertainty.&lt;br&gt;
we can't tell if the letter has a problem, or the judge is just not sure.&lt;/p&gt;

&lt;p&gt;the later version uses 4 values, and "not sure" is recorded separately as uncertain (see section 4).&lt;/p&gt;
&lt;h3&gt;
  
  
  mistake 4: one checklist for 3 different things, judged by the literal text
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;3 kinds of things in one list: what the generation must not do (e.g. no contact info), how the whole letter reads (can the client answer it), and a fixed length limit.&lt;/li&gt;
&lt;li&gt;no split for the template. contact info, prices and repeated questions from the pro's own template were also counted as the AI's problem.&lt;/li&gt;
&lt;li&gt;judging by the literal text gives false flags. e.g. "we know this area well" was treated by the fabrication judge as a fact that needs a source.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we didn't throw the checklist away, it's kept as a reference.&lt;br&gt;
placeholder left, prompt leak and refusal phrases later became candidates for the basic defect check.&lt;/p&gt;

&lt;p&gt;so round 1 started from scratch: find the people, define the standard, then let the machine learn it.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. find the right people: domain experts
&lt;/h2&gt;

&lt;p&gt;engineers can't judge if a cover letter is good, because we don't know what makes a client want to reply.&lt;/p&gt;

&lt;p&gt;we asked CS and Sales to read real samples and write feedback. a lot of it is something engineers won't think of, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"repeating the request at the beginning of the letter is not needed."&lt;/li&gt;
&lt;li&gt;"instead of asking the client's current weight, ask how much they want to lose, or which body part they want to work on."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this feedback became prompt changes directly, and also the material for the eval standard.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. collect small samples: good, bad, and not sure
&lt;/h2&gt;

&lt;p&gt;human time is limited, so we only look at 20-30 letters each time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1st batch: from the first 500 letters after release, stratified random sampling by route, 15 each, 30 in total.&lt;/li&gt;
&lt;li&gt;2nd batch: another 20 (10 for each route), no overlap with the 1st batch, kept only for validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we don't give each letter one overall score. we label per dimension, with one of 4 values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;acceptable&lt;/li&gt;
&lt;li&gt;needs improvement&lt;/li&gt;
&lt;li&gt;not applicable&lt;/li&gt;
&lt;li&gt;uncertain (not enough evidence, or the boundary of the standard is not decided yet, so don't force a pass or fail)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;one trap: no issue marked doesn't mean pass.&lt;br&gt;
a dimension the reviewer didn't mention is "not checked", not "acceptable".&lt;/p&gt;
&lt;h2&gt;
  
  
  5. when people disagree, discuss and write down the standard
&lt;/h2&gt;

&lt;p&gt;in the 1st batch of 30, the human overall rating was good 24, ok 6, bad 0.&lt;br&gt;
most letters have no big problem, the problems are in the details.&lt;br&gt;
so a simple good / bad is not useful, we need to split it into concrete dimensions.&lt;/p&gt;

&lt;p&gt;the 10-rule checklist from round 0 is not used by default anymore, the reason is mistake 4 in section 2.&lt;/p&gt;

&lt;p&gt;after going through each letter together with the original request, we defined 5 dimensions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;dimension&lt;/th&gt;
&lt;th&gt;key in code&lt;/th&gt;
&lt;th&gt;what it checks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;core need&lt;/td&gt;
&lt;td&gt;&lt;code&gt;core_need&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;when it's still unclear what the client wants done, ask that first, before the work details&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reply burden&lt;/td&gt;
&lt;td&gt;&lt;code&gt;reply_burden&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;can the client answer easily ? don't ask for technical categories or a full set of documents first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;alternative fit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;alternative_fit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"if not sure, send a photo": can the photo actually answer the original question ?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;assembly&lt;/td&gt;
&lt;td&gt;&lt;code&gt;assembly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;does it ask again what's already in the request form, and is the paragraph order natural&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;intent&lt;/td&gt;
&lt;td&gt;&lt;code&gt;intent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;does it respond to the real purpose in the client's comment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;each dimension is judged separately, no total score.&lt;br&gt;
a total score mixes "one serious problem" and "a few small issues" into the same number.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. let the LLM judge by the standard
&lt;/h2&gt;

&lt;p&gt;we didn't automate all 5 dimensions at once.&lt;br&gt;
the rule is to only automate dimensions that actually showed up, and whose boundary is already discussed.&lt;br&gt;
we started with 2: &lt;code&gt;core_need&lt;/code&gt; and &lt;code&gt;reply_burden&lt;/code&gt;. the other 3 are still judged by humans.&lt;/p&gt;
&lt;h3&gt;
  
  
  what the judge outputs
&lt;/h3&gt;

&lt;p&gt;for each letter, the judge returns 3 verdicts on 2 axes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;business quality (&lt;code&gt;business&lt;/code&gt;): &lt;code&gt;core_need&lt;/code&gt;, &lt;code&gt;reply_burden&lt;/code&gt;. it looks at the whole letter, not only the AI paragraph.&lt;/li&gt;
&lt;li&gt;prompt compliance (&lt;code&gt;prompt_compliance&lt;/code&gt;): only checks if the AI paragraph breaks the generation instructions of that time.
e.g. facts with no source, treating a reference price as the quote for this request, promising it can take the job, leaking the instructions, breaking the format rules of the route.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the 2 axes are judged independently, no total score.&lt;br&gt;
a letter that follows the generation instructions perfectly can still need improvement on business quality.&lt;br&gt;
so splitting them tells us which one to fix: the model not following the prompt, or the prompt itself.&lt;/p&gt;

&lt;p&gt;each verdict has 5 fixed fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;field&lt;/th&gt;
&lt;th&gt;meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;verdict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;acceptable&lt;/code&gt; / &lt;code&gt;needs_improvement&lt;/code&gt; / &lt;code&gt;not_applicable&lt;/code&gt; / &lt;code&gt;uncertain&lt;/code&gt;, the same 4 values as the human labels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output_quote&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;exact quote of the problem from the letter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input_quote&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;exact quote of the evidence from the generation input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reason&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the reason for the verdict&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;responsibility&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;which layer the problem comes from, see below&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;responsibility&lt;/code&gt; is the most useful field for us.&lt;br&gt;
one letter is assembled from several parts, so the same problem can need a fix at a totally different place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;generated_text&lt;/code&gt;: the AI paragraph. fix the generation prompt&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;template_or_assembly&lt;/code&gt;: the pro's own template, or lines added during assembly. not the AI's problem&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;source_context&lt;/code&gt;: the input itself is missing info or has a conflict&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unclear&lt;/code&gt;: can't tell&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;none&lt;/code&gt;: no problem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;one real output (a rain gutter repair request, it comes back in section 8).&lt;br&gt;
the letter and the judge output are in Japanese, so the values below are translated:&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;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"needs_improvement"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output_quote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"If you don't mind, around which part did you notice the problem: the eaves, the downspout, or the catch basin?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_quote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Request category: rain gutter repair / construction contractor"&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;"The request form doesn't specify what symptom the rain gutter has or what work the client wants. The letter should first ask about the situation, such as a leak, a clog or a detached part, or the work they want. Instead it assumes there is a problem and only asks where it is."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"responsibility"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"generated_text"&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;h3&gt;
  
  
  hard checks in the code
&lt;/h3&gt;

&lt;p&gt;asking "please quote the original text" in the prompt is not enough.&lt;br&gt;
the model may rewrite the quote or miss a field, so after the output comes back, the code checks it again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structured output with a strict schema. one more or one less dimension or field, this call fails.&lt;/li&gt;
&lt;li&gt;quotes must be exact substrings. &lt;code&gt;output_quote&lt;/code&gt; must appear in the generated text or the whole letter, &lt;code&gt;input_quote&lt;/code&gt; must appear in the generation input.
if not, this call fails and we don't take the verdict.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;needs_improvement&lt;/code&gt; must come with a problem quote, and the reason can't be empty.&lt;/li&gt;
&lt;li&gt;the whole config is frozen into 1 hash: the full rubric, the model, the output schema, the call parameters, and the judge code itself.
if any of them changes without a new freeze, the tool refuses to run. so every score can be traced back to the judge version that gave it.&lt;/li&gt;
&lt;li&gt;no auto retry, every letter runs exactly 2 rounds, for checking if the judge is stable by itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. round 1: find the disagreements, check whose problem it is
&lt;/h2&gt;

&lt;p&gt;1st batch, 30 letters, 2 rounds each, 60 calls in total.&lt;/p&gt;

&lt;p&gt;first, is the judge stable by itself ?&lt;br&gt;
same letter, 2 runs, different verdicts: &lt;code&gt;core_need&lt;/code&gt; 2/30, &lt;code&gt;reply_burden&lt;/code&gt; 6/30.&lt;br&gt;
where the judge doesn't even agree with itself, no need to compare it with humans yet.&lt;/p&gt;

&lt;p&gt;then, the disagreements with humans. we picked 23 verdicts and reviewed them one by one:&lt;br&gt;
the ones that changed between rounds, the ones flagged as a problem in both rounds, and spot checks of the ones that passed in both rounds.&lt;br&gt;
for &lt;code&gt;reply_burden&lt;/code&gt;, round 1 matched only 6 out of 11, almost half didn't match.&lt;/p&gt;

&lt;p&gt;after going through them, the disagreements came from 3 places:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the judge is too strict. e.g. it insists "must ask the specific goal first".
a client wants a personal trainer and picked "health improvement" as the goal, and the letter first asks how many times a week they want to train.
the judge said "should ask what they want to improve first" in both rounds. the human said it's fine, the specific goal can be asked at the trial lesson.&lt;/li&gt;
&lt;li&gt;the standard is not clear. e.g. whether a light re-confirmation means the letter doesn't understand the need, the old standard didn't say.&lt;/li&gt;
&lt;li&gt;the human label itself needs a fix. some labels had only a verdict with no reason, some changed after the review.
so a disagreement is not always the LLM's fault.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;for 1 and 2, we only added 3 general paragraphs to the standard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when the target and purpose of the request are already clear, the first letter doesn't need to confirm every detail. it can be asked at the trial or the site visit.&lt;/li&gt;
&lt;li&gt;a light re-confirmation from the template doesn't count as not understanding the core need.&lt;/li&gt;
&lt;li&gt;don't judge by the number of questions or photos mechanically. tell the difference between "could be shorter" and "the burden is really too much".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;one rule: no exceptions for single cases.&lt;br&gt;
if we add special cases just to match every dev sample, the judge overfits.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. validate on new samples
&lt;/h2&gt;

&lt;p&gt;using the samples we tuned the standard on to prove the standard works is like grading your own exam.&lt;br&gt;
so validation uses the 2nd batch, the 20 letters that were not used for tuning.&lt;/p&gt;

&lt;p&gt;the order also matters: humans label blind first, then we run the model.&lt;br&gt;
so the human is not influenced by the model's verdict.&lt;/p&gt;

&lt;p&gt;we set a working threshold before running:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each dimension, each round, at least 18/20 agree with the human (disagreement &amp;lt;= 10%).&lt;/li&gt;
&lt;li&gt;verdict changes between the 2 rounds no more than 1/20.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;how we count the disagreement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;false flag: the human says acceptable, the judge says needs improvement.&lt;/li&gt;
&lt;li&gt;missed issue: the human says needs improvement, the judge says acceptable.&lt;/li&gt;
&lt;li&gt;only items where both sides give acceptable or needs improvement go into the denominator.
if either side says not applicable or uncertain, or the human label is not confirmed yet, it's counted separately, neither right nor wrong.&lt;/li&gt;
&lt;li&gt;by default the tool only compares items where the 2 rounds agree, the ones that changed are counted as unstable. the table below is per round.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;dimension&lt;/th&gt;
&lt;th&gt;round 1 agrees with human&lt;/th&gt;
&lt;th&gt;round 2 agrees with human&lt;/th&gt;
&lt;th&gt;same verdict in 2 rounds&lt;/th&gt;
&lt;th&gt;false flags (round 1 / 2)&lt;/th&gt;
&lt;th&gt;missed issues (round 1 / 2)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;core need&lt;/td&gt;
&lt;td&gt;16/20&lt;/td&gt;
&lt;td&gt;15/20&lt;/td&gt;
&lt;td&gt;19/20&lt;/td&gt;
&lt;td&gt;4 / 5&lt;/td&gt;
&lt;td&gt;0 / 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reply burden&lt;/td&gt;
&lt;td&gt;16/20&lt;/td&gt;
&lt;td&gt;14/20&lt;/td&gt;
&lt;td&gt;18/20&lt;/td&gt;
&lt;td&gt;2 / 4&lt;/td&gt;
&lt;td&gt;2 / 2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;all the &lt;code&gt;core_need&lt;/code&gt; disagreements are false flags, the judge is stricter than the human and tends to require "ask the goal first".&lt;br&gt;
&lt;code&gt;reply_burden&lt;/code&gt; has errors in both directions.&lt;/p&gt;

&lt;p&gt;the disagreement rate is 20-30%.&lt;br&gt;
better than the dev stage, but it hasn't reached the threshold yet: &lt;code&gt;core_need&lt;/code&gt; only passed the stability one, &lt;code&gt;reply_burden&lt;/code&gt; passed neither.&lt;/p&gt;

&lt;p&gt;one more limit: in these 20, only 1 letter was labeled by the human as having a &lt;code&gt;core_need&lt;/code&gt; problem.&lt;br&gt;
with only 1 negative case, it can't prove the judge is able to catch problems.&lt;/p&gt;

&lt;p&gt;so for now, this judge alone can't tell us "the new prompt is better than the old one".&lt;br&gt;
the next step is to review these disagreements and iterate again.&lt;/p&gt;

&lt;p&gt;a typical disagreement: the client only wrote "rain gutter repair", and the letter asks "is the problem around the eaves, the downspout, or the catch basin ?"&lt;br&gt;
the judge said "should ask what the symptom is first" in both rounds, the human said it's acceptable.&lt;br&gt;
this is exactly the kind of case we need to bring back to the original request and discuss.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. after reaching the threshold: release small
&lt;/h2&gt;

&lt;p&gt;after the judge is stable, then we use it to change the production prompt.&lt;br&gt;
if there is no urgent release date, start small:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;shadow mode: the new prompt generates in production in parallel, but doesn't send to clients.&lt;/li&gt;
&lt;li&gt;use these real production samples for a 2nd round of human labeling.&lt;/li&gt;
&lt;li&gt;run the LLM judge again and check the disagreement rate.&lt;/li&gt;
&lt;li&gt;if the agreement is good enough, roll out gradually. if not, go back and review the disagreements one by one like section 7.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;the goal is to keep improving the production prompt with confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  lessons
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;calibrate the judge first, then use it to compare prompts. with an uncalibrated judge, you can't tell if the letter changed or the judge is just flagging randomly.&lt;/li&gt;
&lt;li&gt;no total score. judge per dimension, so the problem is visible.&lt;/li&gt;
&lt;li&gt;not labeled doesn't mean pass. record "not checked" and "acceptable" separately.&lt;/li&gt;
&lt;li&gt;a disagreement is not always the LLM's fault. human labels get fixed, and the standard gets completed too.&lt;/li&gt;
&lt;li&gt;the validation set can't be used to tune the standard, otherwise it's grading your own exam.&lt;/li&gt;
&lt;li&gt;run every item 2 rounds. where the judge isn't stable by itself, don't compare it with humans yet.&lt;/li&gt;
&lt;li&gt;the threshold is a working threshold, not a statistical proof. 20 samples can only say so much, say it clearly.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>product</category>
    </item>
    <item>
      <title>Why I left App Engine for Cloud Run</title>
      <dc:creator>H.Kataoka</dc:creator>
      <pubDate>Thu, 25 Apr 2019 12:50:25 +0000</pubDate>
      <link>https://dev.to/weezer924/why-i-left-app-engine-for-cloud-run-mem</link>
      <guid>https://dev.to/weezer924/why-i-left-app-engine-for-cloud-run-mem</guid>
      <description>&lt;p&gt;Google App Engine is one of my favorite service of GCP.&lt;br&gt;
It makes my application easy to deploy and don't need to worry about the server load.It works well with my Angular/Vue.js SPA applications.&lt;/p&gt;

&lt;p&gt;Then I took an error in the knee...&lt;/p&gt;

&lt;p&gt;Sorry, I mean I took an error in my App Engine Service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: EROFS: read-only file system, mkdir '/srv/sessions' at Object.mkdirSync (fs.js:752:3) at Object.mkdirsSync 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;read-only file system ?!?! &lt;br&gt;
All I just want using node-persist. For some small cache files...&lt;br&gt;
Why're you doing this to me !&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnkfiun0453rmh0c0kd89.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnkfiun0453rmh0c0kd89.gif" width="480" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the reason from App Engine Documents:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In App Engine, the local filesystem that your application is deployed to is not writeable. This behavior ensures the security and scalability of your application.&lt;/p&gt;

&lt;p&gt;However, if the application needs to write and read files at runtime, App Engine provides a built-in Google Cloud Storage stream wrapper that allows you to use many of the standard PHP filesystem functions to read and write files in an App Engine PHP app.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;1.I'm not a PHPer, and using Google Cloud Storage for just some cache files? NaN...&lt;/p&gt;

&lt;p&gt;2.My program works well, I don't want to change it.(Like using Cloud Storage)&lt;/p&gt;

&lt;p&gt;3.Using Google Computer Engine and setup server from zero? Who wants to care about the server load? &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%2Fu0ket7iditxuvpqr3esi.jpg" 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%2Fu0ket7iditxuvpqr3esi.jpg" width="500" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I found a way that it worth to try!&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%2Fbwew3166xerszmkd16tf.jpg" 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%2Fbwew3166xerszmkd16tf.jpg" width="640" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cloud Run is a managed compute platform that enables you to run stateless containers that are invocable via HTTP requests. Cloud Run is serverless: it abstracts away all infrastructure management, so you can focus on what matters most — building great applications. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Humm, This is what I want.&lt;br&gt;
Put my application into a container. Then upload it to Cloud Run.&lt;br&gt;
It will take care of my container offer flexibility and portability of workloads.&lt;/p&gt;

&lt;p&gt;Allright! Let's hack.&lt;/p&gt;

&lt;p&gt;1.Create a docker file.I'm using nuxt.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:10&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT 8080&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; HOST 0.0.0.0&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; npm start&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.build docker image from dockerfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; docker build ./  &lt;span class="nt"&gt;-t&lt;/span&gt;  gcr.io/project-name/image-name:1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.Push the image to your GCP container registry&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker push gcr.io/project-name/image-name:1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4.If success It will look like this.(Sorry for the japanese)&lt;br&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%2Fgq8cqqh3jv4cx6auv6xi.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%2Fgq8cqqh3jv4cx6auv6xi.png" width="799" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5.Click it, select 「create a service at cloud run」. A few mins, your container will run at GCP.&lt;/p&gt;

&lt;p&gt;Okey,&lt;br&gt;
My Cache files works! All my program run as well as my local.&lt;br&gt;
And using container to build micro-services is a good feature.&lt;/p&gt;

&lt;p&gt;So, I will say good-bye to the GAE?&lt;/p&gt;

&lt;p&gt;Well, GAE is still a very good service for All the time. Like SPA, You can pay more attention at your front-end.&lt;/p&gt;

&lt;p&gt;Cloud Run just give me another choice. &lt;br&gt;
To understand what you need. Then you can decide which you need to use.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>nuxt</category>
      <category>gcp</category>
      <category>docker</category>
    </item>
    <item>
      <title>Using Atomic Design with Nuxt.js and have a great hacking time. </title>
      <dc:creator>H.Kataoka</dc:creator>
      <pubDate>Tue, 16 Apr 2019 14:34:31 +0000</pubDate>
      <link>https://dev.to/weezer924/using-atomic-design-with-nuxt-js-and-have-a-great-hacking-time-3lc5</link>
      <guid>https://dev.to/weezer924/using-atomic-design-with-nuxt-js-and-have-a-great-hacking-time-3lc5</guid>
      <description>&lt;p&gt;This is my first time write a english post.&lt;br&gt;
I'm a web application developer working in Japan.And yes, I'm a Chinese.XD&lt;/p&gt;
&lt;h2&gt;
  
  
  What I used in this project
&lt;/h2&gt;

&lt;p&gt;Nuxt.js v2.0&lt;br&gt;
Express v4.0&lt;br&gt;
bootstrap-vue v4.0&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Atomic Design
&lt;/h2&gt;

&lt;p&gt;In case you don't know what is Atomic Design.&lt;br&gt;
Here are some good documents about it.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://atomicdesign.bradfrost.com/"&gt;http://atomicdesign.bradfrost.com/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.indetail.co.jp/blog/10234/"&gt;https://www.indetail.co.jp/blog/10234/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TL;Dr: It's a design system to show you how to create your UI component exactly you need to use.&lt;/p&gt;
&lt;h2&gt;
  
  
  How To Separate Your Folder
&lt;/h2&gt;

&lt;p&gt;components&lt;br&gt;
|&lt;br&gt;
|---- atoms&lt;br&gt;
|------ |---Album.vue&lt;br&gt;
|------ |---BackBtn.vue&lt;br&gt;
|---- molecules &lt;br&gt;
|------ |---AlbumList.vue&lt;br&gt;
|---- organisms&lt;br&gt;
|------ |---AlbumBook.vue&lt;br&gt;
|---- page&lt;br&gt;
|------ |---AlbumBooks.vue&lt;/p&gt;
&lt;h2&gt;
  
  
  Atoms Samples
&lt;/h2&gt;

&lt;p&gt;The smallest UI component in your project.&lt;br&gt;
Like this. Just a nuxt-lint, div and img.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;nuxt-link to="/album"&amp;gt;
      &amp;lt;img class="album-image" :src="album.coverUrl" /&amp;gt;
    &amp;lt;/nuxt-link&amp;gt;
    &amp;lt;div&amp;gt;
      {{ album.title }}
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  props: {
    album: {
      type: Object,
      required: true,
      default: null
    }
  }
}
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;  &amp;lt;-- Use scoped to make the css only in this component
.album-image {
  height: 175px;
  width: 175px;
  object-fit: cover;
  border-radius: 10px;
  margin: 1px;
}
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  molecules
&lt;/h2&gt;

&lt;p&gt;contain some atoms&lt;br&gt;
In my case, I make it to be a array of the atoms&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;b-row&amp;gt;
    &amp;lt;b-col v-for="album in albums" :key="album.id"&amp;gt;
      &amp;lt;Album :album="album"&amp;gt;&amp;lt;/Album&amp;gt;  &amp;lt;!-- use props in atom to receive the data --&amp;gt;
    &amp;lt;/b-col&amp;gt;
  &amp;lt;/b-row&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import Album from '~/components/atoms/Album.vue'  //  import the atom file

export default {
  components: {
    Album // This is the atom componet
  },

  props: {
    data: {
      type: Object,
      required: true,
      default: () =&amp;gt; null
    }
  }
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  organisms
&lt;/h2&gt;

&lt;p&gt;combine different molecules&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;section&amp;gt;
    &amp;lt;nav class="navbar navbar-light bg-light"&amp;gt;
      &amp;lt;BackBtn path="/activity"&amp;gt;&amp;lt;/BackBtn&amp;gt;  &amp;lt;--- You can use atom aslo
    &amp;lt;/nav&amp;gt;
    &amp;lt;div class="container-fluid"&amp;gt;
      &amp;lt;AlbumList :data="albums"&amp;gt;&amp;lt;/AlbumList&amp;gt;  
      &amp;lt;CommentList :comments="comments"&amp;gt;&amp;lt;/CommentList&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/section&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import BackBtn from '~/components/atoms/BackBtn.vue'
import CommentList from '~/components/molecules/CommentList.vue'
import AlbumList from '~/components/molecules/AlbumList.vue'

export default { 
  components: {
    BackBtn, 
    CommentList, // different molecules
    AlbumList    // some molecules
  }
  data() {
    comments: [
       ... // 
    ]
  },
  computed: {
    albums() {
      return this.$store.state.albums // Using Vuex with Atomic Design is aslo a great idea, you can get your data in different ways.In your atom or your molecules.
    }
  },
}
&amp;lt;/script&amp;gt;

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



&lt;h2&gt;
  
  
  Page
&lt;/h2&gt;

&lt;p&gt;Finally, The page.&lt;br&gt;
Make sure your page using less code you need.&lt;br&gt;
If your page is larger you should think about to separate into small things like atom.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;section&amp;gt;
    &amp;lt;AlbumBook&amp;gt;&amp;lt;/AlbumBook&amp;gt;
  &amp;lt;/section&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import AlbumBook from '~/components/organisms/AlbumBook.vue'

export default {
  components: {
    AlbumBook
  }
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  At Last
&lt;/h2&gt;

&lt;p&gt;Separate your files, decide which component should be smaller. Maybe is the most challenge thing in your project.&lt;br&gt;
But once you get to the point. you'll get the benefit from it. &lt;br&gt;
Especially in your early development stage.&lt;/p&gt;

&lt;p&gt;1.Every time you want to change your component. You don't need to change them all.Just in one file.Any other your page import that file can be changed.&lt;/p&gt;

&lt;p&gt;2.Easy for management.Less code you need, Less bugs.&lt;/p&gt;

&lt;p&gt;P.S:&lt;br&gt;
This post is what I wrote in my Qiita.(A Japanese Tech blog)&lt;br&gt;
&lt;a href="https://qiita.com/jakushin/items/e8b19f6a2c7bcd037573"&gt;https://qiita.com/jakushin/items/e8b19f6a2c7bcd037573&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vue</category>
      <category>nuxt</category>
      <category>javascript</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
