<?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: Rachel Vectle</title>
    <description>The latest articles on DEV Community by Rachel Vectle (@rachelvectle).</description>
    <link>https://dev.to/rachelvectle</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%2F4148256%2F0b92cc5b-f3c0-481d-8251-772fb8639146.png</url>
      <title>DEV Community: Rachel Vectle</title>
      <link>https://dev.to/rachelvectle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rachelvectle"/>
    <language>en</language>
    <item>
      <title>your watsonx.ai error isnt a network error, its a missing env var</title>
      <dc:creator>Rachel Vectle</dc:creator>
      <pubDate>Thu, 01 Oct 2026 16:58:00 +0000</pubDate>
      <link>https://dev.to/rachelvectle/your-watsonxai-error-isnt-a-network-error-its-a-missing-env-var-246d</link>
      <guid>https://dev.to/rachelvectle/your-watsonxai-error-isnt-a-network-error-its-a-missing-env-var-246d</guid>
      <description>&lt;p&gt;An agent wired up watsonx.ai credentials from the SDK example repo, ran the script, and got a connection error. It spent the next several steps checking DNS, retrying, and suspecting the network. The actual bug: WATSONX_URL was empty. A missing URL never reads as an auth error. It reads as a network failure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&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;missing required env var: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&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;return&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;

&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IBM_CLOUD_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IBM_CLOUD_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;project_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IBM_CLOUD_PROJECT_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ibm_watsonx_ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Credentials&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ibm_watsonx_ai.foundation_models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ModelInference&lt;/span&gt;

&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_key&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="nc"&gt;ModelInference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's gotcha one. Gotcha two showed up in the same session: the example repo pins ibm/granite-13b-instruct-v2 as the model id. Older granite variants retire, so copying the id from sample code buys you a second cryptic failure a month later. Always list the live model ids for your region.&lt;/p&gt;

&lt;p&gt;The pattern worth stealing: validate the three env vars up front and raise a clear error when one is empty. Every agent that talks to a config-heavy API should do this before the first call, because the API itself will never tell you "you forgot a variable." It will tell you the network is broken.&lt;/p&gt;

&lt;p&gt;Full skill with the verified fix: &lt;a href="https://vectle.com/skills/skl_0lojz_UxrjvMzyHCwPeTzQ" rel="noopener noreferrer"&gt;https://vectle.com/skills/skl_0lojz_UxrjvMzyHCwPeTzQ&lt;/a&gt;&lt;/p&gt;

</description>
      <category>watsonx</category>
      <category>python</category>
      <category>ai</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Your DeepInfra baseURL is wrong: it needs /v1/openai, not /v1</title>
      <dc:creator>Rachel Vectle</dc:creator>
      <pubDate>Wed, 30 Sep 2026 17:18:56 +0000</pubDate>
      <link>https://dev.to/rachelvectle/your-deepinfra-baseurl-is-wrong-it-needs-v1openai-not-v1-43j2</link>
      <guid>https://dev.to/rachelvectle/your-deepinfra-baseurl-is-wrong-it-needs-v1openai-not-v1-43j2</guid>
      <description>&lt;p&gt;Every request 404s and your API key is fine. Your baseURL is the bug.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// wrong: this is what the muscle memory gets you&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createOpenAICompatible&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.deepinfra.com/v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 404 on every request&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// right&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createOpenAICompatible&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.deepinfra.com/v1/openai&lt;/span&gt;&lt;span class="dl"&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;createOpenAICompatible appends /chat/completions to whatever baseURL you hand it. DeepInfra serves its OpenAI-compatible API at /v1/openai/chat/completions, not /v1/chat/completions. So baseURL /v1 builds /v1/chat/completions, which does not exist, and every request dies. The fix is one path segment.&lt;/p&gt;

&lt;p&gt;Two more things from the provider docs while you are here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Model ids are HuggingFace-style paths: deepseek-ai/DeepSeek-V4-Flash, meta-llama/Meta-Llama-3.1-8B-Instruct. Not the short names you would guess.&lt;/li&gt;
&lt;li&gt;Responses carry usage.estimated_cost. Read it off each response for billing reconciliation instead of estimating from your own token counts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the class of bug agents hit hardest: everything says OpenAI-compatible, the key is valid, and requests 404. The name says compatible. The path says otherwise.&lt;/p&gt;

&lt;p&gt;Full skill: &lt;a href="https://vectle.com/skills/skl_0hAQafQeJk2cXpMYQ9FNIg" rel="noopener noreferrer"&gt;https://vectle.com/skills/skl_0hAQafQeJk2cXpMYQ9FNIg&lt;/a&gt;&lt;/p&gt;

</description>
      <category>deepinfra</category>
      <category>llm</category>
      <category>openai</category>
      <category>debugging</category>
    </item>
    <item>
      <title>document.parse.completed does not mean the parse succeeded (Affinda webhook gotcha)</title>
      <dc:creator>Rachel Vectle</dc:creator>
      <pubDate>Tue, 29 Sep 2026 17:01:10 +0000</pubDate>
      <link>https://dev.to/rachelvectle/documentparsecompleted-does-not-mean-the-parse-succeeded-affinda-webhook-gotcha-48mj</link>
      <guid>https://dev.to/rachelvectle/documentparsecompleted-does-not-mean-the-parse-succeeded-affinda-webhook-gotcha-48mj</guid>
      <description>&lt;p&gt;Your Affinda webhook handler is processing failed parses as successes. The event name is lying to you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// this looks fine. it is not.&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks/affinda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document.parse.completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;saveToDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// failed parses land here too&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;Affinda's &lt;code&gt;document.parse.completed&lt;/code&gt; fires when parsing finishes. Finishes includes fails. A document that Affinda could not parse at all still sends you a &lt;code&gt;completed&lt;/code&gt; event, with garbage or missing data attached.&lt;/p&gt;

&lt;p&gt;The fix is one check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks/affinda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document.parse.completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;handleFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// retry, flag, move on&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;saveToDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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;Or skip the ambiguity entirely and subscribe to &lt;code&gt;document.parse.succeeded&lt;/code&gt; and &lt;code&gt;document.parse.failed&lt;/code&gt; as separate events. Then your handler never has to ask what completed meant.&lt;/p&gt;

&lt;p&gt;This is the kind of bug that survives code review because everyone reads the event name and nods. The docs say completed. Completed sounds done-and-good. It is not. It is done-and-maybe-good.&lt;/p&gt;

&lt;p&gt;If you are building agents that wire up third-party webhooks, this pattern repeats across APIs: the event name describes the lifecycle stage, not the outcome. Agents are especially prone to it because they take names at face value.&lt;/p&gt;

&lt;p&gt;Full skill with the gotcha list: &lt;a href="https://vectle.com/skills/skl_0HecrGUcurc8PVGuJu1dzA" rel="noopener noreferrer"&gt;https://vectle.com/skills/skl_0HecrGUcurc8PVGuJu1dzA&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>api</category>
      <category>debugging</category>
      <category>affinda</category>
    </item>
  </channel>
</rss>
