{"id":3570,"date":"2025-09-03T11:59:24","date_gmt":"2025-09-03T09:59:24","guid":{"rendered":"https:\/\/blog.tomayac.com\/2025\/09\/03\/for-all-thats-holy-can-you-just-leverage-the-web-please\/"},"modified":"2025-09-03T11:59:24","modified_gmt":"2025-09-03T09:59:24","slug":"for-all-thats-holy-can-you-just-leverage-the-web-please","status":"publish","type":"post","link":"https:\/\/tomayac.com\/wordpress\/2025\/09\/03\/for-all-thats-holy-can-you-just-leverage-the-web-please\/","title":{"rendered":"For all that&#8217;s holy, can you just leverage the Web, please?"},"content":{"rendered":"\n\t\t\t<p>When I moved in with my wife Laura in 2005, we lived in a shared apartment in\nBarcelona that had an ancient washing machine that was just there already, no\nidea who initially bought it. I managed to break the washing machine door's\nclosing mechanism some time in 2006, so for a few weeks, whenever we did the\nwashing, we had to lean a chair against the door so it wouldn't open. At the\ntime, we were both students and living on a small budget.<\/p>\n<p>Eventually, later in the same year, we bought an Electrolux machine that has\naccompanied us ever since. First on our move to Hamburg, then there through\nthree apartments, and finally back to Spain, where we live now in the Catalonian\ncountryside. Anyway, the washing machine had a motor damage last week, so after\nalmost 20 years, it was time for a new one. I ordered it online (another\nElectrolux, <em>without<\/em> Internet nor WiFi), it was delivered swiftly, and I\ninstalled it hopefully correctly.<\/p>\n<p><img src=\"https:\/\/blog.tomayac.com\/images\/electrolux.png\" alt=\"Our new Electrolux washing machine.\" \/><\/p>\n<p>The washing machine came with a voluntary 10 year warranty if you registered it.\nThe brochure where this offer was announced featured a free telephone number and\na QR code that pointed at the number (in plain text, not making use of the\n<code>tel:<\/code> protocol). I called the number, and to my <em>absolute surprise<\/em> there were\ncurrently more callers than usual. After about 20 minutes, I had an agent on the\nphone, but after saying what I wanted, they just hung up on me (or the\nconnection cut, whatever). Fine, I called again, but now, the call center was\nover capacity and they didn't even let me enter in the wait loop.<\/p>\n<p>They did offer to send me a link to a chat service on their website via SMS,\nthough, so I went for that option. The SMS literally pointed me at something\nlike <code>https:\/\/www.<\/code> broken up by a space and then <code>example.com\/gc\/<\/code>. When I\nclicked the linkified <code>example.com\/gc\/<\/code>, I ended up on a broken site whose\ncertificate wasn't trusted. After fixing the link manually and prepending the\n<code>https:\/\/www.<\/code> part, the page didn't load.<\/p>\n<p>At this point I was close to giving up, but I had one last card that I wanted to\nplay: I searched Google for &quot;electrolux warranty register&quot;, and it pointed me at\na site <code>https:\/\/www.example.com\/mypages\/register-a-product\/<\/code> as the first\nresult. This looked promising. The <code>mypages<\/code> already suggested that this was\ngated behind a login, so I created an account, which was painless. (Turns out,\nafter having an account and being logged in, the chat URL also worked\u2014what an\noversight on their part.) On the page, they had a field where you could enter\nthe washing machine's product number from the identification plate on the door\nof the washing machine, together with helpful information where to find the\ndata.<\/p>\n<p><img src=\"https:\/\/blog.tomayac.com\/images\/find-pnc-description-electrolux-serialplate.avif\" alt=\"Annotated Electrolux identification plate.\" \/><\/p>\n<p>But even better, they offered a service where you could just upload a picture of\nthe identification plate, and some AI on their server then extracted the product\nnumber and let you register the product with two clicks. What a fantastic\nexperience compared to the crappy (and likely for the operator way more\nexpensive) call center experience.<\/p>\n<p><img src=\"https:\/\/blog.tomayac.com\/images\/electrolux-plate.jpg\" alt=\"Electrolux identification plate cell phone photo.\" \/><\/p>\n<p>Why they didn't just put this URL on the brochure and the QR code is beyond me.\nAs the title suggests: <strong>For all that's holy, can you just leverage the Web,\nplease?<\/strong> Don't make me talk to people! They could still offer to register the\nmachine by telephone as an alternative, but in 2025, the default for such things\nshould just be the Web.<\/p>\n<h2 id=\"bonus\" tabindex=\"-1\">Bonus <a class=\"direct-link\" href=\"https:\/\/blog.tomayac.com\/2025\/09\/03\/for-all-thats-holy-can-you-just-leverage-the-web-please\/#bonus\" aria-hidden=\"true\">\ud83d\udd17<\/a><\/h2>\n<p>Since I work on <a href=\"https:\/\/developer.chrome.com\/docs\/ai\/built-in\">built-in AI<\/a> as\nmy day job in the Chrome team at Google, I could not <em>not<\/em> notice this <em>&quot;extract\nthe product number from this identification plate&quot;<\/em> use case for client-side AI.\nI coded up a quick\n<a href=\"https:\/\/tomayac.github.io\/blogccasion-demos\/built-in-ai-product-number-ocr\/\">demo<\/a>\nusing the <a href=\"https:\/\/developer.chrome.com\/docs\/ai\/prompt-api\">Prompt API<\/a> embedded\nbelow that shows this in action. Here's a quick walkthrough of the code:<\/p>\n<ol>\n<li>Create a session with the <code>LanguageModel<\/code>, informing the user of download\nprogress if the model needs to be downloaded, and telling the model about the\nto-be-expected inputs (English texts and images) and outputs (English texts).\nIn the system prompt, I tell the model what its overall task is (identify\nproduct numbers from photos of identification plates).<\/li>\n<li>Prompt the model using the <code>promptStreaming()<\/code> method with a multimodal\nprompt, one textual and one image. The Prompt API supports\n<a href=\"https:\/\/developer.chrome.com\/docs\/ai\/structured-output-for-prompt-api?hl=en\">structured output<\/a>\nin the form of a JSON Schema or regular expression. Product numbers have nine\ndigits, so I pass the regular expression <code>\/\\d{9}\/<\/code> as the\n<code>responseConstraint<\/code> option.<\/li>\n<li>Iterate over the chunks of the response. Since I'm just expecting nine\ndigits, this is probably a bit overkill, but, hey\u2026<\/li>\n<li>(Not shown) On the server, verify that the recognized product number actually\nexist. Companies typically have some sort of verification rules like\nchecksums, or washing machine product numbers always start with <code>91<\/code> or\nsomething. If you know those rules, you can of course make them part of the\n<code>responseConstraint<\/code>, but you always need to verify untrusted user input\n(which the output of an LLM counts as) on the server.<\/li>\n<\/ol>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const<\/span> session <span class=\"token operator\">=<\/span> <span class=\"token keyword\">await<\/span> LanguageModel<span class=\"token punctuation\">.<\/span><span class=\"token function\">create<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">{<\/span>\n  <span class=\"token function\">monitor<\/span><span class=\"token punctuation\">(<\/span><span class=\"token parameter\">m<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n    m<span class=\"token punctuation\">.<\/span><span class=\"token function\">addEventListener<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">'downloadprogress'<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token parameter\">e<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token operator\">=><\/span> <span class=\"token punctuation\">{<\/span>\n      console<span class=\"token punctuation\">.<\/span><span class=\"token function\">log<\/span><span class=\"token punctuation\">(<\/span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`<\/span><span class=\"token string\">Downloaded <\/span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${<\/span>e<span class=\"token punctuation\">.<\/span>loaded <span class=\"token operator\">*<\/span> <span class=\"token number\">100<\/span><span class=\"token interpolation-punctuation punctuation\">}<\/span><\/span><span class=\"token string\">%.<\/span><span class=\"token template-punctuation string\">`<\/span><\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>\n    <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>\n  <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n  <span class=\"token literal-property property\">expectedInputs<\/span><span class=\"token operator\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">{<\/span> <span class=\"token literal-property property\">type<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">'text'<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token literal-property property\">languages<\/span><span class=\"token operator\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token string\">'en'<\/span><span class=\"token punctuation\">]<\/span> <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token literal-property property\">type<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">'image'<\/span> <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">]<\/span><span class=\"token punctuation\">,<\/span>\n  <span class=\"token literal-property property\">expectedOutputs<\/span><span class=\"token operator\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">{<\/span> <span class=\"token literal-property property\">type<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">'text'<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token literal-property property\">languages<\/span><span class=\"token operator\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token string\">'en'<\/span><span class=\"token punctuation\">]<\/span> <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">]<\/span><span class=\"token punctuation\">,<\/span>\n  <span class=\"token literal-property property\">initialPrompts<\/span><span class=\"token operator\">:<\/span> <span class=\"token punctuation\">[<\/span>\n    <span class=\"token punctuation\">{<\/span>\n      <span class=\"token literal-property property\">role<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">'system'<\/span><span class=\"token punctuation\">,<\/span>\n      <span class=\"token literal-property property\">content<\/span><span class=\"token operator\">:<\/span>\n        <span class=\"token string\">'Your task is to identify product numbers from photos of identification plates.'<\/span><span class=\"token punctuation\">,<\/span>\n    <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n  <span class=\"token punctuation\">]<\/span><span class=\"token punctuation\">,<\/span>\n<span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>\n\n<span class=\"token keyword\">const<\/span> stream <span class=\"token operator\">=<\/span> session<span class=\"token punctuation\">.<\/span><span class=\"token function\">promptStreaming<\/span><span class=\"token punctuation\">(<\/span>\n  <span class=\"token punctuation\">[<\/span>\n    <span class=\"token punctuation\">{<\/span>\n      <span class=\"token literal-property property\">role<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">'user'<\/span><span class=\"token punctuation\">,<\/span>\n      <span class=\"token literal-property property\">content<\/span><span class=\"token operator\">:<\/span> <span class=\"token punctuation\">[<\/span>\n        <span class=\"token punctuation\">{<\/span>\n          <span class=\"token literal-property property\">type<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">'text'<\/span><span class=\"token punctuation\">,<\/span>\n          <span class=\"token literal-property property\">value<\/span><span class=\"token operator\">:<\/span>\n            <span class=\"token string\">'Extract the product number from this identification plate. It has nine digits and appears after the text \"Prod.No.\".'<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token punctuation\">{<\/span> <span class=\"token literal-property property\">type<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">'image'<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token literal-property property\">value<\/span><span class=\"token operator\">:<\/span> image <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n      <span class=\"token punctuation\">]<\/span><span class=\"token punctuation\">,<\/span>\n    <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n  <span class=\"token punctuation\">]<\/span><span class=\"token punctuation\">,<\/span>\n  <span class=\"token punctuation\">{<\/span>\n    <span class=\"token literal-property property\">responseConstraint<\/span><span class=\"token operator\">:<\/span> <span class=\"token regex\"><span class=\"token regex-delimiter\">\/<\/span><span class=\"token regex-source language-regex\">\\d{9}<\/span><span class=\"token regex-delimiter\">\/<\/span><\/span><span class=\"token punctuation\">,<\/span>\n  <span class=\"token punctuation\">}<\/span>\n<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>\n\n<span class=\"token keyword\">for<\/span> <span class=\"token keyword\">await<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token keyword\">const<\/span> chunk <span class=\"token keyword\">of<\/span> stream<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n  console<span class=\"token punctuation\">.<\/span><span class=\"token function\">log<\/span><span class=\"token punctuation\">(<\/span>chunk<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<iframe src=\"https:\/\/tomayac.github.io\/blogccasion-demos\/built-in-ai-product-number-ocr\/\" allow=\"language-model\" style=\"border: none; width: 100%; height: 600px;\"><\/iframe>\n\n\t\t\t<p>\n\t\t\t\t<img alt=\"Thomas Steiner\" width=\"32\" height=\"32\" src=\"https:\/\/blog.tomayac.com\/feed.php?dl=https%3A%2F%2Fblog.tomayac.com%2F2025%2F09%2F03%2Ffor-all-thats-holy-can-you-just-leverage-the-web-please%2F&dp=%2F2025%2F09%2F03%2Ffor-all-thats-holy-can-you-just-leverage-the-web-please%2F&dt=For%20all%20that's%20holy%2C%20can%20you%20just%20leverage%20the%20Web%2C%20please%3F\" alt=\"\">\n\t\t\t\t<br\/>This post appeared first on <a href=\"https:\/\/blog.tomayac.com\/2025\/09\/03\/for-all-thats-holy-can-you-just-leverage-the-web-please\/\">https:\/\/blog.tomayac.com\/2025\/09\/03\/for-all-thats-holy-can-you-just-leverage-the-web-please\/<\/a>.\n\t\t\t<\/p>\n\t\t","protected":false},"excerpt":{"rendered":"<p>\t\t\tWhen I moved in with my wife Laura in 2005, we lived in a shared apartment in<br \/>\nBarcelona that had an ancient washing machine that was just there already, no<br \/>\nidea who initially bought it. I managed to break the washing machine door&#8217;s<br \/>\nclosing mechanis&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"intlwemo_wallet_address":"","footnotes":""},"categories":[],"tags":[],"class_list":["post-3570","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/tomayac.com\/wordpress\/wp-json\/wp\/v2\/posts\/3570","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tomayac.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tomayac.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tomayac.com\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tomayac.com\/wordpress\/wp-json\/wp\/v2\/comments?post=3570"}],"version-history":[{"count":1,"href":"https:\/\/tomayac.com\/wordpress\/wp-json\/wp\/v2\/posts\/3570\/revisions"}],"predecessor-version":[{"id":3571,"href":"https:\/\/tomayac.com\/wordpress\/wp-json\/wp\/v2\/posts\/3570\/revisions\/3571"}],"wp:attachment":[{"href":"https:\/\/tomayac.com\/wordpress\/wp-json\/wp\/v2\/media?parent=3570"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tomayac.com\/wordpress\/wp-json\/wp\/v2\/categories?post=3570"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tomayac.com\/wordpress\/wp-json\/wp\/v2\/tags?post=3570"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}