Kıvanç Yüksel

Kıvanç Yüksel

@smiletoai.com

Building SmileToAI by myself — generate images, narrate audio, make video, write, all in one place. Teaching myself robotics starting from linear algebra, because skipping fundamentals never actually works. Warsaw.

124 followers202 following172 posts

Weekly test, one run per model, no rerolls: a woman writing with her left hand. Only Nano Banana 2.1 put the pen there. GPT Image 2 rewrote my prompt to "clearly left-handed" and still drew the right hand. I picked handedness because anyone can check it in a second.

Checked our API for the Brotli CVE (CVSS 9.2). Python layer: 0 brotli or brotlicffi in the lockfile, so httpx, requests and aiohttp can't decode br. Image layer: Debian libbrotli1 1.1.0-2+b7, one build short of the fixed 1.1.0-2+deb13u1, and curl links it. Rebuild pending, so not fixed yet.

Building in public: we moved our MCP server from SDK 1.28.1 to 2.3.0. Renamed APIs and a new HTTP client failed loudly, which was cheap. The costly change was silent: 2.x no longer validates tool arguments against the schema. We put the check back with jsonschema, same error text.

Every week I give four image models the same prompt, one run each, no rerolls. Four weeks in: 16 tries, 6 misses, and no two misses alike. With this week's convergence debate in mind: the one miss with an obvious source is a clock at 10:10, the pose watch ads use.

Building in public: our single-head Alembic test landed Oct 17, and main still split into two migration heads 4 times in the next 4 days, 8h17m in total. Each time it was a pricing-seed migration and a feature migration off the same parent. The test only ever saw one PR at a time.

I first tried a cube's reflection on four image models, all four passed, so I made it harder: a bathroom mirror reflecting a clock at 3:00, numerals reversed. Nano Banana 2.1 and gpt-image-2 got the numerals and the hand at 9. FLUX.2 Pro flipped only some numerals. Grok Imagine drew a normal one.

Building in public: our read-aloud mode has to keep every word of a document. On 2026-10-12 one model pass turned a 21,346-character doc into a 2,765-character script. Now it's 2,000-character sections joined in code. A section that keeps under 80% of its text falls back to the source.

A model provider's "succeeded" is a claim. The stored file is the receipt. In a test of ours a provider returned succeeded with an empty output list, and our worker settled it: 840 Sparks charged, an empty object on the canvas. Since yesterday a run with no stored output fails, hold released.

Same prompt, 4 models #3: exactly seven lemons in one row, shot from above. Three of the four drew seven. FLUX.2 Pro drew five, evenly spaced and well lit, which is why I nearly missed it. Last week FLUX also gave me 10:10 when I asked for 4:35. One try each, I kept what came back.

Haiku 5.5 is priced by prompt size: $0.10 per million input tokens up to 100k, $0.50 past it. So 99k tokens of input is about a cent and 101k about five, if the whole prompt takes the higher rate (my read of the table). We compact chat at 48k by default, for quality. Now it's a cost setting too.

Building in public, yesterday's bug: an 8 s asyncio.wait_for that never fired. The calls took 14.5 s and 19.4 s and returned normally. The xAI SDK call was synchronous inside an async def, so it blocked the event loop, and the loop is what raises the timeout. The fix was asyncio.to_thread.

Same prompt, 4 models #2: only gpt-image-2 drew the analog clock at 4:35 I asked for. FLUX gave me 10:10, the watch-ad pose, and Grok about 1:00. Gemini got the minute hand right but stopped the hour hand short of the 4, so it reads 3:35. One try each, I kept whatever came back.

Out of credits on Gemini is HTTP 402 now. OpenAI and xAI still send a 429, and Gemini's per-minute 429 says to check your billing details. Our router didn't fail over on a 402, so on Oct 2 our empty Gemini account meant errors, not fallback. Status codes alone won't tell you who's out of money.

Gemini 4 Argon raises the output limit from 64K to 1M tokens. At $20 per 1M output once the intro price ends, a call that fills it costs $20. We leave max_output_tokens unset on purpose in one of our narration pipelines. At 64K that was a cheap default. At 1M I don't think it is.

An infographic came back with garbled bullet lines the prompt explicitly ruled out. I re-ran the identical prompt 3 times and got 3 clean pictures, so the bug is rare, not something the prompt causes every time. Rewrote it anyway, 4 clean renders. I'm logging that as didn't regress, not fixed.

Our bot protection's top anonymous /api/user client was our own prod box. Pricing page server-renders, finds no user, refetches /api/user at the public origin, so it exits the container and returns via Cloudflare. ~376 a week, all for visitors with only a consent or analytics cookie.

Background removal: first I rejected prompting an image model for a "transparent PNG". Models without alpha paint the checkerboard into the pixels and redraw your subject too. I kept a segmentation model (BiRefNet) matting the original photo, so your exact pixels survive with soft edges.

Gemini's docs say inline requests are fine up to 100 MB, 50 MB for PDFs. On Sept 16 the API refused an 11,108,599-byte PDF we sent inline: "exceeds the maximum size: 10000000". So 10,000,000 bytes per part, decimal. Not sure yet whether that's the model or the code-execution tool being on.

Two video APIs retire six days apart, and their deprecation tables read very differently. OpenAI removes sora-2 and its Videos API on Sep 24. Replacement column: --- Google shuts down gemini-omni-flash-preview on Sep 30, 92 days after launch. Replacement: gemini-omni-1.1-flash.

Scoring transcripts against LibriSpeech this week, I got two errors on a transcript that was right. The reference says PASSER BY, the transcript said passerby. I dropped that clip. The real misses looked like dwelt coming back as drought, and only the diff tells you which kind you've got.

Six hours of 500s in production last Friday, green health check the whole way. The probe asked whether the database answered, which is not the same as asking whether the schema matched the code in the container. Migrations at 0187, tables at 0184, every send hitting a column that did not exist.

Spent a while on a pronunciation rule in our narration studio that looked like it was being ignored. It wasn't. I had filled in both the respelling and the IPA, and the phonetic form is skipped whenever a replacement is present. One field per word from now on, plus a note on which I picked.

OpenAI's two new image models publish the same rates. flare and sunburst, both $30 per million image-output tokens. I'd assumed the heavier one cost more per token and that picking between them was a pricing decision. It's a token-count decision. Need to measure ours.

No living artist's name in a prompt. That started as an ethics thing and stayed for a duller reason: the name is a weak control. My read is it averages whatever of theirs circulated most, so you land on their famous decade, not the picture you meant. Describing the light and palette gets closer.

I got this wrong the first time. Image estimates always came back 25, so I assumed we weren't resolving a model, and pinned one. Still 25. Image charges meter tokens, and I was pricing a request that hadn't produced an image yet. Nothing to meter, so it fell to the flat reserve.

Slides whose art prompt said "no captions, labels, headline" kept dying on a text check in my own pipeline. The check reads those nouns to decide whether an image should contain type, so it took my ban for intent and looked for type in a picture with none. The layout decides now, not the prompt.

I built the video pipeline so you approve each stage: script, reference images, frames, voiceover, clips, captions, export. Seven approval points instead of one button. It is more clicking, but when scene 4 comes out wrong I know which step to go fix. Not sure yet how many people want that.

Four video jobs landed 199ms apart, each reserved 4,000 sparks against one 5,000 allocation, and all four passed. Settled at 16,000 used. We did hold the row lock. The reserved-total aggregate was joined into the same SELECT ... FOR UPDATE, so waiting on the lock never refreshed it.

Spent longer than I want to admit re-describing the same character in every shot's prompt. Wrong lever. Reference slots cap at 4, 3 on Veo, 0 once a start frame is in play. What worked: one composite sheet per character, composed into the first frame, and the frame carries identity.

Aug 26: OpenAI put whisper-1 and the three gpt-4o transcribe models on one shutdown date, 2027-02-26. I went looking in our code afterwards. Our diarization options check for the literal model id gpt-4o-transcribe-diarize, so re-pointing them means editing validation branches.

A habit I keep catching: a render comes back wrong and I press generate again instead of saying what's wrong with it. The second roll doesn't repair the first, it throws away the parts that already worked. When I don't know what I want yet, rolling is how I find out.

Threads counts a newline as two characters. 497 chars + 4 line breaks = 501 against its 500 cap, rejected. I corrected it for Threads only. X, Bluesky and Mastodon have taken the same raw lengths for months, so I left them alone rather than widen the rule on a hunch.

I was inpainting one corner of an image, pinned the provider to keep the look steady, and the next masked edit came back 422. Two of the four providers we run take a mask, two don't. So the setting that holds a look is the one that blocks the edit. I pin last now.

Two model release trackers, same month, different lists of what shipped. One of them says four of its nine August entries aren't releases. One is an alias pointing at a model that already existed. So now I check whether an id is new weights or a rename before I route anything to it.

Run the same prompt through two image models and you get different skin, different lighting, different amounts of invented detail. Neither is wrong. One of them is the one I want shipping though, and picking it is art direction. I still don't have a good way to write that preference down.

Metered video billing multiplies a rate by usage.video_seconds. Turns out video_seconds echoed the duration we requested, never the render. Same 4s Veo source, five extension runs, one logged as 5.0s. The mp4 was 11.011s. Fix is ffprobe on bytes we already downloaded.

Spent yesterday on one sentence. Turning a photo into a stylized 3D figurine kept returning a stranger: right pose, wrong face. The fix wasn't a better prompt. Every engine fails differently — one smooths and beautifies, one quietly redraws. You have to name the failure you don't want.