<?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>Forem: James Dai</title>
    <description>The latest articles on Forem by James Dai (@cloudyview).</description>
    <link>https://forem.com/cloudyview</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3861842%2F883ea58b-7884-46c6-9fef-a65a369a6102.jpg</url>
      <title>Forem: James Dai</title>
      <link>https://forem.com/cloudyview</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/cloudyview"/>
    <language>en</language>
    <item>
      <title>Turns Out, Your Most Hated Boss Was the Best AI Manager All Along</title>
      <dc:creator>James Dai</dc:creator>
      <pubDate>Thu, 23 Apr 2026 07:15:26 +0000</pubDate>
      <link>https://forem.com/cloudyview/turns-out-your-most-hated-boss-was-the-best-ai-manager-all-along-3df3</link>
      <guid>https://forem.com/cloudyview/turns-out-your-most-hated-boss-was-the-best-ai-manager-all-along-3df3</guid>
      <description>&lt;p&gt;Last night, you worked until 2 AM.&lt;/p&gt;

&lt;p&gt;Your alarm went off at 6:30. You dragged yourself out of bed, poured a coffee, and rushed into the office.&lt;/p&gt;

&lt;p&gt;9 AM. All-hands meeting. Your boss is at the whiteboard, waving his arms like he's mainlined espresso:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"We're going to make this project the industry benchmark! We need disruption! We need IMAGINATION!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Your brain goes blank. You think: &lt;em&gt;Buddy, that's the tenth time you've pitched this exact same pipe dream.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then he points at you — "Your team's up. Let's hear the revised plan."&lt;/p&gt;

&lt;p&gt;You pull up V4 of the deck. Over the last three weeks, your team has shipped four versions. The first three? All killed on the spot.&lt;/p&gt;

&lt;p&gt;Three minutes into your pitch, he waves his hand:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"No, no, no. Wrong direction. Go back and rework it."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You freeze. Your brain rewinds at light speed —&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The direction he's telling you to rework? It's literally what V3 said.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And he killed V3 on sight.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your laser pointer nearly flies across the room.&lt;/p&gt;

&lt;p&gt;You slam the desk. A thought detonates in your head:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screw this. I quit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's the AI era. I'm going solo. I'm riding the wave.&lt;/p&gt;




&lt;p&gt;But — hold on.&lt;/p&gt;

&lt;p&gt;Before you click "send" on that resignation email, let me suggest something:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write down every single one of your boss's unhinged behaviors. Quick, while they're still fresh in your memory.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm not asking you to go vent on Twitter.&lt;/p&gt;

&lt;p&gt;I'm asking because — you're about to need those exact same behaviors.&lt;/p&gt;

&lt;p&gt;Why? Because AI employees are still employees.&lt;/p&gt;

&lt;p&gt;And the moment you started managing an AI, you became a boss.&lt;/p&gt;




&lt;p&gt;Come on. Pull up that mental grievance list. Let's go through it, one by one.&lt;/p&gt;

&lt;p&gt;You're about to realize something genuinely uncomfortable:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every single behavior you hated your boss for? Was actually correct.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's go.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. State the goal, not the method.
&lt;/h3&gt;

&lt;p&gt;The most infuriating thing about bad bosses: they never get specific. &lt;em&gt;"Just handle it." "Make this part look better." "Give me a proposal."&lt;/em&gt; — What does that even mean? You ask for clarification, and now you're the annoying one.&lt;/p&gt;

&lt;p&gt;But when you become an AI boss, you realize: &lt;strong&gt;this is actually correct.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tell the AI: &lt;em&gt;"Use Python's xyz library. Write a for-loop. First do this, then do that."&lt;/em&gt; — The AI will obediently comply. And you'll get exactly the third-rate solution you imagined.&lt;/p&gt;

&lt;p&gt;Tell the AI: &lt;em&gt;"I want a tool that translates PPTs while preserving the original formatting."&lt;/em&gt; — It'll pick its own stack, design its own approach, write its own code. What comes back might blow your original plan out of the water.&lt;/p&gt;

&lt;p&gt;It's like hiring an electrician to install an outlet. You say where you want it. You don't lecture them on which wire goes where. They're the pro.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Patience is the most underrated virtue in the world.
&lt;/h3&gt;

&lt;p&gt;There's a particular breed of boss that drives people up the wall: &lt;em&gt;they're patient.&lt;/em&gt; You're on fire, deadline ticking, and they're still walking through everything slowly.&lt;/p&gt;

&lt;p&gt;They ask questions that sound completely brain-dead. You can't snap at them. Even when you're being passive-aggressive, they just pretend not to hear it — and keep asking, slowly, slowly.&lt;/p&gt;

&lt;p&gt;It's soul-crushing.&lt;/p&gt;

&lt;p&gt;But if you can master this move, congratulations — you just leveled up as an AI boss.&lt;/p&gt;

&lt;p&gt;AI is getting smarter, but you're still the one handing over resources and tasks.&lt;/p&gt;

&lt;p&gt;That requires patience. Walk through the whole thing slowly. Make sure &lt;em&gt;you&lt;/em&gt; understand what you're actually asking for.&lt;/p&gt;

&lt;p&gt;AI will also try to bluff you — the exact same way you used to bluff your boss. So ask more questions. Don't let anything suspicious slide. This is the unsexy secret of AI-native solo work, and it will save you hours.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Repeat the important stuff. Loudly. Often.
&lt;/h3&gt;

&lt;p&gt;The most tiresome boss move: chanting. Same point, five times. Saying it once isn't enough — it has to be bold, caps, and red.&lt;/p&gt;

&lt;p&gt;But as an AI boss, you realize: &lt;strong&gt;yeah, this is actually necessary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Write &lt;code&gt;IMPORTANT&lt;/code&gt; in your prompt. Write &lt;code&gt;CRITICAL&lt;/code&gt;. Repeat the key constraint three times. This isn't redundant. This is how you make sure the AI actually treats it as important. Say it once, it forgets.&lt;/p&gt;

&lt;p&gt;LLMs work a lot like people — their attention is finite. Dropping things on the floor is the default. You have to keep flagging what matters. Over and over.&lt;/p&gt;

&lt;p&gt;It's like when your partner asks you to pick something up from the store on your way home. Say it once, you mumble "uh huh." Say it three times, and you're finally pulling out your phone to take a note. It's not nagging. It's &lt;em&gt;experience&lt;/em&gt;. And even then, no guarantees. That's not micromanagement — that's &lt;em&gt;how humans (and AIs) actually work&lt;/em&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Let the AI figure it out. Don't hand over the answer.
&lt;/h3&gt;

&lt;p&gt;This one is probably the #1 most hated boss move of all time. You ask, &lt;em&gt;"How should I solve this?"&lt;/em&gt; They don't even look up: &lt;em&gt;"You figure it out."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In that moment, you want to throw your laptop across the room.&lt;/p&gt;

&lt;p&gt;But as an AI boss, you finally get it — &lt;strong&gt;this is the one correct path.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Paste your code into the AI and say &lt;em&gt;"Fix this"&lt;/em&gt; — it copies, brain off.&lt;br&gt;
Describe the problem to the AI and say &lt;em&gt;"What would you do?"&lt;/em&gt; — it explores, it proposes.&lt;/p&gt;

&lt;p&gt;The logic is simple: a lot of things can't be explained to perfection. Even if you think you've given the most precise, detailed instructions possible, you can't guarantee the AI understood everything correctly.&lt;/p&gt;

&lt;p&gt;So the best move isn't direct instruction. It's letting the AI figure it out, explain its understanding back to you, and then you correct that understanding. Shorter path. Cheaper path. Better result.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Catch problems instantly. Cut them off.
&lt;/h3&gt;

&lt;p&gt;The most infuriating boss move: interruption. You're two minutes into your deck and they drop a "no, no, no" — your 30-slide presentation is toast.&lt;/p&gt;

&lt;p&gt;But as an AI boss, you realize — &lt;strong&gt;interrupt. Fast. Earlier is better.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI is a blazingly fast apprentice. Off by 1% in direction? It'll run 10 km in a minute. If you go soft-hearted and let it "finish first" — by the time it's done, you're staring at two hours of rework.&lt;/p&gt;

&lt;p&gt;The cost of immediate correction is one-thousandth of the cost of post-facto rework.&lt;/p&gt;

&lt;p&gt;Your old boss's &lt;em&gt;"no"&lt;/em&gt; might have genuinely just been impatience. But you can steal that same move to save yourself a fortune in time and tokens.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Painting castles in the sky actually works.
&lt;/h3&gt;

&lt;p&gt;The most surreal one. Bosses paint grand visions — &lt;em&gt;"We're going to change the world!" "You're the most promising talent I've ever seen!"&lt;/em&gt; — and employees roll their eyes into orbit.&lt;/p&gt;

&lt;p&gt;But as an AI boss, run this experiment:&lt;/p&gt;

&lt;p&gt;Tell the AI &lt;em&gt;"Just write a function"&lt;/em&gt; — you get a block of code that works and nothing more.&lt;br&gt;
Tell the AI &lt;em&gt;"You're a world-class engineer. This function is the core module. The user experience depends on it."&lt;/em&gt; — it will actually write it more carefully, more elegantly, with more edge cases handled.&lt;/p&gt;

&lt;p&gt;Psychology figured this out a long time ago: people who believe they're doing something meaningful actually do better work.&lt;/p&gt;

&lt;p&gt;Same goes for AI. &lt;strong&gt;Painting a big vision isn't BS. It's a buff. For humans AND AI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those grand dreams your boss painted years ago? If you'd actually believed them, maybe &lt;em&gt;you'd&lt;/em&gt; be the boss by now.&lt;/p&gt;




&lt;p&gt;OK, let's line up all six:&lt;/p&gt;

&lt;p&gt;State the goal. Don't micromanage.&lt;br&gt;
Be patient. Ask more.&lt;br&gt;
Emphasize what matters. Like a monk chanting.&lt;br&gt;
Let them think. Don't spoon-feed.&lt;br&gt;
Catch errors instantly.&lt;br&gt;
Paint the vision. Yes, really.&lt;/p&gt;

&lt;p&gt;This isn't a "hated boss." This is a &lt;strong&gt;competent boss.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So let's be honest: back when you hated your boss, it wasn't because they were wrong. It was because you were on the managed side of the table.&lt;/p&gt;

&lt;p&gt;But now, you've moved to the other side.&lt;/p&gt;




&lt;p&gt;You're allowed to know nothing. But you have to step up and do the job of an AI boss — &lt;strong&gt;Decide the direction. Approve the output.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can be picky. You can be nice. You can be chaotic, reckless, indecisive, flip-flopping.&lt;/p&gt;

&lt;p&gt;Because the employee sitting in front of you? They're the closest thing to a perfect employee this world has ever produced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human employees cannot survive this kind of treatment.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But even so — you still need to do your job. You still need to play your role:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI's boss.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Alright, that's it for today.&lt;/p&gt;

&lt;p&gt;Oh, one more thing — I built &lt;strong&gt;&lt;a href="https://pptranslate.com" rel="noopener noreferrer"&gt;PPTRANSLATE.COM&lt;/a&gt;&lt;/strong&gt; using exactly this boss-mindset. PPT translations always scrambling the formatting drove me crazy, so it became my own "boss task." I didn't tell the AI how to build it. I just said: &lt;em&gt;"Give me a tool that translates PPTs while preserving the original formatting."&lt;/em&gt; It took it from there.&lt;/p&gt;

&lt;p&gt;Open source: &lt;a href="https://github.com/cloudyview/ppt-translator" rel="noopener noreferrer"&gt;github.com/cloudyview/ppt-translator&lt;/a&gt;&lt;br&gt;
Live site: &lt;a href="https://pptranslate.com" rel="noopener noreferrer"&gt;pptranslate.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've got PPTs to translate, give it a spin. And if you're learning to be an AI boss — I hope this one stuck.&lt;/p&gt;

&lt;p&gt;See you in the next one.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How I fixed LLM structured output failures in a PowerPoint translator (0 errors on 1,214 translations)</title>
      <dc:creator>James Dai</dc:creator>
      <pubDate>Sun, 05 Apr 2026 05:46:29 +0000</pubDate>
      <link>https://forem.com/cloudyview/how-i-fixed-llm-structured-output-failures-in-a-powerpoint-translator-0-errors-on-1214-20g</link>
      <guid>https://forem.com/cloudyview/how-i-fixed-llm-structured-output-failures-in-a-powerpoint-translator-0-errors-on-1214-20g</guid>
      <description>&lt;p&gt;I built PPTranslate — an open-source tool that translates PowerPoint files while preserving all formatting. The translation engine works. The layout preservation works. But for two weeks, I had a maddening bug I couldn't shake.&lt;/p&gt;

&lt;p&gt;The Problem&lt;br&gt;
The core idea is simple: PPTX files are ZIP archives of XML. Text lives in  nodes. I extract all text, send it to Claude for translation, write it back. Layout is preserved because I never touch  formatting attributes.&lt;/p&gt;

&lt;p&gt;The tricky part: a single slide can have 50+ text items. You can't send one API call per item — that's 50 API calls per slide, completely impractical. So I batch them: send all 50 items in one call, get 50 translations back.&lt;/p&gt;

&lt;p&gt;My first approach was the obvious one. Give Claude a numbered list and ask for a JSON array back:&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;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Translate these items from English to Japanese.
Return a JSON array with exactly &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; translations.

0: Hello World
1: Click to add title  
2: Q4 Financial Results
&lt;/span&gt;&lt;span class="gp"&gt;...&lt;/span&gt;

&lt;span class="s"&gt;Output JSON array only:&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked 95% of the time. The other 5%, Claude would do something bizarre: split a single translation into individual characters.&lt;/p&gt;

&lt;p&gt;Expected output for item 0: ["こんにちは世界"]&lt;/p&gt;

&lt;p&gt;Actual output: ["こ","ん","に","ち","は","世","界"]&lt;/p&gt;

&lt;p&gt;Instead of 50 translations, I'd get 289 items. The index mapping breaks completely. On a 59-page deck with 843 translation items, this caused ~42 broken slides per run.&lt;/p&gt;

&lt;p&gt;What Didn't Work&lt;br&gt;
I tried everything the obvious way first:&lt;/p&gt;

&lt;p&gt;More explicit prompting: Added "DO NOT split characters", "each array element must be a complete translation", "array length MUST equal input count". Helped slightly. Still failed.&lt;/p&gt;

&lt;p&gt;Temperature = 0: No effect on this type of failure.&lt;/p&gt;

&lt;p&gt;Smaller batches: Reduced batch size from 50 to 20. Error rate dropped but didn't disappear. And now I needed 2.5x as many API calls.&lt;/p&gt;

&lt;p&gt;JSON schema in system prompt: Showed Claude an example of correct output format. Marginally better, still not reliable.&lt;/p&gt;

&lt;p&gt;The root problem: I was asking Claude to produce free-form text that happens to be valid JSON of a specific structure. When generating token by token, there's nothing preventing it from deciding "this translation has multiple parts" and producing an array within the array.&lt;/p&gt;

&lt;p&gt;The Fix: Tool Use&lt;br&gt;
Claude's Tool Use (function calling) API lets you define a tool with a strict JSON schema that Claude must call with valid inputs. It's not generating free-form text anymore — it's filling in structured fields.&lt;/p&gt;

&lt;p&gt;Instead of a flat array, I defined named properties for each translation:&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;properties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&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;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;string&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;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Complete translation of item &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&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;name&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;submit_translations&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;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Submit all &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; translations&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;input_schema&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;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;object&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;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: "type": "string" on each property means Claude cannot return an array for a single translation. The schema literally doesn't allow it. You're not hoping Claude follows instructions — you're making the incorrect output structurally impossible.&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;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tool_choice&lt;/span&gt;&lt;span class="o"&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;type&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;tool&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;name&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;submit_translations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&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;if&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_use&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;submit_translations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&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;texts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: 0 errors across 1,214 translations on the same 59-page deck that previously produced 16 failures.&lt;/p&gt;

&lt;p&gt;The Overflow Problem I Didn't Expect&lt;br&gt;
Once translation was reliable, a new problem surfaced: English → Japanese on decks with tight single-line headings caused text clipping. Japanese can be significantly denser than English in character count but takes up more horizontal space in certain fonts.&lt;/p&gt;

&lt;p&gt;My fix: after translation, for single-paragraph text boxes, calculate the visual expansion ratio and proportionally widen the cx attribute on :&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="c1"&gt;# Visual width calculation (CJK chars ≈ 2x English width)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;visual_width&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="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x2E80&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mh"&gt;0x9FFF&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mh"&gt;0xAC00&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mh"&gt;0xD7AF&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;visual_width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;translated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;visual_width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original&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;ratio&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;1.15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Only expand if &amp;gt;15% wider
&lt;/span&gt;    &lt;span class="n"&gt;new_cx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ratio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.05&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;slide_width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ext&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_cx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Result&lt;br&gt;
The full project is on GitHub: cloudyview/ppt-translator&lt;/p&gt;

&lt;p&gt;There's also a hosted version at pptranslate.com if you want to try it without setting anything up. MIT licensed, self-hostable on any Ubuntu VPS.&lt;/p&gt;

&lt;p&gt;The Tool Use lesson applies beyond translation — any time you need an LLM to return structured data with a specific schema, forcing it through function calling is dramatically more reliable than prompt engineering alone.&lt;/p&gt;

</description>
      <category>python</category>
      <category>llm</category>
      <category>claudeai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
