<?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: HeFuAI</title>
    <description>The latest articles on DEV Community by HeFuAI (@hefuai).</description>
    <link>https://dev.to/hefuai</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%2F4116870%2Fb6aee463-c098-45c8-94b2-5b4d9e329481.png</url>
      <title>DEV Community: HeFuAI</title>
      <link>https://dev.to/hefuai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hefuai"/>
    <language>en</language>
    <item>
      <title>Build a Model-Switchable Python CLI with an OpenAI-Compatible API</title>
      <dc:creator>HeFuAI</dc:creator>
      <pubDate>Fri, 30 Oct 2026 06:27:01 +0000</pubDate>
      <link>https://dev.to/hefuai/build-a-model-switchable-python-cli-with-an-openai-compatible-api-414p</link>
      <guid>https://dev.to/hefuai/build-a-model-switchable-python-cli-with-an-openai-compatible-api-414p</guid>
      <description>&lt;p&gt;A small command-line tool is a useful way to test an LLM API before integrating it into a larger application. This tutorial builds a Python CLI that accepts a prompt, switches models, streams output, reports token usage, and handles common API errors.&lt;/p&gt;

&lt;p&gt;The example uses HeFu's documented OpenAI-compatible Chat Completions endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the SDK
&lt;/h2&gt;

&lt;p&gt;Create a virtual environment and install the OpenAI Python package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; openai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows PowerShell, activate it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\.venv\Scripts\Activate.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--upgrade&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the API key as an environment variable. On macOS or Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;HEFU_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;HEFU_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"deepseek-v4-flash"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;HEFU_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;HEFU_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"deepseek-v4-flash"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build the CLI
&lt;/h2&gt;

&lt;p&gt;Save this script as &lt;code&gt;llm_cli.py&lt;/code&gt;:&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;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;APIConnectionError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;APIStatusError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;APITimeoutError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;,&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;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Send a prompt to a chat completion model.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prompt to send to the model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;default&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="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HEFU_MODEL&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;deepseek-v4-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API model ID&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="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;store_true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Print text as it arrives&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="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HEFU_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&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;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing HEFU_API_KEY.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&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;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://hefu.hk/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;30.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&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="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;system&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer clearly and state uncertainty when needed.&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="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;args&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;try&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;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;stream&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;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&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="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&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="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;try&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;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stream&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;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;continue&lt;/span&gt;

                    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
                    &lt;span class="nf"&gt;print&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;end&lt;/span&gt;&lt;span class="o"&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;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&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;response&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;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&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="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&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="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&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="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Usage: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; input tokens, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; output tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;APITimeoutError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Request timed out.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;APIConnectionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Could not connect to the API.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;APIStatusError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&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;HTTP &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&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;exc&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="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;


&lt;span class="k"&gt;if&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;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&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;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The configuration has three separate parts: &lt;code&gt;base_url&lt;/code&gt; selects the API endpoint, &lt;code&gt;api_key&lt;/code&gt; authenticates the request, and &lt;code&gt;model&lt;/code&gt; selects the model for that call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run a request
&lt;/h2&gt;

&lt;p&gt;For a complete response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python llm_cli.py &lt;span class="s2"&gt;"Explain Python context managers in two paragraphs."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For incremental output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python llm_cli.py &lt;span class="nt"&gt;--stream&lt;/span&gt; &lt;span class="s2"&gt;"Explain Python context managers in two paragraphs."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To switch models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python llm_cli.py &lt;span class="nt"&gt;--model&lt;/span&gt; YOUR_MODEL_ID &lt;span class="s2"&gt;"Explain Python context managers."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;YOUR_MODEL_ID&lt;/code&gt; with an exact API ID from the current model catalog.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes when streaming is enabled?
&lt;/h2&gt;

&lt;p&gt;A standard call returns a completed response. Its text is available in &lt;code&gt;response.choices[0].message.content&lt;/code&gt;, and token usage may be available in &lt;code&gt;response.usage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A streaming call returns chunks. New text arrives in &lt;code&gt;chunk.choices[0].delta.content&lt;/code&gt;. Some chunks contain no text, so the script checks &lt;code&gt;choices&lt;/code&gt; and uses an empty string when &lt;code&gt;content&lt;/code&gt; is missing.&lt;/p&gt;

&lt;p&gt;This basic CLI reports usage only for non-streamed requests. If you need usage data from a stream, verify support for the relevant streaming options before relying on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle failures deliberately
&lt;/h2&gt;

&lt;p&gt;The script handles timeouts, connection failures, and non-success HTTP responses separately. For an HTTP error, inspect the status code and response details: &lt;code&gt;401&lt;/code&gt; suggests an authentication problem, &lt;code&gt;404&lt;/code&gt; may indicate an incorrect route or model ID, and &lt;code&gt;429&lt;/code&gt; indicates a rate limit.&lt;/p&gt;

&lt;p&gt;A stream can fail after part of an answer has already appeared. Do not silently replay that request into the same output area. Mark it as incomplete and decide explicitly whether to retry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before using this pattern in an application
&lt;/h2&gt;

&lt;p&gt;Keep API keys on the server, set appropriate timeouts, and avoid logging secrets or sensitive prompts. Test the exact features your application needs on each model. Sharing a Chat Completions request format does not guarantee identical model behavior or support for every optional parameter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt; &lt;a href="https://hefu.hk/docs" rel="noopener noreferrer"&gt;HeFu API documentation&lt;/a&gt; · &lt;a href="https://github.com/openai/openai-python" rel="noopener noreferrer"&gt;OpenAI Python SDK&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: This article was drafted with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Calling DeepSeek, Kimi, and Qwen From Outside China: The Real Blockers (and How to Check You're Not Being Marked Up)</title>
      <dc:creator>HeFuAI</dc:creator>
      <pubDate>Wed, 28 Oct 2026 02:05:08 +0000</pubDate>
      <link>https://dev.to/hefuai/calling-deepseek-kimi-and-qwen-from-outside-china-the-real-blockers-and-how-to-check-youre-not-44ni</link>
      <guid>https://dev.to/hefuai/calling-deepseek-kimi-and-qwen-from-outside-china-the-real-blockers-and-how-to-check-youre-not-44ni</guid>
      <description>&lt;p&gt;If you've tried to call DeepSeek, Kimi, or Qwen from outside mainland China, you already know the model isn't the problem. The weights are fine. The context window is fine. What stops people is the account layer: identity, payment rails, and procurement.&lt;/p&gt;

&lt;p&gt;Most posts on this topic are either stale (written before the 2025–2026 access changes) or thin affiliate content that ends with "just use my gateway." This one tries to be the post I wish I'd had: what actually blocks you, which paths exist today, and — the part nobody writes — how to verify that a gateway isn't quietly marking up the upstream price.&lt;/p&gt;

&lt;p&gt;Disclosure up front:​ We build one of these gateways (hefu.hk). We've tried to keep the checklist below one we'd want applied to us too. If it reads like a sales pitch, that's a failure on my part.&lt;/p&gt;

&lt;p&gt;The three blockers (they're not what you think)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Identity. Several first-party consoles are built around a mainland mobile number (+86) and SMS verification. Foreign numbers often don't receive the code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Payment. Funding an account frequently runs through domestic rails — Alipay or WeChat Pay tied to a Chinese bank account. A US/EU card may or may not work depending on the vendor and your region.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Procurement. Even when a developer gets a key working, finance asks for an invoice with a legal entity, a PO number, or a receipt they can reimburse. A personal Alipay top-up doesn't satisfy that.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only the third one is usually fatal for a company. The first two are annoying; the third one sends you back to a Western model at 10x the price.&lt;/p&gt;

&lt;p&gt;Path 1 — First-party international endpoints&lt;br&gt;
This is the option most people miss: several Chinese labs now run international consoles separate from their mainland platforms.&lt;/p&gt;

&lt;p&gt;DeepSeek — platform.deepseek.com, OpenAI-compatible at &lt;a href="https://api.deepseek.com" rel="noopener noreferrer"&gt;https://api.deepseek.com&lt;/a&gt;&lt;br&gt;
Moonshot / Kimi — platform.moonshot.ai (international, USD) vs. platform.moonshot.cn (mainland, RMB)&lt;br&gt;
Qwen / Alibaba — Model Studio international, e.g. &lt;a href="https://dashscope-intl.aliyuncs.com/compatible-mode/v1" rel="noopener noreferrer"&gt;https://dashscope-intl.aliyuncs.com/compatible-mode/v1&lt;/a&gt; (Singapore) and a US region&lt;br&gt;
Zhipu / GLM — api.z.ai for the international surface&lt;/p&gt;

&lt;p&gt;Because they speak the OpenAI shape, the migration is usually three values:&lt;br&gt;
from openai import OpenAI&lt;/p&gt;

&lt;p&gt;client = OpenAI(&lt;br&gt;
    api_key="YOUR_KEY",&lt;br&gt;
    base_url="&lt;a href="https://api.deepseek.com" rel="noopener noreferrer"&gt;https://api.deepseek.com&lt;/a&gt;",  # or dashscope-intl, api.z.ai&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;resp = client.chat.completions.create(&lt;br&gt;
    model="deepseek-chat",&lt;br&gt;
    messages=[{"role": "user", "content": "Explain MoE in one paragraph."}],&lt;br&gt;
    max_tokens=300,&lt;br&gt;
)&lt;br&gt;
print(resp.choices[0].message.content)&lt;/p&gt;

&lt;p&gt;The honest caveat:​ availability, card acceptance, and even whether a +86 number is required vary by vendor, region, and month. Check the vendor's live pricing and signup page before you assume a path is open or closed. Don't trust a blog post (including this one) as your source of truth for today's signup flow.&lt;/p&gt;

&lt;p&gt;When this path wins:​ you want a first-party relationship, the latest model tier, and you're willing to manage one dashboard per vendor.&lt;/p&gt;

&lt;p&gt;When it loses:​ three vendors means three keys, three invoices, three rate-limit policies, and three support queues.&lt;/p&gt;

&lt;p&gt;Path 2 — Self-host the open weights&lt;br&gt;
DeepSeek, Qwen, and several GLM variants publish open weights. If data cannot leave your VPC — finance, legal, regulated manufacturing — this is often the only acceptable answer.&lt;/p&gt;

&lt;p&gt;pip install vllm&lt;br&gt;
vllm serve Qwen/Qwen3-235B-A22B --tensor-parallel-size 4&lt;/p&gt;

&lt;p&gt;The catch is that you now own the serving stack: GPU budget, quantization choices, cold starts, autoscaling, and every future model upgrade. For a two-person team trying to ship a feature this week, that's a full-time job you didn't sign up for.&lt;/p&gt;

&lt;p&gt;Rule of thumb:​ self-host for data-control reasons, not to save money. At low volume you'll usually lose on idle GPU time.&lt;/p&gt;

&lt;p&gt;Path 3 — A unified gateway&lt;br&gt;
One key, one endpoint, one invoice, many models. The appeal is obvious, and it's why this space has a dozen providers.&lt;/p&gt;

&lt;p&gt;The risk is equally obvious: you're trusting a middleman with your traffic and your billing, and the middleman's margin is often invisible. A gateway's price is upstream cost × margin. When the upstream price moves (and Chinese providers change prices often, including peak/off-peak windows), a non-transparent gateway can quietly widen that margin.&lt;/p&gt;

&lt;p&gt;That's the real question worth asking — not "who is 4% cheaper this week," but "can I verify what I'm paying for."&lt;/p&gt;

&lt;p&gt;The checklist: 7 questions before you route production traffic&lt;br&gt;
Ask any gateway these. A good one answers all seven without flinching.&lt;/p&gt;

&lt;p&gt;Price parity, same day. Open the vendor's public pricing page next to the gateway's page and compare per-1M input/output. If it's higher, ask why and by how much.&lt;br&gt;
Top-up fees and FX spread. Is there a deposit fee? A percentage on top-ups? A currency conversion margin? A "credits" system that hides the rate?&lt;br&gt;
Token-level billing. Are input, output, and cached input billed separately and shown in the usage log? Cached input on DeepSeek is dramatically cheaper — if the gateway doesn't pass that through, you're overpaying on long system prompts.&lt;br&gt;
Upstream transparency. Does the dashboard tell you which provider and model version served the request? Or just a marketing name?&lt;br&gt;
Invoices and sub-accounts. Can you get a proper receipt / invoice for finance? Can you issue scoped keys per team member or per environment?&lt;br&gt;
Data handling. Do they retain prompts? Is there PII redaction? What's the retention window? Get this in writing if you're in a regulated domain.&lt;br&gt;
Failure behavior. What happens on upstream 429/5xx — failover to another provider, or a raw error to your users? Ask for the actual routing policy.&lt;br&gt;
If a provider dodges #1 or #2, that's your answer.&lt;/p&gt;

&lt;p&gt;A production-shaped client (framework-agnostic)&lt;br&gt;
Whichever path you pick, wrap it once so you can swap providers later:&lt;/p&gt;

&lt;p&gt;import os, time&lt;br&gt;
from openai import OpenAI, APIError, RateLimitError&lt;/p&gt;

&lt;p&gt;client = OpenAI(&lt;br&gt;
    api_key=os.environ["LLM_API_KEY"],&lt;br&gt;
    base_url=os.environ["LLM_BASE_URL"],&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;def ask(prompt: str, model: str, retries: int = 3):&lt;br&gt;
    for attempt in range(retries):&lt;br&gt;
        try:&lt;br&gt;
            stream = client.chat.completions.create(&lt;br&gt;
                model=model,&lt;br&gt;
                messages=[{"role": "user", "content": prompt}],&lt;br&gt;
                stream=True,&lt;br&gt;
            )&lt;br&gt;
            for chunk in stream:&lt;br&gt;
                delta = chunk.choices[0].delta.content&lt;br&gt;
                if delta:&lt;br&gt;
                    yield delta&lt;br&gt;
            return&lt;br&gt;
        except (RateLimitError, APIError):&lt;br&gt;
            if attempt == retries - 1:&lt;br&gt;
                raise&lt;br&gt;
            time.sleep(2 ** attempt)&lt;/p&gt;

&lt;p&gt;Two details worth keeping: retry rate limits and 5xx, never retry a 400; and always cap max_tokens, because output tokens are where the bill hides.&lt;/p&gt;

&lt;p&gt;How I'd choose&lt;br&gt;
Need it working this week, one vendor → first-party international endpoint.&lt;br&gt;
Data can't leave your VPC → self-host the open weights.&lt;br&gt;
Multiple models, one bill, small team → gateway, but run the checklist first.&lt;br&gt;
Company procurement involved → decide on invoices and sub-accounts before you decide on price.&lt;br&gt;
The models are genuinely good and genuinely cheaper. The friction was never the model — it was the account layer. Pick the path that matches your constraint, and make the billing transparent before you scale traffic.&lt;/p&gt;

&lt;p&gt;Disclosure: We build hefu.hk, an OpenAI-compatible gateway for Claude/GPT/Gemini plus Chinese models. We price at official rates with no top-up fee and provide token-level billing, receipts, and sub-accounts — which is exactly the checklist above. If you evaluate us, use the same seven questions.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
